diff --git a/.gitignore b/.gitignore index 944fb84181..e1abcaa639 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ CMakeCache.txt cmake_install.cmake Makefile makefile -build +build* prebuilt ## doxygen @@ -41,7 +41,6 @@ resources ## generated objects apps/openmw/config.hpp -components/version/version.hpp docs/mainpage.hpp moc_*.cxx *.cxx_parameters diff --git a/.travis.yml b/.travis.yml index 6b03be1141..0910ac5462 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ os: - linux - - osx +# - osx language: cpp branches: only: @@ -18,8 +18,8 @@ addons: name: "OpenMW/openmw" description: "" notification_email: scrawl@baseoftrash.de - build_command_prepend: "cmake ." - build_command: "make -j3" + build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE" + build_command: "make" branch_pattern: coverity_scan matrix: include: @@ -40,8 +40,8 @@ script: - cd ./build - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j2; fi - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi -after_script: - - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi + - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi + - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi notifications: recipients: - corrmage+travis-ci@gmail.com diff --git a/AUTHORS.md b/AUTHORS.md index ce76c104e1..292c1e9e3d 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -13,12 +13,14 @@ Programmers Marc Zinnschlag (Zini) - Lead Programmer/Project Manager Adam Hogan (aurix) + Aesylwinn Aleksandar Jovanov Alex Haddad (rainChu) Alex McKibben (WeirdSexy) Alexander Nadeau (wareya) Alexander Olofsson (Ace) Artem Kotsynyak (greye) + artemutin Arthur Moore (EmperorArthur) athile Bret Curtis (psi29a) @@ -29,6 +31,7 @@ Programmers Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) darkf + Dieho Dmitry Shkurskiy (endorph) Douglas Diniz (Dgdiniz) Douglas Mencken (dougmencken) @@ -52,6 +55,7 @@ Programmers jeaye Jeffrey Haines (Jyby) Jengerer + Jiří Kuneš (kunesj) Joel Graff (graffy) John Blomberg (fstp) Jordan Ayers @@ -59,6 +63,7 @@ Programmers Julien Voisin (jvoisin/ap0) Karl-Felix Glatzer (k1ll) Kevin Poitra (PuppyKevin) + Koncord Lars Söderberg (Lazaroth) lazydev Leon Saunders (emoose) @@ -70,6 +75,7 @@ Programmers Marco Melletti (mellotanica) Marco Schulze Mateusz Kołaczek (PL_kolek) + Mateusz Malisz (malice) megaton Michael Hogan (Xethik) Michael Mc Donnell @@ -84,8 +90,10 @@ Programmers Nikolay Kasyanov (corristo) nobrakal Nolan Poe (nopoe) + Paul Cercueil (pcercuei) Paul McElroy (Greendogo) Pieter van der Kloet (pvdk) + pkubik Radu-Marius Popovici (rpopovici) rdimesio riothamus @@ -109,6 +117,7 @@ Programmers viadanna Vincent Heuken vocollapse + zelurker Manual ------ diff --git a/CHANGELOG.md b/CHANGELOG.md index 466c2ef253..43e598566c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,237 @@ +0.37.0 +------ + + Bug #385: Light emitting objects have a too short distance of activation + Bug #455: Animation doesn't resize creature's bounding box + Bug #602: Only collision model is updated when modifying objects trough console + Bug #639: Sky horizon at nighttime + Bug #672: incorrect trajectory of the moons + Bug #814: incorrect NPC width + Bug #827: Inaccurate raycasting for dead actors + Bug #996: Can see underwater clearly when at right height/angle + Bug #1317: Erene Llenim in Seyda Neen does not walk around + Bug #1330: Cliff racers fail to hit the player + Bug #1366: Combat AI can't aim down (in order to hit small creatures) + Bug #1511: View distance while under water is much too short + Bug #1563: Terrain positioned incorrectly and appears to vibrate in far-out cells + Bug #1612: First person models clip through walls + Bug #1647: Crash switching from full screen to windows mode - D3D9 + Bug #1650: No textures with directx on windows + Bug #1730: Scripts names starting with digit(s) fail to compile + Bug #1738: Socucius Ergalla's greetings are doubled during the tutorial + Bug #1784: First person weapons always in the same position + Bug #1813: Underwater flora lighting up entire area. + Bug #1871: Handle controller extrapolation flags + Bug #1921: Footstep frequency and velocity do not immediately update when speed attribute changes + Bug #2001: OpenMW crashes on start with OpenGL 1.4 drivers + Bug #2014: Antialiasing setting does nothing on Linux + Bug #2037: Some enemies attack the air when spotting the player + Bug #2052: NIF rotation matrices including scales are not supported + Bug #2062: Crank in Old Mournhold: Forgotten Sewer turns about the wrong axis + Bug #2111: Raindrops in front of fire look wrong + Bug #2140: [OpenGL] Water effects, flames and parts of creatures solid black when observed through brazier flame + Bug #2147: Trueflame and Hopesfire flame effects not properly aligned with blade + Bug #2148: Verminous fabricants have little coloured box beneath their feet + Bug #2149: Sparks in Clockwork City should bounce off the floor + Bug #2151: Clockwork City dicer trap doesn't activate when you're too close + Bug #2186: Mini map contains scrambled pixels that cause the mini map to flicker + Bug #2187: NIF file with more than 255 NiBillboardNodes does not load + Bug #2191: Editor: Crash when trying to view cell in render view in OpenCS + Bug #2270: Objects flicker transparently + Bug #2280: Latest 32bit windows build of openmw runns out of vram + Bug #2281: NPCs don't scream when they die + Bug #2286: Jumping animation restarts when equipping mid-air + Bug #2287: Weapon idle animation stops when turning + Bug #2355: Light spell doesn't work in 1st person view + Bug #2362: Lantern glas opaque to flame effect from certain viewing angles + Bug #2364: Light spells are not as bright as in Morrowind + Bug #2383: Remove the alpha testing override list + Bug #2436: Crash on entering cell "Tower of Tel Fyr, Hall of Fyr" + Bug #2457: Player followers should not report crimes + Bug #2458: crash in some fighting situations + Bug #2464: Hiding an emitter node should make that emitter stop firing particles + Bug #2466: Can't load a save created with OpenMW-0.35.0-win64 + Bug #2468: music from title screen continues after loading savegame + Bug #2494: Map not consistent between saves + Bug #2504: Dialog scroll should always start at the top + Bug #2506: Editor: Undo/Redo shortcuts do not work in script editor + Bug #2513: Mannequins in mods appear as dead bodies + Bug #2524: Editor: TopicInfo "custom" condition section is missing + Bug #2540: Editor: search and verification result table can not be sorted by clicking on the column names + Bug #2543: Editor: there is a problem with spell effects + Bug #2544: Editor fails to save NPC information correctly. + Bug #2545: Editor: delete record in Objects (referenceables) table messes up data + Bug #2546: Editor: race base attributes and skill boni are not displayed, thus not editable + Bug #2547: Editor: some NPC data is not displayed, thus not editable + Bug #2551: Editor: missing data in cell definition + Bug #2553: Editor: value filter does not work for float values + Bug #2555: Editor: undo leaves the record status as Modified + Bug #2559: Make Detect Enchantment marks appear on top of the player arrow + Bug #2563: position consoling npc doesn't work without cell reload + Bug #2564: Editor: Closing a subview from code does not clean up properly and will lead to crash on opening the next subview + Bug #2568: Editor: Setting default window size is ignored + Bug #2569: Editor: saving from an esp to omwaddon file results in data loss for TopicInfo + Bug #2575: Editor: Deleted record (with Added (ModifiedOnly) status) remains in the Dialog SubView + Bug #2576: Editor: Editor doesn't scroll to a newly opened subview, when ScrollBar Only mode is active + Bug #2578: Editor: changing Level or Reputation of an NPC crashes the editor + Bug #2579: Editor: filters not updated when adding or cloning records + Bug #2580: Editor: omwaddon makes OpenMW crash + Bug #2581: Editor: focus problems in edit subviews single- and multiline input fields + Bug #2582: Editor: object verifier should check for non-existing scripts being referenced + Bug #2583: Editor: applying filter to TopicInfo on mods that have added dialouge makes the Editor crash + Bug #2586: Editor: some dialogue only editable items do not refresh after undo + Bug #2588: Editor: Cancel button exits program + Bug #2589: Editor: Regions table - mapcolor does not change correctly + Bug #2591: Placeatme - spurious 5th parameter raises error + Bug #2593: COC command prints multiple times when GUI is hidden + Bug #2598: Editor: scene view of instances has to be zoomed out to displaying something - center camera instance please + Bug #2607: water behind an invisible NPC becomes invisible as well + Bug #2611: Editor: Sort problem in Objects table when few nested rows are added + Bug #2621: crash when a creature has no model + Bug #2624: Editor: missing columns in tables + Bug #2627: Character sheet doesn't properly update when backing out of CharGen + Bug #2642: Editor: endif without if - is not reported as error when "verify" was executed + Bug #2644: Editor: rebuild the list of available content files when opening the open/new dialogues + Bug #2656: OpenMW & OpenMW-CS: setting "Flies" flag for ghosts has no effect + Bug #2659: OpenMW & OpenMW-CS: savegame load fail due to script attached to NPCs + Bug #2668: Editor: reputation value in the input field is not stored + Bug #2696: Horkers use land idle animations under water + Bug #2705: Editor: Sort by Record Type (Objects table) is incorrect + Bug #2711: Map notes on an exterior cell that shows up with a map marker on the world map do not show up in the tooltip for that cell's marker on the world map + Bug #2714: Editor: Can't reorder rows with the same topic in different letter case + Bug #2720: Head tracking for creatures not implemented + Bug #2722: Alchemy should only include effects shared by at least 2 ingredients + Bug #2723: "ori" console command is not working + Bug #2726: Ashlanders in front of Ghostgate start wandering around + Bug #2727: ESM writer does not handle encoding when saving the TES3 header + Bug #2728: Editor: Incorrect position of an added row in Info tables + Bug #2731: Editor: Deleting a record triggers a Qt warning + Bug #2733: Editor: Undo doesn't restore the Modified status of a record when a nested data is changed + Bug #2734: Editor: The Search doesn't work + Bug #2738: Additive moon blending + Bug #2746: NIF node names should be case insensitive + Bug #2752: Fog depth/density not handled correctly + Bug #2753: Editor: line edit in dialogue subview tables shows after a single click + Bug #2755: Combat AI changes target too frequently + Bug #2761: Can't attack during block animations + Bug #2764: Player doesn't raise arm in 3rd person for weathertype 9 + Bug #2768: Current screen resolution not selected in options when starting OpenMW + Bug #2773: Editor: Deleted scripts are editable + Bug #2776: ordinators still think I'm wearing their helm even though Khajiit and argonians can't + Bug #2779: Slider bars continue to move if you don't release mouse button + Bug #2781: sleep interruption is a little off (is this an added feature?) + Bug #2782: erroneously able to ready weapon/magic (+sheathe weapon/magic) while paralyzed + Bug #2785: Editor: Incorrect GMSTs for newly created omwgame files + Bug #2786: Kwama Queen head is inverted under OpenMW + Bug #2788: additem and removeitem incorrect gold behavior + Bug #2790: --start doesn't trace down + Bug #2791: Editor: Listed attributes and skill should not be based on number of NPC objects. + Bug #2792: glitched merchantile/infinite free items + Bug #2794: Need to ignore quotes in names of script function + Bug #2797: Editor: Crash when removing the first row in a nested table + Bug #2800: Show an error message when S3TC support is missing + Bug #2811: Targetted Open spell effect persists. + Bug #2819: Editor: bodypart's race filter not displayed correctly + Bug #2820: Editor: table sorting is inverted + Bug #2821: Editor: undo/redo command labels are incorrect + Bug #2826: locking beds that have been locked via magic psuedo-freezes the game + Bug #2830: Script compiler does not accept IDs as instruction/functions arguments if the ID is also a keyword + Bug #2832: Cell names are not localized on the world map + Bug #2833: [cosmetic] Players swimming at water's surface are slightly too low. + Bug #2840: Save/load menu is not entirely localized + Bug #2853: [exploit/bug] disintegrate weapon incorrectly applying to lockpicks, probes. creates unbreakable lockpicks + Bug #2855: Mouse wheel in journal is not disabled by "Options" panel. + Bug #2856: Heart of Lorkhan doesn't visually respond to attacks + Bug #2863: Inventory highlights wrong category after load + Bug #2864: Illuminated Order 1.0c Bug – The teleport amulet is not placed in the PC inventory. + Bug #2866: Editor: use checkbox instead of combobox for boolean values + Bug #2875: special cases of fSleepRandMod not behaving properly. + Bug #2878: Editor: Verify reports "creature has non-positive level" but there is no level setting + Bug #2879: Editor: entered value of field "Buys *" is not saved for a creature + Bug #2880: OpenMW & OpenMW-CS: having a scale value of 0.000 makes the game laggy + Bug #2882: Freeze when entering cell "Guild of Fighters (Ald'ruhn)" after dropping some items inside + Bug #2883: game not playable if mod providing a spell is removed but the list of known spells still contains it + Bug #2884: NPC chats about wrong player race + Bug #2886: Adding custom races breaks existing numbering of PcRace + Bug #2888: Editor: value entered in "AI Wander Idle" is not kept + Bug #2889: Editor: creatures made with the CS (not cloned) are always dead + Bug #2890: Editor: can't make NPC say a specific "Hello" voice-dialouge + Bug #2893: Editor: making a creature use textual dialogue doesn't work. + Bug #2901: Editor: gold for trading can not be set for creatures + Bug #2907: looking from uderwater part of the PC that is below the surface looks like it would be above the water + Bug #2914: Magicka not recalculated on character generation + Bug #2915: When paralyzed, you can still enter and exit sneak + Bug #2917: chameleon does not work for creatures + Bug #2927: Editor: in the automatic script checker local variable caches are not invalidated/updated on modifications of other scripts + Bug #2930: Editor: AIWander Idle can not be set for a creature + Bug #2932: Editor: you can add rows to "Creature Attack" but you can not enter values + Bug #2938: Editor: Can't add a start script. + Bug #2944: Spell chance for power to show as 0 on hud when used + Bug #2953: Editor: rightclick in an empty place in the menu bar shows an unnamed checkbox + Bug #2956: Editor: freezes while editing Filter + Bug #2959: space character in field enchantment (of an amulet) prevents rendering of surroundings + Bug #2962: OpenMW: Assertion `it != invStore.end()' failed + Bug #2964: Recursive script execution can corrupt script runtime data + Bug #2973: Editor: placing a chest in the game world and activating it heavily blurrs the character portrait + Bug #2978: Editor: Cannot edit alchemy ingredient properties + Bug #2980: Editor: Attribute and Skill can be selected for spells that do not require these parameters, leading to non-functional spells + Bug #2990: Compiling a script with warning mode 2 and enabled error downgrading leads to infinite recursion + Bug #2992: [Mod: Great House Dagoth] Killing Dagoth Gares freezes the game + Bug #3007: PlaceItem takes radians instead of degrees + angle reliability + Feature #706: Editor: Script Editor enhancements + Feature #872: Editor: Colour values in tables + Feature #880: Editor: ID auto-complete + Feature #928: Editor: Partial sorting in info tables + Feature #942: Editor: Dialogue for editing/viewing content file meta information + Feature #1057: NiStencilProperty + Feature #1278: Editor: Mouse picking in worldspace widget + Feature #1280: Editor: Cell border arrows + Feature #1401: Editor: Cloning enhancements + Feature #1463: Editor: Fine grained configuration of extended revert/delete commands + Feature #1591: Editor: Make fields in creation bar drop targets where applicable + Feature #1998: Editor: Magic effect record verifier + Feature #1999: Editor Sound Gen record verifier + Feature #2000: Editor: Pathgrid record verifier + Feature #2528: Game Time Tracker + Feature #2534: Editor: global search does not auomatically focus the search input field + Feature #2535: OpenMW: allow comments in openmw.cfg + Feature #2541: Editor: provide a go to the very bottom button for TopicInfo and JournalInfo + Feature #2549: Editor: add a horizontal slider to scroll between opened tables + Feature #2558: Editor: provide a shortcut for closing the subview that has the focus + Feature #2565: Editor: add context menu for dialogue sub view fields with an item matching "Edit 'x'" from the table subview context menu + Feature #2585: Editor: Ignore mouse wheel input for numeric values unless the respective widget has the focus + Feature #2620: Editor: make the verify-view refreshable + Feature #2622: Editor: Make double click behaviour in result tables configurable (see ID tables) + Feature #2717: Editor: Add severity column to report tables + Feature #2729: Editor: Various dialogue button bar improvements + Feature #2739: Profiling overlay + Feature #2740: Resource manager optimizations + Feature #2741: Make NIF files into proper resources + Feature #2742: Use the skinning data in NIF files as-is + Feature #2743: Small feature culling + Feature #2744: Configurable near clip distance + Feature #2745: GUI scaling option + Feature #2747: Support anonymous textures + Feature #2749: Loading screen optimizations + Feature #2751: Character preview optimization + Feature #2804: Editor: Merge Tool + Feature #2818: Editor: allow copying a record ID to the clipboard + Feature #2946: Editor: add script line number in results of search + Feature #2963: Editor: Mouse button bindings in 3D scene + Feature #2983: Sun Glare fader + Feature #2999: Scaling of journal and books + Task #2665: Support building with Qt5 + Task #2725: Editor: Remove Display_YesNo + Task #2730: Replace hardcoded column numbers in SimpleDialogueSubView/DialogueSubView + Task #2750: Bullet shape instancing optimization + Task #2793: Replace grid size setting with half grid size setting + Task #3003: Support FFMPEG 2.9 (Debian request) + +0.36.1 +------ + + Bug #2590: Start scripts not added correctly + 0.36.0 ------ diff --git a/CI/before_install.linux.sh b/CI/before_install.linux.sh index 27cb714638..6e288aa149 100755 --- a/CI/before_install.linux.sh +++ b/CI/before_install.linux.sh @@ -10,11 +10,13 @@ fi echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" echo "yes" | sudo apt-add-repository ppa:openmw/openmw +echo "yes" | sudo apt-add-repository ppa:boost-latest/ppa sudo apt-get update -qq sudo apt-get install -qq libgtest-dev google-mock -sudo apt-get install -qq libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev libboost-wave-dev -sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavresample-dev -sudo apt-get install -qq libbullet-dev libogre-1.9-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev +sudo apt-get install -qq libboost-filesystem1.55-dev libboost-program-options1.55-dev libboost-system1.55-dev libboost-thread1.55-dev +sudo apt-get install -qq ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev +sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev +sudo apt-get install -qq cmake-data #workaround for broken osgqt cmake script in ubuntu 12.04 if [ "${ANALYZE}" ]; then sudo apt-get install -qq clang-3.6; fi sudo mkdir /usr/src/gtest/build cd /usr/src/gtest/build diff --git a/CI/check_tabs.sh b/CI/check_tabs.sh new file mode 100755 index 0000000000..1e88b57fd4 --- /dev/null +++ b/CI/check_tabs.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +OUTPUT=$(grep -nRP '\t' --include=\*.{cpp,hpp,c,h} --exclude=ui_\* apps components) + +if [[ $OUTPUT ]] ; then + echo "Error: Tab characters found!" + echo $OUTPUT + exit 1 +fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f3a0fcc1f..afe3ce4b70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,8 +3,9 @@ project(OpenMW) # If the user doesn't supply a CMAKE_BUILD_TYPE via command line, choose one for them. IF(NOT CMAKE_BUILD_TYPE) SET(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING - "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." + "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel." FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS None Debug Release RelWithDebInfo MinSizeRel) ENDIF() if (APPLE) @@ -19,7 +20,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 36) +set(OPENMW_VERSION_MINOR 37) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") @@ -45,24 +46,20 @@ endif(EXISTS ${PROJECT_SOURCE_DIR}/.git) # Macros include(OpenMWMacros) -if (ANDROID) - set(CMAKE_FIND_ROOT_PATH ${OPENMW_DEPENDENCIES_DIR} "${CMAKE_FIND_ROOT_PATH}") -endif (ANDROID) - # doxygen main page configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_BINARY_DIR}/docs/mainpage.hpp") option(MYGUI_STATIC "Link static build of Mygui into the binaries" FALSE) -option(OGRE_STATIC "Link static build of Ogre and Ogre Plugins into the binaries" FALSE) option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE) option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE) - -set(CUSTOM_OGRE_PLUGIN_DIR "" CACHE PATH "Specify a custom directory for Ogre plugins (autodetected by default)") +option(OSG_STATIC "Link static build of OpenSceneGraph into the binaries" FALSE) +option(QT_STATIC "Link static build of QT into the binaries" FALSE) option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) # Apps and tools +option(BUILD_OPENMW "build OpenMW" ON) option(BUILD_BSATOOL "build BSA extractor" ON) option(BUILD_ESMTOOL "build ESM inspector" ON) option(BUILD_LAUNCHER "build Launcher" ON) @@ -78,13 +75,33 @@ option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with # OS X deployment option(OPENMW_OSX_DEPLOYMENT OFF) -# Location of morrowind data files +if (MSVC) + option(OPENMW_MP_BUILD "Build OpenMW with /MP flag" OFF) + option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF) +endif() + +# Set up common paths if (APPLE) set(MORROWIND_DATA_FILES "./data" CACHE PATH "location of Morrowind data files") set(OPENMW_RESOURCE_FILES "./resources" CACHE PATH "location of OpenMW resources files") elseif(UNIX) - set(MORROWIND_DATA_FILES "${CMAKE_INSTALL_PREFIX}/share/games/openmw/data/" CACHE PATH "location of Morrowind data files") - set(OPENMW_RESOURCE_FILES "${CMAKE_INSTALL_PREFIX}/share/games/openmw/resources/" CACHE PATH "location of OpenMW resources files") + # Paths + SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") + SET(LIBDIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE PATH "Where to install libraries") + SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") + SET(GLOBAL_DATA_PATH "${DATAROOTDIR}/games/" CACHE PATH "Set data path prefix") + SET(DATADIR "${GLOBAL_DATA_PATH}/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") + SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") + SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") + IF("${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr") + SET(GLOBAL_CONFIG_PATH "/etc/" CACHE PATH "Set config dir prefix") + ELSE() + SET(GLOBAL_CONFIG_PATH "${CMAKE_INSTALL_PREFIX}/etc/" CACHE PATH "Set config dir prefix") + ENDIF() + SET(SYSCONFDIR "${GLOBAL_CONFIG_PATH}/openmw" CACHE PATH "Set config dir") + + set(MORROWIND_DATA_FILES "${DATADIR}/data" CACHE PATH "location of Morrowind data files") + set(OPENMW_RESOURCE_FILES "${DATADIR}/resources" CACHE PATH "location of OpenMW resources files") else() set(MORROWIND_DATA_FILES "data" CACHE PATH "location of Morrowind data files") set(OPENMW_RESOURCE_FILES "resources" CACHE PATH "location of OpenMW resources files") @@ -102,24 +119,13 @@ unset(FFMPEG_LIBRARIES CACHE) find_package(FFmpeg REQUIRED) -set (FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARY}) +set (FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWSCALE_LIBRARY} ${SWRESAMPLE_LIBRARIES}) -if ( NOT AVCODEC_FOUND OR NOT AVFORMAT_FOUND OR NOT AVUTIL_FOUND OR NOT SWSCALE_FOUND ) +if ( NOT AVCODEC_FOUND OR NOT AVFORMAT_FOUND OR NOT AVUTIL_FOUND OR NOT SWSCALE_FOUND OR NOT SWRESAMPLE_FOUND) message(FATAL_ERROR "FFmpeg component required, but not found!") endif() -set(SOUND_INPUT_INCLUDES ${FFMPEG_INCLUDE_DIRS}) -if( SWRESAMPLE_FOUND ) - add_definitions(-DHAVE_LIBSWRESAMPLE) - set (FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${SWRESAMPLE_LIBRARIES}) -else() - if( AVRESAMPLE_FOUND ) - set (FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${AVRESAMPLE_LIBRARIES}) - else() - message(FATAL_ERROR "Install either libswresample (FFmpeg) or libavresample (Libav).") - endif() -endif() - -set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES}) +# Required for building the FFmpeg headers +add_definitions(-D__STDC_CONSTANT_MACROS) # TinyXML option(USE_SYSTEM_TINYXML "Use system TinyXML library instead of internal." OFF) @@ -140,32 +146,49 @@ endif() if (WIN32) if(NOT MINGW) set(Boost_USE_STATIC_LIBS ON) - set(PLATFORM_INCLUDE_DIR "platform") add_definitions(-DBOOST_ALL_NO_LIB) endif(NOT MINGW) # Suppress WinMain(), provided by SDL add_definitions(-DSDL_MAIN_HANDLED) -else (WIN32) - set(PLATFORM_INCLUDE_DIR "") -endif (WIN32) -if (MSVC10) - set(PLATFORM_INCLUDE_DIR "") + + # Get rid of useless crud from windows.h + add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) endif() -# Dependencies +if (ANDROID) + set(CMAKE_FIND_ROOT_PATH ${OPENMW_DEPENDENCIES_DIR} "${CMAKE_FIND_ROOT_PATH}") + set(OPENGL_ES TRUE CACHE BOOL "enable opengl es support for android" FORCE) +endif (ANDROID) -set(DESIRED_QT_VERSION 4 CACHE STRING "The QT version OpenMW should use (4 or 5)") -message(STATUS "Using Qt${DESIRED_QT_VERSION}") +option(OPENGL_ES "enable opengl es support" FALSE ) -if (DESIRED_QT_VERSION MATCHES 4) - find_package(Qt4 REQUIRED COMPONENTS QtCore QtGui QtNetwork) +if (OPENGL_ES) + add_definitions(-DOPENGL_ES) +endif(OPENGL_ES) + +if (NOT BUILD_LAUNCHER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD) + set(USE_QT FALSE) else() - find_package(Qt5Widgets REQUIRED) - find_package(Qt5Core REQUIRED) - find_package(Qt5Network REQUIRED) + set(USE_QT TRUE) +endif() + +# Dependencies +if (USE_QT) + set(DESIRED_QT_VERSION 4 CACHE STRING "The QT version OpenMW should use (4 or 5)") + set_property(CACHE DESIRED_QT_VERSION PROPERTY STRINGS 4 5) + message(STATUS "Using Qt${DESIRED_QT_VERSION}") + + if (DESIRED_QT_VERSION MATCHES 4) + find_package(Qt4 REQUIRED COMPONENTS QtCore QtGui QtNetwork QtOpenGL) + else() + find_package(Qt5Widgets REQUIRED) + find_package(Qt5Core REQUIRED) + find_package(Qt5Network REQUIRED) + find_package(Qt5OpenGL REQUIRED) # Instruct CMake to run moc automatically when needed. #set(CMAKE_AUTOMOC ON) + endif() endif() # Fix for not visible pthreads functions for linker with glibc 2.15 @@ -188,7 +211,7 @@ if (HAVE_UNORDERED_MAP) endif () -set(BOOST_COMPONENTS system filesystem program_options thread wave) +set(BOOST_COMPONENTS system filesystem program_options thread) if(WIN32) set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) endif(WIN32) @@ -197,9 +220,70 @@ IF(BOOST_STATIC) set(Boost_USE_STATIC_LIBS ON) endif() -find_package(OGRE REQUIRED) -if (${OGRE_VERSION} VERSION_LESS "1.9") - message(FATAL_ERROR "OpenMW requires Ogre 1.9 or later, please install the latest stable version from http://ogre3d.org") +if (USE_QT) + set (OSG_QT osgQt) +endif() + +find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle ${OSG_QT} osgUtil osgFX) + +include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) + +if(OSG_STATIC) + macro(use_static_osg_plugin_library PLUGIN_NAME) + set(PLUGIN_NAME_DBG ${PLUGIN_NAME}d ${PLUGIN_NAME}D ${PLUGIN_NAME}_d ${PLUGIN_NAME}_D ${PLUGIN_NAME}_debug ${PLUGIN_NAME}) + + # For now, users wishing to do a static build will need to pass the path to where the plugins reside + # More clever logic would need to deduce the path, probably installed under /lib/osgPlugins- + find_library(${PLUGIN_NAME}_LIBRARY_REL NAMES ${PLUGIN_NAME} HINTS ${OSG_PLUGIN_LIB_SEARCH_PATH}) + find_library(${PLUGIN_NAME}_LIBRARY_DBG NAMES ${PLUGIN_NAME_DBG} HINTS ${OSG_PLUGIN_LIB_SEARCH_PATH}) + make_library_set(${PLUGIN_NAME}_LIBRARY) + + if("${${PLUGIN_NAME}_LIBRARY}" STREQUAL "") + message(FATAL_ERROR "Unable to find static OpenSceneGraph plugin: ${PLUGIN_NAME}") + endif() + + set(OPENSCENEGRAPH_LIBRARIES ${OPENSCENEGRAPH_LIBRARIES} ${${PLUGIN_NAME}_LIBRARY}) + endmacro() + + macro(use_static_osg_plugin_dep DEPENDENCY) + find_package(${DEPENDENCY} REQUIRED) + + set(OPENSCENEGRAPH_LIBRARIES ${OPENSCENEGRAPH_LIBRARIES} ${${DEPENDENCY}_LIBRARIES}) + endmacro() + + add_definitions(-DOSG_LIBRARY_STATIC) + + set(PLUGIN_LIST + osgdb_png # depends on libpng, zlib + osgdb_tga + osgdb_dds + osgdb_jpeg # depends on libjpeg + ) + + foreach(PLUGIN ${PLUGIN_LIST}) + use_static_osg_plugin_library(${PLUGIN}) + endforeach() + + # OSG static plugins need to linked against their respective dependencies + set(PLUGIN_DEPS_LIST + PNG # needed by osgdb_png + ZLIB # needed by osgdb_png + JPEG # needed by osgdb_jpeg + ) + + foreach(DEPENDENCY ${PLUGIN_DEPS_LIST}) + use_static_osg_plugin_dep(${DEPENDENCY}) + endforeach() +endif() + +if(QT_STATIC) + if(WIN32) + if(DESIRED_QT_VERSION MATCHES 4) + # QtCore needs WSAAsyncSelect from Ws2_32.lib + set(QT_QTCORE_LIBRARY ${QT_QTCORE_LIBRARY} Ws2_32.lib) + message("QT_QTCORE_LIBRARY: ${QT_QTCORE_LIBRARY}") + endif() + endif() endif() find_package(MyGUI REQUIRED) @@ -212,88 +296,23 @@ find_package(SDL2 REQUIRED) find_package(OpenAL REQUIRED) find_package(Bullet REQUIRED) -set(OGRE_PLUGIN_INCLUDE_DIRS "") -set(OGRE_STATIC_PLUGINS "") - -macro(add_static_ogre_plugin PLUGIN) - if(OGRE_${PLUGIN}_FOUND) - # strip RenderSystem_ or Plugin_ prefix from plugin name - string(REPLACE "RenderSystem_" "" PLUGIN_TEMP ${PLUGIN}) - string(REPLACE "Plugin_" "" PLUGIN_NAME ${PLUGIN_TEMP}) - add_definitions(-DENABLE_PLUGIN_${PLUGIN_NAME}) - - list(APPEND OGRE_PLUGIN_INCLUDE_DIRS ${OGRE_${PLUGIN}_INCLUDE_DIRS}) - list(APPEND OGRE_STATIC_PLUGINS ${OGRE_${PLUGIN}_LIBRARIES}) - endif(OGRE_${PLUGIN}_FOUND) -endmacro(add_static_ogre_plugin) - -if(OGRE_STATIC) - # set up OGRE_PLUGIN_INCLUDE_DIRS and OGRE_STATIC_PLUGINS - add_static_ogre_plugin(Plugin_OctreeSceneManager) - add_static_ogre_plugin(Plugin_ParticleFX) - find_package(Cg) - if(Cg_FOUND) - add_static_ogre_plugin(Plugin_CgProgramManager) - list(APPEND OGRE_STATIC_PLUGINS ${Cg_LIBRARIES}) - endif(Cg_FOUND) - -if (ANDROID) - add_static_ogre_plugin(RenderSystem_GLES2) -else () - add_static_ogre_plugin(RenderSystem_GL) -endif () - - if(WIN32) - add_static_ogre_plugin(RenderSystem_Direct3D9) - endif(WIN32) -endif(OGRE_STATIC) - -include_directories("." ${LIBS_DIR} +include_directories("." SYSTEM - ${OGRE_INCLUDE_DIR} ${OGRE_INCLUDE_DIR}/Ogre ${OGRE_INCLUDE_DIR}/OGRE ${OGRE_INCLUDE_DIRS} ${OGRE_PLUGIN_INCLUDE_DIRS} - ${OGRE_INCLUDE_DIR}/Overlay ${OGRE_Overlay_INCLUDE_DIR} ${SDL2_INCLUDE_DIR} ${Boost_INCLUDE_DIR} - ${PLATFORM_INCLUDE_DIR} ${MYGUI_INCLUDE_DIRS} - ${MYGUI_PLATFORM_INCLUDE_DIRS} ${OPENAL_INCLUDE_DIR} ${BULLET_INCLUDE_DIRS} ) -link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${OGRE_LIB_DIR} ${MYGUI_LIB_DIR}) +link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${MYGUI_LIB_DIR}) if(MYGUI_STATIC) add_definitions(-DMYGUI_STATIC) endif (MYGUI_STATIC) if (APPLE) - # List used Ogre plugins - SET(USED_OGRE_PLUGINS ${OGRE_RenderSystem_GL_LIBRARY_REL} - ${OGRE_Plugin_ParticleFX_LIBRARY_REL}) - - # Actually we must use OGRE_Plugin_CgProgramManager_FOUND but it's - # not reliable and equals TRUE even if there's no Ogre Cg plugin - if (Cg_FOUND) - set(USED_OGRE_PLUGINS ${USED_OGRE_PLUGINS} - ${OGRE_Plugin_CgProgramManager_LIBRARY_REL}) - endif () - - if (${OGRE_PLUGIN_DIR_REL}}) - set(OGRE_PLUGINS_REL_FOUND TRUE) - endif () - - if (${OGRE_PLUGIN_DIR_DBG}) - set(OGRE_PLUGINS_DBG_FOUND TRUE) - endif () - - if (${OGRE_PLUGINS_REL_FOUND}) - set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_REL}) - else () - set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_DBG}) - endif () - - configure_file(${OpenMW_SOURCE_DIR}/files/mac/Info.plist + configure_file(${OpenMW_SOURCE_DIR}/files/mac/openmw-Info.plist.in "${APP_BUNDLE_DIR}/Contents/Info.plist") configure_file(${OpenMW_SOURCE_DIR}/files/mac/openmw.icns @@ -303,36 +322,17 @@ endif (APPLE) # Set up DEBUG define set_directory_properties(PROPERTIES COMPILE_DEFINITIONS_DEBUG DEBUG=1) -# Set up Ogre plugin folder & debug suffix -if (APPLE) - # Ogre on OS X doesn't use "_d" suffix (see Ogre's CMakeLists.txt) - add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="") -else () - add_definitions(-DOGRE_PLUGIN_DEBUG_SUFFIX="_d") -endif() - -if (APPLE AND OPENMW_OSX_DEPLOYMENT) - # make it empty so plugin loading code can check this and try to find plugins inside app bundle - add_definitions(-DOGRE_PLUGIN_DIR="") -else() - if (CUSTOM_OGRE_PLUGIN_DIR STREQUAL "") - set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_REL}) - else() - set(OGRE_PLUGIN_DIR ${CUSTOM_OGRE_PLUGIN_DIR}) - endif() - - add_definitions(-DOGRE_PLUGIN_DIR="${OGRE_PLUGIN_DIR}") -endif() - - add_subdirectory(files/) -add_subdirectory(files/mygui) # Specify build paths if (APPLE) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${APP_BUNDLE_DIR}/Contents/MacOS") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${APP_BUNDLE_DIR}/Contents/MacOS") + + if (OPENMW_OSX_DEPLOYMENT) + SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + endif() else (APPLE) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}") @@ -343,17 +343,14 @@ endif (APPLE) configure_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg "${OpenMW_BINARY_DIR}/settings-default.cfg") -configure_file(${OpenMW_SOURCE_DIR}/files/transparency-overrides.cfg - "${OpenMW_BINARY_DIR}/transparency-overrides.cfg") - configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg.local "${OpenMW_BINARY_DIR}/openmw.cfg") configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg "${OpenMW_BINARY_DIR}/openmw.cfg.install") -configure_file(${OpenMW_SOURCE_DIR}/files/opencs.ini - "${OpenMW_BINARY_DIR}/opencs.ini") +configure_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.cfg + "${OpenMW_BINARY_DIR}/openmw-cs.cfg") configure_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters "${OpenMW_BINARY_DIR}/resources/defaultfilters" COPYONLY) @@ -364,6 +361,8 @@ configure_file(${OpenMW_SOURCE_DIR}/files/gamecontrollerdb.txt if (NOT WIN32 AND NOT APPLE) configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop "${OpenMW_BINARY_DIR}/openmw.desktop") + configure_file(${OpenMW_SOURCE_DIR}/files/openmw.appdata.xml + "${OpenMW_BINARY_DIR}/openmw.appdata.xml") configure_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.desktop "${OpenMW_BINARY_DIR}/openmw-cs.desktop") endif() @@ -387,31 +386,23 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) endif(CMAKE_CXX_COMPILER_ID STREQUAL GNU AND "${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6) elseif (MSVC) # Enable link-time code generation globally for all linking - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL") - set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG") - set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") - set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /LTCG") + if (OPENMW_LTO_BUILD) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") + set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /LTCG") + endif() + + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /FORCE:MULTIPLE") endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) IF(NOT WIN32 AND NOT APPLE) - # Linux building - # Paths - SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") - SET(LIBDIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE PATH "Where to install libraries") - SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") - SET(GLOBAL_DATA_PATH "${DATAROOTDIR}/games/" CACHE PATH "Set data path prefix") - SET(DATADIR "${GLOBAL_DATA_PATH}/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") - SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") - SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") - IF("${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr") - SET(GLOBAL_CONFIG_PATH "/etc/" CACHE PATH "Set config dir prefix") - ELSE() - SET(GLOBAL_CONFIG_PATH "${CMAKE_INSTALL_PREFIX}/etc/" CACHE PATH "Set config dir prefix") - ENDIF() - SET(SYSCONFDIR "${GLOBAL_CONFIG_PATH}/openmw" CACHE PATH "Set config dir") + # Linux installation # Install binaries - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) + IF(BUILD_OPENMW) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_OPENMW) IF(BUILD_LAUNCHER) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-launcher" DESTINATION "${BINDIR}" ) ENDIF(BUILD_LAUNCHER) @@ -421,6 +412,9 @@ IF(NOT WIN32 AND NOT APPLE) IF(BUILD_ESMTOOL) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" ) ENDIF(BUILD_ESMTOOL) + IF(BUILD_NIFTEST) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/niftest" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_NIFTEST) IF(BUILD_MWINIIMPORTER) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-iniimporter" DESTINATION "${BINDIR}" ) ENDIF(BUILD_MWINIIMPORTER) @@ -430,23 +424,20 @@ IF(NOT WIN32 AND NOT APPLE) IF(BUILD_OPENCS) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-cs" DESTINATION "${BINDIR}" ) ENDIF(BUILD_OPENCS) - IF(BUILD_NIFTEST) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/niftest" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_NIFTEST) IF(BUILD_WIZARD) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-wizard" DESTINATION "${BINDIR}" ) ENDIF(BUILD_WIZARD) - if(BUILD_MYGUI_PLUGIN) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Plugin_MyGUI_OpenMW_Resources.so" DESTINATION "${LIBDIR}" ) - ENDIF(BUILD_MYGUI_PLUGIN) + #if(BUILD_MYGUI_PLUGIN) + # INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Plugin_MyGUI_OpenMW_Resources.so" DESTINATION "${LIBDIR}" ) + #ENDIF(BUILD_MYGUI_PLUGIN) # Install licenses INSTALL(FILES "docs/license/DejaVu Font License.txt" DESTINATION "${LICDIR}" ) - INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" ) # Install icon and desktop file INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw") INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.appdata.xml" DESTINATION "${DATAROOTDIR}/appdata" COMPONENT "openmw") IF(BUILD_OPENCS) INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "opencs") INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/openmw-cs.png" DESTINATION "${ICONDIR}" COMPONENT "opencs") @@ -454,12 +445,12 @@ IF(NOT WIN32 AND NOT APPLE) # Install global configuration files INSTALL(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") - INSTALL(FILES "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") IF(BUILD_OPENCS) - INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs") ENDIF(BUILD_OPENCS) # Install resources @@ -477,7 +468,6 @@ if(WIN32) "${OpenMW_SOURCE_DIR}/Docs/license/GPL3.txt" "${OpenMW_SOURCE_DIR}/Docs/license/DejaVu Font License.txt" "${OpenMW_BINARY_DIR}/settings-default.cfg" - "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" "${OpenMW_BINARY_DIR}/Release/openmw.exe" DESTINATION ".") @@ -493,7 +483,7 @@ if(WIN32) ENDIF(BUILD_ESSIMPORTER) IF(BUILD_OPENCS) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-cs.exe" DESTINATION ".") - INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION ".") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION ".") ENDIF(BUILD_OPENCS) IF(BUILD_WIZARD) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-wizard.exe" DESTINATION ".") @@ -503,6 +493,8 @@ if(WIN32) ENDIF(BUILD_MYGUI_PLUGIN) INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") + FILE(GLOB plugin_dir "${OpenMW_BINARY_DIR}/Release/osgPlugins-*") + INSTALL(DIRECTORY ${plugin_dir} DESTINATION ".") SET(CPACK_GENERATOR "NSIS") SET(CPACK_PACKAGE_NAME "OpenMW") @@ -563,31 +555,22 @@ if(WIN32) include(CPack) endif(WIN32) -# Libs -include_directories(libs) -add_subdirectory(libs/openengine) - # Extern -add_subdirectory (extern/shiny) -add_subdirectory (extern/ogre-ffmpeg-videoplayer) +add_subdirectory (extern/osg-ffmpeg-videoplayer) add_subdirectory (extern/oics) -add_subdirectory (extern/sdl4ogre) # Components add_subdirectory (components) # Plugins -if (BUILD_MYGUI_PLUGIN) - add_subdirectory(plugins/mygui_resource_plugin) -endif() - -#Testing -if (BUILD_NIFTEST) - add_subdirectory(components/nif/tests/) -endif(BUILD_NIFTEST) +#if (BUILD_MYGUI_PLUGIN) +# add_subdirectory(plugins/mygui_resource_plugin) +#endif() # Apps and tools -add_subdirectory( apps/openmw ) +if (BUILD_OPENMW) + add_subdirectory( apps/openmw ) +endif() if (BUILD_BSATOOL) add_subdirectory( apps/bsatool ) @@ -617,6 +600,10 @@ if (BUILD_WIZARD) add_subdirectory(apps/wizard) endif() +if (BUILD_NIFTEST) + add_subdirectory(apps/niftest) +endif(BUILD_NIFTEST) + # UnitTests if (BUILD_UNITTESTS) add_subdirectory( apps/openmw_test_suite ) @@ -624,9 +611,9 @@ endif() if (WIN32) if (MSVC) - if (MULTITHREADED_BUILD) + if (OPENMW_MP_BUILD) set( MT_BUILD "/MP") - endif (MULTITHREADED_BUILD) + endif() foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG ) @@ -634,20 +621,22 @@ if (WIN32) set( CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "$(ProjectDir)$(Configuration)" ) endforeach( OUTPUTCONFIG ) - if (USE_DEBUG_CONSOLE) + if (USE_DEBUG_CONSOLE AND BUILD_OPENMW) set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE") set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE") set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS_DEBUG "_CONSOLE") - else() + elseif (BUILD_OPENMW) # Turn off debug console, debug output will be written to visual studio output instead set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS") set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS") endif() - # Release builds use the debug console - set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:CONSOLE") - set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS_RELEASE "_CONSOLE") - set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:CONSOLE") + if (BUILD_OPENMW) + # Release builds use the debug console + set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:CONSOLE") + set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS_RELEASE "_CONSOLE") + set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:CONSOLE") + endif() # Play a bit with the warning levels @@ -656,8 +645,8 @@ if (WIN32) set(WARNINGS_DISABLE # Warnings that aren't enabled normally and don't need to be enabled # They're unneeded and sometimes completely retarded warnings that /Wall enables - # Not going to bother commenting them as they tend to warn on every standard library files - 4061 4263 4264 4266 4350 4371 4514 4548 4571 4610 4619 4623 4625 4626 4628 4640 4668 4710 4711 4820 4826 4917 4946 + # Not going to bother commenting them as they tend to warn on every standard library file + 4061 4263 4264 4266 4350 4371 4435 4514 4548 4571 4610 4619 4623 4625 4626 4628 4640 4668 4710 4711 4820 4826 4917 4946 # Warnings that are thrown on standard libraries and not OpenMW 4347 # Non-template function with same name and parameter count as template function @@ -669,12 +658,6 @@ if (WIN32) 4987 # nonstandard extension used (triggered by setjmp.h) 4996 # Function was declared deprecated - # cause by ogre extensivly - 4193 # #pragma warning(pop) : no matching '#pragma warning(push)' - 4251 # class 'XXXX' needs to have dll-interface to be used by clients of class 'YYYY' - 4275 # non dll-interface struct 'XXXX' used as base for dll-interface class 'YYYY' - 4315 # undocumented, 'this' pointer for member might not be aligned (OgreMemoryStlAllocator.h) - # caused by boost 4191 # 'type cast' : unsafe conversion (1.56, thread_primitives.hpp, normally off) @@ -685,6 +668,7 @@ if (WIN32) 4127 # Conditional expression is constant 4242 # Storing value in a variable of a smaller type, possible loss of data 4244 # Storing value of one type in variable of another (size_t in int, for example) + 4245 # Signed/unsigned mismatch 4267 # Conversion from 'size_t' to 'int', possible loss of data 4305 # Truncating value (double to float, for example) 4309 # Variable overflow, trying to store 128 in a signed char for example @@ -700,21 +684,42 @@ if (WIN32) set(WARNINGS "${WARNINGS} /wd${d}") endforeach(d) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNINGS} ${MT_BUILD}") - - # boost::wave has a few issues with signed / unsigned conversions, so we suppress those here - set(SHINY_WARNINGS "${WARNINGS} /wd4245") - set_target_properties(shiny PROPERTIES COMPILE_FLAGS "${SHINY_WARNINGS} ${MT_BUILD}") - + set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") # oics uses tinyxml, which has an initialized but unused variable - set(OICS_WARNINGS "${WARNINGS} /wd4189") - set_target_properties(oics PROPERTIES COMPILE_FLAGS "${OICS_WARNINGS} ${MT_BUILD}") + set_target_properties(oics PROPERTIES COMPILE_FLAGS "${WARNINGS} /wd4189 ${MT_BUILD}") + set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + + if (BUILD_BSATOOL) + set_target_properties(bsatool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif() + + if (BUILD_ESMTOOL) + set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif() + + if (BUILD_ESSIMPORTER) + set_target_properties(openmw-essimporter PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif() + + if (BUILD_LAUNCHER) + set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif() + + if (BUILD_MWINIIMPORTER) + set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif() if (BUILD_OPENCS) - # QT triggers an informational warning that the object layout may differ when compiled with /vd2 - set(OPENCS_WARNINGS "${WARNINGS} ${MT_BUILD} /wd4435") - set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS ${OPENCS_WARNINGS}) - endif (BUILD_OPENCS) + set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif() + + if (BUILD_OPENMW) + set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif() + + if (BUILD_WIZARD) + set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif() endif(MSVC) # TODO: At some point release builds should not use the console but rather write to a log file @@ -724,6 +729,18 @@ endif() # Apple bundling if (APPLE) + get_property(QT_COCOA_PLUGIN_PATH TARGET Qt5::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE) + get_filename_component(QT_COCOA_PLUGIN_DIR "${QT_COCOA_PLUGIN_PATH}" DIRECTORY) + get_filename_component(QT_COCOA_PLUGIN_GROUP "${QT_COCOA_PLUGIN_DIR}" NAME) + get_filename_component(QT_COCOA_PLUGIN_NAME "${QT_COCOA_PLUGIN_PATH}" NAME) + configure_file("${QT_COCOA_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/MacOS/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY) + + if (BUILD_OPENCS) + get_property(OPENCS_BUNDLE_NAME_TMP TARGET openmw-cs PROPERTY OUTPUT_NAME) + set(OPENCS_BUNDLE_NAME "${OPENCS_BUNDLE_NAME_TMP}.app") + configure_file("${QT_COCOA_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/MacOS/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY) + endif () + set(INSTALL_SUBDIR OpenMW) install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) @@ -731,8 +748,7 @@ if (APPLE) install(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" RENAME "openmw.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) - install(FILES "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) - install(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) + install(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) set(CPACK_GENERATOR "DragNDrop") set(CPACK_PACKAGE_VERSION ${OPENMW_VERSION}) @@ -740,27 +756,35 @@ if (APPLE) set(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) - set(OPENMW_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}") - - set(OPENCS_BUNDLE_NAME "OpenMW-CS.app") - set(OPENCS_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${OPENCS_BUNDLE_NAME}") - - set(ABSOLUTE_PLUGINS "") - - foreach (PLUGIN ${USED_OGRE_PLUGINS}) - get_filename_component(PLUGIN_ABS ${PLUGIN} REALPATH) - set(ABSOLUTE_PLUGINS ${PLUGIN_ABS} ${ABSOLUTE_PLUGINS}) - endforeach () + set(INSTALLED_OPENMW_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}") + set(INSTALLED_OPENCS_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${OPENCS_BUNDLE_NAME}") install(CODE " set(BU_CHMOD_BUNDLE_ITEMS ON) + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) include(BundleUtilities) + cmake_minimum_required(VERSION 3.1) " COMPONENT Runtime) + set(ABSOLUTE_PLUGINS "") + set(USED_OSG_PLUGINS + osgdb_dds + osgdb_jpeg + osgdb_png + osgdb_tga + ) + + foreach (PLUGIN_NAME ${USED_OSG_PLUGINS}) + set(PLUGIN_ABS "${OSG_PLUGIN_LIB_SEARCH_PATH}/${PLUGIN_NAME}.so") + set(ABSOLUTE_PLUGINS ${PLUGIN_ABS} ${ABSOLUTE_PLUGINS}) + endforeach () + + get_filename_component(OSG_PLUGIN_PREFIX_DIR "${OSG_PLUGIN_LIB_SEARCH_PATH}" NAME) + # installs used plugins in bundle at given path (bundle_path must be relative to ${CMAKE_INSTALL_PREFIX}) # and returns list of install paths for all installed plugins function (install_plugins_for_bundle bundle_path plugins_var) - set(RELATIVE_PLUGIN_INSTALL_BASE "${bundle_path}/Contents/Frameworks") + set(RELATIVE_PLUGIN_INSTALL_BASE "${bundle_path}/Contents/PlugIns/${OSG_PLUGIN_PREFIX_DIR}") set(PLUGINS "") set(PLUGIN_INSTALL_BASE "\${CMAKE_INSTALL_PREFIX}/${RELATIVE_PLUGIN_INSTALL_BASE}") @@ -769,11 +793,11 @@ if (APPLE) get_filename_component(PLUGIN_RELATIVE ${PLUGIN} NAME) get_filename_component(PLUGIN_RELATIVE_WE ${PLUGIN} NAME_WE) - set(PLUGIN_DYLIB_IN_BUNDLE "${PLUGIN_INSTALL_BASE}/${PLUGIN_RELATIVE}/${PLUGIN_RELATIVE_WE}") + set(PLUGIN_DYLIB_IN_BUNDLE "${PLUGIN_INSTALL_BASE}/${PLUGIN_RELATIVE}") set(PLUGINS ${PLUGINS} "${PLUGIN_DYLIB_IN_BUNDLE}") install(CODE " - copy_resolved_framework_into_bundle(\"${PLUGIN}/${PLUGIN_RELATIVE_WE}\" \"${PLUGIN_DYLIB_IN_BUNDLE}\") + copy_resolved_item_into_bundle(\"${PLUGIN}\" \"${PLUGIN_DYLIB_IN_BUNDLE}\") " COMPONENT Runtime) endforeach () @@ -783,53 +807,22 @@ if (APPLE) install_plugins_for_bundle("${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}" PLUGINS) install_plugins_for_bundle("${INSTALL_SUBDIR}/${OPENCS_BUNDLE_NAME}" OPENCS_PLUGINS) - #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 - set(DIRS "") + set(PLUGINS ${PLUGINS} "${INSTALLED_OPENMW_APP}/Contents/MacOS/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}") + set(OPENCS_PLUGINS ${OPENCS_PLUGINS} "${INSTALLED_OPENCS_APP}/Contents/MacOS/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}") - # Overriding item resolving during installation, it needed if - # some library already has been "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 " - cmake_policy(SET CMP0009 OLD) - set(CMAKE_FIND_LIBRARY_PREFIXES ${CMAKE_FIND_LIBRARY_PREFIXES}) - set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) - set(CMAKE_SYSTEM_FRAMEWORK_PATH ${CMAKE_SYSTEM_FRAMEWORK_PATH}) - - set(OPENMW_RESOLVED_ITEMS \"\") - - function(gp_resolve_item_override context item exepath dirs resolved_item_var resolved_var) - if(item MATCHES \"@executable_path\" AND NOT \${\${resolved_var}}) - if (item MATCHES \"Frameworks\") # if it is a framework - # get last segment of path - get_filename_component(fname \"\${item}\" NAME_WE) - find_library(ri NAMES \${fname} PATHS \${exepath} \${dirs} \${CMAKE_SYSTEM_FRAMEWORK_PATH}) - if (ri) - string(REGEX REPLACE \"^.*/Frameworks/.*\\\\.framework\" \"\" item_part \${item}) - set(ri \"\${ri}\${item_part}\") - set(\${resolved_item_var} \${ri} PARENT_SCOPE) - set(\${resolved_var} 1 PARENT_SCOPE) - endif() - else() - # code path for standard (non-framework) libs (ogre & qt pugins) - get_filename_component(fname \"\${item}\" NAME_WE) - string(REGEX REPLACE \"^lib\" \"\" fname \${fname}) - find_library(ri NAMES \${fname} PATHS \${exepath} \${dirs} /usr/lib /usr/local/lib) - if (ri) - set(\${resolved_item_var} \${ri} PARENT_SCOPE) - set(\${resolved_var} 1 PARENT_SCOPE) - endif () - endif() + install(CODE " + function(gp_item_default_embedded_path_override item default_embedded_path_var) + if (\${item} MATCHES ${OSG_PLUGIN_PREFIX_DIR}) + set(path \"@executable_path/../PlugIns/${OSG_PLUGIN_PREFIX_DIR}\") + set(\${default_embedded_path_var} \"\${path}\" PARENT_SCOPE) endif() - endfunction(gp_resolve_item_override) + endfunction() - fixup_bundle(\"${OPENMW_APP}\" \"${PLUGINS}\" \"${DIRS}\") - fixup_bundle(\"${OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"${DIRS}\") + cmake_policy(SET CMP0009 OLD) + fixup_bundle(\"${INSTALLED_OPENMW_APP}\" \"${PLUGINS}\" \"\") + fixup_bundle(\"${INSTALLED_OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"\") " COMPONENT Runtime) - include(CPack) + include(CPack) endif (APPLE) # Doxygen Target -- simply run 'make doc' or 'make doc_pages' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..a9cd6a6903 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +Description +=========== + +Your pull request description should include (if applicable): + +* A link back to the bug report or forum discussion that prompted the change +* Summary of the changes made +* Reasoning / motivation behind the change +* What testing you have carried out to verify the change + +Other notes +=========== + +* Separate your work into multiple pull requests whenever possible. As a rule of thumb, each feature and each bugfix should go into a separate PR, unless they are closely related or dependent upon each other. Small pull requests are easier to review, and are less likely to require further changes before we can merge them. A "mega" pull request with lots of unrelated commits in it is likely to get held up in review for a long time. +* Feel free to submit incomplete pull requests. Even if the work can not be merged yet, pull requests are a great place to collect early feedback. Just make sure to mark it as *[Incomplete]* or *[Do not merge yet]* in the title. +* If you plan on contributing often, please read the [Developer Reference](https://wiki.openmw.org/index.php?title=Developer_Reference) on our wiki, especially the [Policies and Standards](https://wiki.openmw.org/index.php?title=Policies_and_Standards). diff --git a/README.md b/README.md index f62800e1fd..f353cd76ef 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,11 @@ OpenMW [![Build Status](https://img.shields.io/travis/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) -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. +OpenMW is a recreation of 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.36.0 +OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. + +* Version: 0.37.0 * License: GPL (see docs/license/GPL3.txt for more information) * Website: http://www.openmw.org * IRC: #openmw on irc.freenode.net @@ -14,6 +15,13 @@ Morrowind by Bethesda Softworks. You need to own and install the original game f Font Licenses: * DejaVuLGCSansMono.ttf: custom (see docs/license/DejaVu Font License.txt for more information) +Current Status +-------------- + +The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://bugs.openmw.org/versions/21) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces. + +Pre-existing modifications created for the original Morrowind engine can be hit-and-miss. The OpenMW script compiler performs more thorough error-checking than Morrowind does, meaning that a mod created for Morrowind may not necessarily run in OpenMW. Some mods also rely on quirky behaviour or engine bugs in order to work. We are considering such compatibility issues on a case-by-case basis - in some cases adding a workaround to OpenMW may be feasible, in other cases fixing the mod will be the only option. If you know of any mods that work or don't work, feel free to add them to the [Mod status](https://wiki.openmw.org/index.php?title=Mod_status) wiki page. + Getting Started --------------- diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index c0a6dcc81f..7f305052b1 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -237,12 +238,14 @@ int extract(Bsa::BSAFile& bsa, Arguments& info) } // Get a stream for the file to extract - Ogre::DataStreamPtr data = bsa.getFile(archivePath.c_str()); + Files::IStreamPtr stream = bsa.getFile(archivePath.c_str()); + bfs::ofstream out(target, std::ios::binary); // Write the file to disk std::cout << "Extracting " << info.extractfile << " to " << target << std::endl; - out.write(data->getAsString().c_str(), data->size()); + + out << stream->rdbuf(); out.close(); return 0; @@ -276,12 +279,12 @@ int extractAll(Bsa::BSAFile& bsa, Arguments& info) // Get a stream for the file to extract // (inefficient because getFile iter on the list again) - Ogre::DataStreamPtr data = bsa.getFile(archivePath); + Files::IStreamPtr data = bsa.getFile(archivePath); bfs::ofstream out(target, std::ios::binary); // Write the file to disk std::cout << "Extracting " << target << std::endl; - out.write(data->getAsString().c_str(), data->size()); + out << data->rdbuf(); out.close(); } diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index eef970d377..be90afec38 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -26,7 +27,8 @@ struct ESMData std::vector masters; std::deque mRecords; - std::map > mCellRefs; + // Value: (Reference, Deleted flag) + std::map > > mCellRefs; std::map mRecordStats; static const std::set sLabeledRec; @@ -254,7 +256,7 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) while(cell.getNextRef(esm, ref, deleted)) { if (save) { - info.data.mCellRefs[&cell].push_back(ref); + info.data.mCellRefs[&cell].push_back(std::make_pair(ref, deleted)); } if(quiet) continue; @@ -284,7 +286,7 @@ void printRaw(ESM::ESMReader &esm) esm.getRecHeader(); while(esm.hasMoreSubs()) { - uint64_t offs = esm.getOffset(); + size_t offs = esm.getFileOffset(); esm.getSubName(); esm.skipHSub(); n = esm.retSubName(); @@ -351,30 +353,9 @@ int load(Arguments& info) uint32_t flags; esm.getRecHeader(flags); - // Is the user interested in this record type? - bool interested = true; - if (!info.types.empty()) - { - std::vector::iterator match; - match = std::find(info.types.begin(), info.types.end(), - n.toString()); - if (match == info.types.end()) interested = false; - } - - std::string id = esm.getHNOString("NAME"); - if (id.empty()) - id = esm.getHNOString("INAM"); - - if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, id)) - interested = false; - - if(!quiet && interested) - std::cout << "\nRecord: " << n.toString() - << " '" << id << "'\n"; - EsmTool::RecordBase *record = EsmTool::RecordBase::create(n); - - if (record == 0) { + if (record == 0) + { if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end()) { std::cout << "Skipping " << n.toString() << " records." << std::endl; @@ -384,28 +365,46 @@ int load(Arguments& info) esm.skipRecord(); if (quiet) break; std::cout << " Skipping\n"; - } else { - if (record->getType().val == ESM::REC_GMST) { - // preset id for GameSetting record - record->cast()->get().mId = id; - } - record->setId(id); - record->setFlags((int) flags); - record->setPrintPlain(info.plain_given); - record->load(esm); - if (!quiet && interested) record->print(); - if (record->getType().val == ESM::REC_CELL && loadCells && interested) { - loadCell(record->cast()->get(), esm, info); - } - - if (save) { - info.data.mRecords.push_back(record); - } else { - delete record; - } - ++info.data.mRecordStats[n.val]; + continue; } + + record->setFlags(static_cast(flags)); + record->setPrintPlain(info.plain_given); + record->load(esm); + + // Is the user interested in this record type? + bool interested = true; + if (!info.types.empty()) + { + std::vector::iterator match; + match = std::find(info.types.begin(), info.types.end(), n.toString()); + if (match == info.types.end()) interested = false; + } + + if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, record->getId())) + interested = false; + + if(!quiet && interested) + { + std::cout << "\nRecord: " << n.toString() << " '" << record->getId() << "'\n"; + record->print(); + } + + if (record->getType().val == ESM::REC_CELL && loadCells && interested) + { + loadCell(record->cast()->get(), esm, info); + } + + if (save) + { + info.data.mRecords.push_back(record); + } + else + { + delete record; + } + ++info.data.mRecordStats[n.val]; } } catch(std::exception &e) { @@ -492,28 +491,19 @@ int clone(Arguments& info) for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it) { EsmTool::RecordBase *record = *it; - name.val = record->getType().val; esm.startRecord(name.toString(), record->getFlags()); - // TODO wrap this with std::set - if (ESMData::sLabeledRec.count(name.val) > 0) { - esm.writeHNCString("NAME", record->getId()); - } else { - esm.writeHNOString("NAME", record->getId()); - } - record->save(esm); - if (name.val == ESM::REC_CELL) { ESM::Cell *ptr = &record->cast()->get(); if (!info.data.mCellRefs[ptr].empty()) { - typedef std::deque RefList; + typedef std::deque > RefList; RefList &refs = info.data.mCellRefs[ptr]; for (RefList::iterator refIt = refs.begin(); refIt != refs.end(); ++refIt) { - refIt->save(esm); + refIt->first.save(esm, refIt->second); } } } diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 2ee6c54bbf..d7cbea73b0 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -405,6 +405,7 @@ void Record::print() std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Script: " << mData.mScript << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -419,6 +420,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl; printEffectList(mData.mEffects); + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -447,6 +449,7 @@ void Record::print() if (pit->mFemale != "") std::cout << " Female Name: " << pit->mFemale << std::endl; } + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -461,6 +464,7 @@ void Record::print() std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -474,6 +478,7 @@ void Record::print() std::cout << " Part: " << meshPartLabel(mData.mData.mPart) << " (" << (int)mData.mData.mPart << ")" << std::endl; std::cout << " Vampire: " << (int)mData.mData.mVampire << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -493,15 +498,16 @@ void Record::print() std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl; if (mPrintPlain) { - std::cout << " Text:" << std::endl; - std::cout << "START--------------------------------------" << std::endl; - std::cout << mData.mText << std::endl; - std::cout << "END----------------------------------------" << std::endl; + std::cout << " Text:" << std::endl; + std::cout << "START--------------------------------------" << std::endl; + std::cout << mData.mText << std::endl; + std::cout << "END----------------------------------------" << std::endl; } else { - std::cout << " Text: [skipped]" << std::endl; + std::cout << " Text: [skipped]" << std::endl; } + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -513,6 +519,7 @@ void Record::print() std::vector::iterator pit; for (pit = mData.mPowers.mList.begin(); pit != mData.mPowers.mList.end(); ++pit) std::cout << " Power: " << *pit << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -541,6 +548,7 @@ void Record::print() std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl; std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } @@ -563,6 +571,7 @@ void Record::print() for (int i = 0; i != 5; i++) std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][1]) << " (" << mData.mData.mSkills[i][1] << ")" << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -589,6 +598,7 @@ void Record::print() if (pit->mFemale != "") std::cout << " Female Name: " << pit->mFemale << std::endl; } + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -604,6 +614,7 @@ void Record::print() for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); ++cit) std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount << " Item: " << cit->mItem.toString() << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -670,6 +681,7 @@ void Record::print() std::vector::iterator pit; for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit) printAIPackage(*pit); + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -677,6 +689,7 @@ void Record::print() { std::cout << " Type: " << dialogTypeLabel(mData.mType) << " (" << (int)mData.mType << ")" << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; // Sadly, there are no DialInfos, because the loader dumps as it // loads, rather than loading and then dumping. :-( Anyone mind if // I change this? @@ -693,6 +706,7 @@ void Record::print() std::cout << " Script: " << mData.mScript << std::endl; std::cout << " OpenSound: " << mData.mOpenSound << std::endl; std::cout << " CloseSound: " << mData.mCloseSound << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -704,6 +718,7 @@ void Record::print() std::cout << " Charge: " << mData.mData.mCharge << std::endl; std::cout << " AutoCalc: " << mData.mData.mAutocalc << std::endl; printEffectList(mData.mEffects); + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -737,12 +752,14 @@ void Record::print() std::map::iterator rit; for (rit = mData.mReactions.begin(); rit != mData.mReactions.end(); ++rit) std::cout << " Reaction: " << rit->second << " = " << rit->first << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " " << mData.mValue << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -799,16 +816,17 @@ void Record::print() { if (mPrintPlain) { - std::cout << " Result Script:" << std::endl; - std::cout << "START--------------------------------------" << std::endl; - std::cout << mData.mResultScript << std::endl; - std::cout << "END----------------------------------------" << std::endl; + std::cout << " Result Script:" << std::endl; + std::cout << "START--------------------------------------" << std::endl; + std::cout << mData.mResultScript << std::endl; + std::cout << "END----------------------------------------" << std::endl; } else { - std::cout << " Result Script: [skipped]" << std::endl; + std::cout << " Result Script: [skipped]" << std::endl; } } + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -832,6 +850,7 @@ void Record::print() std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i]) << " (" << mData.mData.mAttributes[i] << ")" << std::endl; } + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -841,19 +860,15 @@ void Record::print() std::cout << " Flags: " << landFlags(mData.mFlags) << std::endl; std::cout << " DataTypes: " << mData.mDataTypes << std::endl; - // Seems like this should done with reference counting in the - // loader to me. But I'm not really knowledgable about this - // record type yet. --Cory - bool wasLoaded = (mData.mDataLoaded != 0); - if (mData.mDataTypes) mData.loadData(mData.mDataTypes); - if (mData.mDataLoaded) + if (const ESM::Land::LandData *data = mData.getLandData (mData.mDataTypes)) { - std::cout << " Height Offset: " << mData.mLandData->mHeightOffset << std::endl; + std::cout << " Height Offset: " << data->mHeightOffset << std::endl; // Lots of missing members. - std::cout << " Unknown1: " << mData.mLandData->mUnk1 << std::endl; - std::cout << " Unknown2: " << mData.mLandData->mUnk2 << std::endl; + std::cout << " Unknown1: " << data->mUnk1 << std::endl; + std::cout << " Unknown2: " << data->mUnk2 << std::endl; } - if (!wasLoaded) mData.unloadData(); + mData.unloadData(); + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -866,6 +881,7 @@ void Record::print() for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) std::cout << " Creature: Level: " << iit->mLevel << " Creature: " << iit->mId << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -878,6 +894,7 @@ void Record::print() for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) std::cout << " Inventory: Level: " << iit->mLevel << " Item: " << iit->mId << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -898,6 +915,7 @@ void Record::print() std::cout << " Duration: " << mData.mData.mTime << std::endl; std::cout << " Radius: " << mData.mData.mRadius << std::endl; std::cout << " Color: " << mData.mData.mColor << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -912,6 +930,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -926,6 +945,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -940,6 +960,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -948,6 +969,7 @@ void Record::print() std::cout << " Id: " << mData.mId << std::endl; std::cout << " Index: " << mData.mIndex << std::endl; std::cout << " Texture: " << mData.mTexture << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -998,6 +1020,7 @@ void Record::print() std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Is Key: " << mData.mData.mIsKey << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1083,6 +1106,8 @@ void Record::print() std::vector::iterator pit; for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit) printAIPackage(*pit); + + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1117,6 +1142,8 @@ void Record::print() std::cout << " BAD POINT IN EDGE!" << std::endl; i++; } + + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1157,6 +1184,8 @@ void Record::print() std::vector::iterator sit; for (sit = mData.mPowers.mList.begin(); sit != mData.mPowers.mList.end(); ++sit) std::cout << " Power: " << *sit << std::endl; + + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1207,15 +1236,17 @@ void Record::print() if (mPrintPlain) { - std::cout << " Script:" << std::endl; - std::cout << "START--------------------------------------" << std::endl; - std::cout << mData.mScriptText << std::endl; - std::cout << "END----------------------------------------" << std::endl; + std::cout << " Script:" << std::endl; + std::cout << "START--------------------------------------" << std::endl; + std::cout << mData.mScriptText << std::endl; + std::cout << "END----------------------------------------" << std::endl; } else { - std::cout << " Script: [skipped]" << std::endl; + std::cout << " Script: [skipped]" << std::endl; } + + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1239,6 +1270,7 @@ void Record::print() std::cout << " Sound: " << mData.mSound << std::endl; std::cout << " Type: " << soundTypeLabel(mData.mType) << " (" << mData.mType << ")" << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1249,6 +1281,7 @@ void Record::print() if (mData.mData.mMinRange != 0 && mData.mData.mMaxRange != 0) std::cout << " Range: " << (int)mData.mData.mMinRange << " - " << (int)mData.mData.mMaxRange << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1260,13 +1293,15 @@ void Record::print() std::cout << " Flags: " << spellFlags(mData.mData.mFlags) << std::endl; std::cout << " Cost: " << mData.mData.mCost << std::endl; printEffectList(mData.mEffects); + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { - std::cout << "Start Script: " << mData.mId << std::endl; - std::cout << "Start Data: " << mData.mData << std::endl; + std::cout << " Start Script: " << mData.mId << std::endl; + std::cout << " Start Data: " << mData.mData << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1307,6 +1342,37 @@ void Record::print() if (mData.mData.mThrust[0] != 0 && mData.mData.mThrust[1] != 0) std::cout << " Thrust: " << (int)mData.mData.mThrust[0] << "-" << (int)mData.mData.mThrust[1] << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; +} + +template<> +std::string Record::getId() const +{ + return mData.mName; +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for Land record +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for MagicEffect record +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for Pathgrid record +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for Skill record } } // end namespace diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp index c1b90ac2bc..dca38409fe 100644 --- a/apps/esmtool/record.hpp +++ b/apps/esmtool/record.hpp @@ -32,13 +32,7 @@ namespace EsmTool virtual ~RecordBase() {} - const std::string &getId() const { - return mId; - } - - void setId(const std::string &id) { - mId = id; - } + virtual std::string getId() const = 0; uint32_t getFlags() const { return mFlags; @@ -53,7 +47,7 @@ namespace EsmTool } void setPrintPlain(bool plain) { - mPrintPlain = plain; + mPrintPlain = plain; } virtual void load(ESM::ESMReader &esm) = 0; @@ -73,22 +67,37 @@ namespace EsmTool class Record : public RecordBase { T mData; + bool mIsDeleted; public: + Record() + : mIsDeleted(false) + {} + + std::string getId() const { + return mData.mId; + } + T &get() { return mData; } void save(ESM::ESMWriter &esm) { - mData.save(esm); + mData.save(esm, mIsDeleted); } void load(ESM::ESMReader &esm) { - mData.load(esm); + mData.load(esm, mIsDeleted); } void print(); }; + + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; template<> void Record::print(); template<> void Record::print(); diff --git a/apps/essimporter/convertacdt.cpp b/apps/essimporter/convertacdt.cpp index 55a20ec3df..8f090b3fc4 100644 --- a/apps/essimporter/convertacdt.cpp +++ b/apps/essimporter/convertacdt.cpp @@ -41,9 +41,9 @@ namespace ESSImport { for (int i=0; i -#include -#include +#include #include #include @@ -15,12 +14,14 @@ namespace { - void convertImage(char* data, int size, int width, int height, Ogre::PixelFormat pf, const std::string& out) + void convertImage(char* data, int size, int width, int height, GLenum pf, const std::string& out) { - Ogre::Image screenshot; - Ogre::DataStreamPtr stream (new Ogre::MemoryDataStream(data, size)); - screenshot.loadRawData(stream, width, height, 1, pf); - screenshot.save(out); + osg::ref_ptr image (new osg::Image); + image->allocateImage(width, height, 1, pf, GL_UNSIGNED_BYTE); + memcpy(image->data(), data, size); + image->flipVertical(); + + osgDB::writeImageFile(*image, out); } @@ -71,17 +72,20 @@ namespace ESSImport data.resize(esm.getSubSize()); esm.getExact(&data[0], data.size()); - Ogre::DataStreamPtr stream (new Ogre::MemoryDataStream(&data[0], data.size())); - mGlobalMapImage.loadRawData(stream, maph.size, maph.size, 1, Ogre::PF_BYTE_RGB); + mGlobalMapImage = new osg::Image; + mGlobalMapImage->allocateImage(maph.size, maph.size, 1, GL_RGB, GL_UNSIGNED_BYTE); + memcpy(mGlobalMapImage->data(), &data[0], data.size()); + // to match openmw size - mGlobalMapImage.resize(maph.size*2, maph.size*2, Ogre::Image::FILTER_BILINEAR); + // FIXME: filtering? + mGlobalMapImage->scaleImage(maph.size*2, maph.size*2, 1, GL_UNSIGNED_BYTE); } void ConvertFMAP::write(ESM::ESMWriter &esm) { - int numcells = mGlobalMapImage.getWidth() / 18; // NB truncating, doesn't divide perfectly + int numcells = mGlobalMapImage->s() / 18; // NB truncating, doesn't divide perfectly // with the 512x512 map the game has by default - int cellSize = mGlobalMapImage.getWidth()/numcells; + int cellSize = mGlobalMapImage->s()/numcells; // Note the upper left corner of the (0,0) cell should be at (width/2, height/2) @@ -90,12 +94,14 @@ namespace ESSImport mContext->mGlobalMapState.mBounds.mMinY = -(numcells-1)/2; mContext->mGlobalMapState.mBounds.mMaxY = numcells/2; - Ogre::Image image2; - std::vector data; + osg::ref_ptr image2 (new osg::Image); int width = cellSize*numcells; int height = cellSize*numcells; + std::vector data; data.resize(width*height*4, 0); - image2.loadDynamicImage(&data[0], width, height, Ogre::PF_BYTE_RGBA); + + image2->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE); + memcpy(image2->data(), &data[0], data.size()); for (std::set >::const_iterator it = mContext->mExploredCells.begin(); it != mContext->mExploredCells.end(); ++it) { @@ -108,8 +114,8 @@ namespace ESSImport continue; } - int imageLeftSrc = mGlobalMapImage.getWidth()/2; - int imageTopSrc = mGlobalMapImage.getHeight()/2; + int imageLeftSrc = mGlobalMapImage->s()/2; + int imageTopSrc = mGlobalMapImage->t()/2; imageLeftSrc += it->first * cellSize; imageTopSrc -= it->second * cellSize; int imageLeftDst = width/2; @@ -118,13 +124,31 @@ namespace ESSImport imageTopDst -= it->second * cellSize; for (int x=0; xdata(imageLeftSrc+x, imageTopSrc+y, 0); + *(unsigned int*)image2->data(imageLeftDst+x, imageTopDst+y, 0) = col; + } } - Ogre::DataStreamPtr encoded = image2.encode("png"); - mContext->mGlobalMapState.mImageData.resize(encoded->size()); - encoded->read(&mContext->mGlobalMapState.mImageData[0], encoded->size()); + std::stringstream ostream; + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); + if (!readerwriter) + { + std::cerr << "can't write global map image, no png readerwriter found" << std::endl; + return; + } + + image2->flipVertical(); + + osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image2, ostream); + if (!result.success()) + { + std::cerr << "can't write global map image: " << result.message() << " code " << result.status() << std::endl; + return; + } + + std::string outData = ostream.str(); + mContext->mGlobalMapState.mImageData = std::vector(outData.begin(), outData.end()); esm.startRecord(ESM::REC_GMAP); mContext->mGlobalMapState.save(esm); @@ -134,9 +158,9 @@ namespace ESSImport void ConvertCell::read(ESM::ESMReader &esm) { ESM::Cell cell; - std::string id = esm.getHNString("NAME"); - cell.mName = id; - cell.load(esm, false); + bool isDeleted = false; + + cell.load(esm, isDeleted, false); // I wonder what 0x40 does? if (cell.isExterior() && cell.mData.mFlags & 0x20) @@ -145,7 +169,7 @@ namespace ESSImport } // note if the player is in a nameless exterior cell, we will assign the cellId later based on player position - if (id == mContext->mPlayerCellName) + if (cell.mName == mContext->mPlayerCellName) { mContext->mPlayer.mCellId = cell.getCellId(); } @@ -194,7 +218,7 @@ namespace ESSImport std::ostringstream filename; filename << "fog_" << cell.mData.mX << "_" << cell.mData.mY << ".tga"; - convertImage((char*)&newcell.mFogOfWar[0], newcell.mFogOfWar.size()*4, 16, 16, Ogre::PF_BYTE_RGBA, filename.str()); + convertImage((char*)&newcell.mFogOfWar[0], newcell.mFogOfWar.size()*4, 16, 16, GL_RGBA, filename.str()); } } @@ -253,7 +277,7 @@ namespace ESSImport if (cell.isExterior()) mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell; else - mIntCells[id] = newcell; + mIntCells[cell.mName] = newcell; } void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm) diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 5711e6754b..f364e166c8 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -1,7 +1,10 @@ #ifndef OPENMW_ESSIMPORT_CONVERTER_H #define OPENMW_ESSIMPORT_CONVERTER_H -#include +#include + +#include +#include #include #include @@ -51,6 +54,8 @@ public: void setContext(Context& context) { mContext = &context; } + /// @note The load method of ESM records accept the deleted flag as a parameter. + /// I don't know can the DELE sub-record appear in saved games, so the deleted flag will be ignored. virtual void read(ESM::ESMReader& esm) { } @@ -75,10 +80,11 @@ public: virtual void read(ESM::ESMReader& esm) { - std::string id = esm.getHNString("NAME"); T record; - record.load(esm); - mRecords[id] = record; + bool isDeleted = false; + + record.load(esm, isDeleted); + mRecords[record.mId] = record; } virtual void write(ESM::ESMWriter& esm) @@ -86,7 +92,6 @@ public: for (typename std::map::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it) { esm.startRecord(T::sRecordId); - esm.writeHNString("NAME", it->first); it->second.save(esm); esm.endRecord(T::sRecordId); } @@ -102,14 +107,15 @@ public: virtual void read(ESM::ESMReader &esm) { ESM::NPC npc; - std::string id = esm.getHNString("NAME"); - npc.load(esm); - if (id != "player") + bool isDeleted = false; + + npc.load(esm, isDeleted); + if (npc.mId != "player") { // Handles changes to the NPC struct, but since there is no index here // it will apply to ALL instances of the class. seems to be the reason for the // "feature" in MW where changing AI settings of one guard will change it for all guards of that refID. - mContext->mNpcs[Misc::StringUtils::lowerCase(id)] = npc; + mContext->mNpcs[Misc::StringUtils::lowerCase(npc.mId)] = npc; } else { @@ -139,9 +145,10 @@ public: { // See comment in ConvertNPC ESM::Creature creature; - std::string id = esm.getHNString("NAME"); - creature.load(esm); - mContext->mCreatures[Misc::StringUtils::lowerCase(id)] = creature; + bool isDeleted = false; + + creature.load(esm, isDeleted); + mContext->mCreatures[Misc::StringUtils::lowerCase(creature.mId)] = creature; } }; @@ -154,18 +161,19 @@ class ConvertGlobal : public DefaultConverter public: virtual void read(ESM::ESMReader &esm) { - std::string id = esm.getHNString("NAME"); ESM::Global global; - global.load(esm); - if (Misc::StringUtils::ciEqual(id, "gamehour")) + bool isDeleted = false; + + global.load(esm, isDeleted); + if (Misc::StringUtils::ciEqual(global.mId, "gamehour")) mContext->mHour = global.mValue.getFloat(); - if (Misc::StringUtils::ciEqual(id, "day")) + if (Misc::StringUtils::ciEqual(global.mId, "day")) mContext->mDay = global.mValue.getInteger(); - if (Misc::StringUtils::ciEqual(id, "month")) + if (Misc::StringUtils::ciEqual(global.mId, "month")) mContext->mMonth = global.mValue.getInteger(); - if (Misc::StringUtils::ciEqual(id, "year")) + if (Misc::StringUtils::ciEqual(global.mId, "year")) mContext->mYear = global.mValue.getInteger(); - mRecords[id] = global; + mRecords[global.mId] = global; } }; @@ -174,14 +182,14 @@ class ConvertClass : public DefaultConverter public: virtual void read(ESM::ESMReader &esm) { - std::string id = esm.getHNString("NAME"); ESM::Class class_; - class_.load(esm); + bool isDeleted = false; - if (id == "NEWCLASSID_CHARGEN") + class_.load(esm, isDeleted); + if (class_.mId == "NEWCLASSID_CHARGEN") mContext->mCustomPlayerClassName = class_.mName; - mRecords[id] = class_; + mRecords[class_.mId] = class_; } }; @@ -190,13 +198,14 @@ class ConvertBook : public DefaultConverter public: virtual void read(ESM::ESMReader &esm) { - std::string id = esm.getHNString("NAME"); ESM::Book book; - book.load(esm); - if (book.mData.mSkillID == -1) - mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(id)); + bool isDeleted = false; - mRecords[id] = book; + book.load(esm, isDeleted); + if (book.mData.mSkillID == -1) + mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(book.mId)); + + mRecords[book.mId] = book; } }; @@ -311,7 +320,7 @@ public: virtual void write(ESM::ESMWriter &esm); private: - Ogre::Image mGlobalMapImage; + osg::ref_ptr mGlobalMapImage; }; class ConvertCell : public Converter @@ -368,11 +377,12 @@ class ConvertFACT : public Converter public: virtual void read(ESM::ESMReader& esm) { - std::string id = esm.getHNString("NAME"); ESM::Faction faction; - faction.load(esm); + bool isDeleted = false; + + faction.load(esm, isDeleted); + std::string id = Misc::StringUtils::lowerCase(faction.mId); - Misc::StringUtils::toLower(id); for (std::map::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it) { std::string faction2 = Misc::StringUtils::lowerCase(it->first); @@ -388,7 +398,7 @@ public: virtual void read(ESM::ESMReader &esm) { std::string itemid = esm.getHNString("NAME"); - Misc::StringUtils::toLower(itemid); + Misc::StringUtils::lowerCaseInPlace(itemid); while (esm.isNextSub("FNAM") || esm.isNextSub("ONAM")) { @@ -504,60 +514,40 @@ class ConvertGAME : public Converter public: ConvertGAME() : mHasGame(false) {} - std::string toString(int weatherId) - { - switch (weatherId) - { - case 0: - return "clear"; - case 1: - return "cloudy"; - case 2: - return "foggy"; - case 3: - return "overcast"; - case 4: - return "rain"; - case 5: - return "thunderstorm"; - case 6: - return "ashstorm"; - case 7: - return "blight"; - case 8: - return "snow"; - case 9: - return "blizzard"; - case -1: - return ""; - default: - { - std::stringstream error; - error << "unknown weather id: " << weatherId; - throw std::runtime_error(error.str()); - } - } - } - virtual void read(ESM::ESMReader &esm) { mGame.load(esm); mHasGame = true; } + int validateWeatherID(int weatherID) + { + if(weatherID >= -1 && weatherID < 10) + { + return weatherID; + } + else + { + std::stringstream error; + error << "Invalid weather ID:" << weatherID << std::endl; + throw std::runtime_error(error.str()); + } + } + virtual void write(ESM::ESMWriter &esm) { if (!mHasGame) return; esm.startRecord(ESM::REC_WTHR); ESM::WeatherState weather; - weather.mCurrentWeather = toString(mGame.mGMDT.mCurrentWeather); - weather.mNextWeather = toString(mGame.mGMDT.mNextWeather); - weather.mRemainingTransitionTime = mGame.mGMDT.mWeatherTransition/100.f*(0.015f*24*3600); - weather.mHour = mContext->mHour; - weather.mWindSpeed = 0.f; - weather.mTimePassed = 0.0; - weather.mFirstUpdate = false; + weather.mTimePassed = 0.0f; + weather.mFastForward = false; + weather.mWeatherUpdateTime = mGame.mGMDT.mTimeOfNextTransition - mContext->mHour; + weather.mTransitionFactor = 1 - (mGame.mGMDT.mWeatherTransition / 100.0f); + weather.mCurrentWeather = validateWeatherID(mGame.mGMDT.mCurrentWeather); + weather.mNextWeather = validateWeatherID(mGame.mGMDT.mNextWeather); + weather.mQueuedWeather = -1; + // TODO: Determine how ModRegion modifiers are saved in Morrowind. weather.save(esm); esm.endRecord(ESM::REC_WTHR); } diff --git a/apps/essimporter/convertplayer.cpp b/apps/essimporter/convertplayer.cpp index 532efa7f5b..5718201f74 100644 --- a/apps/essimporter/convertplayer.cpp +++ b/apps/essimporter/convertplayer.cpp @@ -1,5 +1,7 @@ #include "convertplayer.hpp" +#include + namespace ESSImport { @@ -18,7 +20,7 @@ namespace ESSImport for (int i=0; i<8; ++i) out.mObject.mNpcStats.mSkillIncrease[i] = pcdt.mPNAM.mSkillIncreases[i]; for (int i=0; i<27; ++i) - out.mObject.mNpcStats.mSkills[i].mRegular.mProgress = pcdt.mPNAM.mSkillProgress[i]; + out.mObject.mNpcStats.mSkills[i].mProgress = pcdt.mPNAM.mSkillProgress[i]; out.mObject.mNpcStats.mLevelProgress = pcdt.mPNAM.mLevelProgress; if (pcdt.mPNAM.mDrawState & PCDT::DrawState_Weapon) diff --git a/apps/essimporter/importacdt.cpp b/apps/essimporter/importacdt.cpp index 9d881515dd..239c46698d 100644 --- a/apps/essimporter/importacdt.cpp +++ b/apps/essimporter/importacdt.cpp @@ -10,7 +10,21 @@ namespace ESSImport void ActorData::load(ESM::ESMReader &esm) { if (esm.isNextSub("ACTN")) + { + /* + Activation flags: + ActivationFlag_UseEnabled = 1 + ActivationFlag_OnActivate = 2 + ActivationFlag_OnDeath = 10h + ActivationFlag_OnKnockout = 20h + ActivationFlag_OnMurder = 40h + ActivationFlag_DoorOpening = 100h + ActivationFlag_DoorClosing = 200h + ActivationFlag_DoorJammedOpening = 400h + ActivationFlag_DoorJammedClosing = 800h + */ esm.skipHSub(); + } if (esm.isNextSub("STPR")) esm.skipHSub(); @@ -18,7 +32,8 @@ namespace ESSImport if (esm.isNextSub("MNAM")) esm.skipHSub(); - ESM::CellRef::loadData(esm); + bool isDeleted = false; + ESM::CellRef::loadData(esm, isDeleted); mHasACDT = false; if (esm.isNextSub("ACDT")) diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index d5ed43b8a0..4fbf062170 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -1,7 +1,11 @@ #include "importer.hpp" + +#include + #include -#include +#include +#include #include #include @@ -32,13 +36,48 @@ namespace void writeScreenshot(const ESM::Header& fileHeader, ESM::SavedGame& out) { - Ogre::Image screenshot; - std::vector screenshotData = fileHeader.mSCRS; // MemoryDataStream doesn't work with const data :( - Ogre::DataStreamPtr screenshotStream (new Ogre::MemoryDataStream(&screenshotData[0], screenshotData.size())); - screenshot.loadRawData(screenshotStream, 128, 128, 1, Ogre::PF_BYTE_BGRA); - Ogre::DataStreamPtr encoded = screenshot.encode("jpg"); - out.mScreenshot.resize(encoded->size()); - encoded->read(&out.mScreenshot[0], encoded->size()); + if (fileHeader.mSCRS.size() != 128*128*4) + { + std::cerr << "unexpected screenshot size " << std::endl; + return; + } + + osg::ref_ptr image (new osg::Image); + image->allocateImage(128, 128, 1, GL_RGB, GL_UNSIGNED_BYTE); + + // need to convert pixel format from BGRA to RGB as the jpg readerwriter doesn't support it otherwise + std::vector::const_iterator it = fileHeader.mSCRS.begin(); + for (int y=0; y<128; ++y) + { + for (int x=0; x<128; ++x) + { + *(image->data(x,y)+2) = *it++; + *(image->data(x,y)+1) = *it++; + *image->data(x,y) = *it++; + it++; // skip alpha + } + } + + image->flipVertical(); + + std::stringstream ostream; + + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg"); + if (!readerwriter) + { + std::cerr << "can't write screenshot: no jpg readerwriter found" << std::endl; + return; + } + + osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image, ostream); + if (!result.success()) + { + std::cerr << "can't write screenshot: " << result.message() << " code " << result.status() << std::endl; + return; + } + + std::string data = ostream.str(); + out.mScreenshot = std::vector(data.begin(), data.end()); } } @@ -127,7 +166,9 @@ namespace ESSImport if (i >= file2.mRecords.size()) { + std::ios::fmtflags f(std::cout.flags()); std::cout << "Record in file1 not present in file2: (1) 0x" << std::hex << rec.mFileOffset << std::endl; + std::cout.flags(f); return; } @@ -135,7 +176,9 @@ namespace ESSImport if (rec.mName != rec2.mName) { + std::ios::fmtflags f(std::cout.flags()); std::cout << "Different record name at (2) 0x" << std::hex << rec2.mFileOffset << std::endl; + std::cout.flags(f); return; // TODO: try to recover } @@ -146,7 +189,9 @@ namespace ESSImport if (j >= rec2.mSubrecords.size()) { + std::ios::fmtflags f(std::cout.flags()); std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset << std::endl; + std::cout.flags(f); return; } @@ -154,8 +199,10 @@ namespace ESSImport if (sub.mName != sub2.mName) { + std::ios::fmtflags f(std::cout.flags()); std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset << " (2) 0x" << sub2.mFileOffset << std::endl; + std::cout.flags(f); break; // TODO: try to recover } @@ -164,6 +211,8 @@ namespace ESSImport if (blacklist.find(std::make_pair(rec.mName, sub.mName)) != blacklist.end()) continue; + std::ios::fmtflags f(std::cout.flags()); + std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset << " (2) 0x" << sub2.mFileOffset << std::endl; @@ -196,6 +245,7 @@ namespace ESSImport std::cout << "\033[0m"; } std::cout << std::endl; + std::cout.flags(f); } } } @@ -203,10 +253,6 @@ namespace ESSImport void Importer::run() { - // construct Ogre::Root to gain access to image codecs - Ogre::LogManager logman; - Ogre::Root root; - ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding)); ESM::ESMReader esm; esm.open(mEssFile); @@ -284,7 +330,11 @@ namespace ESSImport else { if (unknownRecords.insert(n.val).second) + { + std::ios::fmtflags f(std::cerr.flags()); std::cerr << "unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl; + std::cerr.flags(f); + } esm.skipRecord(); } @@ -292,7 +342,7 @@ namespace ESSImport ESM::ESMWriter writer; - writer.setFormat (ESM::Header::CurrentFormat); + writer.setFormat (ESM::SavedGame::sCurrentFormat); std::ofstream stream(mOutFile.c_str(), std::ios::binary); // all unused @@ -344,7 +394,7 @@ namespace ESSImport } writer.startRecord(ESM::REC_NPC_); - writer.writeHNString("NAME", "player"); + context.mPlayerBase.mId = "player"; context.mPlayerBase.save(writer); writer.endRecord(ESM::REC_NPC_); diff --git a/apps/essimporter/importercontext.hpp b/apps/essimporter/importercontext.hpp index 3b010cb8ff..c93dff269d 100644 --- a/apps/essimporter/importercontext.hpp +++ b/apps/essimporter/importercontext.hpp @@ -49,6 +49,10 @@ namespace ESSImport std::map mNpcs; Context() + : mDay(0) + , mMonth(0) + , mYear(0) + , mHour(0.f) { mPlayer.mAutoMove = 0; ESM::CellId playerCellId; diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp index d27cd5c8ca..3ec640d3d5 100644 --- a/apps/essimporter/importinventory.cpp +++ b/apps/essimporter/importinventory.cpp @@ -32,7 +32,8 @@ namespace ESSImport item.mSCRI.load(esm); // for XSOL and XCHG seen so far, but probably others too - item.ESM::CellRef::loadData(esm); + bool isDeleted = false; + item.ESM::CellRef::loadData(esm, isDeleted); int charge=-1; esm.getHNOT(charge, "XHLT"); diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index caf96054e7..75f76a5327 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -7,8 +7,6 @@ set(LAUNCHER textslotmsgbox.cpp settingspage.cpp - settings/graphicssettings.cpp - utils/profilescombobox.cpp utils/textinputdialog.cpp utils/lineedit.cpp @@ -24,8 +22,6 @@ set(LAUNCHER_HEADER textslotmsgbox.hpp settingspage.hpp - settings/graphicssettings.hpp - utils/profilescombobox.hpp utils/textinputdialog.hpp utils/lineedit.hpp @@ -92,8 +88,6 @@ add_executable(openmw-launcher ) target_link_libraries(openmw-launcher - ${OGRE_LIBRARIES} - ${OGRE_STATIC_PLUGINS} ${SDL2_LIBRARY_ONLY} components ) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index ec2e1246ef..0b0f8c75e2 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -75,7 +75,7 @@ bool Launcher::DataFilesPage::loadSettings() QStringList profiles = mLauncherSettings.getContentLists(); QString currentProfile = mLauncherSettings.getCurrentContentListName(); - qDebug() << "current profile is: " << currentProfile; + qDebug() << "The current profile is: " << currentProfile; foreach (const QString &item, profiles) addProfile (item, false); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 5176d7fa0d..622db4da46 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -12,16 +12,13 @@ #include -#include -#include - #include #include #include -#include "settings/graphicssettings.hpp" +#include QString getAspect(int x, int y) { @@ -35,14 +32,10 @@ QString getAspect(int x, int y) return QString(QString::number(xaspect) + ":" + QString::number(yaspect)); } -Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSetting, QWidget *parent) +Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent) : QWidget(parent) - , mOgre(NULL) - , mSelectedRenderSystem(NULL) - , mOpenGLRenderSystem(NULL) - , mDirect3DRenderSystem(NULL) , mCfgMgr(cfg) - , mGraphicsSettings(graphicsSetting) + , mEngineSettings(engineSettings) { setObjectName ("GraphicsPage"); setupUi(this); @@ -52,79 +45,12 @@ Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsS customWidthSpinBox->setMaximum(res.width()); customHeightSpinBox->setMaximum(res.height()); - connect(rendererComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(rendererChanged(const QString&))); connect(fullScreenCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotFullScreenChanged(int))); connect(standardRadioButton, SIGNAL(toggled(bool)), this, SLOT(slotStandardToggled(bool))); connect(screenComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(screenChanged(int))); } -bool Launcher::GraphicsPage::setupOgre() -{ - try - { - mOgre = mOgreInit.init(mCfgMgr.getLogPath().string() + "/launcherOgre.log"); - } - catch(Ogre::Exception &ex) - { - QString ogreError = QString::fromUtf8(ex.getFullDescription().c_str()); - QMessageBox msgBox; - msgBox.setWindowTitle("Error creating Ogre::Root"); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Failed to create the Ogre::Root object

\ - Press \"Show Details...\" for more information.
")); - msgBox.setDetailedText(ogreError); - msgBox.exec(); - - qCritical("Error creating Ogre::Root, the error reported was:\n %s", qPrintable(ogreError)); - return false; - } - - // Get the available renderers and put them in the combobox - const Ogre::RenderSystemList &renderers = mOgre->getAvailableRenderers(); - - for (Ogre::RenderSystemList::const_iterator r = renderers.begin(); r != renderers.end(); ++r) { - mSelectedRenderSystem = *r; - rendererComboBox->addItem((*r)->getName().c_str()); - } - - QString openGLName = QString("OpenGL Rendering Subsystem"); - QString direct3DName = QString("Direct3D9 Rendering Subsystem"); - - // Create separate rendersystems - mOpenGLRenderSystem = mOgre->getRenderSystemByName(openGLName.toStdString()); - mDirect3DRenderSystem = mOgre->getRenderSystemByName(direct3DName.toStdString()); - - if (!mOpenGLRenderSystem && !mDirect3DRenderSystem) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error creating renderer")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not select a valid render system

\ - Please make sure Ogre plugins were installed correctly.
")); - msgBox.exec(); - return false; - } - - // Now fill the GUI elements - int index = rendererComboBox->findText(mGraphicsSettings.value(QString("Video/render system"))); - if ( index != -1) { - rendererComboBox->setCurrentIndex(index); - } else { -#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 - rendererComboBox->setCurrentIndex(rendererComboBox->findText(direct3DName)); -#else - rendererComboBox->setCurrentIndex(rendererComboBox->findText(openGLName)); -#endif - } - - antiAliasingComboBox->clear(); - antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem)); - - return true; -} - bool Launcher::GraphicsPage::setupSDL() { int displays = SDL_GetNumVideoDisplays(); @@ -153,28 +79,27 @@ bool Launcher::GraphicsPage::loadSettings() { if (!setupSDL()) return false; - if (!mOgre && !setupOgre()) - return false; - if (mGraphicsSettings.value(QString("Video/vsync")) == QLatin1String("true")) + if (mEngineSettings.getBool("vsync", "Video")) vSyncCheckBox->setCheckState(Qt::Checked); - if (mGraphicsSettings.value(QString("Video/fullscreen")) == QLatin1String("true")) + if (mEngineSettings.getBool("fullscreen", "Video")) fullScreenCheckBox->setCheckState(Qt::Checked); - if (mGraphicsSettings.value(QString("Video/window border")) == QLatin1String("true")) + if (mEngineSettings.getBool("window border", "Video")) windowBorderCheckBox->setCheckState(Qt::Checked); - int aaIndex = antiAliasingComboBox->findText(mGraphicsSettings.value(QString("Video/antialiasing"))); + // aaValue is the actual value (0, 1, 2, 4, 8, 16) + int aaValue = mEngineSettings.getInt("antialiasing", "Video"); + // aaIndex is the index into the allowed values in the pull down. + int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue)); if (aaIndex != -1) antiAliasingComboBox->setCurrentIndex(aaIndex); - QString width = mGraphicsSettings.value(QString("Video/resolution x")); - QString height = mGraphicsSettings.value(QString("Video/resolution y")); - QString resolution = width + QString(" x ") + height; - QString screen = mGraphicsSettings.value(QString("Video/screen")); - - screenComboBox->setCurrentIndex(screen.toInt()); + int width = mEngineSettings.getInt("resolution x", "Video"); + int height = mEngineSettings.getInt("resolution y", "Video"); + QString resolution = QString::number(width) + QString(" x ") + QString::number(height); + screenComboBox->setCurrentIndex(mEngineSettings.getInt("screen", "Video")); int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith); @@ -183,9 +108,8 @@ bool Launcher::GraphicsPage::loadSettings() resolutionComboBox->setCurrentIndex(resIndex); } else { customRadioButton->toggle(); - customWidthSpinBox->setValue(width.toInt()); - customHeightSpinBox->setValue(height.toInt()); - + customWidthSpinBox->setValue(width); + customHeightSpinBox->setValue(height); } return true; @@ -193,65 +117,46 @@ bool Launcher::GraphicsPage::loadSettings() void Launcher::GraphicsPage::saveSettings() { - vSyncCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/vsync"), QString("true")) - : mGraphicsSettings.setValue(QString("Video/vsync"), QString("false")); + // Ensure we only set the new settings if they changed. This is to avoid cluttering the + // user settings file (which by definition should only contain settings the user has touched) + bool cVSync = vSyncCheckBox->checkState(); + if (cVSync != mEngineSettings.getBool("vsync", "Video")) + mEngineSettings.setBool("vsync", "Video", cVSync); - fullScreenCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("true")) - : mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("false")); + bool cFullScreen = fullScreenCheckBox->checkState(); + if (cFullScreen != mEngineSettings.getBool("fullscreen", "Video")) + mEngineSettings.setBool("fullscreen", "Video", cFullScreen); - windowBorderCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/window border"), QString("true")) - : mGraphicsSettings.setValue(QString("Video/window border"), QString("false")); - - mGraphicsSettings.setValue(QString("Video/antialiasing"), antiAliasingComboBox->currentText()); - mGraphicsSettings.setValue(QString("Video/render system"), rendererComboBox->currentText()); + bool cWindowBorder = windowBorderCheckBox->checkState(); + if (cWindowBorder != mEngineSettings.getBool("window border", "Video")) + mEngineSettings.setBool("window border", "Video", cWindowBorder); + int cAAValue = antiAliasingComboBox->currentText().toInt(); + if (cAAValue != mEngineSettings.getInt("antialiasing", "Video")) + mEngineSettings.setInt("antialiasing", "Video", cAAValue); + int cWidth = 0; + int cHeight = 0; if (standardRadioButton->isChecked()) { QRegExp resolutionRe(QString("(\\d+) x (\\d+).*")); - if (resolutionRe.exactMatch(resolutionComboBox->currentText().simplified())) { - mGraphicsSettings.setValue(QString("Video/resolution x"), resolutionRe.cap(1)); - mGraphicsSettings.setValue(QString("Video/resolution y"), resolutionRe.cap(2)); + cWidth = resolutionRe.cap(1).toInt(); + cHeight = resolutionRe.cap(2).toInt(); } } else { - mGraphicsSettings.setValue(QString("Video/resolution x"), QString::number(customWidthSpinBox->value())); - mGraphicsSettings.setValue(QString("Video/resolution y"), QString::number(customHeightSpinBox->value())); + cWidth = customWidthSpinBox->value(); + cHeight = customHeightSpinBox->value(); } - mGraphicsSettings.setValue(QString("Video/screen"), QString::number(screenComboBox->currentIndex())); -} + if (cWidth != mEngineSettings.getInt("resolution x", "Video")) + mEngineSettings.setInt("resolution x", "Video", cWidth); -QStringList Launcher::GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer) -{ - QStringList result; + if (cHeight != mEngineSettings.getInt("resolution y", "Video")) + mEngineSettings.setInt("resolution y", "Video", cHeight); - uint row = 0; - Ogre::ConfigOptionMap options = renderer->getConfigOptions(); - - for (Ogre::ConfigOptionMap::iterator i = options.begin (); i != options.end (); ++i, ++row) - { - Ogre::StringVector::iterator opt_it; - uint idx = 0; - - for (opt_it = i->second.possibleValues.begin(); - opt_it != i->second.possibleValues.end(); ++opt_it, ++idx) - { - if (strcmp (key.toStdString().c_str(), i->first.c_str()) == 0) { - result << ((key == "FSAA") ? QString("MSAA ") : QString("")) + QString::fromUtf8((*opt_it).c_str()).simplified(); - } - } - } - - // Sort ascending - qSort(result.begin(), result.end(), naturalSortLessThanCI); - - // Replace the zero option with Off - int index = result.indexOf("MSAA 0"); - - if (index != -1) - result.replace(index, tr("Off")); - - return result; + int cScreen = screenComboBox->currentIndex(); + if (cScreen != mEngineSettings.getInt("screen", "Video")) + mEngineSettings.setInt("screen", "Video", cScreen); } QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) @@ -316,15 +221,6 @@ QRect Launcher::GraphicsPage::getMaximumResolution() return max; } -void Launcher::GraphicsPage::rendererChanged(const QString &renderer) -{ - mSelectedRenderSystem = mOgre->getRenderSystemByName(renderer.toStdString()); - - antiAliasingComboBox->clear(); - - antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem)); -} - void Launcher::GraphicsPage::screenChanged(int screen) { if (screen >= 0) { diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index 213b6bccb4..e6eb53a3b7 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -3,11 +3,9 @@ #include -#include - #include "ui_graphicspage.h" -namespace Ogre { class Root; class RenderSystem; } +#include namespace Files { struct ConfigurationManager; } @@ -20,13 +18,12 @@ namespace Launcher Q_OBJECT public: - GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSettings, QWidget *parent = 0); + GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = 0); void saveSettings(); bool loadSettings(); public slots: - void rendererChanged(const QString &renderer); void screenChanged(int screen); private slots: @@ -34,20 +31,12 @@ namespace Launcher void slotStandardToggled(bool checked); private: - OgreInit::OgreInit mOgreInit; - Ogre::Root *mOgre; - Ogre::RenderSystem *mSelectedRenderSystem; - Ogre::RenderSystem *mOpenGLRenderSystem; - Ogre::RenderSystem *mDirect3DRenderSystem; - Files::ConfigurationManager &mCfgMgr; - GraphicsSettings &mGraphicsSettings; + Settings::Manager &mEngineSettings; - QStringList getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer); QStringList getAvailableResolutions(int screen); QRect getMaximumResolution(); - bool setupOgre(); bool setupSDL(); }; } diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 32fe8c93a7..282b3fb89f 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -41,15 +41,6 @@ int main(int argc, char *argv[]) dir.cdUp(); dir.cdUp(); } - - // force Qt to load only LOCAL plugins, don't touch system Qt installation - QDir pluginsPath(QCoreApplication::applicationDirPath()); - pluginsPath.cdUp(); - pluginsPath.cd("Plugins"); - - QStringList libraryPaths; - libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); - app.setLibraryPaths(libraryPaths); #endif QDir::setCurrent(dir.absolutePath()); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 2f7a9db332..60ae5b3a07 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -24,6 +24,15 @@ using namespace Process; +void cfgError(const QString& title, const QString& msg) { + QMessageBox msgBox; + msgBox.setWindowTitle(title); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(msg); + msgBox.exec(); +} + Launcher::MainDialog::MainDialog(QWidget *parent) : QMainWindow(parent), mGameSettings (mCfgMgr) { @@ -57,26 +66,6 @@ Launcher::MainDialog::MainDialog(QWidget *parent) // Remove what's this? button setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); - // Add version information to bottom of the window - QString revision(OPENMW_VERSION_COMMITHASH); - QString tag(OPENMW_VERSION_TAGHASH); - - versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); - if (!revision.isEmpty() && !tag.isEmpty()) - { - if (revision == tag) { - versionLabel->setText(tr("OpenMW %1 release").arg(OPENMW_VERSION)); - } else { - versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10))); - } - - // Add the compile date and time - versionLabel->setToolTip(tr("Compiled on %1 %2").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(), - QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate), - QLocale(QLocale::C).toTime(QString(__TIME__).simplified(), - QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate))); - } - createIcons(); } @@ -125,7 +114,7 @@ void Launcher::MainDialog::createPages() { mPlayPage = new PlayPage(this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); - mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this); + mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this); mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this); // Set the combobox of the play page to imitate the combobox on the datafilespage @@ -186,11 +175,34 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() return setup() ? FirstRunDialogResultContinue : FirstRunDialogResultFailure; } +void Launcher::MainDialog::setVersionLabel() +{ + // Add version information to bottom of the window + Version::Version v = Version::getOpenmwVersion(mGameSettings.value("resources").toUtf8().constData()); + + QString revision(QString::fromUtf8(v.mCommitHash.c_str())); + QString tag(QString::fromUtf8(v.mTagHash.c_str())); + + versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); + if (!v.mVersion.empty() && (revision.isEmpty() || revision == tag)) + versionLabel->setText(tr("OpenMW %1 release").arg(QString::fromUtf8(v.mVersion.c_str()))); + else + versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10))); + + // Add the compile date and time + versionLabel->setToolTip(tr("Compiled on %1 %2").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(), + QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate), + QLocale(QLocale::C).toTime(QString(__TIME__).simplified(), + QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate))); +} + bool Launcher::MainDialog::setup() { if (!setupGameSettings()) return false; + setVersionLabel(); + mLauncherSettings.setContentList(mGameSettings); if (!setupGraphicsSettings()) @@ -199,7 +211,7 @@ bool Launcher::MainDialog::setup() // Now create the pages as they need the settings createPages(); - // Call this so we can exit on Ogre/SDL errors before mainwindow is shown + // Call this so we can exit on SDL errors before mainwindow is shown if (!mGraphicsPage->loadSettings()) return false; @@ -245,6 +257,8 @@ void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem bool Launcher::MainDialog::setupLauncherSettings() { + mLauncherSettings.clear(); + mLauncherSettings.setMultiValueEnabled(true); QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); @@ -254,18 +268,14 @@ bool Launcher::MainDialog::setupLauncherSettings() paths.append(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName)); foreach (const QString &path, paths) { - qDebug() << "Loading config file:" << qPrintable(path); + qDebug() << "Loading config file:" << path.toUtf8().constData(); QFile file(path); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); + cfgError(tr("Error opening OpenMW configuration file"), + tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); return false; } QTextStream stream(&file); @@ -281,6 +291,8 @@ bool Launcher::MainDialog::setupLauncherSettings() bool Launcher::MainDialog::setupGameSettings() { + mGameSettings.clear(); + QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str()); @@ -289,18 +301,14 @@ bool Launcher::MainDialog::setupGameSettings() QString path = userPath + QLatin1String("openmw.cfg"); QFile file(path); - qDebug() << "Loading config file:" << qPrintable(path); + qDebug() << "Loading config file:" << path.toUtf8().constData(); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); + cfgError(tr("Error opening OpenMW configuration file"), + tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); return false; } QTextStream stream(&file); @@ -309,26 +317,22 @@ bool Launcher::MainDialog::setupGameSettings() mGameSettings.readUserFile(stream); } - // Now the rest + // Now the rest - priority: user > local > global QStringList paths; - paths.append(userPath + QString("openmw.cfg")); - paths.append(QString("openmw.cfg")); paths.append(globalPath + QString("openmw.cfg")); + paths.append(QString("openmw.cfg")); + paths.append(userPath + QString("openmw.cfg")); foreach (const QString &path, paths) { - qDebug() << "Loading config file:" << qPrintable(path); + qDebug() << "Loading config file:" << path.toUtf8().constData(); QFile file(path); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); + cfgError(tr("Error opening OpenMW configuration file"), + tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); return false; } QTextStream stream(&file); @@ -380,53 +384,54 @@ bool Launcher::MainDialog::setupGameSettings() bool Launcher::MainDialog::setupGraphicsSettings() { - mGraphicsSettings.setMultiValueEnabled(false); + // This method is almost a copy of OMW::Engine::loadSettings(). They should definitely + // remain consistent, and possibly be merged into a shared component. At the very least + // the filenames should be in the CfgMgr component. - QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); - QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str()); + // Ensure to clear previous settings in case we had already loaded settings. + mEngineSettings.clear(); - QFile localDefault(QString("settings-default.cfg")); - QFile globalDefault(globalPath + QString("settings-default.cfg")); + // Create the settings manager and load default settings file + const std::string localDefault = (mCfgMgr.getLocalPath() / "settings-default.cfg").string(); + const std::string globalDefault = (mCfgMgr.getGlobalPath() / "settings-default.cfg").string(); + std::string defaultPath; - if (!localDefault.exists() && !globalDefault.exists()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error reading OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not find settings-default.cfg

\ - The problem may be due to an incomplete installation of OpenMW.
\ - Reinstalling OpenMW may resolve the problem.")); - msgBox.exec(); + // Prefer the settings-default.cfg in the current directory. + if (boost::filesystem::exists(localDefault)) + defaultPath = localDefault; + else if (boost::filesystem::exists(globalDefault)) + defaultPath = globalDefault; + // Something's very wrong if we can't find the file at all. + else { + cfgError(tr("Error reading OpenMW configuration file"), + tr("
Could not find settings-default.cfg

\ + The problem may be due to an incomplete installation of OpenMW.
\ + Reinstalling OpenMW may resolve the problem.")); return false; } + // Load the default settings, report any parsing errors. + try { + mEngineSettings.loadDefault(defaultPath); + } + catch (std::exception& e) { + std::string msg = std::string("
Error reading settings-default.cfg

") + e.what(); + cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str())); + return false; + } - QStringList paths; - paths.append(globalPath + QString("settings-default.cfg")); - paths.append(QString("settings-default.cfg")); - paths.append(userPath + QString("settings.cfg")); + // Load user settings if they exist + const std::string userPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); + // User settings are not required to exist, so if they don't we're done. + if (!boost::filesystem::exists(userPath)) return true; - foreach (const QString &path, paths) { - qDebug() << "Loading config file:" << qPrintable(path); - QFile file(path); - if (file.exists()) { - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; - } - QTextStream stream(&file); - stream.setCodec(QTextCodec::codecForName("UTF-8")); - - mGraphicsSettings.readFile(stream); - } - file.close(); + try { + mEngineSettings.loadUser(userPath); + } + catch (std::exception& e) { + std::string msg = std::string("
Error reading settings.cfg

") + e.what(); + cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str())); + return false; } return true; @@ -475,15 +480,11 @@ bool Launcher::MainDialog::writeSettings() if (!dir.exists()) { if (!dir.mkpath(userPath)) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error creating OpenMW configuration directory")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not create %0

\ - Please make sure you have the right permissions \ - and try again.
").arg(userPath)); - msgBox.exec(); - return false; + cfgError(tr("Error creating OpenMW configuration directory"), + tr("
Could not create %0

\ + Please make sure you have the right permissions \ + and try again.
").arg(userPath)); + return false; } } @@ -492,15 +493,11 @@ bool Launcher::MainDialog::writeSettings() if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { // File cannot be opened or created - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; + cfgError(tr("Error writing OpenMW configuration file"), + tr("
Could not open or create %0 for writing

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + return false; } @@ -508,44 +505,30 @@ bool Launcher::MainDialog::writeSettings() file.close(); // Graphics settings - file.setFileName(userPath + QString("settings.cfg")); - - if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { - // File cannot be opened or created - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; + const std::string settingsPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); + try { + mEngineSettings.saveUser(settingsPath); + } + catch (std::exception& e) { + std::string msg = "
Error writing settings.cfg

" + + settingsPath + "

" + e.what(); + cfgError(tr("Error writing user settings file"), tr(msg.c_str())); + return false; } - - QTextStream stream(&file); - stream.setDevice(&file); - stream.setCodec(QTextCodec::codecForName("UTF-8")); - - mGraphicsSettings.writeFile(stream); - file.close(); // Launcher settings file.setFileName(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName)); if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { // File cannot be opened or created - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error writing Launcher configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
").arg(file.fileName())); - msgBox.exec(); - return false; + cfgError(tr("Error writing Launcher configuration file"), + tr("
Could not open or create %0 for writing

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + return false; } + QTextStream stream(&file); stream.setDevice(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index c903099901..0dfea48659 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -13,7 +13,7 @@ #include #include -#include "settings/graphicssettings.hpp" +#include #include "ui_mainwindow.h" @@ -72,6 +72,8 @@ namespace Launcher bool setupGameSettings(); bool setupGraphicsSettings(); + void setVersionLabel(); + void loadSettings(); void saveSettings(); @@ -91,7 +93,7 @@ namespace Launcher Files::ConfigurationManager mCfgMgr; Config::GameSettings mGameSettings; - GraphicsSettings mGraphicsSettings; + Settings::Manager mEngineSettings; Config::LauncherSettings mLauncherSettings; }; diff --git a/apps/launcher/playpage.cpp b/apps/launcher/playpage.cpp index 6cfb9686fa..99b74fdd39 100644 --- a/apps/launcher/playpage.cpp +++ b/apps/launcher/playpage.cpp @@ -2,20 +2,11 @@ #include -#ifdef Q_OS_MAC -#include -#endif - Launcher::PlayPage::PlayPage(QWidget *parent) : QWidget(parent) { setObjectName ("PlayPage"); setupUi(this); - // Hacks to get the stylesheet look properly -#ifdef Q_OS_MAC - QPlastiqueStyle *style = new QPlastiqueStyle; - profilesComboBox->setStyle(style); -#endif profilesComboBox->setView(new QListView()); connect(profilesComboBox, SIGNAL(activated(int)), this, SIGNAL (signalProfileChanged(int))); diff --git a/apps/launcher/settings/graphicssettings.cpp b/apps/launcher/settings/graphicssettings.cpp deleted file mode 100644 index 9dad3dee6b..0000000000 --- a/apps/launcher/settings/graphicssettings.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "graphicssettings.hpp" - -#include -#include -#include -#include - -Launcher::GraphicsSettings::GraphicsSettings() -{ -} - -Launcher::GraphicsSettings::~GraphicsSettings() -{ -} - -bool Launcher::GraphicsSettings::writeFile(QTextStream &stream) -{ - QString sectionPrefix; - QRegExp sectionRe("([^/]+)/(.+)$"); - QMap settings = SettingsBase::getSettings(); - - QMapIterator i(settings); - while (i.hasNext()) { - i.next(); - - QString prefix; - QString key; - - if (sectionRe.exactMatch(i.key())) { - prefix = sectionRe.cap(1); - key = sectionRe.cap(2); - } - - if (sectionPrefix != prefix) { - sectionPrefix = prefix; - stream << "\n[" << prefix << "]\n"; - } - - stream << key << " = " << i.value() << "\n"; - } - - return true; - -} diff --git a/apps/launcher/settings/graphicssettings.hpp b/apps/launcher/settings/graphicssettings.hpp deleted file mode 100644 index a52e0aa84c..0000000000 --- a/apps/launcher/settings/graphicssettings.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef GRAPHICSSETTINGS_HPP -#define GRAPHICSSETTINGS_HPP - -#include - -namespace Launcher -{ - class GraphicsSettings : public Config::SettingsBase > - { - public: - GraphicsSettings(); - ~GraphicsSettings(); - - bool writeFile(QTextStream &stream); - - }; -} -#endif // GRAPHICSSETTINGS_HPP diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 479f8cba28..f90ba4184f 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -20,7 +20,6 @@ MwIniImporter::MwIniImporter() { const char *map[][2] = { - { "fps", "General:Show FPS" }, { "no-sound", "General:Disable Audio" }, { 0, 0 } }; @@ -639,6 +638,9 @@ MwIniImporter::MwIniImporter() "Blood:Texture Name 1", "Blood:Texture Name 2", + // werewolf (Bloodmoon) + "General:Werewolf FOV", + 0 }; @@ -847,7 +849,7 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co for(std::vector::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) { std::string filetype(entry->substr(entry->length()-3)); - Misc::StringUtils::toLower(filetype); + Misc::StringUtils::lowerCaseInPlace(filetype); if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) { boost::filesystem::path filepath(gameFilesDir); diff --git a/components/nif/tests/CMakeLists.txt b/apps/niftest/CMakeLists.txt similarity index 90% rename from components/nif/tests/CMakeLists.txt rename to apps/niftest/CMakeLists.txt index a45298180a..d7f0200d2a 100644 --- a/components/nif/tests/CMakeLists.txt +++ b/apps/niftest/CMakeLists.txt @@ -9,7 +9,7 @@ add_executable(niftest ) target_link_libraries(niftest - ${Boost_LIBRARIES} + ${Boost_FILESYSTEM_LIBRARY} components ) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp new file mode 100644 index 0000000000..72393db40b --- /dev/null +++ b/apps/niftest/niftest.cpp @@ -0,0 +1,165 @@ +///Program to test .nif files both on the FileSystem and in BSA archives. + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +// Create local aliases for brevity +namespace bpo = boost::program_options; +namespace bfs = boost::filesystem; + +///See if the file has the named extension +bool hasExtension(std::string filename, std::string extensionToFind) +{ + std::string extension = filename.substr(filename.find_last_of(".")+1); + + //Convert strings to lower case for comparison + std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); + std::transform(extensionToFind.begin(), extensionToFind.end(), extensionToFind.begin(), ::tolower); + + if(extension == extensionToFind) + return true; + else + return false; +} + +///See if the file has the "nif" extension. +bool isNIF(std::string filename) +{ + return hasExtension(filename,"nif"); +} +///See if the file has the "bsa" extension. +bool isBSA(std::string filename) +{ + return hasExtension(filename,"bsa"); +} + +/// Check all the nif files in a given VFS::Archive +/// \note Takes ownership! +/// \note Can not read a bsa file inside of a bsa file. +void readVFS(VFS::Archive* anArchive,std::string archivePath = "") +{ + VFS::Manager myManager(true); + myManager.addArchive(anArchive); + myManager.buildIndex(); + + std::map files=myManager.getIndex(); + for(std::map::const_iterator it=files.begin(); it!=files.end(); ++it) + { + std::string name = it->first; + + try{ + if(isNIF(name)) + { + // std::cout << "Decoding: " << name << std::endl; + Nif::NIFFile temp_nif(myManager.get(name),archivePath+name); + } + else if(isBSA(name)) + { + if(!archivePath.empty() && !isBSA(archivePath)) + { +// std::cout << "Reading BSA File: " << name << std::endl; + readVFS(new VFS::BsaArchive(archivePath+name),archivePath+name+"/"); +// std::cout << "Done with BSA File: " << name << std::endl; + } + } + } + catch (std::exception& e) + { + std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; + } + } +} + +std::vector parseOptions (int argc, char** argv) +{ + bpo::options_description desc("Ensure that OpenMW can use the provided NIF and BSA files\n\n" + "Usages:\n" + " niftool \n" + " Scan the file or directories for nif errors.\n\n" + "Allowed options"); + desc.add_options() + ("help,h", "print help message.") + ("input-file", bpo::value< std::vector >(), "input file") + ; + + //Default option if none provided + bpo::positional_options_description p; + p.add("input-file", -1); + + bpo::variables_map variables; + try + { + bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv). + options(desc).positional(p).run(); + bpo::store(valid_opts, variables); + } + catch(std::exception &e) + { + std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" + << desc << std::endl; + exit(1); + } + + bpo::notify(variables); + if (variables.count ("help")) + { + std::cout << desc << std::endl; + exit(1); + } + if (variables.count("input-file")) + { + return variables["input-file"].as< std::vector >(); + } + + std::cout << "No input files or directories specified!" << std::endl; + std::cout << desc << std::endl; + exit(1); +} + +int main(int argc, char **argv) +{ + std::vector files = parseOptions (argc, argv); + +// std::cout << "Reading Files" << std::endl; + for(std::vector::const_iterator it=files.begin(); it!=files.end(); ++it) + { + std::string name = *it; + + try{ + if(isNIF(name)) + { + //std::cout << "Decoding: " << name << std::endl; + Nif::NIFFile temp_nif(Files::openConstrainedFileStream(name.c_str()),name); + } + else if(isBSA(name)) + { +// std::cout << "Reading BSA File: " << name << std::endl; + readVFS(new VFS::BsaArchive(name)); + } + else if(bfs::is_directory(bfs::path(name))) + { +// std::cout << "Reading All Files in: " << name << std::endl; + readVFS(new VFS::FileSystemArchive(name),name); + } + else + { + std::cerr << "ERROR: \"" << name << "\" is not a nif file, bsa file, or directory!" << std::endl; + } + } + catch (std::exception& e) + { + std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; + } + } + return 0; +} diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index d86798af95..0e9a49432b 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -23,10 +23,10 @@ opencs_units (model/world opencs_units_noqt (model/world - universalid record commands columnbase scriptcontext cell refidcollection + universalid record commands columnbase columnimp scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection - idcompletionmanager + idcompletionmanager metadata ) opencs_hdrs_noqt (model/world @@ -35,13 +35,18 @@ opencs_hdrs_noqt (model/world opencs_units (model/tools - tools reportmodel + tools reportmodel mergeoperation ) opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck - startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck + startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck + mergestages + ) + +opencs_hdrs_noqt (model/tools + mergestate ) @@ -62,30 +67,30 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator - cellcreator referenceablecreator referencecreator scenesubview + cellcreator referenceablecreator startscriptcreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable - dialoguespinbox + dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator ) opencs_units_noqt (view/world subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate - scripthighlighter idvalidator dialoguecreator physicssystem idcompletiondelegate - colordelegate + scripthighlighter idvalidator dialoguecreator idcompletiondelegate + colordelegate dragdroputils ) opencs_units (view/widget scenetoolbar scenetool scenetoolmode pushbutton scenetooltoggle scenetoolrun modebutton - scenetooltoggle2 completerpopup coloreditor colorpickerpopup + scenetooltoggle2 completerpopup coloreditor colorpickerpopup droplineedit ) opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget - previewwidget editmode + previewwidget editmode instancemode ) opencs_units_noqt (view/render - navigation navigation1st navigationfree navigationorbit lighting lightingday lightingnight - lightingbright object cell terrainstorage textoverlay overlaymask overlaysystem mousestate + lighting lightingday lightingnight + lightingbright object cell terrainstorage tagbase cellarrow ) opencs_hdrs_noqt (view/render @@ -94,38 +99,23 @@ opencs_hdrs_noqt (view/render opencs_units (view/tools - reportsubview reporttable searchsubview searchbox + reportsubview reporttable searchsubview searchbox merge ) opencs_units_noqt (view/tools subviews ) -opencs_units (view/settings - settingwindow - dialog - page - view - booleanview - textview - listview - rangeview - resizeablestackedwidget - spinbox +opencs_units (view/prefs + dialogue pagebase page ) -opencs_units_noqt (view/settings - frame +opencs_units (model/prefs + state setting intsetting doublesetting boolsetting enumsetting coloursetting ) -opencs_units (model/settings - usersettings - setting - connector - ) - -opencs_hdrs_noqt (model/settings - support +opencs_units_noqt (model/prefs + category ) opencs_units_noqt (model/filter @@ -192,6 +182,7 @@ if(APPLE) MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs" MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION} MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION} + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/files/mac/openmw-cs-Info.plist.in" ) set_source_files_properties(${OPENCS_MAC_ICON} PROPERTIES @@ -199,16 +190,16 @@ if(APPLE) endif(APPLE) target_link_libraries(openmw-cs - ${OENGINE_LIBRARY} - ${OGRE_LIBRARIES} - ${OGRE_Overlay_LIBRARIES} - ${OGRE_STATIC_PLUGINS} - ${SHINY_LIBRARIES} + ${OSG_LIBRARIES} + ${OPENTHREADS_LIBRARIES} + ${OSGUTIL_LIBRARIES} + ${OSGVIEWER_LIBRARIES} + ${OSGGA_LIBRARIES} + ${OSGFX_LIBRARIES} + ${OSGQT_LIBRARIES} ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${Boost_WAVE_LIBRARY} - ${BULLET_LIBRARIES} components ) @@ -216,14 +207,14 @@ if (DESIRED_QT_VERSION MATCHES 4) target_link_libraries(openmw-cs ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY} - ${QT_QTNETWORK_LIBRARY}) + ${QT_QTNETWORK_LIBRARY} + ${QT_QTOPENGL_LIBRARY}) if (WIN32) target_link_libraries(openmw-cs ${QT_QTMAIN_LIBRARY}) endif() - else() - qt5_use_modules(openmw-cs Widgets Core Network) + qt5_use_modules(openmw-cs Widgets Core Network OpenGL) if (WIN32) target_link_libraries(Qt5::WinMain) endif() diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index bd4f57304f..92f1082b75 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -1,54 +1,48 @@ - #include "editor.hpp" -#include - #include #include #include #include -#include -#include +#include +#include -#include -#include - -#include -#include -#include +#include #include "model/doc/document.hpp" #include "model/world/data.hpp" -CS::Editor::Editor (OgreInit::OgreInit& ogreInit) -: mUserSettings (mCfgMgr), mOverlaySystem (0), mDocumentManager (mCfgMgr), +#ifdef _WIN32 +#include +#endif + +CS::Editor::Editor () +: mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr), mViewManager (mDocumentManager), mPid(""), - mLock(), mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) + mLock(), mMerge (mDocumentManager), + mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) { std::pair > config = readConfig(); setupDataFiles (config.first); - CSMSettings::UserSettings::instance().loadSettings ("opencs.ini"); - mSettings.setModel (CSMSettings::UserSettings::instance()); + NifOsg::Loader::setShowMarkers(true); - ogreInit.init ((mCfgMgr.getUserConfigPath() / "opencsOgre.log").string()); + mVFS.reset(new VFS::Manager(mFsStrict)); - NifOgre::Loader::setShowMarkers(true); + VFS::registerArchives(mVFS.get(), Files::Collections(config.first, !mFsStrict), config.second, true); - mOverlaySystem.reset (new CSVRender::OverlaySystem); - - Bsa::registerResources (Files::Collections (config.first, !mFsStrict), config.second, true, - mFsStrict); - - mDocumentManager.listResources(); + mDocumentManager.setVFS(mVFS.get()); mNewGame.setLocalData (mLocal); mFileDialog.setLocalData (mLocal); + mMerge.setLocalData (mLocal); connect (&mDocumentManager, SIGNAL (documentAdded (CSMDoc::Document *)), this, SLOT (documentAdded (CSMDoc::Document *))); + connect (&mDocumentManager, SIGNAL (documentAboutToBeRemoved (CSMDoc::Document *)), + this, SLOT (documentAboutToBeRemoved (CSMDoc::Document *))); connect (&mDocumentManager, SIGNAL (lastDocumentDeleted()), this, SLOT (lastDocumentDeleted())); @@ -56,6 +50,7 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ())); connect (&mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ())); connect (&mViewManager, SIGNAL (editSettingsRequest()), this, SLOT (showSettings ())); + connect (&mViewManager, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SLOT (mergeDocument (CSMDoc::Document *))); connect (&mStartup, SIGNAL (createGame()), this, SLOT (createGame ())); connect (&mStartup, SIGNAL (createAddon()), this, SLOT (createAddon ())); @@ -81,9 +76,6 @@ CS::Editor::~Editor () if(mServer && boost::filesystem::exists(mPid)) static_cast ( // silence coverity warning remove(mPid.string().c_str())); // ignore any error - - // cleanup global resources used by OEngine - delete OEngine::Physic::BulletShapeManager::getSingletonPtr(); } void CS::Editor::setupDataFiles (const Files::PathContainer& dataDirs) @@ -364,120 +356,26 @@ int CS::Editor::run() return QApplication::exec(); } -std::auto_ptr CS::Editor::setupGraphics() -{ - std::string renderer = -#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 - "Direct3D9 Rendering Subsystem"; -#else - "OpenGL Rendering Subsystem"; -#endif - std::string renderSystem = mUserSettings.setting("Video/render system", renderer.c_str()).toStdString(); - - Ogre::Root::getSingleton().setRenderSystem(Ogre::Root::getSingleton().getRenderSystemByName(renderSystem)); - - // Initialise Ogre::OverlaySystem after Ogre::Root but before initialisation - mOverlaySystem.get(); - - Ogre::Root::getSingleton().initialise(false); - - // Create a hidden background window to keep resources - Ogre::NameValuePairList params; - params.insert(std::make_pair("title", "")); - - std::string antialiasing = mUserSettings.settingValue("Video/antialiasing").toStdString(); - if(antialiasing == "MSAA 16") antialiasing = "16"; - else if(antialiasing == "MSAA 8") antialiasing = "8"; - else if(antialiasing == "MSAA 4") antialiasing = "4"; - else if(antialiasing == "MSAA 2") antialiasing = "2"; - else antialiasing = "0"; - params.insert(std::make_pair("FSAA", antialiasing)); - - params.insert(std::make_pair("vsync", "false")); - params.insert(std::make_pair("hidden", "true")); -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - params.insert(std::make_pair("macAPI", "cocoa")); -#endif - // NOTE: fullscreen mode not supported (doesn't really make sense for opencs) - Ogre::RenderWindow* hiddenWindow = Ogre::Root::getSingleton().createRenderWindow("InactiveHidden", 1, 1, false, ¶ms); - hiddenWindow->setActive(false); - - sh::OgrePlatform* platform = - new sh::OgrePlatform ("General", (mResources / "materials").string()); - - // for font used in overlays - Ogre::Root::getSingleton().addResourceLocation ((mResources / "mygui").string(), - "FileSystem", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true); - - if (!boost::filesystem::exists (mCfgMgr.getCachePath())) - boost::filesystem::create_directories (mCfgMgr.getCachePath()); - - platform->setCacheFolder (mCfgMgr.getCachePath().string()); - - std::auto_ptr factory (new sh::Factory (platform)); - - QString shLang = mUserSettings.settingValue("General/shader mode"); - QString rend = renderSystem.c_str(); - bool openGL = rend.contains(QRegExp("^OpenGL", Qt::CaseInsensitive)); - bool glES = rend.contains(QRegExp("^OpenGL ES", Qt::CaseInsensitive)); - - // force shader language based on render system - if(shLang == "" - || (openGL && shLang == "hlsl") - || (!openGL && shLang == "glsl") - || (glES && shLang != "glsles")) - { - shLang = openGL ? (glES ? "glsles" : "glsl") : "hlsl"; - //no group means "General" group in the "ini" file standard - mUserSettings.setDefinitions("shader mode", (QStringList() << shLang)); - } - enum sh::Language lang; - if(shLang == "glsl") lang = sh::Language_GLSL; - else if(shLang == "glsles") lang = sh::Language_GLSLES; - else if(shLang == "hlsl") lang = sh::Language_HLSL; - else lang = sh::Language_CG; - - factory->setCurrentLanguage (lang); - factory->setWriteSourceCache (true); - factory->setReadSourceCache (true); - factory->setReadMicrocodeCache (true); - factory->setWriteMicrocodeCache (true); - - factory->loadAllFiles(); - - bool shaders = mUserSettings.setting("3d-render/shaders", QString("true")) == "true" ? true : false; - sh::Factory::getInstance ().setShadersEnabled (shaders); - - std::string fog = mUserSettings.setting("Shader/fog", QString("true")).toStdString(); - sh::Factory::getInstance().setGlobalSetting ("fog", fog); - - - std::string shadows = mUserSettings.setting("Shader/shadows", QString("false")).toStdString(); - sh::Factory::getInstance().setGlobalSetting ("shadows", shadows); - - std::string shadows_pssm = mUserSettings.setting("Shader/shadows_pssm", QString("false")).toStdString(); - sh::Factory::getInstance().setGlobalSetting ("shadows_pssm", shadows_pssm); - - std::string render_refraction = mUserSettings.setting("Shader/render_refraction", QString("false")).toStdString(); - sh::Factory::getInstance ().setGlobalSetting ("render_refraction", render_refraction); - - // internal setting - may be switched on or off by the use of shader configurations - sh::Factory::getInstance ().setGlobalSetting ("viewproj_fix", "false"); - - std::string num_lights = mUserSettings.setting("3d-render-adv/num_lights", QString("8")).toStdString(); - sh::Factory::getInstance ().setGlobalSetting ("num_lights", num_lights); - - /// \todo add more configurable shiny settings - - return factory; -} - void CS::Editor::documentAdded (CSMDoc::Document *document) { mViewManager.addView (document); } +void CS::Editor::documentAboutToBeRemoved (CSMDoc::Document *document) +{ + if (mMerge.getDocument()==document) + mMerge.cancel(); +} + void CS::Editor::lastDocumentDeleted() { QApplication::quit(); } + +void CS::Editor::mergeDocument (CSMDoc::Document *document) +{ + mMerge.configure (document); + mMerge.show(); + mMerge.raise(); + mMerge.activateWindow(); +} diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index cbf306df83..b088e9a442 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -11,30 +11,33 @@ #include #include -#include - #ifndef Q_MOC_RUN #include #endif #include -#include - -#include "model/settings/usersettings.hpp" #include "model/doc/documentmanager.hpp" +#include "model/prefs/state.hpp" + #include "view/doc/viewmanager.hpp" #include "view/doc/startup.hpp" #include "view/doc/filedialog.hpp" #include "view/doc/newgame.hpp" -#include "view/settings/dialog.hpp" -#include "view/render/overlaysystem.hpp" +#include "view/prefs/dialogue.hpp" -namespace OgreInit +#include "view/tools/merge.hpp" + +namespace VFS { - class OgreInit; + class Manager; +} + +namespace CSMDoc +{ + class Document; } namespace CS @@ -43,15 +46,16 @@ namespace CS { Q_OBJECT - Nif::Cache mNifCache; + // FIXME: should be moved to document, so we can have different resources for each opened project + std::auto_ptr mVFS; + Files::ConfigurationManager mCfgMgr; - CSMSettings::UserSettings mUserSettings; - std::auto_ptr mOverlaySystem; + CSMPrefs::State mSettingsState; CSMDoc::DocumentManager mDocumentManager; CSVDoc::ViewManager mViewManager; CSVDoc::StartupDialogue mStartup; CSVDoc::NewGameDialogue mNewGame; - CSVSettings::Dialog mSettings; + CSVPrefs::Dialogue mSettings; CSVDoc::FileDialog mFileDialog; boost::filesystem::path mLocal; boost::filesystem::path mResources; @@ -59,6 +63,7 @@ namespace CS boost::interprocess::file_lock mLock; boost::filesystem::ofstream mPidFile; bool mFsStrict; + CSVTools::Merge mMerge; void setupDataFiles (const Files::PathContainer& dataDirs); @@ -71,7 +76,7 @@ namespace CS public: - Editor (OgreInit::OgreInit& ogreInit); + Editor (); ~Editor (); bool makeIPCServer(); @@ -80,9 +85,6 @@ namespace CS int run(); ///< \return error status - std::auto_ptr setupGraphics(); - ///< The returned factory must persist at least as long as *this. - private slots: void createGame(); @@ -101,8 +103,12 @@ namespace CS void documentAdded (CSMDoc::Document *document); + void documentAboutToBeRemoved (CSMDoc::Document *document); + void lastDocumentDeleted(); + void mergeDocument (CSMDoc::Document *document); + private: QString mIpcServerName; diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index ce434fc43b..c6fe348356 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -1,4 +1,3 @@ - #include "editor.hpp" #include @@ -9,10 +8,6 @@ #include #include -#include - -#include - #include "model/doc/messages.hpp" #include "model/world/universalid.hpp" @@ -48,18 +43,21 @@ class Application : public QApplication int main(int argc, char *argv[]) { + #ifdef Q_OS_MAC + setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); + #endif + try { + // To allow background thread drawing in OSG + QApplication::setAttribute(Qt::AA_X11InitThreads, true); + Q_INIT_RESOURCE (resources); qRegisterMetaType ("std::string"); qRegisterMetaType ("CSMWorld::UniversalId"); qRegisterMetaType ("CSMDoc::Message"); - OgreInit::OgreInit ogreInit; - - std::auto_ptr shinyFactory; - Application application (argc, argv); #ifdef Q_OS_MAC @@ -70,28 +68,17 @@ int main(int argc, char *argv[]) dir.cdUp(); } 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"); - - QStringList libraryPaths; - libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); - application.setLibraryPaths(libraryPaths); #endif application.setWindowIcon (QIcon (":./openmw-cs.png")); - CS::Editor editor (ogreInit); + CS::Editor editor; if(!editor.makeIPCServer()) { editor.connectToIPCServer(); return 0; } - - shinyFactory = editor.setupGraphics(); return editor.run(); } catch (std::exception& e) diff --git a/apps/opencs/model/doc/blacklist.cpp b/apps/opencs/model/doc/blacklist.cpp index 0837264124..b1d402c699 100644 --- a/apps/opencs/model/doc/blacklist.cpp +++ b/apps/opencs/model/doc/blacklist.cpp @@ -1,4 +1,3 @@ - #include "blacklist.hpp" #include diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 1e6ae54550..0e2d4e7d1a 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -9,8 +10,6 @@ #include #endif -#include "../../view/world/physicssystem.hpp" - void CSMDoc::Document::addGmsts() { static const char *gmstFloats[] = @@ -799,9 +798,9 @@ void CSMDoc::Document::addGmsts() "sBookSkillMessage", "sBounty", "sBreath", - "sBribe", - "sBribe", - "sBribe", + "sBribe 10 Gold", + "sBribe 100 Gold", + "sBribe 1000 Gold", "sBribeFail", "sBribeSuccess", "sBuy", @@ -2245,20 +2244,19 @@ void CSMDoc::Document::createBase() } } -CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, +CSMDoc::Document::Document (const VFS::Manager* vfs, const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, bool new_, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, const std::vector& blacklistedScripts) -: mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager), - mTools (*this), +: mVFS(vfs), mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager), + mTools (*this, encoding), mProjectPath ((configuration.getUserDataPath() / "projects") / (savePath.filename().string() + ".project")), mSavingOperation (*this, mProjectPath, encoding), mSaving (&mSavingOperation), mResDir(resDir), - mRunner (mProjectPath), mPhysics(boost::shared_ptr()), - mIdCompletionManager(mData) + mRunner (mProjectPath), mDirty (false), mIdCompletionManager(mData) { if (mContentFiles.empty()) throw std::runtime_error ("Empty content file sequence"); @@ -2282,9 +2280,6 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, if (mNew) { - mData.setDescription (""); - mData.setAuthor (""); - if (mContentFiles.size()==1) createBase(); } @@ -2299,6 +2294,8 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); + connect (&mTools, SIGNAL (mergeDone (CSMDoc::Document*)), + this, SIGNAL (mergeDone (CSMDoc::Document*))); connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); @@ -2314,6 +2311,11 @@ CSMDoc::Document::~Document() { } +const VFS::Manager *CSMDoc::Document::getVFS() const +{ + return mVFS; +} + QUndoStack& CSMDoc::Document::getUndoStack() { return mUndoStack; @@ -2323,7 +2325,7 @@ int CSMDoc::Document::getState() const { int state = 0; - if (!mUndoStack.isClean()) + if (!mUndoStack.isClean() || mDirty) state |= State_Modified; if (mSaving.isRunning()) @@ -2369,9 +2371,9 @@ void CSMDoc::Document::save() emit stateChanged (getState(), this); } -CSMWorld::UniversalId CSMDoc::Document::verify() +CSMWorld::UniversalId CSMDoc::Document::verify (const CSMWorld::UniversalId& reportId) { - CSMWorld::UniversalId id = mTools.runVerifier(); + CSMWorld::UniversalId id = mTools.runVerifier (reportId); emit stateChanged (getState(), this); return id; } @@ -2388,6 +2390,12 @@ void CSMDoc::Document::runSearch (const CSMWorld::UniversalId& searchId, const C emit stateChanged (getState(), this); } +void CSMDoc::Document::runMerge (std::auto_ptr target) +{ + mTools.runMerge (target); + emit stateChanged (getState(), this); +} + void CSMDoc::Document::abortOperation (int type) { if (type==State_Saving) @@ -2409,6 +2417,9 @@ void CSMDoc::Document::reportMessage (const CSMDoc::Message& message, int type) void CSMDoc::Document::operationDone (int type, bool failed) { + if (type==CSMDoc::State_Saving && !failed) + mDirty = false; + emit stateChanged (getState(), this); } @@ -2481,15 +2492,12 @@ void CSMDoc::Document::progress (int current, int max, int type) emit progress (current, max, type, 1, this); } -boost::shared_ptr CSMDoc::Document::getPhysics () -{ - if(!mPhysics) - mPhysics = boost::shared_ptr (new CSVWorld::PhysicsSystem()); - - return mPhysics; -} - CSMWorld::IdCompletionManager &CSMDoc::Document::getIdCompletionManager() { return mIdCompletionManager; } + +void CSMDoc::Document::flagAsDirty() +{ + mDirty = true; +} diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 4aaaf40b03..0e8ae6d454 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -25,6 +25,12 @@ class QAbstractItemModel; +namespace VFS +{ + + class Manager; +} + namespace ESM { struct GameSetting; @@ -42,11 +48,6 @@ namespace CSMWorld class ResourcesManager; } -namespace CSVWorld -{ - class PhysicsSystem; -} - namespace CSMDoc { class Document : public QObject @@ -55,6 +56,7 @@ namespace CSMDoc private: + const VFS::Manager* mVFS; boost::filesystem::path mSavePath; std::vector mContentFiles; bool mNew; @@ -66,7 +68,8 @@ namespace CSMDoc boost::filesystem::path mResDir; Blacklist mBlacklist; Runner mRunner; - boost::shared_ptr mPhysics; + bool mDirty; + CSMWorld::IdCompletionManager mIdCompletionManager; // It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is @@ -95,7 +98,7 @@ namespace CSMDoc public: - Document (const Files::ConfigurationManager& configuration, + Document (const VFS::Manager* vfs, const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, bool new_, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, @@ -103,6 +106,8 @@ namespace CSMDoc ~Document(); + const VFS::Manager* getVFS() const; + QUndoStack& getUndoStack(); int getState() const; @@ -120,12 +125,14 @@ namespace CSMDoc void save(); - CSMWorld::UniversalId verify(); + CSMWorld::UniversalId verify (const CSMWorld::UniversalId& reportId = CSMWorld::UniversalId()); CSMWorld::UniversalId newSearch(); void runSearch (const CSMWorld::UniversalId& searchId, const CSMTools::Search& search); - + + void runMerge (std::auto_ptr target); + void abortOperation (int type); const CSMWorld::Data& getData() const; @@ -144,16 +151,20 @@ namespace CSMDoc QTextDocument *getRunLog(); - boost::shared_ptr getPhysics(); - CSMWorld::IdCompletionManager &getIdCompletionManager(); + void flagAsDirty(); + signals: void stateChanged (int state, CSMDoc::Document *document); void progress (int current, int max, int type, int threads, CSMDoc::Document *document); + /// \attention When this signal is emitted, *this hands over the ownership of the + /// document. This signal must be handled to avoid a leak. + void mergeDone (CSMDoc::Document *document); + private slots: void modificationStateChanged (bool clean); @@ -171,4 +182,3 @@ namespace CSMDoc } #endif - diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 29d7a8d3a8..407a608cab 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -1,4 +1,3 @@ - #include "documentmanager.hpp" #include @@ -13,7 +12,7 @@ #include "document.hpp" CSMDoc::DocumentManager::DocumentManager (const Files::ConfigurationManager& configuration) -: mConfiguration (configuration), mEncoding (ToUTF8::WINDOWS_1252) +: mConfiguration (configuration), mEncoding (ToUTF8::WINDOWS_1252), mVFS(NULL) { boost::filesystem::path projectPath = configuration.getUserDataPath() / "projects"; @@ -57,10 +56,24 @@ bool CSMDoc::DocumentManager::isEmpty() void CSMDoc::DocumentManager::addDocument (const std::vector& files, const boost::filesystem::path& savePath, bool new_) { - Document *document = new Document (mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts); + Document *document = makeDocument (files, savePath, new_); + insertDocument (document); +} +CSMDoc::Document *CSMDoc::DocumentManager::makeDocument ( + const std::vector< boost::filesystem::path >& files, + const boost::filesystem::path& savePath, bool new_) +{ + return new Document (mVFS, mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts); +} + +void CSMDoc::DocumentManager::insertDocument (CSMDoc::Document *document) +{ mDocuments.push_back (document); + connect (document, SIGNAL (mergeDone (CSMDoc::Document*)), + this, SLOT (insertDocument (CSMDoc::Document*))); + emit loadRequest (document); mLoader.hasThingsToDo().wakeAll(); @@ -73,6 +86,8 @@ void CSMDoc::DocumentManager::removeDocument (CSMDoc::Document *document) if (iter==mDocuments.end()) throw std::runtime_error ("removing invalid document"); + emit documentAboutToBeRemoved (document); + mDocuments.erase (iter); document->deleteLater(); @@ -95,11 +110,6 @@ void CSMDoc::DocumentManager::setBlacklistedScripts (const std::vector mBlacklistedScripts; + const VFS::Manager* mVFS; DocumentManager (const DocumentManager&); DocumentManager& operator= (const DocumentManager&); @@ -50,14 +56,22 @@ namespace CSMDoc ///< \param new_ Do not load the last content file in \a files and instead create in an /// appropriate way. + /// Create a new document. The ownership of the created document is transferred to + /// the calling function. The DocumentManager does not manage it. Loading has not + /// taken place at the point when the document is returned. + /// + /// \param new_ Do not load the last content file in \a files and instead create in an + /// appropriate way. + Document *makeDocument (const std::vector< boost::filesystem::path >& files, + const boost::filesystem::path& savePath, bool new_); + void setResourceDir (const boost::filesystem::path& parResDir); void setEncoding (ToUTF8::FromType encoding); void setBlacklistedScripts (const std::vector& scriptIds); - /// Ask OGRE for a list of available resources. - void listResources(); + void setVFS(const VFS::Manager* vfs); bool isEmpty(); @@ -79,10 +93,16 @@ namespace CSMDoc void removeDocument (CSMDoc::Document *document); ///< Emits the lastDocumentDeleted signal, if applicable. + /// Hand over document to *this. The ownership is transferred. The DocumentManager + /// will initiate the load procedure, if necessary + void insertDocument (CSMDoc::Document *document); + signals: void documentAdded (CSMDoc::Document *document); + void documentAboutToBeRemoved (CSMDoc::Document *document); + void loadRequest (CSMDoc::Document *document); void lastDocumentDeleted(); diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index 33725a6f94..cb3ff2cd0f 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -1,4 +1,3 @@ - #include "loader.hpp" #include diff --git a/apps/opencs/model/doc/messages.cpp b/apps/opencs/model/doc/messages.cpp index bd6e808c82..86e96a88d6 100644 --- a/apps/opencs/model/doc/messages.cpp +++ b/apps/opencs/model/doc/messages.cpp @@ -1,4 +1,3 @@ - #include "messages.hpp" CSMDoc::Message::Message() {} @@ -8,6 +7,20 @@ CSMDoc::Message::Message (const CSMWorld::UniversalId& id, const std::string& me : mId (id), mMessage (message), mHint (hint), mSeverity (severity) {} +std::string CSMDoc::Message::toString (Severity severity) +{ + switch (severity) + { + case CSMDoc::Message::Severity_Info: return "Information"; + case CSMDoc::Message::Severity_Warning: return "Warning"; + case CSMDoc::Message::Severity_Error: return "Error"; + case CSMDoc::Message::Severity_SeriousError: return "Serious Error"; + case CSMDoc::Message::Severity_Default: break; + } + + return ""; +} + CSMDoc::Messages::Messages (Message::Severity default_) : mDefault (default_) @@ -18,7 +31,7 @@ void CSMDoc::Messages::add (const CSMWorld::UniversalId& id, const std::string& { if (severity==Message::Severity_Default) severity = mDefault; - + mMessages.push_back (Message (id, message, hint, severity)); } diff --git a/apps/opencs/model/doc/messages.hpp b/apps/opencs/model/doc/messages.hpp index 86f5feb152..429feae4ec 100644 --- a/apps/opencs/model/doc/messages.hpp +++ b/apps/opencs/model/doc/messages.hpp @@ -21,18 +21,20 @@ namespace CSMDoc // reporting it correctly Severity_Default = 4 }; - + CSMWorld::UniversalId mId; std::string mMessage; std::string mHint; Severity mSeverity; Message(); - + Message (const CSMWorld::UniversalId& id, const std::string& message, const std::string& hint, Severity severity); + + static std::string toString (Severity severity); }; - + class Messages { public: diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index 8b27170868..a27ee9f514 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -1,4 +1,3 @@ - #include "operation.hpp" #include @@ -7,7 +6,6 @@ #include #include "../world/universalid.hpp" -#include "../settings/usersettings.hpp" #include "state.hpp" #include "stage.hpp" @@ -24,9 +22,6 @@ void CSMDoc::Operation::prepareStages() { iter->second = iter->first->setup(); mTotalSteps += iter->second; - - for (std::map::const_iterator iter2 (mSettings.begin()); iter2!=mSettings.end(); ++iter2) - iter->first->updateUserSetting (iter2->first, iter2->second); } } @@ -48,7 +43,7 @@ CSMDoc::Operation::~Operation() void CSMDoc::Operation::run() { mTimer->stop(); - + if (!mConnected) { connect (mTimer, SIGNAL (timeout()), this, SLOT (executeStage())); @@ -65,14 +60,6 @@ void CSMDoc::Operation::appendStage (Stage *stage) mStages.push_back (std::make_pair (stage, 0)); } -void CSMDoc::Operation::configureSettings (const std::vector& settings) -{ - for (std::vector::const_iterator iter (settings.begin()); iter!=settings.end(); ++iter) - { - mSettings.insert (std::make_pair (*iter, CSMSettings::UserSettings::instance().definitions (*iter))); - } -} - void CSMDoc::Operation::setDefaultSeverity (Message::Severity severity) { mDefaultSeverity = severity; @@ -102,14 +89,6 @@ void CSMDoc::Operation::abort() mCurrentStage = mStages.end(); } -void CSMDoc::Operation::updateUserSetting (const QString& name, const QStringList& value) -{ - std::map::iterator iter = mSettings.find (name); - - if (iter!=mSettings.end()) - iter->second = value; -} - void CSMDoc::Operation::executeStage() { if (!mPrepared) @@ -117,7 +96,7 @@ void CSMDoc::Operation::executeStage() prepareStages(); mPrepared = true; } - + Messages messages (mDefaultSeverity); while (mCurrentStage!=mStages.end()) diff --git a/apps/opencs/model/doc/operation.hpp b/apps/opencs/model/doc/operation.hpp index 703f9d44b7..ff396fa3ce 100644 --- a/apps/opencs/model/doc/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -34,7 +34,6 @@ namespace CSMDoc bool mError; bool mConnected; QTimer *mTimer; - std::map mSettings; bool mPrepared; Message::Severity mDefaultSeverity; @@ -53,11 +52,6 @@ namespace CSMDoc /// /// \attention Do no call this function while this Operation is running. - /// Specify settings to be passed on to stages. - /// - /// \attention Do no call this function while this Operation is running. - void configureSettings (const std::vector& settings); - /// \attention Do no call this function while this Operation is running. void setDefaultSeverity (Message::Severity severity); @@ -77,13 +71,13 @@ namespace CSMDoc void run(); - void updateUserSetting (const QString& name, const QStringList& value); - private slots: void executeStage(); - void operationDone(); + protected slots: + + virtual void operationDone(); }; } diff --git a/apps/opencs/model/doc/operationholder.cpp b/apps/opencs/model/doc/operationholder.cpp index 25fc6fc263..5fcf24fe4f 100644 --- a/apps/opencs/model/doc/operationholder.cpp +++ b/apps/opencs/model/doc/operationholder.cpp @@ -1,8 +1,5 @@ - #include "operationholder.hpp" -#include "../settings/usersettings.hpp" - #include "operation.hpp" CSMDoc::OperationHolder::OperationHolder (Operation *operation) : mRunning (false) @@ -31,9 +28,6 @@ void CSMDoc::OperationHolder::setOperation (Operation *operation) connect (this, SIGNAL (abortSignal()), mOperation, SLOT (abort())); connect (&mThread, SIGNAL (started()), mOperation, SLOT (run())); - - connect (&CSMSettings::UserSettings::instance(), SIGNAL (userSettingUpdated (const QString&, const QStringList&)), - mOperation, SLOT (updateUserSetting (const QString&, const QStringList&))); } bool CSMDoc::OperationHolder::isRunning() const diff --git a/apps/opencs/model/doc/runner.cpp b/apps/opencs/model/doc/runner.cpp index 14fe0cda8f..5a0bc39be5 100644 --- a/apps/opencs/model/doc/runner.cpp +++ b/apps/opencs/model/doc/runner.cpp @@ -1,4 +1,3 @@ - #include "runner.hpp" #include diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index 9f6e469b8f..95a2feaf24 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -1,4 +1,3 @@ - #include "saving.hpp" #include "../world/data.hpp" @@ -81,22 +80,25 @@ CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& proje appendStage (new WriteCollectionStage > (mDocument.getData().getStartScripts(), mState)); - appendStage (new WriteDialogueCollectionStage (mDocument, mState, false)); - - appendStage (new WriteDialogueCollectionStage (mDocument, mState, true)); - appendStage (new WriteRefIdCollectionStage (mDocument, mState)); appendStage (new CollectionReferencesStage (mDocument, mState)); appendStage (new WriteCellCollectionStage (mDocument, mState)); + // Dialogue can reference objects and cells so must be written after these records for vanilla-compatible files + + appendStage (new WriteDialogueCollectionStage (mDocument, mState, false)); + + appendStage (new WriteDialogueCollectionStage (mDocument, mState, true)); + appendStage (new WritePathgridCollectionStage (mDocument, mState)); - appendStage (new WriteLandCollectionStage (mDocument, mState)); - appendStage (new WriteLandTextureCollectionStage (mDocument, mState)); + // references Land Textures + appendStage (new WriteLandCollectionStage (mDocument, mState)); + // close file and clean up appendStage (new CloseSaveStage (mState)); diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index d6258da6ae..db38c4779b 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -1,4 +1,3 @@ - #include "savingstages.hpp" #include @@ -53,18 +52,16 @@ void CSMDoc::WriteHeaderStage::perform (int stage, Messages& messages) mState.getWriter().clearMaster(); - mState.getWriter().setFormat (0); - if (mSimple) { mState.getWriter().setAuthor (""); mState.getWriter().setDescription (""); mState.getWriter().setRecordCount (0); + mState.getWriter().setFormat (ESM::Header::CurrentFormat); } else { - mState.getWriter().setAuthor (mDocument.getData().getAuthor()); - mState.getWriter().setDescription (mDocument.getData().getDescription()); + mDocument.getData().getMetaData().save (mState.getWriter()); mState.getWriter().setRecordCount ( mDocument.getData().count (CSMWorld::RecordBase::State_Modified) + mDocument.getData().count (CSMWorld::RecordBase::State_ModifiedOnly) + @@ -102,84 +99,77 @@ int CSMDoc::WriteDialogueCollectionStage::setup() void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& messages) { + ESM::ESMWriter& writer = mState.getWriter(); const CSMWorld::Record& topic = mTopics.getRecord (stage); - CSMWorld::RecordBase::State state = topic.mState; - - if (state==CSMWorld::RecordBase::State_Deleted) + if (topic.mState == CSMWorld::RecordBase::State_Deleted) { // if the topic is deleted, we do not need to bother with INFO records. - - /// \todo wrote record with delete flag - + ESM::Dialogue dialogue = topic.get(); + writer.startRecord(dialogue.sRecordId); + dialogue.save(writer, true); + writer.endRecord(dialogue.sRecordId); return; } // Test, if we need to save anything associated info records. bool infoModified = false; - CSMWorld::InfoCollection::Range range = mInfos.getTopicRange (topic.get().mId); for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { - CSMWorld::RecordBase::State state = iter->mState; - - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly || - state==CSMWorld::RecordBase::State_Deleted) + if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) { infoModified = true; break; } } - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly || - infoModified) + if (topic.isModified() || infoModified) { - mState.getWriter().startRecord (topic.mModified.sRecordId); - mState.getWriter().writeHNCString ("NAME", topic.mModified.mId); - topic.mModified.save (mState.getWriter()); - mState.getWriter().endRecord (topic.mModified.sRecordId); + if (infoModified && topic.mState != CSMWorld::RecordBase::State_Modified + && topic.mState != CSMWorld::RecordBase::State_ModifiedOnly) + { + mState.getWriter().startRecord (topic.mBase.sRecordId); + topic.mBase.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); + mState.getWriter().endRecord (topic.mBase.sRecordId); + } + else + { + mState.getWriter().startRecord (topic.mModified.sRecordId); + topic.mModified.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); + mState.getWriter().endRecord (topic.mModified.sRecordId); + } // write modified selected info records - for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; - ++iter) + for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { - CSMWorld::RecordBase::State state = iter->mState; - - if (state==CSMWorld::RecordBase::State_Deleted) - { - /// \todo wrote record with delete flag - } - else if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly) + if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) { ESM::DialInfo info = iter->get(); info.mId = info.mId.substr (info.mId.find_last_of ('#')+1); + info.mPrev = ""; if (iter!=range.first) { CSMWorld::InfoCollection::RecordConstIterator prev = iter; --prev; - info.mPrev = - prev->mModified.mId.substr (prev->mModified.mId.find_last_of ('#')+1); + info.mPrev = prev->get().mId.substr (prev->get().mId.find_last_of ('#')+1); } CSMWorld::InfoCollection::RecordConstIterator next = iter; ++next; + info.mNext = ""; if (next!=range.second) { - info.mNext = - next->mModified.mId.substr (next->mModified.mId.find_last_of ('#')+1); + info.mNext = next->get().mId.substr (next->get().mId.find_last_of ('#')+1); } - mState.getWriter().startRecord (info.sRecordId); - mState.getWriter().writeHNCString ("INAM", info.mId); - info.save (mState.getWriter()); - mState.getWriter().endRecord (info.sRecordId); + writer.startRecord (info.sRecordId); + info.save (writer, iter->mState == CSMWorld::RecordBase::State_Deleted); + writer.endRecord (info.sRecordId); } } } @@ -227,9 +217,7 @@ void CSMDoc::CollectionReferencesStage::perform (int stage, Messages& messages) const CSMWorld::Record& record = mDocument.getData().getReferences().getRecord (i); - if (record.mState==CSMWorld::RecordBase::State_Deleted || - record.mState==CSMWorld::RecordBase::State_Modified || - record.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (record.isModified() || record.mState == CSMWorld::RecordBase::State_Deleted) { std::string cellId = record.get().mOriginalCell.empty() ? record.get().mCell : record.get().mOriginalCell; @@ -271,36 +259,34 @@ int CSMDoc::WriteCellCollectionStage::setup() void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& cell = - mDocument.getData().getCells().getRecord (stage); + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& cell = mDocument.getData().getCells().getRecord (stage); std::map >::const_iterator references = mState.getSubRecords().find (Misc::StringUtils::lowerCase (cell.get().mId)); - if (cell.mState==CSMWorld::RecordBase::State_Modified || - cell.mState==CSMWorld::RecordBase::State_ModifiedOnly || + if (cell.isModified() || + cell.mState == CSMWorld::RecordBase::State_Deleted || references!=mState.getSubRecords().end()) { - bool interior = cell.get().mId.substr (0, 1)!="#"; + CSMWorld::Cell cellRecord = cell.get(); + bool interior = cellRecord.mId.substr (0, 1)!="#"; // write cell data - mState.getWriter().startRecord (cell.mModified.sRecordId); - - mState.getWriter().writeHNOCString ("NAME", cell.get().mName); - - ESM::Cell cell2 = cell.get(); + writer.startRecord (cellRecord.sRecordId); if (interior) - cell2.mData.mFlags |= ESM::Cell::Interior; + cellRecord.mData.mFlags |= ESM::Cell::Interior; else { - cell2.mData.mFlags &= ~ESM::Cell::Interior; + cellRecord.mData.mFlags &= ~ESM::Cell::Interior; - std::istringstream stream (cell.get().mId.c_str()); + std::istringstream stream (cellRecord.mId.c_str()); char ignore; - stream >> ignore >> cell2.mData.mX >> cell2.mData.mY; + stream >> ignore >> cellRecord.mData.mX >> cellRecord.mData.mY; } - cell2.save (mState.getWriter()); + + cellRecord.save (writer, cell.mState == CSMWorld::RecordBase::State_Deleted); // write references if (references!=mState.getSubRecords().end()) @@ -311,24 +297,25 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) const CSMWorld::Record& ref = mDocument.getData().getReferences().getRecord (*iter); - if (ref.mState==CSMWorld::RecordBase::State_Modified || - ref.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (ref.isModified() || ref.mState == CSMWorld::RecordBase::State_Deleted) { + CSMWorld::CellRef refRecord = ref.get(); + // recalculate the ref's cell location std::ostringstream stream; if (!interior) { - std::pair index = ref.get().getCellIndex(); + std::pair index = refRecord.getCellIndex(); stream << "#" << index.first << " " << index.second; } // An empty mOriginalCell is meant to indicate that it is the same as // the current cell. It is possible that a moved ref is moved again. - if ((ref.get().mOriginalCell.empty() ? ref.get().mCell : ref.get().mOriginalCell) + if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) != stream.str() && !interior) { ESM::MovedCellRef moved; - moved.mRefNum = ref.get().mRefNum; + moved.mRefNum = refRecord.mRefNum; // Need to fill mTarget with the ref's new position. std::istringstream istream (stream.str().c_str()); @@ -336,24 +323,16 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) char ignore; istream >> ignore >> moved.mTarget[0] >> moved.mTarget[1]; - ref.get().mRefNum.save (mState.getWriter(), false, "MVRF"); - mState.getWriter().writeHNT ("CNDT", moved.mTarget, 8); + refRecord.mRefNum.save (writer, false, "MVRF"); + writer.writeHNT ("CNDT", moved.mTarget, 8); } - ref.get().save (mState.getWriter()); - } - else if (ref.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + refRecord.save (writer, false, false, ref.mState == CSMWorld::RecordBase::State_Deleted); } } } - mState.getWriter().endRecord (cell.mModified.sRecordId); - } - else if (cell.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.endRecord (cellRecord.sRecordId); } } @@ -370,11 +349,11 @@ int CSMDoc::WritePathgridCollectionStage::setup() void CSMDoc::WritePathgridCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& pathgrid = + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& pathgrid = mDocument.getData().getPathgrids().getRecord (stage); - if (pathgrid.mState==CSMWorld::RecordBase::State_Modified || - pathgrid.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (pathgrid.isModified() || pathgrid.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::Pathgrid record = pathgrid.get(); @@ -387,15 +366,9 @@ void CSMDoc::WritePathgridCollectionStage::perform (int stage, Messages& message else record.mCell = record.mId; - mState.getWriter().startRecord (record.sRecordId); - - record.save (mState.getWriter()); - - mState.getWriter().endRecord (record.sRecordId); - } - else if (pathgrid.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.startRecord (record.sRecordId); + record.save (writer, pathgrid.mState == CSMWorld::RecordBase::State_Deleted); + writer.endRecord (record.sRecordId); } } @@ -412,25 +385,20 @@ int CSMDoc::WriteLandCollectionStage::setup() void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& land = + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& land = mDocument.getData().getLand().getRecord (stage); - if (land.mState==CSMWorld::RecordBase::State_Modified || - land.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (land.isModified() || land.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::Land record = land.get(); + writer.startRecord (record.sRecordId); + record.save (writer, land.mState == CSMWorld::RecordBase::State_Deleted); - mState.getWriter().startRecord (record.mLand->sRecordId); + if (const ESM::Land::LandData *data = record.getLandData (record.mDataTypes)) + data->save (mState.getWriter()); - record.mLand->save (mState.getWriter()); - if(record.mLand->mLandData) - record.mLand->mLandData->save (mState.getWriter()); - - mState.getWriter().endRecord (record.mLand->sRecordId); - } - else if (land.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.endRecord (record.sRecordId); } } @@ -447,23 +415,16 @@ int CSMDoc::WriteLandTextureCollectionStage::setup() void CSMDoc::WriteLandTextureCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& landTexture = + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& landTexture = mDocument.getData().getLandTextures().getRecord (stage); - if (landTexture.mState==CSMWorld::RecordBase::State_Modified || - landTexture.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (landTexture.isModified() || landTexture.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::LandTexture record = landTexture.get(); - - mState.getWriter().startRecord (record.sRecordId); - - record.save (mState.getWriter()); - - mState.getWriter().endRecord (record.sRecordId); - } - else if (landTexture.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.startRecord (record.sRecordId); + record.save (writer, landTexture.mState == CSMWorld::RecordBase::State_Deleted); + writer.endRecord (record.sRecordId); } } diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index 188f22f961..64afd0dd8e 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -100,26 +100,17 @@ namespace CSMDoc if (CSMWorld::getScopeFromId (mCollection.getRecord (stage).get().mId)!=mScope) return; + ESM::ESMWriter& writer = mState.getWriter(); CSMWorld::RecordBase::State state = mCollection.getRecord (stage).mState; + typename CollectionT::ESXRecord record = mCollection.getRecord (stage).get(); - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly) + if (state == CSMWorld::RecordBase::State_Modified || + state == CSMWorld::RecordBase::State_ModifiedOnly || + state == CSMWorld::RecordBase::State_Deleted) { - // FIXME: A quick Workaround to support records which should not write - // NAME, including SKIL, MGEF and SCPT. If there are many more - // idcollection records that doesn't use NAME then a more generic - // solution may be required. - uint32_t name = mCollection.getRecord (stage).mModified.sRecordId; - mState.getWriter().startRecord (name); - - if(name != ESM::REC_SKIL && name != ESM::REC_MGEF && name != ESM::REC_SCPT) - mState.getWriter().writeHNCString ("NAME", mCollection.getId (stage)); - mCollection.getRecord (stage).mModified.save (mState.getWriter()); - mState.getWriter().endRecord (mCollection.getRecord (stage).mModified.sRecordId); - } - else if (state==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.startRecord (record.sRecordId); + record.save (writer, state == CSMWorld::RecordBase::State_Deleted); + writer.endRecord (record.sRecordId); } } diff --git a/apps/opencs/model/doc/savingstate.cpp b/apps/opencs/model/doc/savingstate.cpp index e7ad551b27..10539c1b55 100644 --- a/apps/opencs/model/doc/savingstate.cpp +++ b/apps/opencs/model/doc/savingstate.cpp @@ -1,4 +1,3 @@ - #include "savingstate.hpp" #include "operation.hpp" diff --git a/apps/opencs/model/doc/stage.cpp b/apps/opencs/model/doc/stage.cpp index 78aa145742..3c8c107ba0 100644 --- a/apps/opencs/model/doc/stage.cpp +++ b/apps/opencs/model/doc/stage.cpp @@ -1,6 +1,3 @@ - #include "stage.hpp" CSMDoc::Stage::~Stage() {} - -void CSMDoc::Stage::updateUserSetting (const QString& name, const QStringList& value) {} diff --git a/apps/opencs/model/doc/stage.hpp b/apps/opencs/model/doc/stage.hpp index e0328a91a1..1eae27a982 100644 --- a/apps/opencs/model/doc/stage.hpp +++ b/apps/opencs/model/doc/stage.hpp @@ -23,11 +23,7 @@ namespace CSMDoc virtual void perform (int stage, Messages& messages) = 0; ///< Messages resulting from this stage will be appended to \a messages. - - /// Default-implementation: ignore - virtual void updateUserSetting (const QString& name, const QStringList& value); }; } #endif - diff --git a/apps/opencs/model/doc/state.hpp b/apps/opencs/model/doc/state.hpp index 78f4681010..7352b4b999 100644 --- a/apps/opencs/model/doc/state.hpp +++ b/apps/opencs/model/doc/state.hpp @@ -12,7 +12,7 @@ namespace CSMDoc State_Saving = 16, State_Verifying = 32, - State_Compiling = 64, // not implemented yet + State_Merging = 64, State_Searching = 128, State_Loading = 256 // pseudo-state; can not be encountered in a loaded document }; diff --git a/apps/opencs/model/filter/andnode.cpp b/apps/opencs/model/filter/andnode.cpp index 4249fc228e..9086627991 100644 --- a/apps/opencs/model/filter/andnode.cpp +++ b/apps/opencs/model/filter/andnode.cpp @@ -1,4 +1,3 @@ - #include "andnode.hpp" #include diff --git a/apps/opencs/model/filter/booleannode.cpp b/apps/opencs/model/filter/booleannode.cpp index 35fc98e085..ee7ddc1c05 100644 --- a/apps/opencs/model/filter/booleannode.cpp +++ b/apps/opencs/model/filter/booleannode.cpp @@ -1,4 +1,3 @@ - #include "booleannode.hpp" CSMFilter::BooleanNode::BooleanNode (bool true_) : mTrue (true_) {} diff --git a/apps/opencs/model/filter/leafnode.cpp b/apps/opencs/model/filter/leafnode.cpp index 055a1747cc..6745e165ec 100644 --- a/apps/opencs/model/filter/leafnode.cpp +++ b/apps/opencs/model/filter/leafnode.cpp @@ -1,4 +1,3 @@ - #include "leafnode.hpp" std::vector CSMFilter::LeafNode::getReferencedColumns() const diff --git a/apps/opencs/model/filter/narynode.cpp b/apps/opencs/model/filter/narynode.cpp index 98f706c875..f2e0e5cb2c 100644 --- a/apps/opencs/model/filter/narynode.cpp +++ b/apps/opencs/model/filter/narynode.cpp @@ -1,4 +1,3 @@ - #include "narynode.hpp" #include diff --git a/apps/opencs/model/filter/node.cpp b/apps/opencs/model/filter/node.cpp index 091dc46988..e25610675e 100644 --- a/apps/opencs/model/filter/node.cpp +++ b/apps/opencs/model/filter/node.cpp @@ -1,4 +1,3 @@ - #include "node.hpp" CSMFilter::Node::Node() {} diff --git a/apps/opencs/model/filter/notnode.cpp b/apps/opencs/model/filter/notnode.cpp index b5d9da7b7d..ba5302bbee 100644 --- a/apps/opencs/model/filter/notnode.cpp +++ b/apps/opencs/model/filter/notnode.cpp @@ -1,4 +1,3 @@ - #include "notnode.hpp" CSMFilter::NotNode::NotNode (boost::shared_ptr child) : UnaryNode (child, "not") {} diff --git a/apps/opencs/model/filter/ornode.cpp b/apps/opencs/model/filter/ornode.cpp index c5d15a3846..41ec7b5cf3 100644 --- a/apps/opencs/model/filter/ornode.cpp +++ b/apps/opencs/model/filter/ornode.cpp @@ -1,4 +1,3 @@ - #include "ornode.hpp" #include diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index 51338dfc98..7936a1ae2b 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -1,4 +1,3 @@ - #include "parser.hpp" #include diff --git a/apps/opencs/model/filter/textnode.cpp b/apps/opencs/model/filter/textnode.cpp index 73c378f113..246ebae249 100644 --- a/apps/opencs/model/filter/textnode.cpp +++ b/apps/opencs/model/filter/textnode.cpp @@ -1,4 +1,3 @@ - #include "textnode.hpp" #include diff --git a/apps/opencs/model/filter/unarynode.cpp b/apps/opencs/model/filter/unarynode.cpp index c40d191b62..cbdadf6fc1 100644 --- a/apps/opencs/model/filter/unarynode.cpp +++ b/apps/opencs/model/filter/unarynode.cpp @@ -1,4 +1,3 @@ - #include "unarynode.hpp" CSMFilter::UnaryNode::UnaryNode (boost::shared_ptr child, const std::string& name) diff --git a/apps/opencs/model/filter/valuenode.cpp b/apps/opencs/model/filter/valuenode.cpp index 6fdb5cb021..85c5a8e27f 100644 --- a/apps/opencs/model/filter/valuenode.cpp +++ b/apps/opencs/model/filter/valuenode.cpp @@ -1,4 +1,3 @@ - #include "valuenode.hpp" #include diff --git a/apps/opencs/model/prefs/boolsetting.cpp b/apps/opencs/model/prefs/boolsetting.cpp new file mode 100644 index 0000000000..6c0babaf7d --- /dev/null +++ b/apps/opencs/model/prefs/boolsetting.cpp @@ -0,0 +1,47 @@ + +#include "boolsetting.hpp" + +#include +#include + +#include + +#include "category.hpp" +#include "state.hpp" + +CSMPrefs::BoolSetting::BoolSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, bool default_) +: Setting (parent, values, mutex, key, label), mDefault (default_) +{} + +CSMPrefs::BoolSetting& CSMPrefs::BoolSetting::setTooltip (const std::string& tooltip) +{ + mTooltip = tooltip; + return *this; +} + +std::pair CSMPrefs::BoolSetting::makeWidgets (QWidget *parent) +{ + QCheckBox *widget = new QCheckBox (QString::fromUtf8 (getLabel().c_str()), parent); + widget->setCheckState (mDefault ? Qt::Checked : Qt::Unchecked); + + if (!mTooltip.empty()) + { + QString tooltip = QString::fromUtf8 (mTooltip.c_str()); + widget->setToolTip (tooltip); + } + + connect (widget, SIGNAL (stateChanged (int)), this, SLOT (valueChanged (int))); + + return std::make_pair (static_cast (0), widget); +} + +void CSMPrefs::BoolSetting::valueChanged (int value) +{ + { + QMutexLocker lock (getMutex()); + getValues().setBool (getKey(), getParent()->getKey(), value); + } + + getParent()->getState()->update (*this); +} diff --git a/apps/opencs/model/prefs/boolsetting.hpp b/apps/opencs/model/prefs/boolsetting.hpp new file mode 100644 index 0000000000..f8a321859f --- /dev/null +++ b/apps/opencs/model/prefs/boolsetting.hpp @@ -0,0 +1,31 @@ +#ifndef CSM_PREFS_BOOLSETTING_H +#define CSM_PREFS_BOOLSETTING_H + +#include "setting.hpp" + +namespace CSMPrefs +{ + class BoolSetting : public Setting + { + Q_OBJECT + + std::string mTooltip; + bool mDefault; + + public: + + BoolSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, bool default_); + + BoolSetting& setTooltip (const std::string& tooltip); + + /// Return label, input widget. + virtual std::pair makeWidgets (QWidget *parent); + + private slots: + + void valueChanged (int value); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/category.cpp b/apps/opencs/model/prefs/category.cpp new file mode 100644 index 0000000000..6af0ac645b --- /dev/null +++ b/apps/opencs/model/prefs/category.cpp @@ -0,0 +1,51 @@ + +#include "category.hpp" + +#include + +#include "setting.hpp" +#include "state.hpp" + +CSMPrefs::Category::Category (State *parent, const std::string& key) +: mParent (parent), mKey (key) +{} + +const std::string& CSMPrefs::Category::getKey() const +{ + return mKey; +} + +CSMPrefs::State *CSMPrefs::Category::getState() const +{ + return mParent; +} + +void CSMPrefs::Category::addSetting (Setting *setting) +{ + mSettings.push_back (setting); +} + +CSMPrefs::Category::Iterator CSMPrefs::Category::begin() +{ + return mSettings.begin(); +} + +CSMPrefs::Category::Iterator CSMPrefs::Category::end() +{ + return mSettings.end(); +} + +CSMPrefs::Setting& CSMPrefs::Category::operator[] (const std::string& key) +{ + for (Iterator iter = mSettings.begin(); iter!=mSettings.end(); ++iter) + if ((*iter)->getKey()==key) + return **iter; + + throw std::logic_error ("Invalid user setting: " + key); +} + +void CSMPrefs::Category::update() +{ + for (Iterator iter = mSettings.begin(); iter!=mSettings.end(); ++iter) + mParent->update (**iter); +} diff --git a/apps/opencs/model/prefs/category.hpp b/apps/opencs/model/prefs/category.hpp new file mode 100644 index 0000000000..b70716aa0e --- /dev/null +++ b/apps/opencs/model/prefs/category.hpp @@ -0,0 +1,45 @@ +#ifndef CSM_PREFS_CATEGORY_H +#define CSM_PREFS_CATEGORY_H + +#include +#include + +namespace CSMPrefs +{ + class State; + class Setting; + + class Category + { + public: + + typedef std::vector Container; + typedef Container::iterator Iterator; + + private: + + State *mParent; + std::string mKey; + Container mSettings; + + public: + + Category (State *parent, const std::string& key); + + const std::string& getKey() const; + + State *getState() const; + + void addSetting (Setting *setting); + + Iterator begin(); + + Iterator end(); + + Setting& operator[] (const std::string& key); + + void update(); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/coloursetting.cpp b/apps/opencs/model/prefs/coloursetting.cpp new file mode 100644 index 0000000000..d51bfad567 --- /dev/null +++ b/apps/opencs/model/prefs/coloursetting.cpp @@ -0,0 +1,52 @@ + +#include "coloursetting.hpp" + +#include +#include + +#include + +#include "../../view/widget/coloreditor.hpp" + +#include "category.hpp" +#include "state.hpp" + +CSMPrefs::ColourSetting::ColourSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, QColor default_) +: Setting (parent, values, mutex, key, label), mDefault (default_) +{} + +CSMPrefs::ColourSetting& CSMPrefs::ColourSetting::setTooltip (const std::string& tooltip) +{ + mTooltip = tooltip; + return *this; +} + +std::pair CSMPrefs::ColourSetting::makeWidgets (QWidget *parent) +{ + QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); + + CSVWidget::ColorEditor *widget = new CSVWidget::ColorEditor (mDefault, parent); + + if (!mTooltip.empty()) + { + QString tooltip = QString::fromUtf8 (mTooltip.c_str()); + label->setToolTip (tooltip); + widget->setToolTip (tooltip); + } + + connect (widget, SIGNAL (pickingFinished()), this, SLOT (valueChanged())); + + return std::make_pair (label, widget); +} + +void CSMPrefs::ColourSetting::valueChanged() +{ + CSVWidget::ColorEditor& widget = dynamic_cast (*sender()); + { + QMutexLocker lock (getMutex()); + getValues().setString (getKey(), getParent()->getKey(), widget.color().name().toUtf8().data()); + } + + getParent()->getState()->update (*this); +} diff --git a/apps/opencs/model/prefs/coloursetting.hpp b/apps/opencs/model/prefs/coloursetting.hpp new file mode 100644 index 0000000000..be58426f2a --- /dev/null +++ b/apps/opencs/model/prefs/coloursetting.hpp @@ -0,0 +1,34 @@ +#ifndef CSM_PREFS_COLOURSETTING_H +#define CSM_PREFS_COLOURSETTING_H + +#include "setting.hpp" + +#include + +namespace CSMPrefs +{ + class ColourSetting : public Setting + { + Q_OBJECT + + std::string mTooltip; + QColor mDefault; + + public: + + ColourSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, + QColor default_); + + ColourSetting& setTooltip (const std::string& tooltip); + + /// Return label, input widget. + virtual std::pair makeWidgets (QWidget *parent); + + private slots: + + void valueChanged(); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp new file mode 100644 index 0000000000..7c247777d2 --- /dev/null +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -0,0 +1,75 @@ + +#include "doublesetting.hpp" + +#include + +#include +#include +#include + +#include + +#include "category.hpp" +#include "state.hpp" + +CSMPrefs::DoubleSetting::DoubleSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, double default_) +: Setting (parent, values, mutex, key, label), + mMin (0), mMax (std::numeric_limits::max()), + mDefault (default_) +{} + +CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setRange (double min, double max) +{ + mMin = min; + mMax = max; + return *this; +} + +CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setMin (double min) +{ + mMin = min; + return *this; +} + +CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setMax (double max) +{ + mMax = max; + return *this; +} + +CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setTooltip (const std::string& tooltip) +{ + mTooltip = tooltip; + return *this; +} + +std::pair CSMPrefs::DoubleSetting::makeWidgets (QWidget *parent) +{ + QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); + + QDoubleSpinBox *widget = new QDoubleSpinBox (parent); + widget->setRange (mMin, mMax); + widget->setValue (mDefault); + + if (!mTooltip.empty()) + { + QString tooltip = QString::fromUtf8 (mTooltip.c_str()); + label->setToolTip (tooltip); + widget->setToolTip (tooltip); + } + + connect (widget, SIGNAL (valueChanged (double)), this, SLOT (valueChanged (double))); + + return std::make_pair (label, widget); +} + +void CSMPrefs::DoubleSetting::valueChanged (double value) +{ + { + QMutexLocker lock (getMutex()); + getValues().setFloat (getKey(), getParent()->getKey(), value); + } + + getParent()->getState()->update (*this); +} diff --git a/apps/opencs/model/prefs/doublesetting.hpp b/apps/opencs/model/prefs/doublesetting.hpp new file mode 100644 index 0000000000..3868f014e2 --- /dev/null +++ b/apps/opencs/model/prefs/doublesetting.hpp @@ -0,0 +1,41 @@ +#ifndef CSM_PREFS_DOUBLESETTING_H +#define CSM_PREFS_DOUBLESETTING_H + +#include "setting.hpp" + +namespace CSMPrefs +{ + class DoubleSetting : public Setting + { + Q_OBJECT + + double mMin; + double mMax; + std::string mTooltip; + double mDefault; + + public: + + DoubleSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, + double default_); + + // defaults to [0, std::numeric_limits::max()] + DoubleSetting& setRange (double min, double max); + + DoubleSetting& setMin (double min); + + DoubleSetting& setMax (double max); + + DoubleSetting& setTooltip (const std::string& tooltip); + + /// Return label, input widget. + virtual std::pair makeWidgets (QWidget *parent); + + private slots: + + void valueChanged (double value); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/enumsetting.cpp b/apps/opencs/model/prefs/enumsetting.cpp new file mode 100644 index 0000000000..e69f717ead --- /dev/null +++ b/apps/opencs/model/prefs/enumsetting.cpp @@ -0,0 +1,112 @@ + +#include "enumsetting.hpp" + +#include +#include +#include + +#include + +#include "category.hpp" +#include "state.hpp" + + +CSMPrefs::EnumValue::EnumValue (const std::string& value, const std::string& tooltip) +: mValue (value), mTooltip (tooltip) +{} + +CSMPrefs::EnumValue::EnumValue (const char *value) +: mValue (value) +{} + + +CSMPrefs::EnumValues& CSMPrefs::EnumValues::add (const EnumValues& values) +{ + mValues.insert (mValues.end(), values.mValues.begin(), values.mValues.end()); + return *this; +} + +CSMPrefs::EnumValues& CSMPrefs::EnumValues::add (const EnumValue& value) +{ + mValues.push_back (value); + return *this; +} + +CSMPrefs::EnumValues& CSMPrefs::EnumValues::add (const std::string& value, const std::string& tooltip) +{ + mValues.push_back (EnumValue (value, tooltip)); + return *this; +} + + +CSMPrefs::EnumSetting::EnumSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, const EnumValue& default_) +: Setting (parent, values, mutex, key, label), mDefault (default_) +{} + +CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::setTooltip (const std::string& tooltip) +{ + mTooltip = tooltip; + return *this; +} + +CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValues (const EnumValues& values) +{ + mValues.add (values); + return *this; +} + +CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue (const EnumValue& value) +{ + mValues.add (value); + return *this; +} + +CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue (const std::string& value, const std::string& tooltip) +{ + mValues.add (value, tooltip); + return *this; +} + +std::pair CSMPrefs::EnumSetting::makeWidgets (QWidget *parent) +{ + QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); + + QComboBox *widget = new QComboBox (parent); + + int index = 0; + + for (int i=0; i (mValues.mValues.size()); ++i) + { + if (mDefault.mValue==mValues.mValues[i].mValue) + index = i; + + widget->addItem (QString::fromUtf8 (mValues.mValues[i].mValue.c_str())); + + if (!mValues.mValues[i].mTooltip.empty()) + widget->setItemData (i, QString::fromUtf8 (mValues.mValues[i].mTooltip.c_str()), + Qt::ToolTipRole); + } + + widget->setCurrentIndex (index); + + if (!mTooltip.empty()) + { + QString tooltip = QString::fromUtf8 (mTooltip.c_str()); + label->setToolTip (tooltip); + } + + connect (widget, SIGNAL (currentIndexChanged (int)), this, SLOT (valueChanged (int))); + + return std::make_pair (label, widget); +} + +void CSMPrefs::EnumSetting::valueChanged (int value) +{ + { + QMutexLocker lock (getMutex()); + getValues().setString (getKey(), getParent()->getKey(), mValues.mValues.at (value).mValue); + } + + getParent()->getState()->update (*this); +} diff --git a/apps/opencs/model/prefs/enumsetting.hpp b/apps/opencs/model/prefs/enumsetting.hpp new file mode 100644 index 0000000000..728de3acd2 --- /dev/null +++ b/apps/opencs/model/prefs/enumsetting.hpp @@ -0,0 +1,62 @@ +#ifndef CSM_PREFS_ENUMSETTING_H +#define CSM_PREFS_ENUMSETTING_H + +#include + +#include "setting.hpp" + +namespace CSMPrefs +{ + struct EnumValue + { + std::string mValue; + std::string mTooltip; + + EnumValue (const std::string& value, const std::string& tooltip = ""); + + EnumValue (const char *value); + }; + + struct EnumValues + { + std::vector mValues; + + EnumValues& add (const EnumValues& values); + + EnumValues& add (const EnumValue& value); + + EnumValues& add (const std::string& value, const std::string& tooltip); + }; + + class EnumSetting : public Setting + { + Q_OBJECT + + std::string mTooltip; + EnumValue mDefault; + EnumValues mValues; + + public: + + EnumSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, + const EnumValue& default_); + + EnumSetting& setTooltip (const std::string& tooltip); + + EnumSetting& addValues (const EnumValues& values); + + EnumSetting& addValue (const EnumValue& value); + + EnumSetting& addValue (const std::string& value, const std::string& tooltip); + + /// Return label, input widget. + virtual std::pair makeWidgets (QWidget *parent); + + private slots: + + void valueChanged (int value); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/intsetting.cpp b/apps/opencs/model/prefs/intsetting.cpp new file mode 100644 index 0000000000..269a63af43 --- /dev/null +++ b/apps/opencs/model/prefs/intsetting.cpp @@ -0,0 +1,74 @@ + +#include "intsetting.hpp" + +#include + +#include +#include +#include + +#include + +#include "category.hpp" +#include "state.hpp" + +CSMPrefs::IntSetting::IntSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, int default_) +: Setting (parent, values, mutex, key, label), mMin (0), mMax (std::numeric_limits::max()), + mDefault (default_) +{} + +CSMPrefs::IntSetting& CSMPrefs::IntSetting::setRange (int min, int max) +{ + mMin = min; + mMax = max; + return *this; +} + +CSMPrefs::IntSetting& CSMPrefs::IntSetting::setMin (int min) +{ + mMin = min; + return *this; +} + +CSMPrefs::IntSetting& CSMPrefs::IntSetting::setMax (int max) +{ + mMax = max; + return *this; +} + +CSMPrefs::IntSetting& CSMPrefs::IntSetting::setTooltip (const std::string& tooltip) +{ + mTooltip = tooltip; + return *this; +} + +std::pair CSMPrefs::IntSetting::makeWidgets (QWidget *parent) +{ + QLabel *label = new QLabel (QString::fromUtf8 (getLabel().c_str()), parent); + + QSpinBox *widget = new QSpinBox (parent); + widget->setRange (mMin, mMax); + widget->setValue (mDefault); + + if (!mTooltip.empty()) + { + QString tooltip = QString::fromUtf8 (mTooltip.c_str()); + label->setToolTip (tooltip); + widget->setToolTip (tooltip); + } + + connect (widget, SIGNAL (valueChanged (int)), this, SLOT (valueChanged (int))); + + return std::make_pair (label, widget); +} + +void CSMPrefs::IntSetting::valueChanged (int value) +{ + { + QMutexLocker lock (getMutex()); + getValues().setInt (getKey(), getParent()->getKey(), value); + } + + getParent()->getState()->update (*this); +} diff --git a/apps/opencs/model/prefs/intsetting.hpp b/apps/opencs/model/prefs/intsetting.hpp new file mode 100644 index 0000000000..0ee6cf9e34 --- /dev/null +++ b/apps/opencs/model/prefs/intsetting.hpp @@ -0,0 +1,40 @@ +#ifndef CSM_PREFS_INTSETTING_H +#define CSM_PREFS_INTSETTING_H + +#include "setting.hpp" + +namespace CSMPrefs +{ + class IntSetting : public Setting + { + Q_OBJECT + + int mMin; + int mMax; + std::string mTooltip; + int mDefault; + + public: + + IntSetting (Category *parent, Settings::Manager *values, + QMutex *mutex, const std::string& key, const std::string& label, int default_); + + // defaults to [0, std::numeric_limits::max()] + IntSetting& setRange (int min, int max); + + IntSetting& setMin (int min); + + IntSetting& setMax (int max); + + IntSetting& setTooltip (const std::string& tooltip); + + /// Return label, input widget. + virtual std::pair makeWidgets (QWidget *parent); + + private slots: + + void valueChanged (int value); + }; +} + +#endif diff --git a/apps/opencs/model/prefs/setting.cpp b/apps/opencs/model/prefs/setting.cpp new file mode 100644 index 0000000000..75b58322d9 --- /dev/null +++ b/apps/opencs/model/prefs/setting.cpp @@ -0,0 +1,97 @@ + +#include "setting.hpp" + +#include +#include + +#include "category.hpp" +#include "state.hpp" + +Settings::Manager& CSMPrefs::Setting::getValues() +{ + return *mValues; +} + +QMutex *CSMPrefs::Setting::getMutex() +{ + return mMutex; +} + +CSMPrefs::Setting::Setting (Category *parent, Settings::Manager *values, QMutex *mutex, + const std::string& key, const std::string& label) +: QObject (parent->getState()), mParent (parent), mValues (values), mMutex (mutex), mKey (key), + mLabel (label) +{} + +CSMPrefs::Setting:: ~Setting() {} + +std::pair CSMPrefs::Setting::makeWidgets (QWidget *parent) +{ + return std::pair (0, 0); +} + +const CSMPrefs::Category *CSMPrefs::Setting::getParent() const +{ + return mParent; +} + +const std::string& CSMPrefs::Setting::getKey() const +{ + return mKey; +} + +const std::string& CSMPrefs::Setting::getLabel() const +{ + return mLabel; +} + +int CSMPrefs::Setting::toInt() const +{ + QMutexLocker lock (mMutex); + return mValues->getInt (mKey, mParent->getKey()); +} + +double CSMPrefs::Setting::toDouble() const +{ + QMutexLocker lock (mMutex); + return mValues->getFloat (mKey, mParent->getKey()); +} + +std::string CSMPrefs::Setting::toString() const +{ + QMutexLocker lock (mMutex); + return mValues->getString (mKey, mParent->getKey()); +} + +bool CSMPrefs::Setting::isTrue() const +{ + QMutexLocker lock (mMutex); + return mValues->getBool (mKey, mParent->getKey()); +} + +QColor CSMPrefs::Setting::toColor() const +{ + // toString() handles lock + return QColor (QString::fromUtf8 (toString().c_str())); +} + +bool CSMPrefs::operator== (const Setting& setting, const std::string& key) +{ + std::string fullKey = setting.getParent()->getKey() + "/" + setting.getKey(); + return fullKey==key; +} + +bool CSMPrefs::operator== (const std::string& key, const Setting& setting) +{ + return setting==key; +} + +bool CSMPrefs::operator!= (const Setting& setting, const std::string& key) +{ + return !(setting==key); +} + +bool CSMPrefs::operator!= (const std::string& key, const Setting& setting) +{ + return !(key==setting); +} diff --git a/apps/opencs/model/prefs/setting.hpp b/apps/opencs/model/prefs/setting.hpp new file mode 100644 index 0000000000..00bcc638bb --- /dev/null +++ b/apps/opencs/model/prefs/setting.hpp @@ -0,0 +1,74 @@ +#ifndef CSM_PREFS_SETTING_H +#define CSM_PREFS_SETTING_H + +#include +#include + +#include + +class QWidget; +class QColor; +class QMutex; + +namespace Settings +{ + class Manager; +} + +namespace CSMPrefs +{ + class Category; + + class Setting : public QObject + { + Q_OBJECT + + Category *mParent; + Settings::Manager *mValues; + QMutex *mMutex; + std::string mKey; + std::string mLabel; + + protected: + + Settings::Manager& getValues(); + + QMutex *getMutex(); + + public: + + Setting (Category *parent, Settings::Manager *values, QMutex *mutex, const std::string& key, const std::string& label); + + virtual ~Setting(); + + /// Return label, input widget. + /// + /// \note first can be a 0-pointer, which means that the label is part of the input + /// widget. + virtual std::pair makeWidgets (QWidget *parent); + + const Category *getParent() const; + + const std::string& getKey() const; + + const std::string& getLabel() const; + + int toInt() const; + + double toDouble() const; + + std::string toString() const; + + bool isTrue() const; + + QColor toColor() const; + }; + + // note: fullKeys have the format categoryKey/settingKey + bool operator== (const Setting& setting, const std::string& fullKey); + bool operator== (const std::string& fullKey, const Setting& setting); + bool operator!= (const Setting& setting, const std::string& fullKey); + bool operator!= (const std::string& fullKey, const Setting& setting); +} + +#endif diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp new file mode 100644 index 0000000000..8b827d0a2b --- /dev/null +++ b/apps/opencs/model/prefs/state.cpp @@ -0,0 +1,392 @@ + +#include "state.hpp" + +#include +#include +#include + +#include "intsetting.hpp" +#include "doublesetting.hpp" +#include "boolsetting.hpp" +#include "coloursetting.hpp" + +CSMPrefs::State *CSMPrefs::State::sThis = 0; + +void CSMPrefs::State::load() +{ + // default settings file + boost::filesystem::path local = mConfigurationManager.getLocalPath() / mConfigFile; + boost::filesystem::path global = mConfigurationManager.getGlobalPath() / mConfigFile; + + if (boost::filesystem::exists (local)) + mSettings.loadDefault (local.string()); + else if (boost::filesystem::exists (global)) + mSettings.loadDefault (global.string()); + else + throw std::runtime_error ("No default settings file found! Make sure the file \"openmw-cs.cfg\" was properly installed."); + + // user settings file + boost::filesystem::path user = mConfigurationManager.getUserConfigPath() / mConfigFile; + + if (boost::filesystem::exists (user)) + mSettings.loadUser (user.string()); +} + +void CSMPrefs::State::declare() +{ + declareCategory ("Windows"); + declareInt ("default-width", "Default window width", 800). + setTooltip ("Newly opened top-level windows will open with this width."). + setMin (80); + declareInt ("default-height", "Default window height", 600). + setTooltip ("Newly opened top-level windows will open with this height."). + setMin (80); + declareBool ("show-statusbar", "Show Status Bar", true). + setTooltip ("If a newly open top level window is showing status bars or not. " + " Note that this does not affect existing windows."); + declareSeparator(); + declareBool ("reuse", "Reuse Subviews", true). + setTooltip ("When a new subview is requested and a matching subview already " + " exist, do not open a new subview and use the existing one instead."); + declareInt ("max-subviews", "Maximum number of subviews per top-level window", 256). + setTooltip ("If the maximum number is reached and a new subview is opened " + "it will be placed into a new top-level window."). + setRange (1, 256); + declareBool ("hide-subview", "Hide single subview", false). + setTooltip ("When a view contains only a single subview, hide the subview title " + "bar and if this subview is closed also close the view (unless it is the last " + "view for this document)"); + declareInt ("minimum-width", "Minimum subview width", 325). + setTooltip ("Minimum width of subviews."). + setRange (50, 10000); + declareSeparator(); + EnumValue scrollbarOnly ("Scrollbar Only", "Simple addition of scrollbars, the view window " + "does not grow automatically."); + declareEnum ("mainwindow-scrollbar", "Horizontal scrollbar mode for main window.", scrollbarOnly). + addValue (scrollbarOnly). + addValue ("Grow Only", "The view window grows as subviews are added. No scrollbars."). + addValue ("Grow then Scroll", "The view window grows. The scrollbar appears once it cannot grow any further."); + declareBool ("grow-limit", "Grow Limit Screen", false). + setTooltip ("When \"Grow then Scroll\" option is selected, the window size grows to" + " the width of the virtual desktop. \nIf this option is selected the the window growth" + "is limited to the current screen."); + + declareCategory ("Records"); + EnumValue iconAndText ("Icon and Text"); + EnumValues recordValues; + recordValues.add (iconAndText).add ("Icon Only").add ("Text Only"); + declareEnum ("status-format", "Modification status display format", iconAndText). + addValues (recordValues); + declareEnum ("type-format", "ID type display format", iconAndText). + addValues (recordValues); + + declareCategory ("ID Tables"); + EnumValue inPlaceEdit ("Edit in Place", "Edit the clicked cell"); + EnumValue editRecord ("Edit Record", "Open a dialogue subview for the clicked record"); + EnumValue view ("View", "Open a scene subview for the clicked record (not available everywhere)"); + EnumValue editRecordAndClose ("Edit Record and Close"); + EnumValues doubleClickValues; + doubleClickValues.add (inPlaceEdit).add (editRecord).add (view).add ("Revert"). + add ("Delete").add (editRecordAndClose). + add ("View and Close", "Open a scene subview for the clicked record and close the table subview"); + declareEnum ("double", "Double Click", inPlaceEdit).addValues (doubleClickValues); + declareEnum ("double-s", "Shift Double Click", editRecord).addValues (doubleClickValues); + declareEnum ("double-c", "Control Double Click", view).addValues (doubleClickValues); + declareEnum ("double-sc", "Shift Control Double Click", editRecordAndClose).addValues (doubleClickValues); + declareSeparator(); + EnumValue jumpAndSelect ("Jump and Select", "Scroll new record into view and make it the selection"); + declareEnum ("jump-to-added", "Action on adding or cloning a record", jumpAndSelect). + addValue (jumpAndSelect). + addValue ("Jump Only", "Scroll new record into view"). + addValue ("No Jump", "No special action"); + declareBool ("extended-config", + "Manually specify affected record types for an extended delete/revert", false). + setTooltip ("Delete and revert commands have an extended form that also affects " + "associated records.\n\n" + "If this option is enabled, types of affected records are selected " + "manually before a command execution.\nOtherwise, all associated " + "records are deleted/reverted immediately."); + + declareCategory ("ID Dialogues"); + declareBool ("toolbar", "Show toolbar", true); + + declareCategory ("Reports"); + EnumValue actionNone ("None"); + EnumValue actionEdit ("Edit", "Open a table or dialogue suitable for addressing the listed report"); + EnumValue actionRemove ("Remove", "Remove the report from the report table"); + EnumValue actionEditAndRemove ("Edit And Remove", "Open a table or dialogue suitable for addressing the listed report, then remove the report from the report table"); + EnumValues reportValues; + reportValues.add (actionNone).add (actionEdit).add (actionRemove).add (actionEditAndRemove); + declareEnum ("double", "Double Click", actionEdit).addValues (reportValues); + declareEnum ("double-s", "Shift Double Click", actionRemove).addValues (reportValues); + declareEnum ("double-c", "Control Double Click", actionEditAndRemove).addValues (reportValues); + declareEnum ("double-sc", "Shift Control Double Click", actionNone).addValues (reportValues); + + declareCategory ("Search & Replace"); + declareInt ("char-before", "Characters before search string", 10). + setTooltip ("Maximum number of character to display in search result before the searched text"); + declareInt ("char-after", "Characters after search string", 10). + setTooltip ("Maximum number of character to display in search result after the searched text"); + declareBool ("auto-delete", "Delete row from result table after a successful replace", true); + + declareCategory ("Scripts"); + declareBool ("show-linenum", "Show Line Numbers", true). + setTooltip ("Show line numbers to the left of the script editor window." + "The current row and column numbers of the text cursor are shown at the bottom."); + declareBool ("mono-font", "Use monospace font", true); + EnumValue warningsNormal ("Normal", "Report warnings as warning"); + declareEnum ("warnings", "Warning Mode", warningsNormal). + addValue ("Ignore", "Do not report warning"). + addValue (warningsNormal). + addValue ("Strcit", "Promote warning to an error"); + declareBool ("toolbar", "Show toolbar", true); + declareInt ("compile-delay", "Delay between updating of source errors", 100). + setTooltip ("Delay in milliseconds"). + setRange (0, 10000); + declareInt ("error-height", "Initial height of the error panel", 100). + setRange (100, 10000); + declareSeparator(); + declareColour ("colour-int", "Highlight Colour: Integer Literals", QColor ("darkmagenta")); + declareColour ("colour-float", "Highlight Colour: Float Literals", QColor ("magenta")); + declareColour ("colour-name", "Highlight Colour: Names", QColor ("grey")); + declareColour ("colour-keyword", "Highlight Colour: Keywords", QColor ("red")); + declareColour ("colour-special", "Highlight Colour: Special Characters", QColor ("darkorange")); + declareColour ("colour-comment", "Highlight Colour: Comments", QColor ("green")); + declareColour ("colour-id", "Highlight Colour: IDs", QColor ("blue")); + + declareCategory ("General Input"); + declareBool ("cycle", "Cyclic next/previous", false). + setTooltip ("When using next/previous functions at the last/first item of a " + "list go to the first/last item"); + + declareCategory ("3D Scene Input"); + EnumValue left ("Left Mouse-Button"); + EnumValue cLeft ("Ctrl-Left Mouse-Button"); + EnumValue right ("Right Mouse-Button"); + EnumValue cRight ("Ctrl-Right Mouse-Button"); + EnumValue middle ("Middle Mouse-Button"); + EnumValue cMiddle ("Ctrl-Middle Mouse-Button"); + EnumValues inputButtons; + inputButtons.add (left).add (cLeft).add (right).add (cRight).add (middle).add (cMiddle); + declareEnum ("p-navi", "Primary Camera Navigation Button", left).addValues (inputButtons); + declareEnum ("s-navi", "Secondary Camera Navigation Button", cLeft).addValues (inputButtons); + declareEnum ("p-edit", "Primary Editing Button", right).addValues (inputButtons); + declareEnum ("s-edit", "Secondary Editing Button", cRight).addValues (inputButtons); + declareEnum ("p-select", "Primary Selection Button", middle).addValues (inputButtons); + declareEnum ("s-select", "Secondary Selection Button", cMiddle).addValues (inputButtons); + declareSeparator(); + declareBool ("context-select", "Context Sensitive Selection", false); + declareDouble ("drag-factor", "Mouse sensitivity during drag operations", 1.0). + setRange (0.001, 100.0); + declareDouble ("drag-wheel-factor", "Mouse wheel sensitivity during drag operations", 1.0). + setRange (0.001, 100.0); + declareDouble ("drag-shift-factor", + "Shift-acceleration factor during drag operations", 4.0). + setTooltip ("Acceleration factor during drag operations while holding down shift"). + setRange (0.001, 100.0); + + declareCategory ("Tooltips"); + declareBool ("scene", "Show Tooltips in 3D scenes", true); + declareBool ("scene-hide-basic", "Hide basic 3D scenes tooltips", false); + declareInt ("scene-delay", "Tooltip delay in milliseconds", 500). + setMin (1); +} + +void CSMPrefs::State::declareCategory (const std::string& key) +{ + std::map::iterator iter = mCategories.find (key); + + if (iter!=mCategories.end()) + { + mCurrentCategory = iter; + } + else + { + mCurrentCategory = + mCategories.insert (std::make_pair (key, Category (this, key))).first; + } +} + +CSMPrefs::IntSetting& CSMPrefs::State::declareInt (const std::string& key, + const std::string& label, int default_) +{ + if (mCurrentCategory==mCategories.end()) + throw std::logic_error ("no category for setting"); + + std::ostringstream stream; + stream << default_; + setDefault (key, stream.str()); + + default_ = mSettings.getInt (key, mCurrentCategory->second.getKey()); + + CSMPrefs::IntSetting *setting = + new CSMPrefs::IntSetting (&mCurrentCategory->second, &mSettings, &mMutex, key, label, + default_); + + mCurrentCategory->second.addSetting (setting); + + return *setting; +} + +CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble (const std::string& key, + const std::string& label, double default_) +{ + if (mCurrentCategory==mCategories.end()) + throw std::logic_error ("no category for setting"); + + std::ostringstream stream; + stream << default_; + setDefault (key, stream.str()); + + default_ = mSettings.getFloat (key, mCurrentCategory->second.getKey()); + + CSMPrefs::DoubleSetting *setting = + new CSMPrefs::DoubleSetting (&mCurrentCategory->second, &mSettings, &mMutex, + key, label, default_); + + mCurrentCategory->second.addSetting (setting); + + return *setting; +} + +CSMPrefs::BoolSetting& CSMPrefs::State::declareBool (const std::string& key, + const std::string& label, bool default_) +{ + if (mCurrentCategory==mCategories.end()) + throw std::logic_error ("no category for setting"); + + setDefault (key, default_ ? "true" : "false"); + + default_ = mSettings.getBool (key, mCurrentCategory->second.getKey()); + + CSMPrefs::BoolSetting *setting = + new CSMPrefs::BoolSetting (&mCurrentCategory->second, &mSettings, &mMutex, key, label, + default_); + + mCurrentCategory->second.addSetting (setting); + + return *setting; +} + +CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum (const std::string& key, + const std::string& label, EnumValue default_) +{ + if (mCurrentCategory==mCategories.end()) + throw std::logic_error ("no category for setting"); + + setDefault (key, default_.mValue); + + default_.mValue = mSettings.getString (key, mCurrentCategory->second.getKey()); + + CSMPrefs::EnumSetting *setting = + new CSMPrefs::EnumSetting (&mCurrentCategory->second, &mSettings, &mMutex, key, label, + default_); + + mCurrentCategory->second.addSetting (setting); + + return *setting; +} + +CSMPrefs::ColourSetting& CSMPrefs::State::declareColour (const std::string& key, + const std::string& label, QColor default_) +{ + if (mCurrentCategory==mCategories.end()) + throw std::logic_error ("no category for setting"); + + setDefault (key, default_.name().toUtf8().data()); + + default_.setNamedColor (QString::fromUtf8 (mSettings.getString (key, mCurrentCategory->second.getKey()).c_str())); + + CSMPrefs::ColourSetting *setting = + new CSMPrefs::ColourSetting (&mCurrentCategory->second, &mSettings, &mMutex, key, label, + default_); + + mCurrentCategory->second.addSetting (setting); + + return *setting; +} + +void CSMPrefs::State::declareSeparator() +{ + if (mCurrentCategory==mCategories.end()) + throw std::logic_error ("no category for setting"); + + CSMPrefs::Setting *setting = + new CSMPrefs::Setting (&mCurrentCategory->second, &mSettings, &mMutex, "", ""); + + mCurrentCategory->second.addSetting (setting); +} + +void CSMPrefs::State::setDefault (const std::string& key, const std::string& default_) +{ + Settings::CategorySetting fullKey (mCurrentCategory->second.getKey(), key); + + Settings::CategorySettingValueMap::iterator iter = + mSettings.mDefaultSettings.find (fullKey); + + if (iter==mSettings.mDefaultSettings.end()) + mSettings.mDefaultSettings.insert (std::make_pair (fullKey, default_)); +} + +CSMPrefs::State::State (const Files::ConfigurationManager& configurationManager) +: mConfigFile ("openmw-cs.cfg"), mConfigurationManager (configurationManager), + mCurrentCategory (mCategories.end()) +{ + if (sThis) + throw std::logic_error ("An instance of CSMPRefs::State already exists"); + + load(); + declare(); + + sThis = this; +} + +CSMPrefs::State::~State() +{ + sThis = 0; +} + +void CSMPrefs::State::save() +{ + boost::filesystem::path user = mConfigurationManager.getUserConfigPath() / mConfigFile; + mSettings.saveUser (user.string()); +} + +CSMPrefs::State::Iterator CSMPrefs::State::begin() +{ + return mCategories.begin(); +} + +CSMPrefs::State::Iterator CSMPrefs::State::end() +{ + return mCategories.end(); +} + +CSMPrefs::Category& CSMPrefs::State::operator[] (const std::string& key) +{ + Iterator iter = mCategories.find (key); + + if (iter==mCategories.end()) + throw std::logic_error ("Invalid user settings category: " + key); + + return iter->second; +} + +void CSMPrefs::State::update (const Setting& setting) +{ + emit (settingChanged (&setting)); +} + +CSMPrefs::State& CSMPrefs::State::get() +{ + if (!sThis) + throw std::logic_error ("No instance of CSMPrefs::State"); + + return *sThis; +} + + +CSMPrefs::State& CSMPrefs::get() +{ + return State::get(); +} diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp new file mode 100644 index 0000000000..bcd76c6712 --- /dev/null +++ b/apps/opencs/model/prefs/state.hpp @@ -0,0 +1,105 @@ +#ifndef CSV_PREFS_STATE_H +#define CSM_PREFS_STATE_H + +#include +#include + +#include +#include + +#ifndef Q_MOC_RUN +#include +#endif + +#include + +#include "category.hpp" +#include "setting.hpp" +#include "enumsetting.hpp" + +class QColor; + +namespace CSMPrefs +{ + class IntSetting; + class DoubleSetting; + class BoolSetting; + class ColourSetting; + + /// \brief User settings state + /// + /// \note Access to the user settings is thread-safe once all declarations and loading has + /// been completed. + class State : public QObject + { + Q_OBJECT + + static State *sThis; + + public: + + typedef std::map Collection; + typedef Collection::iterator Iterator; + + private: + + const std::string mConfigFile; + const Files::ConfigurationManager& mConfigurationManager; + Settings::Manager mSettings; + Collection mCategories; + Iterator mCurrentCategory; + QMutex mMutex; + + // not implemented + State (const State&); + State& operator= (const State&); + + private: + + void load(); + + void declare(); + + void declareCategory (const std::string& key); + + IntSetting& declareInt (const std::string& key, const std::string& label, int default_); + DoubleSetting& declareDouble (const std::string& key, const std::string& label, double default_); + + BoolSetting& declareBool (const std::string& key, const std::string& label, bool default_); + + EnumSetting& declareEnum (const std::string& key, const std::string& label, EnumValue default_); + + ColourSetting& declareColour (const std::string& key, const std::string& label, QColor default_); + + void declareSeparator(); + + void setDefault (const std::string& key, const std::string& default_); + + public: + + State (const Files::ConfigurationManager& configurationManager); + + ~State(); + + void save(); + + Iterator begin(); + + Iterator end(); + + Category& operator[](const std::string& key); + + void update (const Setting& setting); + + static State& get(); + + signals: + + void settingChanged (const CSMPrefs::Setting *setting); + }; + + // convenience function + State& get(); +} + +#endif diff --git a/apps/opencs/model/settings/connector.cpp b/apps/opencs/model/settings/connector.cpp deleted file mode 100644 index 3cf21123c5..0000000000 --- a/apps/opencs/model/settings/connector.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include "connector.hpp" -#include "../../view/settings/view.hpp" -#include "../../view/settings/page.hpp" - -CSMSettings::Connector::Connector(CSVSettings::View *master, - QObject *parent) - : QObject(parent), mMasterView (master) -{} - -void CSMSettings::Connector::addSlaveView (CSVSettings::View *view, - QList &masterProxyValues) -{ - mSlaveViews.append (view); - - mProxyListMap[view->viewKey()].append (masterProxyValues); -} - -QList CSMSettings::Connector::getSlaveViewValues() const -{ - QList list; - - foreach (const CSVSettings::View *view, mSlaveViews) - list.append (view->selectedValues()); - - return list; -} - -bool CSMSettings::Connector::proxyListsMatch ( - const QList &list1, - const QList &list2) const -{ - bool success = true; - - for (int i = 0; i < list1.size(); i++) - { - success = stringListsMatch (list1.at(i), list2.at(i)); - - if (!success) - break; - } - return success; -} - -void CSMSettings::Connector::slotUpdateMaster() const -{ - //list of the current values for each slave. - QList slaveValueList = getSlaveViewValues(); - - int masterColumn = -1; - - /* - * A row in the master view is one of the values in the - * master view's data model. This corresponds directly to the number of - * values in a proxy list contained in the ProxyListMap member. - * Thus, we iterate each "column" in the master proxy list - * (one for each vlaue in the master. Each column represents - * one master value's corresponding list of slave values. We examine - * each master value's list, comparing it to the current slave value list, - * stopping when we find a match using proxyListsMatch(). - * - * If no match is found, clear the master view's value - */ - - for (int i = 0; i < mMasterView->rowCount(); i++) - { - QList proxyValueList; - - foreach (const QString &settingKey, mProxyListMap.keys()) - { - // append the proxy value list stored in the i'th column - // for each setting key. A setting key is the id of the setting - // in page.name format. - proxyValueList.append (mProxyListMap.value(settingKey).at(i)); - } - - if (proxyListsMatch (slaveValueList, proxyValueList)) - { - masterColumn = i; - break; - } - } - - QString masterValue = mMasterView->value (masterColumn); - - mMasterView->setSelectedValue (masterValue); -} - -void CSMSettings::Connector::slotUpdateSlaves() const -{ - int row = mMasterView->currentIndex(); - - if (row == -1) - return; - - //iterate the proxy lists for the chosen master index - //and pass the list to each slave for updating - for (int i = 0; i < mSlaveViews.size(); i++) - { - QList proxyList = - mProxyListMap.value(mSlaveViews.at(i)->viewKey()); - - mSlaveViews.at(i)->setSelectedValues (proxyList.at(row)); - } -} - -bool CSMSettings::Connector::stringListsMatch ( - const QStringList &list1, - const QStringList &list2) const -{ - //returns a "sloppy" match, verifying that each list contains all the same - //items, though not necessarily in the same order. - - if (list1.size() != list2.size()) - return false; - - QStringList tempList(list2); - - //iterate each value in the list, removing one occurrence of the value in - //the other list. If no corresponding value is found, test fails - foreach (const QString &value, list1) - { - if (!tempList.contains(value)) - return false; - - tempList.removeOne(value); - } - return true; -} diff --git a/apps/opencs/model/settings/connector.hpp b/apps/opencs/model/settings/connector.hpp deleted file mode 100644 index aaf9936d54..0000000000 --- a/apps/opencs/model/settings/connector.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef CSMSETTINGS_CONNECTOR_HPP -#define CSMSETTINGS_CONNECTOR_HPP - -#include -#include -#include -#include - -#include "support.hpp" - -namespace CSVSettings { - class View; -} - -namespace CSMSettings { - - class Connector : public QObject - { - Q_OBJECT - - CSVSettings::View *mMasterView; - - ///map using the view pointer as a key to it's index value - QList mSlaveViews; - - ///list of proxy values for each master value. - ///value list order is indexed to the master value index. - QMap < QString, QList > mProxyListMap; - - public: - explicit Connector(CSVSettings::View *master, - QObject *parent = 0); - - ///Set the view which acts as a proxy for other setting views - void setMasterView (CSVSettings::View *view); - - ///Add a view to be updated / update to the master - void addSlaveView (CSVSettings::View *view, - QList &masterProxyValues); - - private: - - ///loosely matches lists of proxy values across registered slaves - ///against a proxy value list for a given master value - bool proxyListsMatch (const QList &list1, - const QList &list2) const; - - ///loosely matches two string lists - bool stringListsMatch (const QStringList &list1, - const QStringList &list2) const; - - ///retrieves current values of registered slave views - QList getSlaveViewValues() const; - - public slots: - - ///updates slave views with proxy values associated with current - ///master value - void slotUpdateSlaves() const; - - ///updates master value associated with the currently selected - ///slave values, if applicable. - void slotUpdateMaster() const; - }; -} - -#endif // CSMSETTINGS_CONNECTOR_HPP diff --git a/apps/opencs/model/settings/setting.cpp b/apps/opencs/model/settings/setting.cpp deleted file mode 100644 index 9e33ab9168..0000000000 --- a/apps/opencs/model/settings/setting.cpp +++ /dev/null @@ -1,414 +0,0 @@ -#include "setting.hpp" -#include "support.hpp" - -CSMSettings::Setting::Setting(SettingType typ, const QString &settingName, - const QString &pageName, const QString& label) -: mIsEditorSetting (true) -{ - buildDefaultSetting(); - - int settingType = static_cast (typ); - - //even-numbered setting types are multi-valued - if ((settingType % 2) == 0) - setProperty (Property_IsMultiValue, QVariant(true).toString()); - - //view type is related to setting type by an order of magnitude - setProperty (Property_SettingType, QVariant (settingType).toString()); - setProperty (Property_Page, pageName); - setProperty (Property_Name, settingName); - setProperty (Property_Label, label.isEmpty() ? settingName : label); -} - -void CSMSettings::Setting::buildDefaultSetting() -{ - int arrLen = sizeof(sPropertyDefaults) / sizeof (*sPropertyDefaults); - - for (int i = 0; i < arrLen; i++) - { - QStringList propertyList; - - if (i list; - - foreach (const QString &val, vals) - list << (QStringList() << val); - - mProxies [setting->page() + '/' + setting->name()] = list; -} - -void CSMSettings::Setting::addProxy (const Setting *setting, - const QList &list) -{ - if (serializable()) - setProperty (Property_Serializable, false); - - mProxies [setting->page() + '/' + setting->name()] = list; -} - -void CSMSettings::Setting::setColumnSpan (int value) -{ - setProperty (Property_ColumnSpan, value); -} - -int CSMSettings::Setting::columnSpan() const -{ - return property (Property_ColumnSpan).at(0).toInt(); -} - -void CSMSettings::Setting::setDeclaredValues (QStringList list) -{ - setProperty (Property_DeclaredValues, list); -} - -QStringList CSMSettings::Setting::declaredValues() const -{ - return property (Property_DeclaredValues); -} - -QStringList CSMSettings::Setting::property (SettingProperty prop) const -{ - if (prop >= mProperties.size()) - return QStringList(); - - return mProperties.at(prop); -} - -void CSMSettings::Setting::setDefaultValue (int value) -{ - setDefaultValues (QStringList() << QVariant (value).toString()); -} - -void CSMSettings::Setting::setDefaultValue (double value) -{ - setDefaultValues (QStringList() << QVariant (value).toString()); -} - -void CSMSettings::Setting::setDefaultValue (const QString &value) -{ - setDefaultValues (QStringList() << value); -} - -void CSMSettings::Setting::setDefaultValues (const QStringList &values) -{ - setProperty (Property_DefaultValues, values); -} - -QStringList CSMSettings::Setting::defaultValues() const -{ - return property (Property_DefaultValues); -} - -void CSMSettings::Setting::setDelimiter (const QString &value) -{ - setProperty (Property_Delimiter, value); -} - -QString CSMSettings::Setting::delimiter() const -{ - return property (Property_Delimiter).at(0); -} - -void CSMSettings::Setting::setEditorSetting(bool state) -{ - mIsEditorSetting = true; -} - -bool CSMSettings::Setting::isEditorSetting() const -{ - return mIsEditorSetting; -} -void CSMSettings::Setting::setIsMultiLine (bool state) -{ - setProperty (Property_IsMultiLine, state); -} - -bool CSMSettings::Setting::isMultiLine() const -{ - return (property (Property_IsMultiLine).at(0) == "true"); -} - -void CSMSettings::Setting::setIsMultiValue (bool state) -{ - setProperty (Property_IsMultiValue, state); -} - -bool CSMSettings::Setting::isMultiValue() const -{ - return (property (Property_IsMultiValue).at(0) == "true"); -} - -const CSMSettings::ProxyValueMap &CSMSettings::Setting::proxyLists() const -{ - return mProxies; -} - -void CSMSettings::Setting::setSerializable (bool state) -{ - setProperty (Property_Serializable, state); -} - -bool CSMSettings::Setting::serializable() const -{ - return (property (Property_Serializable).at(0) == "true"); -} - -void CSMSettings::Setting::setSpecialValueText(const QString &text) -{ - setProperty (Property_SpecialValueText, text); -} - -QString CSMSettings::Setting::specialValueText() const -{ - return property (Property_SpecialValueText).at(0); -} - -void CSMSettings::Setting::setName (const QString &value) -{ - setProperty (Property_Name, value); -} - -QString CSMSettings::Setting::name() const -{ - return property (Property_Name).at(0); -} - -void CSMSettings::Setting::setPage (const QString &value) -{ - setProperty (Property_Page, value); -} - -QString CSMSettings::Setting::page() const -{ - return property (Property_Page).at(0); -} - -void CSMSettings::Setting::setStyleSheet (const QString &value) -{ - setProperty (Property_StyleSheet, value); -} - -QString CSMSettings::Setting::styleSheet() const -{ - return property (Property_StyleSheet).at(0); -} - -void CSMSettings::Setting::setPrefix (const QString &value) -{ - setProperty (Property_Prefix, value); -} - -QString CSMSettings::Setting::prefix() const -{ - return property (Property_Prefix).at(0); -} - -void CSMSettings::Setting::setRowSpan (const int value) -{ - setProperty (Property_RowSpan, value); -} - -int CSMSettings::Setting::rowSpan () const -{ - return property (Property_RowSpan).at(0).toInt(); -} - -void CSMSettings::Setting::setSingleStep (int value) -{ - setProperty (Property_SingleStep, value); -} - -void CSMSettings::Setting::setSingleStep (double value) -{ - setProperty (Property_SingleStep, value); -} - -QString CSMSettings::Setting::singleStep() const -{ - return property (Property_SingleStep).at(0); -} - -void CSMSettings::Setting::setSuffix (const QString &value) -{ - setProperty (Property_Suffix, value); -} - -QString CSMSettings::Setting::suffix() const -{ - return property (Property_Suffix).at(0); -} - -void CSMSettings::Setting::setTickInterval (int value) -{ - setProperty (Property_TickInterval, value); -} - -int CSMSettings::Setting::tickInterval () const -{ - return property (Property_TickInterval).at(0).toInt(); -} - -void CSMSettings::Setting::setTicksAbove (bool state) -{ - setProperty (Property_TicksAbove, state); -} - -bool CSMSettings::Setting::ticksAbove() const -{ - return (property (Property_TicksAbove).at(0) == "true"); -} - -void CSMSettings::Setting::setTicksBelow (bool state) -{ - setProperty (Property_TicksBelow, state); -} - -bool CSMSettings::Setting::ticksBelow() const -{ - return (property (Property_TicksBelow).at(0) == "true"); -} - -void CSMSettings::Setting::setType (int settingType) -{ - setProperty (Property_SettingType, settingType); -} - -CSMSettings::SettingType CSMSettings::Setting::type() const -{ - return static_cast ( property ( - Property_SettingType).at(0).toInt()); -} - -void CSMSettings::Setting::setRange (int min, int max) -{ - setProperty (Property_Minimum, min); - setProperty (Property_Maximum, max); -} - -void CSMSettings::Setting::setRange (double min, double max) -{ - setProperty (Property_Minimum, min); - setProperty (Property_Maximum, max); -} - -QString CSMSettings::Setting::maximum() const -{ - return property (Property_Maximum).at(0); -} - -QString CSMSettings::Setting::minimum() const -{ - return property (Property_Minimum).at(0); -} - -CSVSettings::ViewType CSMSettings::Setting::viewType() const -{ - return static_cast ( property ( - Property_SettingType).at(0).toInt() / 10); -} - -void CSMSettings::Setting::setViewColumn (int value) -{ - setProperty (Property_ViewColumn, value); -} - -int CSMSettings::Setting::viewColumn() const -{ - return property (Property_ViewColumn).at(0).toInt(); -} - -void CSMSettings::Setting::setViewLocation (int row, int column) -{ - setViewRow (row); - setViewColumn (column); -} - -void CSMSettings::Setting::setViewRow (int value) -{ - setProperty (Property_ViewRow, value); -} - -int CSMSettings::Setting::viewRow() const -{ - return property (Property_ViewRow).at(0).toInt(); -} - -void CSMSettings::Setting::setWidgetWidth (int value) -{ - setProperty (Property_WidgetWidth, value); -} - -int CSMSettings::Setting::widgetWidth() const -{ - return property (Property_WidgetWidth).at(0).toInt(); -} - -void CSMSettings::Setting::setWrapping (bool state) -{ - setProperty (Property_Wrapping, state); -} - -bool CSMSettings::Setting::wrapping() const -{ - return (property (Property_Wrapping).at(0) == "true"); -} - -void CSMSettings::Setting::setLabel (const QString& label) -{ - setProperty (Property_Label, label); -} - -QString CSMSettings::Setting::getLabel() const -{ - return property (Property_Label).at (0); -} - -void CSMSettings::Setting::setToolTip (const QString& toolTip) -{ - setProperty (Property_ToolTip, toolTip); -} - -QString CSMSettings::Setting::getToolTip() const -{ - return property (Property_ToolTip).at (0); -} - -void CSMSettings::Setting::setProperty (SettingProperty prop, bool value) -{ - setProperty (prop, QStringList() << QVariant (value).toString()); -} - -void CSMSettings::Setting::setProperty (SettingProperty prop, int value) -{ - setProperty (prop, QStringList() << QVariant (value).toString()); -} - -void CSMSettings::Setting::setProperty (SettingProperty prop, double value) -{ - setProperty (prop, QStringList() << QVariant (value).toString()); -} - -void CSMSettings::Setting::setProperty (SettingProperty prop, - const QString &value) -{ - setProperty (prop, QStringList() << value); -} - -void CSMSettings::Setting::setProperty (SettingProperty prop, - const QStringList &value) -{ - if (prop < mProperties.size()) - mProperties.replace (prop, value); -} diff --git a/apps/opencs/model/settings/setting.hpp b/apps/opencs/model/settings/setting.hpp deleted file mode 100644 index be51a531a9..0000000000 --- a/apps/opencs/model/settings/setting.hpp +++ /dev/null @@ -1,159 +0,0 @@ -#ifndef CSMSETTINGS_SETTING_HPP -#define CSMSETTINGS_SETTING_HPP - -#include -#include -#include "support.hpp" - -namespace CSMSettings -{ - //QString is the setting id in the form of "page/name" - //QList is a list of stringlists of proxy values. - //Order is important! Proxy stringlists are matched against - //master values by their position in the QList. - typedef QMap > ProxyValueMap; - - ///Setting class is the interface for the User Settings. It contains - ///a great deal of boiler plate to provide the core API functions, as - ///well as the property() functions which use enumeration to be iterable. - ///This makes the Setting class capable of being manipulated by script. - ///See CSMSettings::support.hpp for enumerations / string values. - class Setting - { - QList mProperties; - QStringList mDefaults; - - bool mIsEditorSetting; - - ProxyValueMap mProxies; - - public: - - Setting(SettingType typ, const QString &settingName, - const QString &pageName, const QString& label = ""); - - void addProxy (const Setting *setting, const QStringList &vals); - void addProxy (const Setting *setting, const QList &list); - - const QList &properties() const { return mProperties; } - const ProxyValueMap &proxies() const { return mProxies; } - - void setColumnSpan (int value); - int columnSpan() const; - - void setDeclaredValues (QStringList list); - QStringList declaredValues() const; - - void setDefaultValue (int value); - void setDefaultValue (double value); - void setDefaultValue (const QString &value); - - void setDefaultValues (const QStringList &values); - QStringList defaultValues() const; - - void setDelimiter (const QString &value); - QString delimiter() const; - - void setEditorSetting (bool state); - bool isEditorSetting() const; - - void setIsMultiLine (bool state); - bool isMultiLine() const; - - void setIsMultiValue (bool state); - bool isMultiValue() const; - - void setMask (const QString &value); - QString mask() const; - - void setRange (int min, int max); - void setRange (double min, double max); - - QString maximum() const; - - QString minimum() const; - - void setName (const QString &value); - QString name() const; - - void setPage (const QString &value); - QString page() const; - - void setStyleSheet (const QString &value); - QString styleSheet() const; - - void setPrefix (const QString &value); - QString prefix() const; - - void setRowSpan (const int value); - int rowSpan() const; - - const ProxyValueMap &proxyLists() const; - - void setSerializable (bool state); - bool serializable() const; - - void setSpecialValueText (const QString &text); - QString specialValueText() const; - - void setSingleStep (int value); - void setSingleStep (double value); - QString singleStep() const; - - void setSuffix (const QString &value); - QString suffix() const; - - void setTickInterval (int value); - int tickInterval() const; - - void setTicksAbove (bool state); - bool ticksAbove() const; - - void setTicksBelow (bool state); - bool ticksBelow() const; - - void setViewColumn (int value); - int viewColumn() const; - - void setViewLocation (int row = -1, int column = -1); - - void setViewRow (int value); - int viewRow() const; - - void setType (int settingType); - CSMSettings::SettingType type() const; - - CSVSettings::ViewType viewType() const; - - void setWrapping (bool state); - bool wrapping() const; - - void setWidgetWidth (int value); - int widgetWidth() const; - - /// This is the text the user gets to see. - void setLabel (const QString& label); - QString getLabel() const; - - void setToolTip (const QString& toolTip); - QString getToolTip() const; - - ///returns the specified property value - QStringList property (SettingProperty prop) const; - - ///boilerplate code to convert setting values of common types - void setProperty (SettingProperty prop, bool value); - void setProperty (SettingProperty prop, int value); - void setProperty (SettingProperty prop, double value); - void setProperty (SettingProperty prop, const QString &value); - void setProperty (SettingProperty prop, const QStringList &value); - - void addProxy (Setting* setting, - QMap &proxyMap); - - protected: - void buildDefaultSetting(); - }; -} - -#endif // CSMSETTINGS_SETTING_HPP diff --git a/apps/opencs/model/settings/support.hpp b/apps/opencs/model/settings/support.hpp deleted file mode 100644 index ab0e5088ce..0000000000 --- a/apps/opencs/model/settings/support.hpp +++ /dev/null @@ -1,149 +0,0 @@ -#ifndef SETTING_SUPPORT_HPP -#define SETTING_SUPPORT_HPP - -#include -#include -#include -#include -#include - -//Enums -namespace CSMSettings -{ - ///Enumerated properties for scripting - enum SettingProperty - { - Property_Name = 0, - Property_Page = 1, - Property_SettingType = 2, - Property_IsMultiValue = 3, - Property_IsMultiLine = 4, - Property_WidgetWidth = 5, - Property_ViewRow = 6, - Property_ViewColumn = 7, - Property_Delimiter = 8, - Property_Serializable = 9, - Property_ColumnSpan = 10, - Property_RowSpan = 11, - Property_Minimum = 12, - Property_Maximum = 13, - Property_SpecialValueText = 14, - Property_Prefix = 15, - Property_Suffix = 16, - Property_SingleStep = 17, - Property_Wrapping = 18, - Property_TickInterval = 19, - Property_TicksAbove = 20, - Property_TicksBelow = 21, - Property_StyleSheet = 22, - Property_Label = 23, - Property_ToolTip = 24, - - //Stringlists should always be the last items - Property_DefaultValues = 25, - Property_DeclaredValues = 26, - Property_DefinedValues = 27, - Property_Proxies = 28 - }; - - ///Basic setting widget types. - enum SettingType - { - /* - * 0 - 9 - Boolean widgets - * 10-19 - List widgets - * 21-29 - Range widgets - * 31-39 - Text widgets - * - * Each range corresponds to a View_Type enum by a factor of 10. - * - * Even-numbered values are single-value widgets - * Odd-numbered values are multi-valued widgets - */ - - Type_CheckBox = 0, - Type_RadioButton = 1, - Type_ListView = 10, - Type_ComboBox = 11, - Type_SpinBox = 21, - Type_DoubleSpinBox = 23, - Type_Slider = 25, - Type_Dial = 27, - Type_TextArea = 30, - Type_LineEdit = 31, - Type_Undefined = 40 - }; - -} - -namespace CSVSettings -{ - ///Categorical view types which encompass the setting widget types - enum ViewType - { - ViewType_Boolean = 0, - ViewType_List = 1, - ViewType_Range = 2, - ViewType_Text = 3, - ViewType_Undefined = 4 - }; -} - - -namespace CSMSettings -{ - ///used to construct default settings in the Setting class - struct PropertyDefaultValues - { - int id; - QString name; - QVariant value; - }; - - ///strings which correspond to setting values. These strings represent - ///the script language keywords which would be used to declare setting - ///views for 3rd party addons - const QString sPropertyNames[] = - { - "name", "page", "setting_type", "is_multi_value", - "is_multi_line", "widget_width", "view_row", "view_column", "delimiter", - "is_serializable","column_span", "row_span", "minimum", "maximum", - "special_value_text", "prefix", "suffix", "single_step", "wrapping", - "tick_interval", "ticks_above", "ticks_below", "stylesheet", - "defaults", "declarations", "definitions", "proxies" - }; - - ///Default values for a setting. Used in setting creation. - const QString sPropertyDefaults[] = - { - "", //name - "", //page - "40", //setting type - "false", //multivalue - "false", //multiline - "7", //widget width - "-1", //view row - "-1", //view column - ",", //delimiter - "true", //serialized - "1", //column span - "1", //row span - "0", //value range - "0", //value minimum - "0", //value maximum - "", //special text - "", //prefix - "", //suffix - "false", //wrapping - "1", //tick interval - "false", //ticks above - "true", //ticks below - "", //StyleSheet - "", //default values - "", //declared values - "", //defined values - "" //proxy values - }; -} - -#endif // VIEW_SUPPORT_HPP diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp deleted file mode 100644 index 3e59d0582f..0000000000 --- a/apps/opencs/model/settings/usersettings.cpp +++ /dev/null @@ -1,744 +0,0 @@ -#include "usersettings.hpp" - -#include -#include - -#include -#include -#include - -#include "setting.hpp" -#include "support.hpp" -#include -#include - -#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) */ - -CSMSettings::UserSettings *CSMSettings::UserSettings::sUserSettingsInstance = 0; - - CSMSettings::UserSettings::UserSettings (const Files::ConfigurationManager& configurationManager) - : mCfgMgr (configurationManager) - , mSettingDefinitions(NULL) -{ - assert(!sUserSettingsInstance); - sUserSettingsInstance = this; - - buildSettingModelDefaults(); -} - -void CSMSettings::UserSettings::buildSettingModelDefaults() -{ - QString section; - - declareSection ("3d-render", "3D Rendering"); - { - Setting *shaders = createSetting (Type_CheckBox, "shaders", "Enable Shaders"); - shaders->setDefaultValue ("true"); - - Setting *farClipDist = createSetting (Type_DoubleSpinBox, "far-clip-distance", "Far clipping distance"); - farClipDist->setDefaultValue (300000); - farClipDist->setRange (0, 1000000); - farClipDist->setToolTip ("The maximum distance objects are still rendered at."); - - QString defaultValue = "None"; - Setting *antialiasing = createSetting (Type_ComboBox, "antialiasing", "Antialiasing"); - antialiasing->setDeclaredValues (QStringList() - << defaultValue << "MSAA 2" << "MSAA 4" << "MSAA 8" << "MSAA 16"); - antialiasing->setDefaultValue (defaultValue); - } - - declareSection ("3d-render-adv", "3D Rendering (Advanced)"); - { - Setting *numLights = createSetting (Type_SpinBox, "num_lights", - "Number of lights per pass"); - numLights->setDefaultValue (8); - numLights->setRange (1, 100); - } - - declareSection ("scene-input", "Scene Input"); - { - Setting *timer = createSetting (Type_SpinBox, "timer", "Input responsiveness"); - timer->setDefaultValue (20); - timer->setRange (1, 100); - timer->setToolTip ("The time between two checks for user input in milliseconds.

" - "Lower value result in higher responsiveness."); - - Setting *fastFactor = createSetting (Type_SpinBox, "fast-factor", - "Fast movement factor"); - fastFactor->setDefaultValue (4); - fastFactor->setRange (1, 100); - fastFactor->setToolTip ( - "Factor by which movement is speed up while the shift key is held down."); - } - - declareSection ("window", "Window"); - { - Setting *preDefined = createSetting (Type_ComboBox, "pre-defined", - "Default window size"); - preDefined->setEditorSetting (false); - preDefined->setDeclaredValues ( - QStringList() << "640 x 480" << "800 x 600" << "1024 x 768" << "1440 x 900"); - preDefined->setViewLocation (1, 1); - preDefined->setColumnSpan (2); - preDefined->setToolTip ("Newly opened top-level windows will open with this size " - "(picked from a list of pre-defined values)"); - - Setting *width = createSetting (Type_LineEdit, "default-width", - "Default window width"); - width->setDefaultValues (QStringList() << "1024"); - width->setViewLocation (2, 1); - width->setColumnSpan (1); - width->setToolTip ("Newly opened top-level windows will open with this width."); - preDefined->addProxy (width, QStringList() << "640" << "800" << "1024" << "1440"); - - Setting *height = createSetting (Type_LineEdit, "default-height", - "Default window height"); - height->setDefaultValues (QStringList() << "768"); - height->setViewLocation (2, 2); - height->setColumnSpan (1); - height->setToolTip ("Newly opened top-level windows will open with this height."); - preDefined->addProxy (height, QStringList() << "480" << "600" << "768" << "900"); - - Setting *reuse = createSetting (Type_CheckBox, "reuse", "Reuse Subviews"); - reuse->setDefaultValue ("true"); - reuse->setToolTip ("When a new subview is requested and a matching subview already " - " exist, do not open a new subview and use the existing one instead."); - - Setting *statusBar = createSetting (Type_CheckBox, "show-statusbar", "Show Status Bar"); - statusBar->setDefaultValue ("true"); - statusBar->setToolTip ("If a newly open top level window is showing status bars or not. " - " Note that this does not affect existing windows."); - - Setting *maxSubView = createSetting (Type_SpinBox, "max-subviews", - "Maximum number of subviews per top-level window"); - maxSubView->setDefaultValue (256); - maxSubView->setRange (1, 256); - maxSubView->setToolTip ("If the maximum number is reached and a new subview is opened " - "it will be placed into a new top-level window."); - - Setting *hide = createSetting (Type_CheckBox, "hide-subview", "Hide single subview"); - hide->setDefaultValue ("false"); - hide->setToolTip ("When a view contains only a single subview, hide the subview title " - "bar and if this subview is closed also close the view (unless it is the last " - "view for this document)"); - - Setting *minWidth = createSetting (Type_SpinBox, "minimum-width", - "Minimum subview width"); - minWidth->setDefaultValue (325); - minWidth->setRange (50, 10000); - minWidth->setToolTip ("Minimum width of subviews."); - - QString defaultScroll = "Scrollbar Only"; - QStringList scrollValues = QStringList() << defaultScroll << "Grow Only" << "Grow then Scroll"; - - Setting *mainwinScroll = createSetting (Type_RadioButton, "mainwindow-scrollbar", - "Add a horizontal scrollbar to the main view window."); - mainwinScroll->setDefaultValue (defaultScroll); - mainwinScroll->setDeclaredValues (scrollValues); - mainwinScroll->setToolTip ("Scrollbar Only: Simple addition of scrollbars, the view window does not grow" - " automatically.\n" - "Grow Only: Original Editor behaviour. The view window grows as subviews are added. No scrollbars.\n" - "Grow then Scroll: The view window grows. The scrollbar appears once it cannot grow any further."); - - Setting *grow = createSetting (Type_CheckBox, "grow-limit", "Grow Limit Screen"); - grow->setDefaultValue ("false"); - grow->setToolTip ("When \"Grow then Scroll\" option is selected, the window size grows to" - " the width of the virtual desktop. \nIf this option is selected the the window growth" - "is limited to the current screen."); - } - - declareSection ("records", "Records"); - { - QString defaultValue = "Icon and Text"; - QStringList values = QStringList() << defaultValue << "Icon Only" << "Text Only"; - - Setting *rsd = createSetting (Type_RadioButton, "status-format", - "Modification status display format"); - rsd->setDefaultValue (defaultValue); - rsd->setDeclaredValues (values); - - Setting *ritd = createSetting (Type_RadioButton, "type-format", - "ID type display format"); - ritd->setDefaultValue (defaultValue); - ritd->setDeclaredValues (values); - } - - declareSection ("table-input", "Table Input"); - { - QString inPlaceEdit ("Edit in Place"); - QString editRecord ("Edit Record"); - QString view ("View"); - QString editRecordAndClose ("Edit Record and Close"); - - QStringList values; - values - << "None" << inPlaceEdit << editRecord << view << "Revert" << "Delete" - << editRecordAndClose << "View and Close"; - - QString toolTip = "

    " - "
  • None
  • " - "
  • Edit in Place: Edit the clicked cell
  • " - "
  • Edit Record: Open a dialogue subview for the clicked record
  • " - "
  • View: Open a scene subview for the clicked record (not available everywhere)
  • " - "
  • Revert: Revert record
  • " - "
  • Delete: Delete recordy
  • " - "
  • Edit Record and Close: Open a dialogue subview for the clicked record and close the table subview
  • " - "
  • View And Close: Open a scene subview for the clicked record and close the table subview
  • " - "
"; - - Setting *doubleClick = createSetting (Type_ComboBox, "double", "Double Click"); - doubleClick->setDeclaredValues (values); - doubleClick->setDefaultValue (inPlaceEdit); - doubleClick->setToolTip ("Action on double click in table:

" + toolTip); - - Setting *shiftDoubleClick = createSetting (Type_ComboBox, "double-s", - "Shift Double Click"); - shiftDoubleClick->setDeclaredValues (values); - shiftDoubleClick->setDefaultValue (editRecord); - shiftDoubleClick->setToolTip ("Action on shift double click in table:

" + toolTip); - - Setting *ctrlDoubleClick = createSetting (Type_ComboBox, "double-c", - "Control Double Click"); - ctrlDoubleClick->setDeclaredValues (values); - ctrlDoubleClick->setDefaultValue (view); - ctrlDoubleClick->setToolTip ("Action on control double click in table:

" + toolTip); - - Setting *shiftCtrlDoubleClick = createSetting (Type_ComboBox, "double-sc", - "Shift Control Double Click"); - shiftCtrlDoubleClick->setDeclaredValues (values); - shiftCtrlDoubleClick->setDefaultValue (editRecordAndClose); - shiftCtrlDoubleClick->setToolTip ("Action on shift control double click in table:

" + toolTip); - - QString defaultValue = "Jump and Select"; - QStringList jumpValues = QStringList() << defaultValue << "Jump Only" << "No Jump"; - - Setting *jumpToAdded = createSetting (Type_RadioButton, "jump-to-added", - "Jump to the added or cloned record."); - jumpToAdded->setDefaultValue (defaultValue); - jumpToAdded->setDeclaredValues (jumpValues); - } - - declareSection ("report-input", "Report Input"); - { - QString none ("None"); - QString edit ("Edit"); - QString remove ("Remove"); - QString editAndRemove ("Edit And Remove"); - - QStringList values; - values << none << edit << remove << editAndRemove; - - QString toolTip = "

    " - "
  • None
  • " - "
  • Edit: Open a table or dialogue suitable for addressing the listed report
  • " - "
  • Remove: Remove the report from the report table
  • " - "
  • Edit and Remove: Open a table or dialogue suitable for addressing the listed report, then remove the report from the report table
  • " - "
"; - - Setting *doubleClick = createSetting (Type_ComboBox, "double", "Double Click"); - doubleClick->setDeclaredValues (values); - doubleClick->setDefaultValue (edit); - doubleClick->setToolTip ("Action on double click in report table:

" + toolTip); - - Setting *shiftDoubleClick = createSetting (Type_ComboBox, "double-s", - "Shift Double Click"); - shiftDoubleClick->setDeclaredValues (values); - shiftDoubleClick->setDefaultValue (remove); - shiftDoubleClick->setToolTip ("Action on shift double click in report table:

" + toolTip); - - Setting *ctrlDoubleClick = createSetting (Type_ComboBox, "double-c", - "Control Double Click"); - ctrlDoubleClick->setDeclaredValues (values); - ctrlDoubleClick->setDefaultValue (editAndRemove); - ctrlDoubleClick->setToolTip ("Action on control double click in report table:

" + toolTip); - - Setting *shiftCtrlDoubleClick = createSetting (Type_ComboBox, "double-sc", - "Shift Control Double Click"); - shiftCtrlDoubleClick->setDeclaredValues (values); - shiftCtrlDoubleClick->setDefaultValue (none); - shiftCtrlDoubleClick->setToolTip ("Action on shift control double click in report table:

" + toolTip); - } - - declareSection ("search", "Search & Replace"); - { - Setting *before = createSetting (Type_SpinBox, "char-before", - "Characters before search string"); - before->setDefaultValue (10); - before->setRange (0, 1000); - before->setToolTip ("Maximum number of character to display in search result before the searched text"); - - Setting *after = createSetting (Type_SpinBox, "char-after", - "Characters after search string"); - after->setDefaultValue (10); - after->setRange (0, 1000); - after->setToolTip ("Maximum number of character to display in search result after the searched text"); - - Setting *autoDelete = createSetting (Type_CheckBox, "auto-delete", "Delete row from result table after a successful replace"); - autoDelete->setDefaultValue ("true"); - } - - declareSection ("script-editor", "Scripts"); - { - Setting *lineNum = createSetting (Type_CheckBox, "show-linenum", "Show Line Numbers"); - lineNum->setDefaultValue ("true"); - lineNum->setToolTip ("Show line numbers to the left of the script editor window." - "The current row and column numbers of the text cursor are shown at the bottom."); - - Setting *monoFont = createSetting (Type_CheckBox, "mono-font", "Use monospace font"); - monoFont->setDefaultValue ("true"); - monoFont->setToolTip ("Whether to use monospaced fonts on script edit subview."); - - QString tooltip = - "\n#RGB (each of R, G, and B is a single hex digit)" - "\n#RRGGBB" - "\n#RRRGGGBBB" - "\n#RRRRGGGGBBBB" - "\nA name from the list of colors defined in the list of SVG color keyword names." - "\nX11 color names may also work."; - - QString modeNormal ("Normal"); - - QStringList modes; - modes << "Ignore" << modeNormal << "Strict"; - - Setting *warnings = createSetting (Type_ComboBox, "warnings", - "Warning Mode"); - warnings->setDeclaredValues (modes); - warnings->setDefaultValue (modeNormal); - warnings->setToolTip ("

    How to handle warning messages during compilation:

    " - "

  • Ignore: Do not report warning
  • " - "
  • Normal: Report warning as a warning
  • " - "
  • Strict: Promote warning to an error
  • " - "
"); - - Setting *formatInt = createSetting (Type_LineEdit, "colour-int", "Highlight Colour: Int"); - formatInt->setDefaultValues (QStringList() << "Dark magenta"); - formatInt->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip); - - Setting *formatFloat = createSetting (Type_LineEdit, "colour-float", "Highlight Colour: Float"); - formatFloat->setDefaultValues (QStringList() << "Magenta"); - formatFloat->setToolTip ("(Default: Magenta) Use one of the following formats:" + tooltip); - - Setting *formatName = createSetting (Type_LineEdit, "colour-name", "Highlight Colour: Name"); - formatName->setDefaultValues (QStringList() << "Gray"); - formatName->setToolTip ("(Default: Gray) Use one of the following formats:" + tooltip); - - Setting *formatKeyword = createSetting (Type_LineEdit, "colour-keyword", "Highlight Colour: Keyword"); - formatKeyword->setDefaultValues (QStringList() << "Red"); - formatKeyword->setToolTip ("(Default: Red) Use one of the following formats:" + tooltip); - - Setting *formatSpecial = createSetting (Type_LineEdit, "colour-special", "Highlight Colour: Special"); - formatSpecial->setDefaultValues (QStringList() << "Dark yellow"); - formatSpecial->setToolTip ("(Default: Dark yellow) Use one of the following formats:" + tooltip); - - Setting *formatComment = createSetting (Type_LineEdit, "colour-comment", "Highlight Colour: Comment"); - formatComment->setDefaultValues (QStringList() << "Green"); - formatComment->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip); - - Setting *formatId = createSetting (Type_LineEdit, "colour-id", "Highlight Colour: Id"); - formatId->setDefaultValues (QStringList() << "Blue"); - formatId->setToolTip ("(Default: Blue) Use one of the following formats:" + tooltip); - } - - { - /****************************************************************** - * There are three types of values: - * - * Declared values - * - * Pre-determined values, typically for - * combobox drop downs and boolean (radiobutton / checkbox) labels. - * These values represent the total possible list of values that - * may define a setting. No other values are allowed. - * - * Defined values - * - * Values which represent the actual, current value of - * a setting. For settings with declared values, this must be one - * or several declared values, as appropriate. - * - * Proxy values - * Values the proxy master updates the proxy slave when - * it's own definition is set / changed. These are definitions for - * proxy slave settings, but must match any declared values the - * proxy slave has, if any. - *******************************************************************/ -/* - //create setting objects, specifying the basic widget type, - //the page name, and the view name - - Setting *masterBoolean = createSetting (Type_RadioButton, section, - "Master Proxy"); - - Setting *slaveBoolean = createSetting (Type_CheckBox, section, - "Proxy Checkboxes"); - - Setting *slaveSingleText = createSetting (Type_LineEdit, section, - "Proxy TextBox 1"); - - Setting *slaveMultiText = createSetting (Type_LineEdit, section, - "ProxyTextBox 2"); - - Setting *slaveAlphaSpinbox = createSetting (Type_SpinBox, section, - "Alpha Spinbox"); - - Setting *slaveIntegerSpinbox = createSetting (Type_SpinBox, section, - "Int Spinbox"); - - Setting *slaveDoubleSpinbox = createSetting (Type_DoubleSpinBox, - section, "Double Spinbox"); - - Setting *slaveSlider = createSetting (Type_Slider, section, "Slider"); - - Setting *slaveDial = createSetting (Type_Dial, section, "Dial"); - - //set declared values for selected views - masterBoolean->setDeclaredValues (QStringList() - << "Profile One" << "Profile Two" - << "Profile Three" << "Profile Four"); - - slaveBoolean->setDeclaredValues (QStringList() - << "One" << "Two" << "Three" << "Four" << "Five"); - - slaveAlphaSpinbox->setDeclaredValues (QStringList() - << "One" << "Two" << "Three" << "Four"); - - - masterBoolean->addProxy (slaveBoolean, QList () - << (QStringList() << "One" << "Three") - << (QStringList() << "One" << "Three") - << (QStringList() << "One" << "Three" << "Five") - << (QStringList() << "Two" << "Four") - ); - - masterBoolean->addProxy (slaveSingleText, QList () - << (QStringList() << "Text A") - << (QStringList() << "Text B") - << (QStringList() << "Text A") - << (QStringList() << "Text C") - ); - - masterBoolean->addProxy (slaveMultiText, QList () - << (QStringList() << "One" << "Three") - << (QStringList() << "One" << "Three") - << (QStringList() << "One" << "Three" << "Five") - << (QStringList() << "Two" << "Four") - ); - - masterBoolean->addProxy (slaveAlphaSpinbox, QList () - << (QStringList() << "Four") - << (QStringList() << "Three") - << (QStringList() << "Two") - << (QStringList() << "One")); - - masterBoolean->addProxy (slaveIntegerSpinbox, QList () - << (QStringList() << "0") - << (QStringList() << "7") - << (QStringList() << "14") - << (QStringList() << "21")); - - masterBoolean->addProxy (slaveDoubleSpinbox, QList () - << (QStringList() << "0.17") - << (QStringList() << "0.34") - << (QStringList() << "0.51") - << (QStringList() << "0.68")); - - masterBoolean->addProxy (slaveSlider, QList () - << (QStringList() << "25") - << (QStringList() << "50") - << (QStringList() << "75") - << (QStringList() << "100") - ); - - masterBoolean->addProxy (slaveDial, QList () - << (QStringList() << "25") - << (QStringList() << "50") - << (QStringList() << "75") - << (QStringList() << "100") - ); - - //settings with proxies are not serialized by default - //other settings non-serialized for demo purposes - slaveBoolean->setSerializable (false); - slaveSingleText->setSerializable (false); - slaveMultiText->setSerializable (false); - slaveAlphaSpinbox->setSerializable (false); - slaveIntegerSpinbox->setSerializable (false); - slaveDoubleSpinbox->setSerializable (false); - slaveSlider->setSerializable (false); - slaveDial->setSerializable (false); - - slaveBoolean->setDefaultValues (QStringList() - << "One" << "Three" << "Five"); - - slaveSingleText->setDefaultValue ("Text A"); - - slaveMultiText->setDefaultValues (QStringList() - << "One" << "Three" << "Five"); - - slaveSingleText->setWidgetWidth (24); - slaveMultiText->setWidgetWidth (24); - - slaveAlphaSpinbox->setDefaultValue ("Two"); - slaveAlphaSpinbox->setWidgetWidth (20); - //slaveAlphaSpinbox->setPrefix ("No. "); - //slaveAlphaSpinbox->setSuffix ("!"); - slaveAlphaSpinbox->setWrapping (true); - - slaveIntegerSpinbox->setDefaultValue (14); - slaveIntegerSpinbox->setMinimum (0); - slaveIntegerSpinbox->setMaximum (58); - slaveIntegerSpinbox->setPrefix ("$"); - slaveIntegerSpinbox->setSuffix (".00"); - slaveIntegerSpinbox->setWidgetWidth (10); - slaveIntegerSpinbox->setSpecialValueText ("Nothing!"); - - slaveDoubleSpinbox->setDefaultValue (0.51); - slaveDoubleSpinbox->setSingleStep(0.17); - slaveDoubleSpinbox->setMaximum(4.0); - - slaveSlider->setMinimum (0); - slaveSlider->setMaximum (100); - slaveSlider->setDefaultValue (75); - slaveSlider->setWidgetWidth (100); - slaveSlider->setTicksAbove (true); - slaveSlider->setTickInterval (25); - - slaveDial->setMinimum (0); - slaveDial->setMaximum (100); - slaveDial->setSingleStep (5); - slaveDial->setDefaultValue (75); - slaveDial->setTickInterval (25); -*/ - } -} - -CSMSettings::UserSettings::~UserSettings() -{ - sUserSettingsInstance = 0; -} - -void CSMSettings::UserSettings::loadSettings (const QString &fileName) -{ - QString userFilePath = QString::fromUtf8 - (mCfgMgr.getUserConfigPath().string().c_str()); - - QString globalFilePath = QString::fromUtf8 - (mCfgMgr.getGlobalPath().string().c_str()); - - QString otherFilePath = globalFilePath; - - //test for local only if global fails (uninstalled copy) - if (!QFile (globalFilePath + fileName).exists()) - { - //if global is invalid, use the local path - otherFilePath = QString::fromUtf8 - (mCfgMgr.getLocalPath().string().c_str()); - } - - QSettings::setPath - (QSettings::IniFormat, QSettings::UserScope, userFilePath); - - QSettings::setPath - (QSettings::IniFormat, QSettings::SystemScope, otherFilePath); - - mSettingDefinitions = new QSettings - (QSettings::IniFormat, QSettings::UserScope, "opencs", QString(), this); -} - -// if the key is not found create one with a default value -QString CSMSettings::UserSettings::setting(const QString &viewKey, const QString &value) -{ - if(mSettingDefinitions->contains(viewKey)) - return settingValue(viewKey); - else if(value != QString()) - { - mSettingDefinitions->setValue (viewKey, QStringList() << value); - return value; - } - - return QString(); -} - -bool CSMSettings::UserSettings::hasSettingDefinitions (const QString &viewKey) const -{ - return (mSettingDefinitions->contains (viewKey)); -} - -void CSMSettings::UserSettings::setDefinitions - (const QString &key, const QStringList &list) -{ - mSettingDefinitions->setValue (key, list); -} - -void CSMSettings::UserSettings::saveDefinitions() const -{ - mSettingDefinitions->sync(); -} - -QString CSMSettings::UserSettings::settingValue (const QString &settingKey) -{ - QStringList defs; - - if (!mSettingDefinitions->contains (settingKey)) - return QString(); - - defs = mSettingDefinitions->value (settingKey).toStringList(); - - if (defs.isEmpty()) - return QString(); - - return defs.at(0); -} - -CSMSettings::UserSettings& CSMSettings::UserSettings::instance() -{ - assert(sUserSettingsInstance); - return *sUserSettingsInstance; -} - -void CSMSettings::UserSettings::updateUserSetting(const QString &settingKey, - const QStringList &list) -{ - mSettingDefinitions->setValue (settingKey ,list); - - if(settingKey == "3d-render-adv/num_lights" && !list.empty()) - { - sh::Factory::getInstance ().setGlobalSetting ("num_lights", list.at(0).toStdString()); - } - else if(settingKey == "3d-render/shaders" && !list.empty()) - { - sh::Factory::getInstance ().setShadersEnabled (list.at(0).toStdString() == "true" ? true : false); - } - - emit userSettingUpdated (settingKey, list); -} - -CSMSettings::Setting *CSMSettings::UserSettings::findSetting - (const QString &pageName, const QString &settingName) -{ - foreach (Setting *setting, mSettings) - { - if (setting->name() == settingName) - { - if (setting->page() == pageName) - return setting; - } - } - return 0; -} - -void CSMSettings::UserSettings::removeSetting - (const QString &pageName, const QString &settingName) -{ - if (mSettings.isEmpty()) - return; - - QList ::iterator removeIterator = mSettings.begin(); - - while (removeIterator != mSettings.end()) - { - if ((*removeIterator)->name() == settingName) - { - if ((*removeIterator)->page() == pageName) - { - mSettings.erase (removeIterator); - break; - } - } - removeIterator++; - } -} - -CSMSettings::SettingPageMap CSMSettings::UserSettings::settingPageMap() const -{ - SettingPageMap pageMap; - - foreach (Setting *setting, mSettings) - { - SettingPageMap::iterator iter = pageMap.find (setting->page()); - - if (iter==pageMap.end()) - { - QPair > value; - - std::map::const_iterator iter2 = - mSectionLabels.find (setting->page()); - - value.first = iter2!=mSectionLabels.end() ? iter2->second : ""; - - iter = pageMap.insert (setting->page(), value); - } - - iter->second.append (setting); - } - - return pageMap; -} - -CSMSettings::Setting *CSMSettings::UserSettings::createSetting - (CSMSettings::SettingType type, const QString &name, const QString& label) -{ - Setting *setting = new Setting (type, name, mSection, label); - - // set useful defaults - int row = 1; - - if (!mSettings.empty()) - row = mSettings.back()->viewRow()+1; - - setting->setViewLocation (row, 1); - - setting->setColumnSpan (3); - - int width = 10; - - if (type==Type_CheckBox) - width = 40; - - setting->setWidgetWidth (width); - - if (type==Type_CheckBox) - setting->setStyleSheet ("QGroupBox { border: 0px; }"); - - if (type==Type_CheckBox) - setting->setDeclaredValues(QStringList() << "true" << "false"); - - if (type==Type_CheckBox) - setting->setSpecialValueText (setting->getLabel()); - - //add declaration to the model - mSettings.append (setting); - - return setting; -} - -void CSMSettings::UserSettings::declareSection (const QString& page, const QString& label) -{ - mSection = page; - mSectionLabels[page] = label; -} - -QStringList CSMSettings::UserSettings::definitions (const QString &viewKey) const -{ - if (mSettingDefinitions->contains (viewKey)) - return mSettingDefinitions->value (viewKey).toStringList(); - - return QStringList(); -} diff --git a/apps/opencs/model/settings/usersettings.hpp b/apps/opencs/model/settings/usersettings.hpp deleted file mode 100644 index 5188a98429..0000000000 --- a/apps/opencs/model/settings/usersettings.hpp +++ /dev/null @@ -1,107 +0,0 @@ -#ifndef USERSETTINGS_HPP -#define USERSETTINGS_HPP - -#include - -#include -#include -#include -#include -#include - -#include -#include "support.hpp" - -#ifndef Q_MOC_RUN -#include -#endif - -namespace Files { typedef std::vector PathContainer; - struct ConfigurationManager;} - -class QFile; -class QSettings; - -namespace CSMSettings { - - class Setting; - typedef QMap > > SettingPageMap; - - class UserSettings: public QObject - { - - Q_OBJECT - - static UserSettings *sUserSettingsInstance; - const Files::ConfigurationManager& mCfgMgr; - - QSettings *mSettingDefinitions; - QList mSettings; - QString mSection; - std::map mSectionLabels; - - public: - - /// Singleton implementation - static UserSettings& instance(); - - UserSettings (const Files::ConfigurationManager& configurationManager); - ~UserSettings(); - - UserSettings (UserSettings const &); //not implemented - UserSettings& operator= (UserSettings const &); //not implemented - - /// Retrieves the settings file at all three levels (global, local and user). - void loadSettings (const QString &fileName); - - /// Updates QSettings and syncs with the ini file - void setDefinitions (const QString &key, const QStringList &defs); - - QString settingValue (const QString &settingKey); - - ///retrieve a setting object from a given page and setting name - Setting *findSetting - (const QString &pageName, const QString &settingName = QString()); - - ///remove a setting from the list - void removeSetting - (const QString &pageName, const QString &settingName); - - ///Retrieve a map of the settings, keyed by page name - SettingPageMap settingPageMap() const; - - ///Returns a string list of defined vlaues for the specified setting - ///in "page/name" format. - QStringList definitions (const QString &viewKey) const; - - ///Test to indicate whether or not a setting has any definitions - bool hasSettingDefinitions (const QString &viewKey) const; - - ///Save any unsaved changes in the QSettings object - void saveDefinitions() const; - - QString setting(const QString &viewKey, const QString &value = QString()); - - private: - - void buildSettingModelDefaults(); - - ///add a new setting to the model and return it - Setting *createSetting (CSMSettings::SettingType type, const QString &name, - const QString& label); - - /// Set the section for createSetting calls. - /// - /// Sections can be declared multiple times. - void declareSection (const QString& page, const QString& label); - - signals: - - void userSettingUpdated (const QString &, const QStringList &); - - public slots: - - void updateUserSetting (const QString &, const QStringList &); - }; -} -#endif // USERSETTINGS_HPP diff --git a/apps/opencs/model/tools/birthsigncheck.cpp b/apps/opencs/model/tools/birthsigncheck.cpp index 4e6da4631f..9898352f10 100644 --- a/apps/opencs/model/tools/birthsigncheck.cpp +++ b/apps/opencs/model/tools/birthsigncheck.cpp @@ -1,4 +1,3 @@ - #include "birthsigncheck.hpp" #include diff --git a/apps/opencs/model/tools/classcheck.cpp b/apps/opencs/model/tools/classcheck.cpp index be57a37290..e4964d4e34 100644 --- a/apps/opencs/model/tools/classcheck.cpp +++ b/apps/opencs/model/tools/classcheck.cpp @@ -1,4 +1,3 @@ - #include "classcheck.hpp" #include diff --git a/apps/opencs/model/tools/factioncheck.cpp b/apps/opencs/model/tools/factioncheck.cpp index 0dfdee7754..621b28070f 100644 --- a/apps/opencs/model/tools/factioncheck.cpp +++ b/apps/opencs/model/tools/factioncheck.cpp @@ -1,4 +1,3 @@ - #include "factioncheck.hpp" #include diff --git a/apps/opencs/model/tools/magiceffectcheck.cpp b/apps/opencs/model/tools/magiceffectcheck.cpp new file mode 100644 index 0000000000..5435881b34 --- /dev/null +++ b/apps/opencs/model/tools/magiceffectcheck.cpp @@ -0,0 +1,133 @@ +#include "magiceffectcheck.hpp" + +#include + +#include "../world/resources.hpp" +#include "../world/data.hpp" + +namespace +{ + void addMessageIfNotEmpty(CSMDoc::Messages &messages, const CSMWorld::UniversalId &id, const std::string text) + { + if (!text.empty()) + { + messages.push_back(std::make_pair(id, text)); + } + } +} + +bool CSMTools::MagicEffectCheckStage::isTextureExists(const std::string &texture, bool isIcon) const +{ + const CSMWorld::Resources &textures = isIcon ? mIcons : mTextures; + bool exists = false; + + if (textures.searchId(texture) != -1) + { + exists = true; + } + else + { + std::string ddsTexture = texture; + if (Misc::ResourceHelpers::changeExtensionToDds(ddsTexture) && textures.searchId(ddsTexture) != -1) + { + exists = true; + } + } + + return exists; +} + +std::string CSMTools::MagicEffectCheckStage::checkReferenceable(const std::string &id, + const CSMWorld::UniversalId &type, + const std::string &column) const +{ + std::string error; + if (!id.empty()) + { + CSMWorld::RefIdData::LocalIndex index = mReferenceables.getDataSet().searchId(id); + if (index.first == -1) + { + error = "No such " + column + " '" + id + "'"; + } + else if (index.second != type.getType()) + { + error = column + " is not of type " + type.getTypeName(); + } + } + return error; +} + +std::string CSMTools::MagicEffectCheckStage::checkSound(const std::string &id, const std::string &column) const +{ + std::string error; + if (!id.empty() && mSounds.searchId(id) == -1) + { + error = "No such " + column + " '" + id + "'"; + } + return error; +} + +CSMTools::MagicEffectCheckStage::MagicEffectCheckStage(const CSMWorld::IdCollection &effects, + const CSMWorld::IdCollection &sounds, + const CSMWorld::RefIdCollection &referenceables, + const CSMWorld::Resources &icons, + const CSMWorld::Resources &textures) + : mMagicEffects(effects), + mSounds(sounds), + mReferenceables(referenceables), + mIcons(icons), + mTextures(textures) +{} + +int CSMTools::MagicEffectCheckStage::setup() +{ + return mMagicEffects.getSize(); +} + +void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages &messages) +{ + ESM::MagicEffect effect = mMagicEffects.getRecord(stage).get(); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, effect.mId); + + if (effect.mData.mBaseCost < 0.0f) + { + messages.push_back(std::make_pair(id, "Base Cost is negative")); + } + + if (effect.mIcon.empty()) + { + messages.push_back(std::make_pair(id, "Icon is not specified")); + } + else if (!isTextureExists(effect.mIcon, true)) + { + messages.push_back(std::make_pair(id, "No such Icon '" + effect.mIcon + "'")); + } + + if (!effect.mParticle.empty() && !isTextureExists(effect.mParticle, false)) + { + messages.push_back(std::make_pair(id, "No such Particle '" + effect.mParticle + "'")); + } + + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mCasting, CSMWorld::UniversalId::Type_Static, "Casting Object")); + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mHit, CSMWorld::UniversalId::Type_Static, "Hit Object")); + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mArea, CSMWorld::UniversalId::Type_Static, "Area Object")); + addMessageIfNotEmpty(messages, + id, + checkReferenceable(effect.mBolt, CSMWorld::UniversalId::Type_Weapon, "Bolt Object")); + + addMessageIfNotEmpty(messages, id, checkSound(effect.mCastSound, "Casting Sound")); + addMessageIfNotEmpty(messages, id, checkSound(effect.mHitSound, "Hit Sound")); + addMessageIfNotEmpty(messages, id, checkSound(effect.mAreaSound, "Area Sound")); + addMessageIfNotEmpty(messages, id, checkSound(effect.mBoltSound, "Bolt Sound")); + + if (effect.mDescription.empty()) + { + messages.push_back(std::make_pair(id, "Description is empty")); + } +} diff --git a/apps/opencs/model/tools/magiceffectcheck.hpp b/apps/opencs/model/tools/magiceffectcheck.hpp new file mode 100644 index 0000000000..0ad6760d3d --- /dev/null +++ b/apps/opencs/model/tools/magiceffectcheck.hpp @@ -0,0 +1,50 @@ +#ifndef CSM_TOOLS_MAGICEFFECTCHECK_HPP +#define CSM_TOOLS_MAGICEFFECTCHECK_HPP + +#include +#include + +#include "../world/idcollection.hpp" +#include "../world/refidcollection.hpp" + +#include "../doc/stage.hpp" + +namespace CSMWorld +{ + class Resources; +} + +namespace CSMTools +{ + /// \brief VerifyStage: make sure that magic effect records are internally consistent + class MagicEffectCheckStage : public CSMDoc::Stage + { + const CSMWorld::IdCollection &mMagicEffects; + const CSMWorld::IdCollection &mSounds; + const CSMWorld::RefIdCollection &mReferenceables; + const CSMWorld::Resources &mIcons; + const CSMWorld::Resources &mTextures; + + private: + bool isTextureExists(const std::string &texture, bool isIcon) const; + + std::string checkReferenceable(const std::string &id, + const CSMWorld::UniversalId &type, + const std::string &column) const; + std::string checkSound(const std::string &id, const std::string &column) const; + + public: + MagicEffectCheckStage(const CSMWorld::IdCollection &effects, + const CSMWorld::IdCollection &sounds, + const CSMWorld::RefIdCollection &referenceables, + const CSMWorld::Resources &icons, + const CSMWorld::Resources &textures); + + virtual int setup(); + ///< \return number of steps + virtual void perform (int stage, CSMDoc::Messages &messages); + ///< Messages resulting from this tage will be appended to \a messages. + }; +} + +#endif diff --git a/apps/opencs/model/tools/mandatoryid.cpp b/apps/opencs/model/tools/mandatoryid.cpp index 4c97d22665..23adb9d376 100644 --- a/apps/opencs/model/tools/mandatoryid.cpp +++ b/apps/opencs/model/tools/mandatoryid.cpp @@ -1,4 +1,3 @@ - #include "mandatoryid.hpp" #include "../world/collectionbase.hpp" diff --git a/apps/opencs/model/tools/mergeoperation.cpp b/apps/opencs/model/tools/mergeoperation.cpp new file mode 100644 index 0000000000..907d742edf --- /dev/null +++ b/apps/opencs/model/tools/mergeoperation.cpp @@ -0,0 +1,59 @@ + +#include "mergeoperation.hpp" + +#include "../doc/state.hpp" +#include "../doc/document.hpp" + +#include "mergestages.hpp" + +CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding) +: CSMDoc::Operation (CSMDoc::State_Merging, true), mState (document) +{ + appendStage (new StartMergeStage (mState)); + + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getGlobals)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getGmsts)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSkills)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getClasses)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getFactions)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getRaces)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSounds)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getScripts)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getRegions)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getBirthsigns)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSpells)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getTopics)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getJournals)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getCells)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getFilters)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getEnchantments)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getBodyParts)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getDebugProfiles)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getSoundGens)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getMagicEffects)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getStartScripts)); + appendStage (new MergeIdCollectionStage > (mState, &CSMWorld::Data::getPathgrids)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getTopicInfos)); + appendStage (new MergeIdCollectionStage (mState, &CSMWorld::Data::getJournalInfos)); + appendStage (new MergeRefIdsStage (mState)); + appendStage (new MergeReferencesStage (mState)); + appendStage (new MergeReferencesStage (mState)); + appendStage (new ListLandTexturesMergeStage (mState)); + appendStage (new MergeLandTexturesStage (mState)); + appendStage (new MergeLandStage (mState)); + + appendStage (new FinishMergedDocumentStage (mState, encoding)); +} + +void CSMTools::MergeOperation::setTarget (std::auto_ptr document) +{ + mState.mTarget = document; +} + +void CSMTools::MergeOperation::operationDone() +{ + CSMDoc::Operation::operationDone(); + + if (mState.mCompleted) + emit mergeDone (mState.mTarget.release()); +} diff --git a/apps/opencs/model/tools/mergeoperation.hpp b/apps/opencs/model/tools/mergeoperation.hpp new file mode 100644 index 0000000000..bdaeb2ccda --- /dev/null +++ b/apps/opencs/model/tools/mergeoperation.hpp @@ -0,0 +1,45 @@ +#ifndef CSM_TOOLS_MERGEOPERATION_H +#define CSM_TOOLS_MERGEOPERATION_H + +#include + +#include + +#include "../doc/operation.hpp" + +#include "mergestate.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSMTools +{ + class MergeOperation : public CSMDoc::Operation + { + Q_OBJECT + + MergeState mState; + + public: + + MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding); + + /// \attention Do not call this function while a merge is running. + void setTarget (std::auto_ptr document); + + protected slots: + + virtual void operationDone(); + + signals: + + /// \attention When this signal is emitted, *this hands over the ownership of the + /// document. This signal must be handled to avoid a leak. + void mergeDone (CSMDoc::Document *document); + + }; +} + +#endif diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp new file mode 100644 index 0000000000..52e1e69649 --- /dev/null +++ b/apps/opencs/model/tools/mergestages.cpp @@ -0,0 +1,258 @@ + +#include "mergestages.hpp" + +#include + +#include + +#include "mergestate.hpp" + +#include "../doc/document.hpp" +#include "../world/data.hpp" + + +CSMTools::StartMergeStage::StartMergeStage (MergeState& state) +: mState (state) +{} + +int CSMTools::StartMergeStage::setup() +{ + return 1; +} + +void CSMTools::StartMergeStage::perform (int stage, CSMDoc::Messages& messages) +{ + mState.mCompleted = false; + mState.mTextureIndices.clear(); +} + + +CSMTools::FinishMergedDocumentStage::FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding) +: mState (state), mEncoder (encoding) +{} + +int CSMTools::FinishMergedDocumentStage::setup() +{ + return 1; +} + +void CSMTools::FinishMergedDocumentStage::perform (int stage, CSMDoc::Messages& messages) +{ + // We know that the content file list contains at least two entries and that the first one + // does exist on disc (otherwise it would have been impossible to initiate a merge on that + // document). + boost::filesystem::path path = mState.mSource.getContentFiles()[0]; + + ESM::ESMReader reader; + reader.setEncoder (&mEncoder); + reader.open (path.string()); + + CSMWorld::MetaData source; + source.mId = "sys::meta"; + source.load (reader); + + CSMWorld::MetaData target = mState.mTarget->getData().getMetaData(); + + target.mAuthor = source.mAuthor; + target.mDescription = source.mDescription; + + mState.mTarget->getData().setMetaData (target); + + mState.mCompleted = true; +} + + +CSMTools::MergeRefIdsStage::MergeRefIdsStage (MergeState& state) : mState (state) {} + +int CSMTools::MergeRefIdsStage::setup() +{ + return mState.mSource.getData().getReferenceables().getSize(); +} + +void CSMTools::MergeRefIdsStage::perform (int stage, CSMDoc::Messages& messages) +{ + mState.mSource.getData().getReferenceables().copyTo ( + stage, mState.mTarget->getData().getReferenceables()); +} + + +CSMTools::MergeReferencesStage::MergeReferencesStage (MergeState& state) +: mState (state) +{} + +int CSMTools::MergeReferencesStage::setup() +{ + mIndex.clear(); + return mState.mSource.getData().getReferences().getSize(); +} + +void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = + mState.mSource.getData().getReferences().getRecord (stage); + + if (!record.isDeleted()) + { + CSMWorld::CellRef ref = record.get(); + + ref.mOriginalCell = ref.mCell; + + ref.mRefNum.mIndex = mIndex[Misc::StringUtils::lowerCase (ref.mCell)]++; + ref.mRefNum.mContentFile = 0; + + CSMWorld::Record newRecord ( + CSMWorld::RecordBase::State_ModifiedOnly, 0, &ref); + + mState.mTarget->getData().getReferences().appendRecord (newRecord); + } +} + + +CSMTools::ListLandTexturesMergeStage::ListLandTexturesMergeStage (MergeState& state) +: mState (state) +{} + +int CSMTools::ListLandTexturesMergeStage::setup() +{ + return mState.mSource.getData().getLand().getSize(); +} + +void CSMTools::ListLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = + mState.mSource.getData().getLand().getRecord (stage); + + if (!record.isDeleted()) + { + const CSMWorld::Land& land = record.get(); + + // make sure record is loaded + land.loadData (ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | + ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); + + if (const ESM::Land::LandData *data = land.getLandData (ESM::Land::DATA_VTEX)) + { + // list texture indices + std::pair key; + key.second = land.mPlugin; + + for (int i=0; imTextures[i]; + + mState.mTextureIndices[key] = -1; + } + } + } +} + + +CSMTools::MergeLandTexturesStage::MergeLandTexturesStage (MergeState& state) +: mState (state), mNext (mState.mTextureIndices.end()) +{} + +int CSMTools::MergeLandTexturesStage::setup() +{ + // Should use the size of mState.mTextureIndices instead, but that is not available at this + // point. Unless there are any errors in the land and land texture records this will not + // make a difference. + return mState.mSource.getData().getLandTextures().getSize(); +} + +void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& messages) +{ + if (stage==0) + mNext = mState.mTextureIndices.begin(); + + bool found = false; + + do + { + if (mNext==mState.mTextureIndices.end()) + return; + + mNext->second = stage+1; + + std::ostringstream stream; + stream << mNext->first.first-1 << "_" << mNext->first.second; + + int index = mState.mSource.getData().getLandTextures().searchId (stream.str()); + + if (index!=-1) + { + CSMWorld::LandTexture texture = + mState.mSource.getData().getLandTextures().getRecord (index).get(); + + std::ostringstream stream; + stream << mNext->second-1 << "_0"; + + texture.mIndex = mNext->second-1; + texture.mId = stream.str(); + + CSMWorld::Record newRecord ( + CSMWorld::RecordBase::State_ModifiedOnly, 0, &texture); + + mState.mTarget->getData().getLandTextures().appendRecord (newRecord); + + found = true; + } + + ++mNext; + } + while (!found); +} + + +CSMTools::MergeLandStage::MergeLandStage (MergeState& state) : mState (state) {} + +int CSMTools::MergeLandStage::setup() +{ + return mState.mSource.getData().getLand().getSize(); +} + +void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = + mState.mSource.getData().getLand().getRecord (stage); + + if (!record.isDeleted()) + { + const CSMWorld::Land& land = record.get(); + + land.loadData (ESM::Land::DATA_VCLR | ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | + ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); + + CSMWorld::Land newLand (land); + + newLand.mEsm = 0; // avoid potential dangling pointer (ESMReader isn't needed anyway, + // because record is already fully loaded) + newLand.mPlugin = 0; + + if (land.mDataTypes & ESM::Land::DATA_VTEX) + { + // adjust land texture references + if (ESM::Land::LandData *data = newLand.getLandData()) + { + std::pair key; + key.second = land.mPlugin; + + for (int i=0; imTextures[i]; + std::map, int>::const_iterator iter = + mState.mTextureIndices.find (key); + + if (iter!=mState.mTextureIndices.end()) + data->mTextures[i] = iter->second; + else + data->mTextures[i] = 0; + } + } + } + + CSMWorld::Record newRecord ( + CSMWorld::RecordBase::State_ModifiedOnly, 0, &newLand); + + mState.mTarget->getData().getLand().appendRecord (newRecord); + } +} diff --git a/apps/opencs/model/tools/mergestages.hpp b/apps/opencs/model/tools/mergestages.hpp new file mode 100644 index 0000000000..f88f5be9f6 --- /dev/null +++ b/apps/opencs/model/tools/mergestages.hpp @@ -0,0 +1,166 @@ +#ifndef CSM_TOOLS_MERGESTAGES_H +#define CSM_TOOLS_MERGESTAGES_H + +#include +#include + +#include + +#include "../doc/stage.hpp" + +#include "../world/data.hpp" + +#include "mergestate.hpp" + +namespace CSMTools +{ + class StartMergeStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + StartMergeStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class FinishMergedDocumentStage : public CSMDoc::Stage + { + MergeState& mState; + ToUTF8::Utf8Encoder mEncoder; + + public: + + FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + template > + class MergeIdCollectionStage : public CSMDoc::Stage + { + MergeState& mState; + Collection& (CSMWorld::Data::*mAccessor)(); + + public: + + MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + template + MergeIdCollectionStage::MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)()) + : mState (state), mAccessor (accessor) + {} + + template + int MergeIdCollectionStage::setup() + { + return (mState.mSource.getData().*mAccessor)().getSize(); + } + + template + void MergeIdCollectionStage::perform (int stage, CSMDoc::Messages& messages) + { + const Collection& source = (mState.mSource.getData().*mAccessor)(); + Collection& target = (mState.mTarget->getData().*mAccessor)(); + + const CSMWorld::Record& record = source.getRecord (stage); + + if (!record.isDeleted()) + target.appendRecord (CSMWorld::Record (CSMWorld::RecordBase::State_ModifiedOnly, 0, &record.get())); + } + + class MergeRefIdsStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + MergeRefIdsStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class MergeReferencesStage : public CSMDoc::Stage + { + MergeState& mState; + std::map mIndex; + + public: + + MergeReferencesStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class ListLandTexturesMergeStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + ListLandTexturesMergeStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class MergeLandTexturesStage : public CSMDoc::Stage + { + MergeState& mState; + std::map, int>::iterator mNext; + + public: + + MergeLandTexturesStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class MergeLandStage : public CSMDoc::Stage + { + MergeState& mState; + + public: + + MergeLandStage (MergeState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; +} + +#endif diff --git a/apps/opencs/model/tools/mergestate.hpp b/apps/opencs/model/tools/mergestate.hpp new file mode 100644 index 0000000000..29e1bbda72 --- /dev/null +++ b/apps/opencs/model/tools/mergestate.hpp @@ -0,0 +1,24 @@ +#ifndef CSM_TOOLS_MERGESTATE_H +#define CSM_TOOLS_MERGESTATE_H + +#include + +#include +#include + +#include "../doc/document.hpp" + +namespace CSMTools +{ + struct MergeState + { + std::auto_ptr mTarget; + CSMDoc::Document& mSource; + bool mCompleted; + std::map, int> mTextureIndices; // (texture, content file) -> new texture + + MergeState (CSMDoc::Document& source) : mSource (source), mCompleted (false) {} + }; +} + +#endif diff --git a/apps/opencs/model/tools/pathgridcheck.cpp b/apps/opencs/model/tools/pathgridcheck.cpp index 76edeb573d..69ee5a8098 100644 --- a/apps/opencs/model/tools/pathgridcheck.cpp +++ b/apps/opencs/model/tools/pathgridcheck.cpp @@ -30,9 +30,9 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message // check the number of pathgrid points if (pathgrid.mData.mS2 > static_cast(pathgrid.mPoints.size())) - messages.push_back (std::make_pair (id, pathgrid.mId + " has less points than expected")); + messages.add (id, pathgrid.mId + " has less points than expected", "", CSMDoc::Message::Severity_Error); else if (pathgrid.mData.mS2 > static_cast(pathgrid.mPoints.size())) - messages.push_back (std::make_pair (id, pathgrid.mId + " has more points than expected")); + messages.add (id, pathgrid.mId + " has more points than expected", "", CSMDoc::Message::Severity_Error); std::vector pointList(pathgrid.mPoints.size()); std::vector duplList; @@ -51,7 +51,7 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message std::ostringstream ss; ss << "has a duplicate edge between points" << pathgrid.mEdges[i].mV0 << " and " << pathgrid.mEdges[i].mV1; - messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); break; } } @@ -64,7 +64,7 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message { std::ostringstream ss; ss << " has an edge connecting a non-existent point " << pathgrid.mEdges[i].mV0; - messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); } } @@ -75,13 +75,13 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message { std::ostringstream ss; ss << " has has less edges than expected for point " << i; - messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); } else if (pathgrid.mPoints[i].mConnectionNum < pointList[i].mConnectionNum) { std::ostringstream ss; ss << " has has more edges than expected for point " << i; - messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); } // check that edges are bidirectional @@ -101,7 +101,7 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message { std::ostringstream ss; ss << " has a missing edge between points " << i << " and " << pointList[i].mOtherIndex[j]; - messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); } } @@ -124,7 +124,7 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message << ") x=" << pathgrid.mPoints[i].mX << ", y=" << pathgrid.mPoints[i].mY << ", z=" << pathgrid.mPoints[i].mZ; - messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Warning); duplList.push_back(i); break; @@ -143,7 +143,7 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message << ") x=" << pathgrid.mPoints[i].mX << ", y=" << pathgrid.mPoints[i].mY << ", z=" << pathgrid.mPoints[i].mZ; - messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Warning); } } diff --git a/apps/opencs/model/tools/racecheck.cpp b/apps/opencs/model/tools/racecheck.cpp index 3b2c8d290e..b300886208 100644 --- a/apps/opencs/model/tools/racecheck.cpp +++ b/apps/opencs/model/tools/racecheck.cpp @@ -1,4 +1,3 @@ - #include "racecheck.hpp" #include diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index 6b323547f0..ce78e52b22 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -397,6 +397,9 @@ void CSMTools::ReferenceableCheckStage::containerCheck( //checking for name if (container.mName.empty()) messages.push_back (std::make_pair (id, container.mId + " has an empty name")); + + //checking contained items + inventoryListCheck(container.mInventory.mList, messages, id.toString()); // Check that mentioned scripts exist scriptCheck(container, messages, id.toString()); @@ -468,6 +471,12 @@ void CSMTools::ReferenceableCheckStage::creatureCheck ( if (creature.mData.mGold < 0) //It seems that this is for gold in merchant creatures messages.push_back (std::make_pair (id, creature.mId + " has negative gold ")); + if (creature.mScale == 0) + messages.push_back (std::make_pair (id, creature.mId + " has zero scale value")); + + // Check inventory + inventoryListCheck(creature.mInventory.mList, messages, id.toString()); + // Check that mentioned scripts exist scriptCheck(creature, messages, id.toString()); } @@ -739,6 +748,9 @@ void CSMTools::ReferenceableCheckStage::npcCheck ( //TODO: reputation, Disposition, rank, everything else + // Check inventory + inventoryListCheck(npc.mInventory.mList, messages, id.toString()); + // Check that mentioned scripts exist scriptCheck(npc, messages, id.toString()); } @@ -888,6 +900,45 @@ void CSMTools::ReferenceableCheckStage::finalCheck (CSMDoc::Messages& messages) "There is no player record")); } +void CSMTools::ReferenceableCheckStage::inventoryListCheck( + const std::vector& itemList, + CSMDoc::Messages& messages, + const std::string& id) +{ + for (size_t i = 0; i < itemList.size(); ++i) + { + std::string itemName = itemList[i].mItem.toString(); + CSMWorld::RefIdData::LocalIndex localIndex = mReferencables.searchId(itemName); + + if (localIndex.first == -1) + messages.push_back (std::make_pair (id, + id + " contains non-existing item (" + itemName + ")")); + else + { + // Needs to accomodate Containers, Creatures, and NPCs + switch (localIndex.second) + { + case CSMWorld::UniversalId::Type_Potion: + case CSMWorld::UniversalId::Type_Apparatus: + case CSMWorld::UniversalId::Type_Armor: + case CSMWorld::UniversalId::Type_Book: + case CSMWorld::UniversalId::Type_Clothing: + case CSMWorld::UniversalId::Type_Ingredient: + case CSMWorld::UniversalId::Type_Light: + case CSMWorld::UniversalId::Type_Lockpick: + case CSMWorld::UniversalId::Type_Miscellaneous: + case CSMWorld::UniversalId::Type_Probe: + case CSMWorld::UniversalId::Type_Repair: + case CSMWorld::UniversalId::Type_Weapon: + case CSMWorld::UniversalId::Type_ItemLevelledList: + break; + default: + messages.push_back (std::make_pair(id, + id + " contains item of invalid type (" + itemName + ")")); + } + } + } +} //Templates begins here diff --git a/apps/opencs/model/tools/referenceablecheck.hpp b/apps/opencs/model/tools/referenceablecheck.hpp index a34f3a7891..4356e50b22 100644 --- a/apps/opencs/model/tools/referenceablecheck.hpp +++ b/apps/opencs/model/tools/referenceablecheck.hpp @@ -47,7 +47,9 @@ namespace CSMTools //FINAL CHECK void finalCheck (CSMDoc::Messages& messages); - //TEMPLATE CHECKS + //Convenience functions + void inventoryListCheck(const std::vector& itemList, CSMDoc::Messages& messages, const std::string& id); + template void inventoryItemCheck(const ITEM& someItem, CSMDoc::Messages& messages, const std::string& someID, diff --git a/apps/opencs/model/tools/regioncheck.cpp b/apps/opencs/model/tools/regioncheck.cpp index 42abc35c93..2fdff5f388 100644 --- a/apps/opencs/model/tools/regioncheck.cpp +++ b/apps/opencs/model/tools/regioncheck.cpp @@ -1,4 +1,3 @@ - #include "regioncheck.hpp" #include diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index 480691710d..77a14de841 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -1,4 +1,3 @@ - #include "reportmodel.hpp" #include @@ -13,7 +12,7 @@ CSMTools::ReportModel::ReportModel (bool fieldColumn, bool severityColumn) if (severityColumn) mColumnSeverity = index++; - + if (fieldColumn) mColumnField = index++; @@ -46,7 +45,7 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const case Column_Type: return static_cast (mRows.at (index.row()).mId.getType()); - + case Column_Id: { CSMWorld::UniversalId id = mRows.at (index.row()).mId; @@ -56,7 +55,7 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const return QString ("-"); } - + case Column_Hint: return QString::fromUtf8 (mRows.at (index.row()).mHint.c_str()); @@ -85,16 +84,10 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const if (index.column()==mColumnSeverity) { - switch (mRows.at (index.row()).mSeverity) - { - case CSMDoc::Message::Severity_Info: return "Information"; - case CSMDoc::Message::Severity_Warning: return "Warning"; - case CSMDoc::Message::Severity_Error: return "Error"; - case CSMDoc::Message::Severity_SeriousError: return "Serious Error"; - case CSMDoc::Message::Severity_Default: break; - } + return QString::fromUtf8 ( + CSMDoc::Message::toString (mRows.at (index.row()).mSeverity).c_str()); } - + return QVariant(); } @@ -144,7 +137,7 @@ bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& p void CSMTools::ReportModel::add (const CSMDoc::Message& message) { beginInsertRows (QModelIndex(), mRows.size(), mRows.size()); - + mRows.push_back (message); endInsertRows(); diff --git a/apps/opencs/model/tools/scriptcheck.cpp b/apps/opencs/model/tools/scriptcheck.cpp index 928ae156f8..268aea3798 100644 --- a/apps/opencs/model/tools/scriptcheck.cpp +++ b/apps/opencs/model/tools/scriptcheck.cpp @@ -1,4 +1,3 @@ - #include "scriptcheck.hpp" #include @@ -11,6 +10,8 @@ #include "../world/data.hpp" +#include "../prefs/state.hpp" + CSMDoc::Message::Severity CSMTools::ScriptCheckStage::getSeverity (Type type) { switch (type) @@ -47,7 +48,7 @@ void CSMTools::ScriptCheckStage::report (const std::string& message, Type type) std::ostringstream stream; stream << "script " << mFile << ": " << message; - + mMessages->add (id, stream.str(), "", getSeverity (type)); } @@ -63,6 +64,15 @@ CSMTools::ScriptCheckStage::ScriptCheckStage (const CSMDoc::Document& document) int CSMTools::ScriptCheckStage::setup() { + std::string warnings = CSMPrefs::get()["Scripts"]["warnings"].toString(); + + if (warnings=="Ignore") + mWarningMode = Mode_Ignore; + else if (warnings=="Normal") + mWarningMode = Mode_Normal; + else if (warnings=="Strict") + mWarningMode = Mode_Strict; + mContext.clear(); mMessages = 0; mId.clear(); @@ -111,22 +121,9 @@ void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages) std::ostringstream stream; stream << "script " << mFile << ": " << error.what(); - + messages.add (id, stream.str(), "", CSMDoc::Message::Severity_SeriousError); } mMessages = 0; } - -void CSMTools::ScriptCheckStage::updateUserSetting (const QString& name, const QStringList& value) -{ - if (name=="script-editor/warnings") - { - if (value.at (0)=="Ignore") - mWarningMode = Mode_Ignore; - else if (value.at (0)=="Normal") - mWarningMode = Mode_Normal; - else if (value.at (0)=="Strict") - mWarningMode = Mode_Strict; - } -} diff --git a/apps/opencs/model/tools/scriptcheck.hpp b/apps/opencs/model/tools/scriptcheck.hpp index 3f02766526..f58215800f 100644 --- a/apps/opencs/model/tools/scriptcheck.hpp +++ b/apps/opencs/model/tools/scriptcheck.hpp @@ -34,7 +34,7 @@ namespace CSMTools WarningMode mWarningMode; CSMDoc::Message::Severity getSeverity (Type type); - + virtual void report (const std::string& message, const Compiler::TokenLoc& loc, Type type); ///< Report error to the user. @@ -50,8 +50,6 @@ namespace CSMTools virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. - - virtual void updateUserSetting (const QString& name, const QStringList& value); }; } diff --git a/apps/opencs/model/tools/search.cpp b/apps/opencs/model/tools/search.cpp index 449df2c638..0409199afe 100644 --- a/apps/opencs/model/tools/search.cpp +++ b/apps/opencs/model/tools/search.cpp @@ -1,4 +1,3 @@ - #include "search.hpp" #include diff --git a/apps/opencs/model/tools/searchoperation.cpp b/apps/opencs/model/tools/searchoperation.cpp index 8cbc5dc8e6..8fba1cc1ef 100644 --- a/apps/opencs/model/tools/searchoperation.cpp +++ b/apps/opencs/model/tools/searchoperation.cpp @@ -1,4 +1,3 @@ - #include "searchoperation.hpp" #include "../doc/state.hpp" diff --git a/apps/opencs/model/tools/searchstage.cpp b/apps/opencs/model/tools/searchstage.cpp index 17859d9309..3db10b0c37 100644 --- a/apps/opencs/model/tools/searchstage.cpp +++ b/apps/opencs/model/tools/searchstage.cpp @@ -1,4 +1,3 @@ - #include "searchstage.hpp" #include "../world/idtablebase.hpp" diff --git a/apps/opencs/model/tools/skillcheck.cpp b/apps/opencs/model/tools/skillcheck.cpp index 2b55526e09..77ba8d4a21 100644 --- a/apps/opencs/model/tools/skillcheck.cpp +++ b/apps/opencs/model/tools/skillcheck.cpp @@ -1,4 +1,3 @@ - #include "skillcheck.hpp" #include diff --git a/apps/opencs/model/tools/soundcheck.cpp b/apps/opencs/model/tools/soundcheck.cpp index f78932a32b..6a059bee22 100644 --- a/apps/opencs/model/tools/soundcheck.cpp +++ b/apps/opencs/model/tools/soundcheck.cpp @@ -1,4 +1,3 @@ - #include "soundcheck.hpp" #include diff --git a/apps/opencs/model/tools/spellcheck.cpp b/apps/opencs/model/tools/spellcheck.cpp index bd076d2a5a..91aed37edf 100644 --- a/apps/opencs/model/tools/spellcheck.cpp +++ b/apps/opencs/model/tools/spellcheck.cpp @@ -1,4 +1,3 @@ - #include "spellcheck.hpp" #include diff --git a/apps/opencs/model/tools/startscriptcheck.cpp b/apps/opencs/model/tools/startscriptcheck.cpp index e3c01368bd..2207517971 100644 --- a/apps/opencs/model/tools/startscriptcheck.cpp +++ b/apps/opencs/model/tools/startscriptcheck.cpp @@ -1,4 +1,3 @@ - #include "startscriptcheck.hpp" #include diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index f0d649f388..608ed9922f 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -1,4 +1,3 @@ - #include "tools.hpp" #include @@ -28,6 +27,8 @@ #include "searchoperation.hpp" #include "pathgridcheck.hpp" #include "soundgencheck.hpp" +#include "magiceffectcheck.hpp" +#include "mergeoperation.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { @@ -35,6 +36,7 @@ CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { case CSMDoc::State_Verifying: return &mVerifier; case CSMDoc::State_Searching: return &mSearch; + case CSMDoc::State_Merging: return &mMerge; } return 0; @@ -51,11 +53,6 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() { mVerifierOperation = new CSMDoc::Operation (CSMDoc::State_Verifying, false); - std::vector settings; - settings.push_back ("script-editor/warnings"); - - mVerifierOperation->configureSettings (settings); - connect (&mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); connect (&mVerifier, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); connect (&mVerifier, SIGNAL (reportMessage (const CSMDoc::Message&, int)), @@ -108,15 +105,21 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() mData.getSounds(), mData.getReferenceables())); + mVerifierOperation->appendStage (new MagicEffectCheckStage (mData.getMagicEffects(), + mData.getSounds(), + mData.getReferenceables(), + mData.getResources (CSMWorld::UniversalId::Type_Icons), + mData.getResources (CSMWorld::UniversalId::Type_Textures))); + mVerifier.setOperation (mVerifierOperation); } return &mVerifier; } -CSMTools::Tools::Tools (CSMDoc::Document& document) +CSMTools::Tools::Tools (CSMDoc::Document& document, ToUTF8::FromType encoding) : mDocument (document), mData (document.getData()), mVerifierOperation (0), - mSearchOperation (0), mNextReportNumber (0) + mSearchOperation (0), mMergeOperation (0), mNextReportNumber (0), mEncoding (encoding) { // index 0: load error log mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); @@ -126,6 +129,10 @@ CSMTools::Tools::Tools (CSMDoc::Document& document) connect (&mSearch, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); connect (&mSearch, SIGNAL (reportMessage (const CSMDoc::Message&, int)), this, SLOT (verifierMessage (const CSMDoc::Message&, int))); + + connect (&mMerge, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); + connect (&mMerge, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); + // don't need to connect report message, since there are no messages for merge } CSMTools::Tools::~Tools() @@ -142,18 +149,29 @@ CSMTools::Tools::~Tools() delete mSearchOperation; } + if (mMergeOperation) + { + mMerge.abortAndWait(); + delete mMergeOperation; + } + for (std::map::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter) delete iter->second; } -CSMWorld::UniversalId CSMTools::Tools::runVerifier() +CSMWorld::UniversalId CSMTools::Tools::runVerifier (const CSMWorld::UniversalId& reportId) { - mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); - mActiveReports[CSMDoc::State_Verifying] = mNextReportNumber-1; + int reportNumber = reportId.getType()==CSMWorld::UniversalId::Type_VerificationResults ? + reportId.getIndex() : mNextReportNumber++; + + if (mReports.find (reportNumber)==mReports.end()) + mReports.insert (std::make_pair (reportNumber, new ReportModel)); + + mActiveReports[CSMDoc::State_Verifying] = reportNumber; getVerifier()->start(); - return CSMWorld::UniversalId (CSMWorld::UniversalId::Type_VerificationResults, mNextReportNumber-1); + return CSMWorld::UniversalId (CSMWorld::UniversalId::Type_VerificationResults, reportNumber); } CSMWorld::UniversalId CSMTools::Tools::newSearch() @@ -178,6 +196,25 @@ void CSMTools::Tools::runSearch (const CSMWorld::UniversalId& searchId, const Se mSearch.start(); } +void CSMTools::Tools::runMerge (std::auto_ptr target) +{ + // not setting an active report, because merge does not produce messages + + if (!mMergeOperation) + { + mMergeOperation = new MergeOperation (mDocument, mEncoding); + mMerge.setOperation (mMergeOperation); + connect (mMergeOperation, SIGNAL (mergeDone (CSMDoc::Document*)), + this, SIGNAL (mergeDone (CSMDoc::Document*))); + } + + target->flagAsDirty(); + + mMergeOperation->setTarget (target); + + mMerge.start(); +} + void CSMTools::Tools::abortOperation (int type) { if (CSMDoc::OperationHolder *operation = get (type)) @@ -190,6 +227,7 @@ int CSMTools::Tools::getRunningOperations() const { CSMDoc::State_Verifying, CSMDoc::State_Searching, + CSMDoc::State_Merging, -1 }; @@ -220,4 +258,3 @@ void CSMTools::Tools::verifierMessage (const CSMDoc::Message& message, int type) if (iter!=mActiveReports.end()) mReports[iter->second]->add (message); } - diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index 2912fc4717..e16a3854cc 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -1,9 +1,14 @@ #ifndef CSM_TOOLS_TOOLS_H #define CSM_TOOLS_TOOLS_H +#include +#include + +#include + #include -#include +#include #include "../doc/operationholder.hpp" @@ -24,6 +29,7 @@ namespace CSMTools class ReportModel; class Search; class SearchOperation; + class MergeOperation; class Tools : public QObject { @@ -35,9 +41,12 @@ namespace CSMTools CSMDoc::OperationHolder mVerifier; SearchOperation *mSearchOperation; CSMDoc::OperationHolder mSearch; + MergeOperation *mMergeOperation; + CSMDoc::OperationHolder mMerge; std::map mReports; int mNextReportNumber; std::map mActiveReports; // type, report number + ToUTF8::FromType mEncoding; // not implemented Tools (const Tools&); @@ -53,18 +62,23 @@ namespace CSMTools public: - Tools (CSMDoc::Document& document); + Tools (CSMDoc::Document& document, ToUTF8::FromType encoding); virtual ~Tools(); - CSMWorld::UniversalId runVerifier(); - ///< \return ID of the report for this verification run + /// \param reportId If a valid VerificationResults ID, run verifier for the + /// specified report instead of creating a new one. + /// + /// \return ID of the report for this verification run + CSMWorld::UniversalId runVerifier (const CSMWorld::UniversalId& reportId = CSMWorld::UniversalId()); /// Return ID of the report for this search. CSMWorld::UniversalId newSearch(); void runSearch (const CSMWorld::UniversalId& searchId, const Search& search); - + + void runMerge (std::auto_ptr target); + void abortOperation (int type); ///< \attention The operation is not aborted immediately. @@ -82,6 +96,10 @@ namespace CSMTools void progress (int current, int max, int type); void done (int type, bool failed); + + /// \attention When this signal is emitted, *this hands over the ownership of the + /// document. This signal must be handled to avoid a leak. + void mergeDone (CSMDoc::Document *document); }; } diff --git a/apps/opencs/model/world/cell.cpp b/apps/opencs/model/world/cell.cpp index 40520a9ba7..93f3c500d7 100644 --- a/apps/opencs/model/world/cell.cpp +++ b/apps/opencs/model/world/cell.cpp @@ -1,20 +1,16 @@ - #include "cell.hpp" #include -void CSMWorld::Cell::load (ESM::ESMReader &esm) +void CSMWorld::Cell::load (ESM::ESMReader &esm, bool &isDeleted) { - mName = mId; + ESM::Cell::load (esm, isDeleted, false); - ESM::Cell::load (esm, false); - - if (!(mData.mFlags & Interior)) + mId = mName; + if (isExterior()) { std::ostringstream stream; - stream << "#" << mData.mX << " " << mData.mY; - mId = stream.str(); } } diff --git a/apps/opencs/model/world/cell.hpp b/apps/opencs/model/world/cell.hpp index f393e2cf97..160610874c 100644 --- a/apps/opencs/model/world/cell.hpp +++ b/apps/opencs/model/world/cell.hpp @@ -16,7 +16,7 @@ namespace CSMWorld { std::string mId; - void load (ESM::ESMReader &esm); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/cellcoordinates.cpp b/apps/opencs/model/world/cellcoordinates.cpp index b1c8441e63..3ef3e6c693 100644 --- a/apps/opencs/model/world/cellcoordinates.cpp +++ b/apps/opencs/model/world/cellcoordinates.cpp @@ -1,4 +1,3 @@ - #include "cellcoordinates.hpp" #include @@ -33,6 +32,23 @@ std::string CSMWorld::CellCoordinates::getId (const std::string& worldspace) con return stream.str(); } +std::pair CSMWorld::CellCoordinates::fromId ( + const std::string& id) +{ + // no worldspace for now, needs to be changed for 1.1 + if (!id.empty() && id[0]=='#') + { + int x, y; + char ignore; + + std::istringstream stream (id); + if (stream >> ignore >> x >> y) + return std::make_pair (CellCoordinates (x, y), true); + } + + return std::make_pair (CellCoordinates(), false); +} + bool CSMWorld::operator== (const CellCoordinates& left, const CellCoordinates& right) { return left.getX()==right.getX() && left.getY()==right.getY(); diff --git a/apps/opencs/model/world/cellcoordinates.hpp b/apps/opencs/model/world/cellcoordinates.hpp index ee395ea7ee..63db60c29d 100644 --- a/apps/opencs/model/world/cellcoordinates.hpp +++ b/apps/opencs/model/world/cellcoordinates.hpp @@ -28,6 +28,12 @@ namespace CSMWorld std::string getId (const std::string& worldspace) const; ///< Return the ID for the cell at these coordinates. + + /// \return first: CellCoordinates (or 0, 0 if cell does not have coordinates), + /// second: is cell paged? + /// + /// \note The worldspace part of \a id is ignored + static std::pair fromId (const std::string& id); }; bool operator== (const CellCoordinates& left, const CellCoordinates& right); diff --git a/apps/opencs/model/world/cellselection.cpp b/apps/opencs/model/world/cellselection.cpp index 73b5196f13..c6988e4880 100644 --- a/apps/opencs/model/world/cellselection.cpp +++ b/apps/opencs/model/world/cellselection.cpp @@ -1,4 +1,3 @@ - #include "cellselection.hpp" #include diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index b0571bbed1..16f5ce51f4 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -43,6 +43,12 @@ namespace CSMWorld template > class Collection : public CollectionBase { + public: + + typedef ESXRecordT ESXRecord; + + private: + std::vector > mRecords; std::map mIndex; std::vector *> mColumns; diff --git a/apps/opencs/model/world/collectionbase.cpp b/apps/opencs/model/world/collectionbase.cpp index b8eed4192d..6134dc1727 100644 --- a/apps/opencs/model/world/collectionbase.cpp +++ b/apps/opencs/model/world/collectionbase.cpp @@ -1,4 +1,3 @@ - #include "collectionbase.hpp" #include diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index 9385038757..1f16c9695b 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -77,16 +77,18 @@ bool CSMWorld::ColumnBase::isId (Display display) Display_Video, Display_Id, - Display_SkillImpact, + Display_SkillId, Display_EffectRange, Display_EffectId, Display_PartRefType, Display_AiPackageType, - Display_YesNo, Display_InfoCondFunc, Display_InfoCondVar, Display_InfoCondComp, - Display_RaceSkill, + + Display_EffectSkill, + Display_EffectAttribute, + Display_IngredEffectId, Display_None }; @@ -100,7 +102,8 @@ bool CSMWorld::ColumnBase::isId (Display display) bool CSMWorld::ColumnBase::isText (Display display) { - return display==Display_String || display==Display_LongString; + return display==Display_String || display==Display_LongString || + display==Display_String32 || display==Display_LongString256; } bool CSMWorld::ColumnBase::isScript (Display display) diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 789823d5cd..c75a3c2a12 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -14,6 +14,13 @@ namespace CSMWorld { struct ColumnBase { + enum TableEditModes + { + TableEdit_None, // no editing + TableEdit_Full, // edit cells and add/remove rows + TableEdit_FixedRows // edit cells only + }; + enum Roles { Role_Flags = Qt::UserRole, @@ -113,16 +120,20 @@ namespace CSMWorld Display_SoundGeneratorType, Display_School, Display_Id, - Display_SkillImpact, + Display_SkillId, Display_EffectRange, Display_EffectId, Display_PartRefType, Display_AiPackageType, - Display_YesNo, Display_InfoCondFunc, Display_InfoCondVar, Display_InfoCondComp, - Display_RaceSkill, + Display_String32, + Display_LongString256, + + Display_EffectSkill, // must display at least one, unlike Display_Skill + Display_EffectAttribute, // must display at least one, unlike Display_Attribute + Display_IngredEffectId, // display none allowed, unlike Display_EffectId //top level columns that nest other columns Display_NestedHeader @@ -186,19 +197,32 @@ namespace CSMWorld template struct NestedParentColumn : public Column { - NestedParentColumn (int id, int flags = ColumnBase::Flag_Dialogue) : Column (id, - ColumnBase::Display_NestedHeader, flags) + NestedParentColumn (int id, int flags = ColumnBase::Flag_Dialogue, bool fixedRows = false) + : Column (id, ColumnBase::Display_NestedHeader, flags), mFixedRows(fixedRows) {} + virtual void set (Record& record, const QVariant& data) + { + // There is nothing to do here. + // This prevents exceptions from parent's implementation + } + virtual QVariant get (const Record& record) const { - return true; // required by IdTree::hasChildren() + // by default editable; also see IdTree::hasChildren() + if (mFixedRows) + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); + else + return QVariant::fromValue(ColumnBase::TableEdit_Full); } virtual bool isEditable() const { return true; } + + private: + bool mFixedRows; }; struct NestedChildColumn : public NestableColumn @@ -213,4 +237,6 @@ namespace CSMWorld }; } +Q_DECLARE_METATYPE(CSMWorld::ColumnBase::TableEditModes) + #endif diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp new file mode 100644 index 0000000000..dc3d39edb2 --- /dev/null +++ b/apps/opencs/model/world/columnimp.cpp @@ -0,0 +1,28 @@ +#include "columnimp.hpp" + +CSMWorld::BodyPartRaceColumn::BodyPartRaceColumn(const MeshTypeColumn *meshType) + : mMeshType(meshType) +{} + +QVariant CSMWorld::BodyPartRaceColumn::get(const Record &record) const +{ + if (mMeshType != NULL && mMeshType->get(record) == ESM::BodyPart::MT_Skin) + { + return QString::fromUtf8(record.get().mRace.c_str()); + } + return QVariant(QVariant::UserType); +} + +void CSMWorld::BodyPartRaceColumn::set(Record &record, const QVariant &data) +{ + ESM::BodyPart record2 = record.get(); + + record2.mRace = data.toString().toUtf8().constData(); + + record.setModified(record2); +} + +bool CSMWorld::BodyPartRaceColumn::isEditable() const +{ + return true; +} diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index a8ae5dfa12..4e608dbbde 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -9,6 +9,10 @@ #include +#include +#include +#include + #include "columnbase.hpp" #include "columns.hpp" #include "info.hpp" @@ -1911,8 +1915,8 @@ namespace CSMWorld template struct MeshTypeColumn : public Column { - MeshTypeColumn() - : Column (Columns::ColumnId_MeshType, ColumnBase::Display_MeshType) + MeshTypeColumn(int flags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue) + : Column (Columns::ColumnId_MeshType, ColumnBase::Display_MeshType, flags) {} virtual QVariant get (const Record& record) const @@ -2308,6 +2312,89 @@ namespace CSMWorld return true; } }; + + template + struct FormatColumn : public Column + { + FormatColumn() + : Column (Columns::ColumnId_FileFormat, ColumnBase::Display_Integer) + {} + + virtual QVariant get (const Record& record) const + { + return record.get().mFormat; + } + + virtual bool isEditable() const + { + return false; + } + }; + + template + struct AuthorColumn : public Column + { + AuthorColumn() + : Column (Columns::ColumnId_Author, ColumnBase::Display_String32) + {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mAuthor.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mAuthor = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct FileDescriptionColumn : public Column + { + FileDescriptionColumn() + : Column (Columns::ColumnId_FileDescription, ColumnBase::Display_LongString256) + {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mDescription.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mDescription = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + struct BodyPartRaceColumn : public RaceColumn + { + const MeshTypeColumn *mMeshType; + + BodyPartRaceColumn(const MeshTypeColumn *meshType); + + virtual QVariant get(const Record &record) const; + virtual void set(Record &record, const QVariant &data); + virtual bool isEditable() const; + }; } #endif diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 9491c32469..d0d3a1671d 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -1,4 +1,3 @@ - #include "columns.hpp" #include @@ -35,6 +34,8 @@ namespace CSMWorld { ColumnId_Volume, "Volume" }, { ColumnId_MinRange, "Min Range" }, { ColumnId_MaxRange, "Max Range" }, + { ColumnId_MinMagnitude, "Min Magnitude" }, + { ColumnId_MaxMagnitude, "Max Magnitude" }, { ColumnId_SoundFile, "Sound File" }, { ColumnId_MapColour, "Map Colour" }, { ColumnId_SleepEncounter, "Sleep Encounter" }, @@ -70,7 +71,6 @@ namespace CSMWorld { ColumnId_Weight, "Weight" }, { ColumnId_EnchantmentPoints, "Enchantment Points" }, { ColumnId_Quality, "Quality" }, - { ColumnId_Ai, "AI" }, { ColumnId_AiHello, "AI Hello" }, { ColumnId_AiFlee, "AI Flee" }, { ColumnId_AiFight, "AI Fight" }, @@ -92,7 +92,7 @@ namespace CSMWorld { ColumnId_Trainer, "Trainer" }, { ColumnId_Spellmaking, "Spellmaking" }, { ColumnId_EnchantingService, "Enchanting Service" }, - { ColumnId_RepairService, "Repair Serivce" }, + { ColumnId_RepairService, "Repair Service" }, { ColumnId_ApparatusType, "Apparatus Type" }, { ColumnId_ArmorType, "Armor Type" }, { ColumnId_Health, "Health" }, @@ -107,7 +107,6 @@ namespace CSMWorld { ColumnId_OriginalCreature, "Original Creature" }, { ColumnId_Biped, "Biped" }, { ColumnId_HasWeapon, "Has Weapon" }, - { ColumnId_NoMovement, "No Movement" }, { ColumnId_Swims, "Swims" }, { ColumnId_Flies, "Flies" }, { ColumnId_Walks, "Walks" }, @@ -199,8 +198,6 @@ namespace CSMWorld { ColumnId_RotY, "Rotation Y"}, { ColumnId_RotZ, "Rotation Z"}, - { ColumnId_Skill, "Skill" }, - { ColumnId_OwnerGlobal, "Owner Global" }, { ColumnId_DefaultProfile, "Default Profile" }, { ColumnId_BypassNewGame, "Bypass New Game" }, @@ -252,7 +249,7 @@ namespace CSMWorld { ColumnId_AiWanderDist, "Wander Dist" }, { ColumnId_AiDuration, "Ai Duration" }, { ColumnId_AiWanderToD, "Wander ToD" }, - { ColumnId_AiWanderIdle, "Wander Idle" }, + //{ ColumnId_AiWanderIdle, "Wander Idle" }, { ColumnId_AiWanderRepeat, "Wander Repeat" }, { ColumnId_AiActivateName, "Activate" }, { ColumnId_AiTargetId, "Target ID" }, @@ -265,13 +262,13 @@ namespace CSMWorld { ColumnId_LevelledList,"Levelled List" }, { ColumnId_LevelledItemId,"Levelled Item" }, - { ColumnId_LevelledItemLevel,"Level" }, + { ColumnId_LevelledItemLevel,"Item Level" }, { ColumnId_LevelledItemType, "Calculate all levels <= player" }, { ColumnId_LevelledItemTypeEach, "Select a new item each instance" }, { ColumnId_LevelledItemChanceNone, "Chance None" }, { ColumnId_PowerList, "Powers" }, - { ColumnId_SkillImpact, "Skills" }, + { ColumnId_Skill, "Skill" }, { ColumnId_InfoList, "Info List" }, { ColumnId_InfoCondition, "Info Conditions" }, @@ -281,26 +278,23 @@ namespace CSMWorld { ColumnId_InfoCondValue, "Values" }, { ColumnId_OriginalCell, "Original Cell" }, - { ColumnId_NpcAttributes, "Attributes" }, - { ColumnId_NpcSkills, "Skills" }, + { ColumnId_NpcAttributes, "NPC Attributes" }, + { ColumnId_NpcSkills, "NPC Skill" }, { ColumnId_UChar, "Value [0..255]" }, - { ColumnId_NpcMisc, "Misc" }, - { ColumnId_NpcLevel, "Level" }, + { ColumnId_NpcMisc, "NPC Misc" }, + { ColumnId_Level, "Level" }, { ColumnId_NpcFactionID, "Faction ID" }, - { ColumnId_NpcHealth, "Health" }, - { ColumnId_NpcMana, "Mana" }, - { ColumnId_NpcFatigue, "Fatigue" }, - { ColumnId_NpcDisposition, "Disposition" }, + { ColumnId_Mana, "Mana" }, + { ColumnId_Fatigue, "Fatigue" }, + { ColumnId_NpcDisposition, "NPC Disposition" }, { ColumnId_NpcReputation, "Reputation" }, - { ColumnId_NpcRank, "Rank" }, - { ColumnId_NpcGold, "Gold" }, + { ColumnId_NpcRank, "NPC Rank" }, + { ColumnId_Gold, "Gold" }, { ColumnId_NpcPersistence, "Persistent" }, - { ColumnId_RaceAttributes, "Attributes" }, - { ColumnId_RaceMaleValue, "Male" }, - { ColumnId_RaceFemaleValue, "Female" }, + { ColumnId_RaceAttributes, "Race Attributes" }, + { ColumnId_Male, "Male" }, { ColumnId_RaceSkillBonus, "Skill Bonus" }, - { ColumnId_RaceSkill, "Skills" }, { ColumnId_RaceBonus, "Bonus" }, { ColumnId_Interior, "Interior" }, @@ -311,6 +305,26 @@ namespace CSMWorld { ColumnId_WaterLevel, "Water Level" }, { ColumnId_MapColor, "Map Color" }, + { ColumnId_FileFormat, "File Format" }, + { ColumnId_FileDescription, "File Description" }, + { ColumnId_Author, "Author" }, + + { ColumnId_CreatureAttributes, "Creature Attributes" }, + { ColumnId_AttributeValue, "Attrib Value" }, + { ColumnId_CreatureAttack, "Creature Attack" }, + { ColumnId_MinAttack, "Min Attack" }, + { ColumnId_MaxAttack, "Max Attack" }, + { ColumnId_CreatureMisc, "Creature Misc" }, + + { ColumnId_Idle1, "Idle 1" }, + { ColumnId_Idle2, "Idle 2" }, + { ColumnId_Idle3, "Idle 3" }, + { ColumnId_Idle4, "Idle 4" }, + { ColumnId_Idle5, "Idle 5" }, + { ColumnId_Idle6, "Idle 6" }, + { ColumnId_Idle7, "Idle 7" }, + { ColumnId_Idle8, "Idle 8" }, + { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, { ColumnId_UseValue3, "Use value 3" }, @@ -532,11 +546,6 @@ namespace "AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0 }; - static const char *sAiWanderRepeat[] = - { - "No", "Yes", 0 - }; - static const char *sInfoCondFunc[] = { " ", "Function", "Global", "Local", "Journal", @@ -571,17 +580,15 @@ namespace case CSMWorld::Columns::ColumnId_MeshType: return sMeshTypes; case CSMWorld::Columns::ColumnId_SoundGeneratorType: return sSoundGeneratorType; case CSMWorld::Columns::ColumnId_School: return sSchools; - case CSMWorld::Columns::ColumnId_SkillImpact: return sSkills; + case CSMWorld::Columns::ColumnId_Skill: return sSkills; case CSMWorld::Columns::ColumnId_EffectRange: return sEffectRange; case CSMWorld::Columns::ColumnId_EffectId: return sEffectId; case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType; case CSMWorld::Columns::ColumnId_AiPackageType: return sAiPackageType; - case CSMWorld::Columns::ColumnId_AiWanderRepeat: return sAiWanderRepeat; case CSMWorld::Columns::ColumnId_InfoCondFunc: return sInfoCondFunc; // FIXME: don't have dynamic value enum delegate, use Display_String for now //case CSMWorld::Columns::ColumnId_InfoCond: return sInfoCond; case CSMWorld::Columns::ColumnId_InfoCondComp: return sInfoCondComp; - case CSMWorld::Columns::ColumnId_RaceSkill: return sSkills; default: return 0; } diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 191bbdea8c..a504e5f659 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -65,7 +65,7 @@ namespace CSMWorld ColumnId_Weight = 50, ColumnId_EnchantmentPoints = 51, ColumnId_Quality = 52, - ColumnId_Ai = 53, + // unused ColumnId_AiHello = 54, ColumnId_AiFlee = 55, ColumnId_AiFight = 56, @@ -102,7 +102,7 @@ namespace CSMWorld ColumnId_OriginalCreature = 87, ColumnId_Biped = 88, ColumnId_HasWeapon = 89, - ColumnId_NoMovement = 90, + // unused ColumnId_Swims = 91, ColumnId_Flies = 92, ColumnId_Walks = 93, @@ -189,7 +189,7 @@ namespace CSMWorld ColumnId_RotX = 174, ColumnId_RotY = 175, ColumnId_RotZ = 176, - ColumnId_Skill = 177, + // unused ColumnId_OwnerGlobal = 178, ColumnId_DefaultProfile = 179, ColumnId_BypassNewGame = 180, @@ -241,7 +241,7 @@ namespace CSMWorld ColumnId_AiWanderDist = 221, ColumnId_AiDuration = 222, ColumnId_AiWanderToD = 223, - ColumnId_AiWanderIdle = 224, + // unused ColumnId_AiWanderRepeat = 225, ColumnId_AiActivateName = 226, // use ColumnId_PosX, etc for AI destinations @@ -261,7 +261,7 @@ namespace CSMWorld ColumnId_LevelledItemChanceNone = 238, ColumnId_PowerList = 239, - ColumnId_SkillImpact = 240, // impact from magic effects + ColumnId_Skill = 240, ColumnId_InfoList = 241, ColumnId_InfoCondition = 242, @@ -276,22 +276,22 @@ namespace CSMWorld ColumnId_NpcSkills = 249, ColumnId_UChar = 250, ColumnId_NpcMisc = 251, - ColumnId_NpcLevel = 252, + ColumnId_Level = 252, ColumnId_NpcFactionID = 253, - ColumnId_NpcHealth = 254, - ColumnId_NpcMana = 255, - ColumnId_NpcFatigue = 256, + // unused + ColumnId_Mana = 255, + ColumnId_Fatigue = 256, ColumnId_NpcDisposition = 257, ColumnId_NpcReputation = 258, ColumnId_NpcRank = 259, - ColumnId_NpcGold = 260, + ColumnId_Gold = 260, ColumnId_NpcPersistence = 261, ColumnId_RaceAttributes = 262, - ColumnId_RaceMaleValue = 263, - ColumnId_RaceFemaleValue = 264, + ColumnId_Male = 263, + // unused ColumnId_RaceSkillBonus = 265, - ColumnId_RaceSkill = 266, + // unused ColumnId_RaceBonus = 267, ColumnId_Interior = 268, @@ -302,6 +302,29 @@ namespace CSMWorld ColumnId_WaterLevel = 273, ColumnId_MapColor = 274, + ColumnId_FileFormat = 275, + ColumnId_FileDescription = 276, + ColumnId_Author = 277, + + ColumnId_MinMagnitude = 278, + ColumnId_MaxMagnitude = 279, + + ColumnId_CreatureAttributes = 280, + ColumnId_AttributeValue = 281, + ColumnId_CreatureAttack = 282, + ColumnId_MinAttack = 283, + ColumnId_MaxAttack = 284, + ColumnId_CreatureMisc = 285, + + ColumnId_Idle1 = 286, + ColumnId_Idle2 = 287, + ColumnId_Idle3 = 288, + ColumnId_Idle4 = 289, + ColumnId_Idle5 = 290, + ColumnId_Idle6 = 291, + ColumnId_Idle7 = 292, + ColumnId_Idle8 = 293, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index b9d5bd7fec..a1fc980eb5 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -1,4 +1,3 @@ - #include "commanddispatcher.hpp" #include @@ -94,7 +93,7 @@ void CSMWorld::CommandDispatcher::setEditLock (bool locked) void CSMWorld::CommandDispatcher::setSelection (const std::vector& selection) { mSelection = selection; - std::for_each (mSelection.begin(), mSelection.end(), Misc::StringUtils::toLower); + std::for_each (mSelection.begin(), mSelection.end(), Misc::StringUtils::lowerCaseInPlace); std::sort (mSelection.begin(), mSelection.end()); } diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index a44d8770f6..d510cd1038 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -21,19 +21,31 @@ CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelI // Replace proxy with actual model mIndex = proxy->mapToSource (index); mModel = proxy->sourceModel(); + } + if (mIndex.parent().isValid()) + { setText ("Modify " + dynamic_cast(mModel)->nestedHeaderData ( mIndex.parent().column(), mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); } else + { setText ("Modify " + mModel->headerData (mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); + } // Remember record state before the modification if (CSMWorld::IdTable *table = dynamic_cast(mModel)) { mHasRecordState = true; int stateColumnIndex = table->findColumnIndex(Columns::ColumnId_Modification); - mRecordStateIndex = table->index(mIndex.row(), stateColumnIndex); + + int rowIndex = mIndex.row(); + if (mIndex.parent().isValid()) + { + rowIndex = mIndex.parent().row(); + } + + mRecordStateIndex = table->index(rowIndex, stateColumnIndex); mOldRecordState = static_cast(table->data(mRecordStateIndex).toInt()); } } @@ -58,6 +70,25 @@ void CSMWorld::CreateCommand::applyModifications() { for (std::map::const_iterator iter (mValues.begin()); iter!=mValues.end(); ++iter) mModel.setData (mModel.getModelIndex (mId, iter->first), iter->second); + + if (!mNestedValues.empty()) + { + CSMWorld::IdTree *tree = dynamic_cast(&mModel); + if (tree == NULL) + { + throw std::logic_error("CSMWorld::CreateCommand: Attempt to add nested values to the non-nested model"); + } + + std::map >::const_iterator current = mNestedValues.begin(); + std::map >::const_iterator end = mNestedValues.end(); + for (; current != end; ++current) + { + QModelIndex index = tree->index(0, + current->second.first, + tree->getNestedModelIndex(mId, current->first)); + tree->setData(index, current->second.second); + } + } } CSMWorld::CreateCommand::CreateCommand (IdTable& model, const std::string& id, QUndoCommand* parent) @@ -71,6 +102,11 @@ void CSMWorld::CreateCommand::addValue (int column, const QVariant& value) mValues[column] = value; } +void CSMWorld::CreateCommand::addNestedValue(int parentColumn, int nestedColumn, const QVariant &value) +{ + mNestedValues[parentColumn] = std::make_pair(nestedColumn, value); +} + void CSMWorld::CreateCommand::setType (UniversalId::Type type) { mType = type; @@ -258,21 +294,24 @@ CSMWorld::DeleteNestedCommand::DeleteNestedCommand (IdTree& model, std::string title = model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); setText (("Delete row in " + title + " sub-table of " + mId).c_str()); + + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); + mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this); } void CSMWorld::DeleteNestedCommand::redo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.removeRows (mNestedRow, 1, parentIndex); + mModifyParentCommand->redo(); } void CSMWorld::DeleteNestedCommand::undo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.setNestedTable(parentIndex, getOld()); + mModifyParentCommand->undo(); } CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent) @@ -286,20 +325,23 @@ CSMWorld::AddNestedCommand::AddNestedCommand(IdTree& model, const std::string& i std::string title = model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); setText (("Add row in " + title + " sub-table of " + mId).c_str()); + + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); + mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this); } void CSMWorld::AddNestedCommand::redo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.addNestedRow (parentIndex, mNewRow); + mModifyParentCommand->redo(); } void CSMWorld::AddNestedCommand::undo() { - const QModelIndex& parentIndex = mModel.getModelIndex(mId, mParentColumn); - + QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.setNestedTable(parentIndex, getOld()); + mModifyParentCommand->undo(); } CSMWorld::NestedTableStoring::NestedTableStoring(const IdTree& model, const std::string& id, int parentColumn) diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index cdd398153c..23ffccbd7e 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -48,6 +48,9 @@ namespace CSMWorld class CreateCommand : public QUndoCommand { std::map mValues; + std::map > mNestedValues; + ///< Parameter order: a parent column, a nested column, a data. + ///< A nested row has index of 0. protected: @@ -68,6 +71,8 @@ namespace CSMWorld void addValue (int column, const QVariant& value); + void addNestedValue(int parentColumn, int nestedColumn, const QVariant &value); + virtual void redo(); virtual void undo(); @@ -195,6 +200,9 @@ namespace CSMWorld int mNestedRow; + // The command to redo/undo the Modified status of a record + ModifyCommand *mModifyParentCommand; + public: DeleteNestedCommand (IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); @@ -214,6 +222,9 @@ namespace CSMWorld int mParentColumn; + // The command to redo/undo the Modified status of a record + ModifyCommand *mModifyParentCommand; + public: AddNestedCommand(IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent = 0); diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 5acd80339e..1f98b24757 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1,4 +1,3 @@ - #include "data.hpp" #include @@ -62,7 +61,7 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) : mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), - mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0) + mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0), mResourceSystem(new Resource::ResourceSystem(resourcesManager.getVFS())) { int index = 0; @@ -137,22 +136,24 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_SpellId, ColumnBase::Display_Spell)); // Race attributes - mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceAttributes)); + mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceAttributes, + ColumnBase::Flag_Dialogue, true)); // fixed rows table index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceAttributeAdapter())); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceAttributes, ColumnBase::Display_String, + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute, ColumnBase::Flag_Dialogue, false)); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceMaleValue, ColumnBase::Display_Integer)); + new NestedChildColumn (Columns::ColumnId_Male, ColumnBase::Display_Integer)); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceFemaleValue, ColumnBase::Display_Integer)); + new NestedChildColumn (Columns::ColumnId_Female, ColumnBase::Display_Integer)); // Race skill bonus - mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceSkillBonus)); + mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceSkillBonus, + ColumnBase::Flag_Dialogue, true)); // fixed rows table index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceSkillsBonusAdapter())); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceSkill, ColumnBase::Display_RaceSkill)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_RaceBonus, ColumnBase::Display_Integer)); @@ -214,9 +215,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mSpells.getNestableColumn(index)->addColumn( @@ -224,9 +225,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MinRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MaxRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); mTopics.addColumn (new StringIdColumn); mTopics.addColumn (new RecordStateColumn); @@ -330,9 +331,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mEnchantments.getNestableColumn(index)->addColumn( @@ -340,9 +341,9 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MinRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_MaxRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); mBodyParts.addColumn (new StringIdColumn); mBodyParts.addColumn (new RecordStateColumn); @@ -352,9 +353,12 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mBodyParts.addColumn (new FlagColumn (Columns::ColumnId_Female, ESM::BodyPart::BPF_Female)); mBodyParts.addColumn (new FlagColumn (Columns::ColumnId_Playable, ESM::BodyPart::BPF_NotPlayable, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, true)); - mBodyParts.addColumn (new MeshTypeColumn); + + int meshTypeFlags = ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh; + MeshTypeColumn *meshTypeColumn = new MeshTypeColumn(meshTypeFlags); + mBodyParts.addColumn (meshTypeColumn); mBodyParts.addColumn (new ModelColumn); - mBodyParts.addColumn (new RaceColumn); + mBodyParts.addColumn (new BodyPartRaceColumn(meshTypeColumn)); mSoundGens.addColumn (new StringIdColumn); mSoundGens.addColumn (new RecordStateColumn); @@ -475,6 +479,15 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mDebugProfiles.addColumn (new ScriptColumn ( ScriptColumn::Type_Lines)); + mMetaData.appendBlankRecord ("sys::meta"); + + mMetaData.addColumn (new StringIdColumn (true)); + mMetaData.addColumn (new RecordStateColumn); + mMetaData.addColumn (new FixedRecordTypeColumn (UniversalId::Type_MetaData)); + mMetaData.addColumn (new FormatColumn); + mMetaData.addColumn (new AuthorColumn); + mMetaData.addColumn (new FileDescriptionColumn); + addModel (new IdTable (&mGlobals), UniversalId::Type_Global); addModel (new IdTable (&mGmsts), UniversalId::Type_Gmst); addModel (new IdTable (&mSkills), UniversalId::Type_Skill); @@ -515,6 +528,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc UniversalId::Type_Texture); addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Videos)), UniversalId::Type_Video); + addModel (new IdTable (&mMetaData), UniversalId::Type_MetaData); mRefLoadCache.clear(); // clear here rather than startLoading() and continueLoading() for multiple content files } @@ -527,6 +541,16 @@ CSMWorld::Data::~Data() delete mReader; } +boost::shared_ptr CSMWorld::Data::getResourceSystem() +{ + return mResourceSystem; +} + +boost::shared_ptr CSMWorld::Data::getResourceSystem() const +{ + return mResourceSystem; +} + const CSMWorld::IdCollection& CSMWorld::Data::getGlobals() const { return mGlobals; @@ -753,11 +777,21 @@ const CSMWorld::IdCollection& CSMWorld::Data::getLand() const return mLand; } +CSMWorld::IdCollection& CSMWorld::Data::getLand() +{ + return mLand; +} + const CSMWorld::IdCollection& CSMWorld::Data::getLandTextures() const { return mLandTextures; } +CSMWorld::IdCollection& CSMWorld::Data::getLandTextures() +{ + return mLandTextures; +} + const CSMWorld::IdCollection& CSMWorld::Data::getSoundGens() const { return mSoundGens; @@ -803,6 +837,17 @@ const CSMWorld::Resources& CSMWorld::Data::getResources (const UniversalId& id) return mResourcesManager.get (id.getType()); } +const CSMWorld::MetaData& CSMWorld::Data::getMetaData() const +{ + return mMetaData.getRecord (0).get(); +} + +void CSMWorld::Data::setMetaData (const MetaData& metaData) +{ + Record record (RecordBase::State_ModifiedOnly, 0, &metaData); + mMetaData.setRecord (0, record); +} + QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id) { std::map::iterator iter = mModelIndex.find (id.getType()); @@ -847,8 +892,14 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base mBase = base; mProject = project; - mAuthor = mReader->getAuthor(); - mDescription = mReader->getDesc(); + if (!mProject && !mBase) + { + MetaData metaData; + metaData.mId = "sys::meta"; + metaData.load (*mReader); + + mMetaData.setRecord (0, Record (RecordBase::State_ModifiedOnly, 0, &metaData)); + } return mReader->getRecordCount(); } @@ -908,8 +959,10 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) { int index = mLand.load(*mReader, mBase); - if (index!=-1 && !mBase) - mLand.getRecord (index).mModified.mLand->loadData ( + // Load all land data for now. A future optimisation may only load non-base data + // if a suitable mechanism for avoiding race conditions can be established. + if (index!=-1/* && !mBase*/) + mLand.getRecord (index).get().loadData ( ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); @@ -957,41 +1010,43 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) case ESM::REC_DIAL: { - std::string id = mReader->getHNOString ("NAME"); - ESM::Dialogue record; - record.mId = id; - record.load (*mReader); + bool isDeleted = false; - if (record.mType==ESM::Dialogue::Journal) - { - mJournals.load (record, mBase); - mDialogue = &mJournals.getRecord (id).get(); - } - else if (record.mType==ESM::Dialogue::Deleted) - { - mDialogue = 0; // record vector can be shuffled around which would make pointer - // to record invalid + record.load (*mReader, isDeleted); - if (mJournals.tryDelete (id)) + if (isDeleted) + { + // record vector can be shuffled around which would make pointer to record invalid + mDialogue = 0; + + if (mJournals.tryDelete (record.mId)) { - /// \todo handle info records + mJournalInfos.removeDialogueInfos(record.mId); } - else if (mTopics.tryDelete (id)) + else if (mTopics.tryDelete (record.mId)) { - /// \todo handle info records + mTopicInfos.removeDialogueInfos(record.mId); } else { messages.add (UniversalId::Type_None, - "Trying to delete dialogue record " + id + " which does not exist", + "Trying to delete dialogue record " + record.mId + " which does not exist", "", CSMDoc::Message::Severity_Warning); } } else { - mTopics.load (record, mBase); - mDialogue = &mTopics.getRecord (id).get(); + if (record.mType == ESM::Dialogue::Journal) + { + mJournals.load (record, mBase); + mDialogue = &mJournals.getRecord (record.mId).get(); + } + else + { + mTopics.load (record, mBase); + mDialogue = &mTopics.getRecord (record.mId).get(); + } } break; @@ -1103,26 +1158,6 @@ int CSMWorld::Data::count (RecordBase::State state) const count (state, mPathgrids); } -void CSMWorld::Data::setDescription (const std::string& description) -{ - mDescription = description; -} - -std::string CSMWorld::Data::getDescription() const -{ - return mDescription; -} - -void CSMWorld::Data::setAuthor (const std::string& author) -{ - mAuthor = author; -} - -std::string CSMWorld::Data::getAuthor() const -{ - return mAuthor; -} - std::vector CSMWorld::Data::getIds (bool listDeleted) const { std::vector ids; @@ -1161,3 +1196,8 @@ void CSMWorld::Data::rowsChanged (const QModelIndex& parent, int start, int end) { emit idListChanged(); } + +const VFS::Manager* CSMWorld::Data::getVFS() const +{ + return mResourcesManager.getVFS(); +} diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 060e47bd95..c6623279a6 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -29,6 +29,8 @@ #include #include +#include + #include #include "../doc/stage.hpp" @@ -44,12 +46,18 @@ #include "infocollection.hpp" #include "nestedinfocollection.hpp" #include "pathgrid.hpp" +#include "metadata.hpp" #ifndef Q_MOC_RUN #include "subcellcollection.hpp" #endif class QAbstractItemModel; +namespace VFS +{ + class Manager; +} + namespace ESM { class ESMReader; @@ -94,11 +102,10 @@ namespace CSMWorld RefIdCollection mReferenceables; RefCollection mRefs; IdCollection mFilters; + Collection mMetaData; const ResourcesManager& mResourcesManager; std::vector mModels; std::map mModelIndex; - std::string mAuthor; - std::string mDescription; ESM::ESMReader *mReader; const ESM::Dialogue *mDialogue; // last loaded dialogue bool mBase; @@ -106,6 +113,8 @@ namespace CSMWorld std::map > mRefLoadCache; int mReaderIndex; + boost::shared_ptr mResourceSystem; + std::vector > mReaders; // not implemented @@ -127,6 +136,12 @@ namespace CSMWorld virtual ~Data(); + const VFS::Manager* getVFS() const; + + boost::shared_ptr getResourceSystem(); + + boost::shared_ptr getResourceSystem() const; + const IdCollection& getGlobals() const; IdCollection& getGlobals(); @@ -217,8 +232,12 @@ namespace CSMWorld const IdCollection& getLand() const; + IdCollection& getLand(); + const IdCollection& getLandTextures() const; + IdCollection& getLandTextures(); + const IdCollection& getSoundGens() const; IdCollection& getSoundGens(); @@ -238,6 +257,10 @@ namespace CSMWorld /// Throws an exception, if \a id does not match a resources list. const Resources& getResources (const UniversalId& id) const; + const MetaData& getMetaData() const; + + void setMetaData (const MetaData& metaData); + QAbstractItemModel *getTableModel (const UniversalId& id); ///< If no table model is available for \a id, an exception is thrown. /// @@ -267,14 +290,6 @@ namespace CSMWorld int count (RecordBase::State state) const; ///< Return number of top-level records with the given \a state. - void setDescription (const std::string& description); - - std::string getDescription() const; - - void setAuthor (const std::string& author); - - std::string getAuthor() const; - signals: void idListChanged(); diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 4eafc59bd5..ea6eefb882 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -11,7 +11,7 @@ namespace CSMWorld template > class IdCollection : public Collection { - virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader); + virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted); public: @@ -33,77 +33,46 @@ namespace CSMWorld template void IdCollection::loadRecord (ESXRecordT& record, - ESM::ESMReader& reader) + ESM::ESMReader& reader, + bool& isDeleted) { - record.load (reader); + record.load (reader, isDeleted); } template int IdCollection::load (ESM::ESMReader& reader, bool base) { - std::string id = reader.getHNOString ("NAME"); + ESXRecordT record; + bool isDeleted = false; - if (reader.isNextSub ("DELE")) + loadRecord (record, reader, isDeleted); + + std::string id = IdAccessorT().getId (record); + int index = this->searchId (id); + + if (isDeleted) { - int index = Collection::searchId (id); - - reader.skipRecord(); - if (index==-1) { // deleting a record that does not exist - // ignore it for now - /// \todo report the problem to the user - } - else if (base) - { - Collection::removeRows (index, 1); - } - else - { - Record record = Collection::getRecord (index); - record.mState = RecordBase::State_Deleted; - this->setRecord (index, record); + return -1; } - return -1; + if (base) + { + this->removeRows (index, 1); + return -1; + } + + Record baseRecord = this->getRecord (index); + baseRecord.mState = RecordBase::State_Deleted; + this->setRecord (index, baseRecord); + return index; } - else - { - ESXRecordT record; - // Sometimes id (i.e. NAME of the cell) may be different to the id we stored - // earlier. e.g. NAME == "Vivec, Arena" but id == "#-4 11". Sometime NAME is - // missing altogether for scripts or cells. - // - // In such cases the returned index will be -1. We then try updating the - // IdAccessor's id manually (e.g. set mId of the record to "Vivec, Arena") - // and try getting the index once more after loading the record. The mId of the - // record would have changed to "#-4 11" after the load, and searchId() should find - // it (if this is a modify) - int index = this->searchId (id); - - if (index==-1) - IdAccessorT().getId (record) = id; - else - { - record = this->getRecord (index).get(); - } - - loadRecord (record, reader); - - if (index==-1) - { - std::string newId = IdAccessorT().getId(record); - int newIndex = this->searchId(newId); - if (newIndex != -1 && id != newId) - index = newIndex; - } - - return load (record, base, index); - } + return load (record, base, index); } template diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 8ca19f7e96..bd1179cea2 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -76,8 +76,15 @@ bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value if (mIdCollection->getColumn (index.column()).isEditable() && role==Qt::EditRole) { mIdCollection->setData (index.row(), index.column(), value); + emit dataChanged(index, index); - emit dataChanged (index, index); + // Modifying a value can also change the Modified status of a record. + int stateColumn = searchColumnIndex(Columns::ColumnId_Modification); + if (stateColumn != -1) + { + QModelIndex stateIndex = this->index(index.row(), stateColumn); + emit dataChanged(stateIndex, stateIndex); + } return true; } diff --git a/apps/opencs/model/world/idtablebase.cpp b/apps/opencs/model/world/idtablebase.cpp index 389f5396e4..274446b796 100644 --- a/apps/opencs/model/world/idtablebase.cpp +++ b/apps/opencs/model/world/idtablebase.cpp @@ -1,4 +1,3 @@ - #include "idtablebase.hpp" CSMWorld::IdTableBase::IdTableBase (unsigned int features) : mFeatures (features) {} diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index 5166447138..fbf7b6cf36 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -1,29 +1,40 @@ - #include "idtableproxymodel.hpp" #include #include "idtablebase.hpp" +namespace +{ + std::string getEnumValue(const std::vector &values, int index) + { + if (index < 0 || index >= static_cast(values.size())) + { + return ""; + } + return values[index]; + } +} + void CSMWorld::IdTableProxyModel::updateColumnMap() { - mColumnMap.clear(); + Q_ASSERT(mSourceModel != NULL); + mColumnMap.clear(); if (mFilter) { std::vector columns = mFilter->getReferencedColumns(); - - const IdTableBase& table = dynamic_cast (*sourceModel()); - for (std::vector::const_iterator iter (columns.begin()); iter!=columns.end(); ++iter) - mColumnMap.insert (std::make_pair (*iter, - table.searchColumnIndex (static_cast (*iter)))); + mColumnMap.insert (std::make_pair (*iter, + mSourceModel->searchColumnIndex (static_cast (*iter)))); } } bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const { + Q_ASSERT(mSourceModel != NULL); + // It is not possible to use filterAcceptsColumn() and check for // sourceModel()->headerData (sourceColumn, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags) // because the sourceColumn parameter excludes the hidden columns, i.e. wrong columns can @@ -35,19 +46,40 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelI if (!mFilter) return true; - return mFilter->test ( - dynamic_cast (*sourceModel()), sourceRow, mColumnMap); + return mFilter->test (*mSourceModel, sourceRow, mColumnMap); } CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) -: QSortFilterProxyModel (parent) + : QSortFilterProxyModel (parent), + mSourceModel(NULL) { setSortCaseSensitivity (Qt::CaseInsensitive); } QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const { - return mapFromSource (dynamic_cast (*sourceModel()).getModelIndex (id, column)); + Q_ASSERT(mSourceModel != NULL); + + return mapFromSource(mSourceModel->getModelIndex (id, column)); +} + +void CSMWorld::IdTableProxyModel::setSourceModel(QAbstractItemModel *model) +{ + QSortFilterProxyModel::setSourceModel(model); + + mSourceModel = dynamic_cast(sourceModel()); + connect(mSourceModel, + SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, + SLOT(sourceRowsInserted(const QModelIndex &, int, int))); + connect(mSourceModel, + SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, + SLOT(sourceRowsRemoved(const QModelIndex &, int, int))); + connect(mSourceModel, + SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), + this, + SLOT(sourceDataChanged(const QModelIndex &, const QModelIndex &))); } void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr& filter) @@ -60,11 +92,54 @@ void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr(left.data(ColumnBase::Role_ColumnId).toInt()); + EnumColumnCache::const_iterator valuesIt = mEnumColumnCache.find(id); + if (valuesIt == mEnumColumnCache.end()) + { + if (Columns::hasEnums(id)) + { + valuesIt = mEnumColumnCache.insert(std::make_pair(id, Columns::getEnums(id))).first; + } + } + + if (valuesIt != mEnumColumnCache.end()) + { + std::string first = getEnumValue(valuesIt->second, left.data().toInt()); + std::string second = getEnumValue(valuesIt->second, right.data().toInt()); + return first < second; + } return QSortFilterProxyModel::lessThan(left, right); } +QString CSMWorld::IdTableProxyModel::getRecordId(int sourceRow) const +{ + Q_ASSERT(mSourceModel != NULL); + + int idColumn = mSourceModel->findColumnIndex(Columns::ColumnId_Id); + return mSourceModel->data(mSourceModel->index(sourceRow, idColumn)).toString(); +} + void CSMWorld::IdTableProxyModel::refreshFilter() { updateColumnMap(); invalidateFilter(); } + +void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end) +{ + refreshFilter(); + if (!parent.isValid()) + { + emit rowAdded(getRecordId(end).toUtf8().constData()); + } +} + +void CSMWorld::IdTableProxyModel::sourceRowsRemoved(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) +{ + refreshFilter(); +} + +void CSMWorld::IdTableProxyModel::sourceDataChanged(const QModelIndex &/*topLeft*/, const QModelIndex &/*bottomRight*/) +{ + refreshFilter(); +} diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp index d2a2405291..cf31b5c11b 100644 --- a/apps/opencs/model/world/idtableproxymodel.hpp +++ b/apps/opencs/model/world/idtableproxymodel.hpp @@ -11,6 +11,8 @@ #include "../filter/node.hpp" +#include "columns.hpp" + namespace CSMWorld { class IdTableProxyModel : public QSortFilterProxyModel @@ -20,6 +22,15 @@ namespace CSMWorld boost::shared_ptr mFilter; std::map mColumnMap; // column ID, column index in this model (or -1) + // Cache of enum values for enum columns (e.g. Modified, Record Type). + // Used to speed up comparisons during the sort by such columns. + typedef std::map > EnumColumnCache; + mutable EnumColumnCache mEnumColumnCache; + + protected: + + IdTableBase *mSourceModel; + private: void updateColumnMap(); @@ -30,15 +41,31 @@ namespace CSMWorld virtual QModelIndex getModelIndex (const std::string& id, int column) const; + virtual void setSourceModel(QAbstractItemModel *model); + void setFilter (const boost::shared_ptr& filter); void refreshFilter(); protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; virtual bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const; + + QString getRecordId(int sourceRow) const; + + protected slots: + + virtual void sourceRowsInserted(const QModelIndex &parent, int start, int end); + + virtual void sourceRowsRemoved(const QModelIndex &parent, int start, int end); + + virtual void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + + signals: + + void rowAdded(const std::string &id); }; } diff --git a/apps/opencs/model/world/idtree.cpp b/apps/opencs/model/world/idtree.cpp index 1e81d6ac2f..124a94e8ca 100644 --- a/apps/opencs/model/world/idtree.cpp +++ b/apps/opencs/model/world/idtree.cpp @@ -95,8 +95,15 @@ bool CSMWorld::IdTree::setData (const QModelIndex &index, const QVariant &value, const std::pair& parentAddress(unfoldIndexAddress(index.internalId())); mNestedCollection->setNestedData(parentAddress.first, parentAddress.second, value, index.row(), index.column()); + emit dataChanged(index, index); - emit dataChanged (index, index); + // Modifying a value can also change the Modified status of a record. + int stateColumn = searchColumnIndex(Columns::ColumnId_Modification); + if (stateColumn != -1) + { + QModelIndex stateIndex = this->index(index.parent().row(), stateColumn); + emit dataChanged(stateIndex, stateIndex); + } return true; } @@ -171,10 +178,10 @@ QModelIndex CSMWorld::IdTree::index (int row, int column, const QModelIndex& par encodedId = this->foldIndexAddress(parent); } - if (row<0 || row>=idCollection()->getSize()) + if (row < 0 || row >= rowCount(parent)) return QModelIndex(); - if (column<0 || column>=idCollection()->getColumns()) + if (column < 0 || column >= columnCount(parent)) return QModelIndex(); return createIndex(row, column, encodedId); // store internal id @@ -261,3 +268,13 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::IdTree::nestedTable(const QModelInde return mNestedCollection->nestedTable(index.row(), index.column()); } + +int CSMWorld::IdTree::searchNestedColumnIndex(int parentColumn, Columns::ColumnId id) +{ + return mNestedCollection->searchNestedColumnIndex(parentColumn, id); +} + +int CSMWorld::IdTree::findNestedColumnIndex(int parentColumn, Columns::ColumnId id) +{ + return mNestedCollection->findNestedColumnIndex(parentColumn, id); +} diff --git a/apps/opencs/model/world/idtree.hpp b/apps/opencs/model/world/idtree.hpp index 5337ed82b8..79e93fc3df 100644 --- a/apps/opencs/model/world/idtree.hpp +++ b/apps/opencs/model/world/idtree.hpp @@ -73,6 +73,12 @@ namespace CSMWorld virtual bool hasChildren (const QModelIndex& index) const; + virtual int searchNestedColumnIndex(int parentColumn, Columns::ColumnId id); + ///< \return the column index or -1 if the requested column wasn't found. + + virtual int findNestedColumnIndex(int parentColumn, Columns::ColumnId id); + ///< \return the column index or throws an exception if the requested column wasn't found. + signals: void resetStart(const QString& id); diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index a508d28f3d..f5ec4d4587 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -1,4 +1,3 @@ - #include "infocollection.hpp" #include @@ -97,7 +96,8 @@ bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector erasedRecords; + + std::map::const_iterator current = getIdMap().lower_bound(id); + std::map::const_iterator end = getIdMap().end(); + for (; current != end; ++current) + { + Record record = getRecord(current->second); + + if (Misc::StringUtils::ciEqual(dialogueId, record.get().mTopicId)) + { + if (record.mState == RecordBase::State_ModifiedOnly) + { + erasedRecords.push_back(current->second); + } + else + { + record.mState = RecordBase::State_Deleted; + setRecord(current->second, record); + } + } + else + { + break; + } + } + + while (!erasedRecords.empty()) + { + removeRows(erasedRecords.back(), 1); + erasedRecords.pop_back(); + } +} diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index 6db47373d0..e5a5575c78 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -44,6 +44,8 @@ namespace CSMWorld Range getTopicRange (const std::string& topic) const; ///< Return iterators that point to the beginning and past the end of the range for /// the given topic. + + void removeDialogueInfos(const std::string& dialogueId); }; } diff --git a/apps/opencs/model/world/infotableproxymodel.cpp b/apps/opencs/model/world/infotableproxymodel.cpp index 7522e492b6..4f0b2e7528 100644 --- a/apps/opencs/model/world/infotableproxymodel.cpp +++ b/apps/opencs/model/world/infotableproxymodel.cpp @@ -1,12 +1,25 @@ #include "infotableproxymodel.hpp" +#include + #include "idtablebase.hpp" #include "columns.hpp" +namespace +{ + QString toLower(const QString &str) + { + return QString::fromUtf8(Misc::StringUtils::lowerCase(str.toUtf8().constData()).c_str()); + } +} + CSMWorld::InfoTableProxyModel::InfoTableProxyModel(CSMWorld::UniversalId::Type type, QObject *parent) : IdTableProxyModel(parent), mType(type), - mSourceModel(NULL) + mInfoColumnId(type == UniversalId::Type_TopicInfos ? Columns::ColumnId_Topic : + Columns::ColumnId_Journal), + mInfoColumnIndex(-1), + mLastAddedSourceRow(-1) { Q_ASSERT(type == UniversalId::Type_TopicInfos || type == UniversalId::Type_JournalInfos); } @@ -14,45 +27,85 @@ CSMWorld::InfoTableProxyModel::InfoTableProxyModel(CSMWorld::UniversalId::Type t void CSMWorld::InfoTableProxyModel::setSourceModel(QAbstractItemModel *sourceModel) { IdTableProxyModel::setSourceModel(sourceModel); - mSourceModel = dynamic_cast(sourceModel); - connect(mSourceModel, - SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), - this, - SLOT(modelDataChanged(const QModelIndex &, const QModelIndex &))); - mFirstRowCache.clear(); + + if (mSourceModel != NULL) + { + mInfoColumnIndex = mSourceModel->findColumnIndex(mInfoColumnId); + mFirstRowCache.clear(); + } } bool CSMWorld::InfoTableProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { + Q_ASSERT(mSourceModel != NULL); + QModelIndex first = mSourceModel->index(getFirstInfoRow(left.row()), left.column()); QModelIndex second = mSourceModel->index(getFirstInfoRow(right.row()), right.column()); + + // If both indexes are belonged to the same Topic/Journal, compare their original rows only + if (first.row() == second.row()) + { + return sortOrder() == Qt::AscendingOrder ? left.row() < right.row() : right.row() < left.row(); + } return IdTableProxyModel::lessThan(first, second); } int CSMWorld::InfoTableProxyModel::getFirstInfoRow(int currentRow) const { - Columns::ColumnId columnId = Columns::ColumnId_Topic; - if (mType == UniversalId::Type_JournalInfos) - { - columnId = Columns::ColumnId_Journal; - } + Q_ASSERT(mSourceModel != NULL); - int column = mSourceModel->findColumnIndex(columnId); - QString info = mSourceModel->data(mSourceModel->index(currentRow, column)).toString(); + int row = currentRow; + int column = mInfoColumnIndex; + QString info = toLower(mSourceModel->data(mSourceModel->index(row, column)).toString()); if (mFirstRowCache.contains(info)) { return mFirstRowCache[info]; } - while (--currentRow >= 0 && - mSourceModel->data(mSourceModel->index(currentRow, column)) == info); + while (--row >= 0 && + toLower(mSourceModel->data(mSourceModel->index(row, column)).toString()) == info); + ++row; - mFirstRowCache[info] = currentRow + 1; - return currentRow + 1; + mFirstRowCache[info] = row; + return row; } -void CSMWorld::InfoTableProxyModel::modelDataChanged(const QModelIndex &/*topLeft*/, const QModelIndex &/*bottomRight*/) +void CSMWorld::InfoTableProxyModel::sourceRowsRemoved(const QModelIndex &/*parent*/, int /*start*/, int /*end*/) { + refreshFilter(); mFirstRowCache.clear(); } + +void CSMWorld::InfoTableProxyModel::sourceRowsInserted(const QModelIndex &parent, int /*start*/, int end) +{ + refreshFilter(); + + if (!parent.isValid()) + { + mFirstRowCache.clear(); + // We can't re-sort the model here, because the topic of the added row isn't set yet. + // Store the row index for using in the first dataChanged() after this row insertion. + mLastAddedSourceRow = end; + } +} + +void CSMWorld::InfoTableProxyModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + refreshFilter(); + + if (mLastAddedSourceRow != -1 && + topLeft.row() <= mLastAddedSourceRow && bottomRight.row() >= mLastAddedSourceRow) + { + // Now the topic of the last added row is set, + // so we can re-sort the model to ensure the corrent position of this row + int column = sortColumn(); + Qt::SortOrder order = sortOrder(); + sort(mInfoColumnIndex); // Restore the correct position of an added row + sort(column, order); // Restore the original sort order + emit rowAdded(getRecordId(mLastAddedSourceRow).toUtf8().constData()); + + // Make sure that we perform a re-sorting only in the first dataChanged() after a row insertion + mLastAddedSourceRow = -1; + } +} diff --git a/apps/opencs/model/world/infotableproxymodel.hpp b/apps/opencs/model/world/infotableproxymodel.hpp index 3228316264..51d93f9a16 100644 --- a/apps/opencs/model/world/infotableproxymodel.hpp +++ b/apps/opencs/model/world/infotableproxymodel.hpp @@ -4,6 +4,7 @@ #include #include "idtableproxymodel.hpp" +#include "columns.hpp" #include "universalid.hpp" namespace CSMWorld @@ -15,23 +16,29 @@ namespace CSMWorld Q_OBJECT UniversalId::Type mType; - IdTableBase *mSourceModel; + Columns::ColumnId mInfoColumnId; + ///< Contains ID for Topic or Journal ID + int mInfoColumnIndex; + int mLastAddedSourceRow; mutable QHash mFirstRowCache; int getFirstInfoRow(int currentRow) const; ///< Finds the first row with the same topic (journal entry) as in \a currentRow + ///< \a currentRow is a row of the source model. public: InfoTableProxyModel(UniversalId::Type type, QObject *parent = 0); - void setSourceModel(QAbstractItemModel *sourceModel); + virtual void setSourceModel(QAbstractItemModel *sourceModel); protected: virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; - private slots: - void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + protected slots: + virtual void sourceRowsInserted(const QModelIndex &parent, int start, int end); + virtual void sourceRowsRemoved(const QModelIndex &parent, int start, int end); + virtual void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); }; } diff --git a/apps/opencs/model/world/land.cpp b/apps/opencs/model/world/land.cpp index 119e187616..80f86c7467 100644 --- a/apps/opencs/model/world/land.cpp +++ b/apps/opencs/model/world/land.cpp @@ -4,25 +4,12 @@ namespace CSMWorld { - - Land::Land() + void Land::load(ESM::ESMReader &esm, bool &isDeleted) { - mLand.reset(new ESM::Land()); - } - - void Land::load(ESM::ESMReader &esm) - { - mLand->load(esm); + ESM::Land::load(esm, isDeleted); std::ostringstream stream; - stream << "#" << mLand->mX << " " << mLand->mY; - + stream << "#" << mX << " " << mY; mId = stream.str(); } - - void Land::blank() - { - /// \todo - } - } diff --git a/apps/opencs/model/world/land.hpp b/apps/opencs/model/world/land.hpp index e97a2d7dd8..e5f25c1d3d 100644 --- a/apps/opencs/model/world/land.hpp +++ b/apps/opencs/model/world/land.hpp @@ -2,7 +2,7 @@ #define CSM_WORLD_LAND_H #include -#include + #include namespace CSMWorld @@ -10,19 +10,12 @@ namespace CSMWorld /// \brief Wrapper for Land record. Encodes X and Y cell index in the ID. /// /// \todo Add worldspace support to the Land record. - /// \todo Add a proper copy constructor (currently worked around using shared_ptr) - struct Land + struct Land : public ESM::Land { - Land(); - - boost::shared_ptr mLand; - std::string mId; /// Loads the metadata and ID - void load (ESM::ESMReader &esm); - - void blank(); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/landtexture.cpp b/apps/opencs/model/world/landtexture.cpp index 4725866a56..266377d0f6 100644 --- a/apps/opencs/model/world/landtexture.cpp +++ b/apps/opencs/model/world/landtexture.cpp @@ -4,18 +4,11 @@ namespace CSMWorld { - - void LandTexture::load(ESM::ESMReader &esm) + void LandTexture::load(ESM::ESMReader &esm, bool &isDeleted) { - ESM::LandTexture::load(esm); + ESM::LandTexture::load(esm, isDeleted); - int plugin = esm.getIndex(); - - std::ostringstream stream; - - stream << mIndex << "_" << plugin; - - mId = stream.str(); + mPluginIndex = esm.getIndex(); } } diff --git a/apps/opencs/model/world/landtexture.hpp b/apps/opencs/model/world/landtexture.hpp index b13903186b..91459eee28 100644 --- a/apps/opencs/model/world/landtexture.hpp +++ b/apps/opencs/model/world/landtexture.hpp @@ -7,15 +7,12 @@ namespace CSMWorld { - /// \brief Wrapper for LandTexture record. Encodes mIndex and the plugin index (obtained from ESMReader) - /// in the ID. - /// - /// \attention The mId field of the ESM::LandTexture struct is not used. + /// \brief Wrapper for LandTexture record, providing info which plugin the LandTexture was loaded from. struct LandTexture : public ESM::LandTexture { - std::string mId; + int mPluginIndex; - void load (ESM::ESMReader &esm); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/metadata.cpp b/apps/opencs/model/world/metadata.cpp new file mode 100644 index 0000000000..960fdc9e4e --- /dev/null +++ b/apps/opencs/model/world/metadata.cpp @@ -0,0 +1,26 @@ +#include "metadata.hpp" + +#include +#include +#include + +void CSMWorld::MetaData::blank() +{ + mFormat = ESM::Header::CurrentFormat; + mAuthor.clear(); + mDescription.clear(); +} + +void CSMWorld::MetaData::load (ESM::ESMReader& esm) +{ + mFormat = esm.getHeader().mFormat; + mAuthor = esm.getHeader().mData.author.toString(); + mDescription = esm.getHeader().mData.desc.toString(); +} + +void CSMWorld::MetaData::save (ESM::ESMWriter& esm) const +{ + esm.setFormat (mFormat); + esm.setAuthor (mAuthor); + esm.setDescription (mDescription); +} diff --git a/apps/opencs/model/world/metadata.hpp b/apps/opencs/model/world/metadata.hpp new file mode 100644 index 0000000000..f8df2690ec --- /dev/null +++ b/apps/opencs/model/world/metadata.hpp @@ -0,0 +1,29 @@ +#ifndef CSM_WOLRD_METADATA_H +#define CSM_WOLRD_METADATA_H + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; +} + +namespace CSMWorld +{ + struct MetaData + { + std::string mId; + + int mFormat; + std::string mAuthor; + std::string mDescription; + + void blank(); + + void load (ESM::ESMReader& esm); + void save (ESM::ESMWriter& esm) const; + }; +} + +#endif diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index b7d09777d4..bfacc15dc9 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -606,7 +606,7 @@ namespace CSMWorld funcMap["09"] = "PC Fatigue"; funcMap["10"] = "PC Strength"; funcMap["11"] = "PC Block"; - funcMap["12"] = "PC Armoror"; + funcMap["12"] = "PC Armorer"; funcMap["13"] = "PC Medium Armor"; funcMap["14"] = "PC Heavy Armor"; funcMap["15"] = "PC Blunt Weapon"; @@ -906,7 +906,7 @@ namespace CSMWorld NestedTableWrapperBase* RaceAttributeAdapter::table(const Record& record) const { - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(wrap); @@ -922,7 +922,7 @@ namespace CSMWorld switch (subColIndex) { - case 0: return QString(ESM::Attribute::sAttributeNames[subRowIndex].c_str()); + case 0: return subRowIndex; case 1: return race.mData.mAttributeValues[subRowIndex].mMale; case 2: return race.mData.mAttributeValues[subRowIndex].mFemale; default: throw std::runtime_error("Race Attribute subcolumn index out of range"); @@ -983,7 +983,7 @@ namespace CSMWorld NestedTableWrapperBase* RaceSkillsBonusAdapter::table(const Record& record) const { - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring return new NestedTableWrapper >(wrap); diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 81c52588bb..2fd569bd0d 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -317,8 +317,34 @@ namespace CSMWorld else throw std::runtime_error("Magic effects ID unexpected value"); } - case 1: return effect.mSkill; - case 2: return effect.mAttribute; + case 1: + { + switch (effect.mEffectID) + { + case ESM::MagicEffect::DrainSkill: + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::RestoreSkill: + case ESM::MagicEffect::FortifySkill: + case ESM::MagicEffect::AbsorbSkill: + return effect.mSkill; + default: + return QVariant(); + } + } + case 2: + { + switch (effect.mEffectID) + { + case ESM::MagicEffect::DrainAttribute: + case ESM::MagicEffect::DamageAttribute: + case ESM::MagicEffect::RestoreAttribute: + case ESM::MagicEffect::FortifyAttribute: + case ESM::MagicEffect::AbsorbAttribute: + return effect.mAttribute; + default: + return QVariant(); + } + } case 3: { if (effect.mRange >=0 && effect.mRange <=2) diff --git a/apps/opencs/model/world/nestedcollection.cpp b/apps/opencs/model/world/nestedcollection.cpp index 937ad6ad60..850d8c3859 100644 --- a/apps/opencs/model/world/nestedcollection.cpp +++ b/apps/opencs/model/world/nestedcollection.cpp @@ -15,3 +15,28 @@ int CSMWorld::NestedCollection::getNestedColumnsCount(int row, int column) const { return 0; } + +int CSMWorld::NestedCollection::searchNestedColumnIndex(int parentColumn, Columns::ColumnId id) +{ + // Assumed that the parentColumn is always a valid index + const NestableColumn *parent = getNestableColumn(parentColumn); + int nestedColumnCount = getNestedColumnsCount(0, parentColumn); + for (int i = 0; i < nestedColumnCount; ++i) + { + if (parent->nestedColumn(i).mColumnId == id) + { + return i; + } + } + return -1; +} + +int CSMWorld::NestedCollection::findNestedColumnIndex(int parentColumn, Columns::ColumnId id) +{ + int index = searchNestedColumnIndex(parentColumn, id); + if (index == -1) + { + throw std::logic_error("CSMWorld::NestedCollection: No such nested column"); + } + return index; +} diff --git a/apps/opencs/model/world/nestedcollection.hpp b/apps/opencs/model/world/nestedcollection.hpp index b075f53c41..4548cfb2b9 100644 --- a/apps/opencs/model/world/nestedcollection.hpp +++ b/apps/opencs/model/world/nestedcollection.hpp @@ -1,6 +1,8 @@ #ifndef CSM_WOLRD_NESTEDCOLLECTION_H #define CSM_WOLRD_NESTEDCOLLECTION_H +#include "columns.hpp" + class QVariant; namespace CSMWorld @@ -33,6 +35,12 @@ namespace CSMWorld virtual int getNestedColumnsCount(int row, int column) const; virtual NestableColumn *getNestableColumn(int column) = 0; + + virtual int searchNestedColumnIndex(int parentColumn, Columns::ColumnId id); + ///< \return the column index or -1 if the requested column wasn't found. + + virtual int findNestedColumnIndex(int parentColumn, Columns::ColumnId id); + ///< \return the column index or throws an exception if the requested column wasn't found. }; } diff --git a/apps/opencs/model/world/nestedidcollection.hpp b/apps/opencs/model/world/nestedidcollection.hpp index 792a13b7db..56b1123659 100644 --- a/apps/opencs/model/world/nestedidcollection.hpp +++ b/apps/opencs/model/world/nestedidcollection.hpp @@ -161,8 +161,19 @@ namespace CSMWorld template int NestedIdCollection::getNestedColumnsCount(int row, int column) const { - return getAdapter(Collection::getColumn(column)).getColumnsCount( - Collection::getRecord(row)); + const ColumnBase &nestedColumn = Collection::getColumn(column); + int numRecords = Collection::getSize(); + if (row >= 0 && row < numRecords) + { + const Record& record = Collection::getRecord(row); + return getAdapter(nestedColumn).getColumnsCount(record); + } + else + { + // If the row is invalid (or there no records), retrieve the column count using a blank record + const Record record; + return getAdapter(nestedColumn).getColumnsCount(record); + } } template diff --git a/apps/opencs/model/world/nestedtableproxymodel.cpp b/apps/opencs/model/world/nestedtableproxymodel.cpp index acf1977168..edcc7a0706 100644 --- a/apps/opencs/model/world/nestedtableproxymodel.cpp +++ b/apps/opencs/model/world/nestedtableproxymodel.cpp @@ -75,10 +75,10 @@ QModelIndex CSMWorld::NestedTableProxyModel::index(int row, int column, const QM { assert (!parent.isValid()); - int rows = mMainModel->rowCount(parent); - int columns = mMainModel->columnCount(parent); + int numRows = rowCount(parent); + int numColumns = columnCount(parent); - if (row < 0 || row >= rows || column < 0 || column >= columns) + if (row < 0 || row >= numRows || column < 0 || column >= numColumns) return QModelIndex(); return createIndex(row, column); @@ -192,4 +192,8 @@ void CSMWorld::NestedTableProxyModel::forwardDataChanged (const QModelIndex& top emit dataChanged(index(0,0), index(mMainModel->rowCount(parent)-1, mMainModel->columnCount(parent)-1)); } + else if (topLeft.parent() == parent && bottomRight.parent() == parent) + { + emit dataChanged(index(topLeft.row(), topLeft.column()), index(bottomRight.row(), bottomRight.column())); + } } diff --git a/apps/opencs/model/world/pathgrid.cpp b/apps/opencs/model/world/pathgrid.cpp index 5c66e7d8ea..c995bd8f09 100644 --- a/apps/opencs/model/world/pathgrid.cpp +++ b/apps/opencs/model/world/pathgrid.cpp @@ -4,33 +4,28 @@ #include -void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, const IdCollection& cells) +void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection& cells) { - load (esm); + load (esm, isDeleted); // correct ID if (!mId.empty() && mId[0]!='#' && cells.searchId (mId)==-1) { std::ostringstream stream; - stream << "#" << mData.mX << " " << mData.mY; - mId = stream.str(); } } -void CSMWorld::Pathgrid::load (ESM::ESMReader &esm) +void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, bool &isDeleted) { - ESM::Pathgrid::load (esm); + ESM::Pathgrid::load (esm, isDeleted); + mId = mCell; if (mCell.empty()) { std::ostringstream stream; - stream << "#" << mData.mX << " " << mData.mY; - mId = stream.str(); } - else - mId = mCell; } diff --git a/apps/opencs/model/world/pathgrid.hpp b/apps/opencs/model/world/pathgrid.hpp index 7e7b7c3bb6..22d01b0710 100644 --- a/apps/opencs/model/world/pathgrid.hpp +++ b/apps/opencs/model/world/pathgrid.hpp @@ -20,9 +20,8 @@ namespace CSMWorld { std::string mId; - void load (ESM::ESMReader &esm, const IdCollection& cells); - - void load (ESM::ESMReader &esm); + void load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection& cells); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/record.cpp b/apps/opencs/model/world/record.cpp index ef2f4d3202..f13a36afc0 100644 --- a/apps/opencs/model/world/record.cpp +++ b/apps/opencs/model/world/record.cpp @@ -1,4 +1,3 @@ - #include "record.hpp" CSMWorld::RecordBase::~RecordBase() {} diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index 13706c950c..638f7ec9ca 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -1,4 +1,3 @@ - #include "ref.hpp" #include diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index ff30dafae6..65251a81db 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -1,4 +1,3 @@ - #include "refcollection.hpp" #include @@ -20,12 +19,11 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool Cell& cell2 = base ? cell.mBase : cell.mModified; CellRef ref; - - bool deleted = false; ESM::MovedCellRef mref; + bool isDeleted = false; // hack to initialise mindex - while (!(mref.mRefNum.mIndex = 0) && ESM::Cell::getNextRef(reader, ref, deleted, true, &mref)) + while (!(mref.mRefNum.mIndex = 0) && ESM::Cell::getNextRef(reader, ref, isDeleted, true, &mref)) { // Keep mOriginalCell empty when in modified (as an indicator that the // original cell will always be equal the current cell). @@ -50,17 +48,6 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool // https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30 ref.mOriginalCell = cell2.mId; - if (deleted) - { - // FIXME: how to mark the record deleted? - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, - mCells.getId (cellIndex)); - - messages.add (id, "Moved reference "+ref.mRefID+" is in DELE state"); - - continue; - } - // It is not always possibe to ignore moved references sub-record and // calculate from coordinates. Some mods may place the ref in positions // outside normal bounds, resulting in non sensical cell id's. This often @@ -92,7 +79,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool break; } - if (deleted) + if (isDeleted) { if (iter==cache.end()) { @@ -100,7 +87,6 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool mCells.getId (cellIndex)); messages.add (id, "Attempt to delete a non-existing reference"); - continue; } @@ -108,7 +94,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool Record record = getRecord (index); - if (record.mState==RecordBase::State_BaseOnly) + if (base) { removeRows (index, 1); cache.erase (iter); diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 4c369ef24f..90a710fc89 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include "nestedtablewrapper.hpp" CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns) @@ -25,8 +27,9 @@ QVariant CSMWorld::PotionRefIdAdapter::getData (const RefIdColumn *column, const if (column==mAutoCalc) return record.get().mData.mAutoCalc!=0; + // to show nested tables in dialogue subview, see IdTree::hasChildren() if (column==mColumns.mEffects) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); return InventoryRefIdAdapter::getData (column, data, index); } @@ -52,6 +55,156 @@ void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData } +CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns) +: InventoryColumns (columns) {} + +CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns) +: InventoryRefIdAdapter (UniversalId::Type_Ingredient, columns), + mColumns(columns) +{} + +QVariant CSMWorld::IngredientRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, + int index) const +{ + if (column==mColumns.mEffects) + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); + + return InventoryRefIdAdapter::getData (column, data, index); +} + +void CSMWorld::IngredientRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, + const QVariant& value) const +{ + InventoryRefIdAdapter::setData (column, data, index, value); + + return; +} + + +CSMWorld::IngredEffectRefIdAdapter::IngredEffectRefIdAdapter() +: mType(UniversalId::Type_Ingredient) +{} + +CSMWorld::IngredEffectRefIdAdapter::~IngredEffectRefIdAdapter() +{} + +void CSMWorld::IngredEffectRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::IngredEffectRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::IngredEffectRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + ESM::Ingredient ingredient = record.get(); + + ingredient.mData = + static_cast >&>(nestedTable).mNestedTable.at(0); + + record.setModified (ingredient); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::IngredEffectRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + // return the whole struct + std::vector wrap; + wrap.push_back(record.get().mData); + + // deleted by dtor of NestedTableStoring + return new NestedTableWrapper >(wrap); +} + +QVariant CSMWorld::IngredEffectRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + if (subRowIndex < 0 || subRowIndex >= 4) + throw std::runtime_error ("index out of range"); + + switch (subColIndex) + { + case 0: return record.get().mData.mEffectID[subRowIndex]; + case 1: + { + switch (record.get().mData.mEffectID[subRowIndex]) + { + case ESM::MagicEffect::DrainSkill: + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::RestoreSkill: + case ESM::MagicEffect::FortifySkill: + case ESM::MagicEffect::AbsorbSkill: + return record.get().mData.mSkills[subRowIndex]; + default: + return QVariant(); + } + } + case 2: + { + switch (record.get().mData.mEffectID[subRowIndex]) + { + case ESM::MagicEffect::DrainAttribute: + case ESM::MagicEffect::DamageAttribute: + case ESM::MagicEffect::RestoreAttribute: + case ESM::MagicEffect::FortifyAttribute: + case ESM::MagicEffect::AbsorbAttribute: + return record.get().mData.mAttributes[subRowIndex]; + default: + return QVariant(); + } + } + default: + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + } +} + +void CSMWorld::IngredEffectRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + ESM::Ingredient ingredient = record.get(); + + if (subRowIndex < 0 || subRowIndex >= 4) + throw std::runtime_error ("index out of range"); + + switch(subColIndex) + { + case 0: ingredient.mData.mEffectID[subRowIndex] = value.toInt(); break; + case 1: ingredient.mData.mSkills[subRowIndex] = value.toInt(); break; + case 2: ingredient.mData.mAttributes[subRowIndex] = value.toInt(); break; + default: + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + } + + record.setModified (ingredient); +} + +int CSMWorld::IngredEffectRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 3; // effect, skill, attribute +} + +int CSMWorld::IngredEffectRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + return 4; // up to 4 effects +} + + CSMWorld::ApparatusRefIdAdapter::ApparatusRefIdAdapter (const InventoryColumns& columns, const RefIdColumn *type, const RefIdColumn *quality) : InventoryRefIdAdapter (UniversalId::Type_Apparatus, columns), @@ -118,7 +271,7 @@ QVariant CSMWorld::ArmorRefIdAdapter::getData (const RefIdColumn *column, return record.get().mData.mArmor; if (column==mPartRef) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); return EnchantableRefIdAdapter::getData (column, data, index); } @@ -206,7 +359,7 @@ QVariant CSMWorld::ClothingRefIdAdapter::getData (const RefIdColumn *column, return record.get().mData.mType; if (column==mPartRef) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); return EnchantableRefIdAdapter::getData (column, data, index); } @@ -254,7 +407,7 @@ QVariant CSMWorld::ContainerRefIdAdapter::getData (const RefIdColumn *column, return (record.get().mFlags & ESM::Container::Respawn)!=0; if (column==mContent) - return true; // Required to show nested tables in dialogue subview + return QVariant::fromValue(ColumnBase::TableEdit_Full); return NameRefIdAdapter::getData (column, data, index); } @@ -296,12 +449,11 @@ void CSMWorld::ContainerRefIdAdapter::setData (const RefIdColumn *column, RefIdD CSMWorld::CreatureColumns::CreatureColumns (const ActorColumns& actorColumns) : ActorColumns (actorColumns), mType(NULL), - mSoul(NULL), mScale(NULL), mOriginal(NULL), - mCombat(NULL), - mMagic(NULL), - mStealth(NULL) + mAttributes(NULL), + mAttacks(NULL), + mMisc(NULL) {} CSMWorld::CreatureRefIdAdapter::CreatureRefIdAdapter (const CreatureColumns& columns) @@ -317,23 +469,20 @@ QVariant CSMWorld::CreatureRefIdAdapter::getData (const RefIdColumn *column, con if (column==mColumns.mType) return record.get().mData.mType; - if (column==mColumns.mSoul) - return record.get().mData.mSoul; - if (column==mColumns.mScale) return record.get().mScale; if (column==mColumns.mOriginal) return QString::fromUtf8 (record.get().mOriginal.c_str()); - if (column==mColumns.mCombat) - return static_cast (record.get().mData.mCombat); + if (column==mColumns.mAttributes) + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); - if (column==mColumns.mMagic) - return static_cast (record.get().mData.mMagic); + if (column==mColumns.mAttacks) + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); - if (column==mColumns.mStealth) - return static_cast (record.get().mData.mStealth); + if (column==mColumns.mMisc) + return QVariant::fromValue(ColumnBase::TableEdit_Full); std::map::const_iterator iter = mColumns.mFlags.find (column); @@ -354,18 +503,10 @@ void CSMWorld::CreatureRefIdAdapter::setData (const RefIdColumn *column, RefIdDa if (column==mColumns.mType) creature.mData.mType = value.toInt(); - else if (column==mColumns.mSoul) - creature.mData.mSoul = value.toInt(); else if (column==mColumns.mScale) creature.mScale = value.toFloat(); else if (column==mColumns.mOriginal) creature.mOriginal = value.toString().toUtf8().constData(); - else if (column==mColumns.mCombat) - creature.mData.mCombat = value.toInt(); - else if (column==mColumns.mMagic) - creature.mData.mMagic = value.toInt(); - else if (column==mColumns.mStealth) - creature.mData.mStealth = value.toInt(); else { std::map::const_iterator iter = @@ -581,13 +722,13 @@ QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const Re if (column==mColumns.mAttributes || column==mColumns.mSkills) { if ((record.get().mFlags & ESM::NPC::Autocalc) != 0) - return QVariant(QVariant::UserType); + return QVariant::fromValue(ColumnBase::TableEdit_None); else - return true; + return QVariant::fromValue(ColumnBase::TableEdit_FixedRows); } if (column==mColumns.mMisc) - return true; + return QVariant::fromValue(ColumnBase::TableEdit_Full); std::map::const_iterator iter = mColumns.mFlags.find (column); @@ -667,7 +808,7 @@ void CSMWorld::NpcAttributesRefIdAdapter::setNestedTable (const RefIdColumn* col // store the whole struct npc.mNpdt52 = - static_cast > &>(nestedTable).mNestedTable.at(0); + static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (npc); } @@ -679,10 +820,10 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::NpcAttributesRefIdAdapter::nestedTab static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); // return the whole struct - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mNpdt52); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper >(wrap); } QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn *column, @@ -694,18 +835,7 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn * const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt52; if (subColIndex == 0) - switch (subRowIndex) - { - case 0: return QString("Strength"); - case 1: return QString("Intelligence"); - case 2: return QString("Willpower"); - case 3: return QString("Agility"); - case 4: return QString("Speed"); - case 5: return QString("Endurance"); - case 6: return QString("Personality"); - case 7: return QString("Luck"); - default: return QVariant(); // throw an exception here? - } + return subRowIndex; else if (subColIndex == 1) switch (subRowIndex) { @@ -785,7 +915,7 @@ void CSMWorld::NpcSkillsRefIdAdapter::setNestedTable (const RefIdColumn* column, // store the whole struct npc.mNpdt52 = - static_cast > &>(nestedTable).mNestedTable.at(0); + static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (npc); } @@ -797,10 +927,10 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::NpcSkillsRefIdAdapter::nestedTable ( static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); // return the whole struct - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mNpdt52); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper >(wrap); } QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *column, @@ -815,7 +945,7 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu throw std::runtime_error ("index out of range"); if (subColIndex == 0) - return QString(ESM::Skill::sSkillNames[subRowIndex].c_str()); + return subRowIndex; else if (subColIndex == 1) return static_cast(npcStruct.mSkills[subRowIndex]); else @@ -975,6 +1105,289 @@ int CSMWorld::NpcMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, return 1; // fixed at size 1 } +CSMWorld::CreatureAttributesRefIdAdapter::CreatureAttributesRefIdAdapter() +{} + +void CSMWorld::CreatureAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttributesRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttributesRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + // store the whole struct + creature.mData = + static_cast > &>(nestedTable).mNestedTable.at(0); + + record.setModified (creature); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttributesRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + // return the whole struct + std::vector wrap; + wrap.push_back(record.get().mData); + // deleted by dtor of NestedTableStoring + return new NestedTableWrapper >(wrap); +} + +QVariant CSMWorld::CreatureAttributesRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + const ESM::Creature creature = record.get(); + + if (subColIndex == 0) + return subRowIndex; + else if (subColIndex == 1) + switch (subRowIndex) + { + case 0: return creature.mData.mStrength; + case 1: return creature.mData.mIntelligence; + case 2: return creature.mData.mWillpower; + case 3: return creature.mData.mAgility; + case 4: return creature.mData.mSpeed; + case 5: return creature.mData.mEndurance; + case 6: return creature.mData.mPersonality; + case 7: return creature.mData.mLuck; + default: return QVariant(); // throw an exception here? + } + else + return QVariant(); // throw an exception here? +} + +void CSMWorld::CreatureAttributesRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + if (subColIndex == 1) + switch(subRowIndex) + { + case 0: creature.mData.mStrength = value.toInt(); break; + case 1: creature.mData.mIntelligence = value.toInt(); break; + case 2: creature.mData.mWillpower = value.toInt(); break; + case 3: creature.mData.mAgility = value.toInt(); break; + case 4: creature.mData.mSpeed = value.toInt(); break; + case 5: creature.mData.mEndurance = value.toInt(); break; + case 6: creature.mData.mPersonality = value.toInt(); break; + case 7: creature.mData.mLuck = value.toInt(); break; + default: return; // throw an exception here? + } + else + return; // throw an exception here? + + record.setModified (creature); +} + +int CSMWorld::CreatureAttributesRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 2; +} + +int CSMWorld::CreatureAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + // There are 8 attributes + return 8; +} + +CSMWorld::CreatureAttackRefIdAdapter::CreatureAttackRefIdAdapter() +{} + +void CSMWorld::CreatureAttackRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttackRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + // Do nothing, this table cannot be changed by the user +} + +void CSMWorld::CreatureAttackRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + // store the whole struct + creature.mData = + static_cast > &>(nestedTable).mNestedTable.at(0); + + record.setModified (creature); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttackRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + // return the whole struct + std::vector wrap; + wrap.push_back(record.get().mData); + // deleted by dtor of NestedTableStoring + return new NestedTableWrapper >(wrap); +} + +QVariant CSMWorld::CreatureAttackRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + const ESM::Creature creature = record.get(); + + if (subRowIndex < 0 || subRowIndex > 2 || subColIndex < 0 || subColIndex > 2) + throw std::runtime_error ("index out of range"); + + if (subColIndex == 0) + return subRowIndex + 1; + else if (subColIndex < 3) // 1 or 2 + return creature.mData.mAttack[(subRowIndex * 2) + (subColIndex - 1)]; + else + return QVariant(); // throw an exception here? +} + +void CSMWorld::CreatureAttackRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + if (subRowIndex < 0 || subRowIndex > 2) + throw std::runtime_error ("index out of range"); + + if (subColIndex == 1 || subColIndex == 2) + creature.mData.mAttack[(subRowIndex * 2) + (subColIndex - 1)] = value.toInt(); + else + return; // throw an exception here? + + record.setModified (creature); +} + +int CSMWorld::CreatureAttackRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 3; +} + +int CSMWorld::CreatureAttackRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + // There are 3 attacks + return 3; +} + +CSMWorld::CreatureMiscRefIdAdapter::CreatureMiscRefIdAdapter() +{} + +CSMWorld::CreatureMiscRefIdAdapter::~CreatureMiscRefIdAdapter() +{} + +void CSMWorld::CreatureMiscRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + throw std::logic_error ("cannot add a row to a fixed table"); +} + +void CSMWorld::CreatureMiscRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + throw std::logic_error ("cannot remove a row to a fixed table"); +} + +void CSMWorld::CreatureMiscRefIdAdapter::setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const +{ + throw std::logic_error ("table operation not supported"); +} + +CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureMiscRefIdAdapter::nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const +{ + throw std::logic_error ("table operation not supported"); +} + +QVariant CSMWorld::CreatureMiscRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); + + const ESM::Creature creature = record.get(); + + switch (subColIndex) + { + case 0: return creature.mData.mLevel; + case 1: return creature.mData.mHealth; + case 2: return creature.mData.mMana; + case 3: return creature.mData.mFatigue; + case 4: return creature.mData.mSoul; + case 5: return creature.mData.mCombat; + case 6: return creature.mData.mMagic; + case 7: return creature.mData.mStealth; + case 8: return creature.mData.mGold; + default: return QVariant(); // throw an exception here? + } +} + +void CSMWorld::CreatureMiscRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, UniversalId::Type_Creature))); + ESM::Creature creature = record.get(); + + switch(subColIndex) + { + case 0: creature.mData.mLevel = value.toInt(); break; + case 1: creature.mData.mHealth = value.toInt(); break; + case 2: creature.mData.mMana = value.toInt(); break; + case 3: creature.mData.mFatigue = value.toInt(); break; + case 4: creature.mData.mSoul = value.toInt(); break; + case 5: creature.mData.mCombat = value.toInt(); break; + case 6: creature.mData.mMagic = value.toInt(); break; + case 7: creature.mData.mStealth = value.toInt(); break; + case 8: creature.mData.mGold = value.toInt(); break; + default: return; // throw an exception here? + } + + record.setModified (creature); +} + +int CSMWorld::CreatureMiscRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const +{ + return 9; // Level, Health, Mana, Fatigue, Soul, Combat, Magic, Steath, Gold +} + +int CSMWorld::CreatureMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + return 1; // fixed at size 1 +} + CSMWorld::WeaponColumns::WeaponColumns (const EnchantableColumns& columns) : EnchantableColumns (columns) {} diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 869996da5b..eff7167def 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -2,7 +2,6 @@ #define CSM_WOLRD_REFIDADAPTERIMP_H #include -#include #include @@ -12,6 +11,7 @@ #include #include +#include "columnbase.hpp" #include "record.hpp" #include "refiddata.hpp" #include "universalid.hpp" @@ -341,6 +341,66 @@ namespace CSMWorld ///< If the data type does not match an exception is thrown. }; + struct IngredientColumns : public InventoryColumns + { + const RefIdColumn *mEffects; + + IngredientColumns (const InventoryColumns& columns); + }; + + class IngredientRefIdAdapter : public InventoryRefIdAdapter + { + IngredientColumns mColumns; + + public: + + IngredientRefIdAdapter (const IngredientColumns& columns); + + virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) + const; + + virtual void setData (const RefIdColumn *column, RefIdData& data, int index, + const QVariant& value) const; + ///< If the data type does not match an exception is thrown. + }; + + class IngredEffectRefIdAdapter : public NestedRefIdAdapterBase + { + UniversalId::Type mType; + + // not implemented + IngredEffectRefIdAdapter (const IngredEffectRefIdAdapter&); + IngredEffectRefIdAdapter& operator= (const IngredEffectRefIdAdapter&); + + public: + + IngredEffectRefIdAdapter(); + + virtual ~IngredEffectRefIdAdapter(); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + struct EnchantableColumns : public InventoryColumns { const RefIdColumn *mEnchantment; @@ -480,7 +540,6 @@ namespace CSMWorld struct ActorColumns : public NameColumns { - const RefIdColumn *mHasAi; const RefIdColumn *mHello; const RefIdColumn *mFlee; const RefIdColumn *mFight; @@ -525,9 +584,6 @@ namespace CSMWorld const Record& record = static_cast&> ( data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); - if (column==mActors.mHasAi) - return record.get().mHasAI!=0; - if (column==mActors.mHello) return record.get().mAiData.mHello; @@ -541,16 +597,16 @@ namespace CSMWorld return record.get().mAiData.mAlarm; if (column==mActors.mInventory) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column==mActors.mSpells) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column==mActors.mDestinations) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); if (column==mActors.mAiPackages) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); std::map::const_iterator iter = mActors.mServices.find (column); @@ -569,9 +625,7 @@ namespace CSMWorld data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); RecordT record2 = record.get(); - if (column==mActors.mHasAi) - record2.mHasAI = value.toInt(); - else if (column==mActors.mHello) + if (column==mActors.mHello) record2.mAiData.mHello = value.toInt(); else if (column==mActors.mFlee) record2.mAiData.mFlee = value.toInt(); @@ -697,12 +751,11 @@ namespace CSMWorld { std::map mFlags; const RefIdColumn *mType; - const RefIdColumn *mSoul; const RefIdColumn *mScale; const RefIdColumn *mOriginal; - const RefIdColumn *mCombat; - const RefIdColumn *mMagic; - const RefIdColumn *mStealth; + const RefIdColumn *mAttributes; + const RefIdColumn *mAttacks; + const RefIdColumn *mMisc; CreatureColumns (const ActorColumns& actorColumns); }; @@ -939,6 +992,97 @@ namespace CSMWorld virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; }; + class CreatureAttributesRefIdAdapter : public NestedRefIdAdapterBase + { + public: + + CreatureAttributesRefIdAdapter (); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + + class CreatureAttackRefIdAdapter : public NestedRefIdAdapterBase + { + public: + + CreatureAttackRefIdAdapter (); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + + class CreatureMiscRefIdAdapter : public NestedRefIdAdapterBase + { + CreatureMiscRefIdAdapter (const CreatureMiscRefIdAdapter&); + CreatureMiscRefIdAdapter& operator= (const CreatureMiscRefIdAdapter&); + + public: + + CreatureMiscRefIdAdapter (); + virtual ~CreatureMiscRefIdAdapter(); + + virtual void addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const; + + virtual void removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const; + + virtual void setNestedTable (const RefIdColumn* column, + RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* nestedTable (const RefIdColumn* column, + const RefIdData& data, int index) const; + + virtual QVariant getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; + + virtual void setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; + + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; + }; + template class EffectsListAdapter; @@ -1449,6 +1593,8 @@ namespace CSMWorld virtual ~ActorAiRefIdAdapter() {} + // FIXME: should check if the AI package type is already in the list and use a default + // that wasn't used already (in extreme case do not add anything at all? virtual void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { @@ -1532,6 +1678,7 @@ namespace CSMWorld switch (subColIndex) { case 0: + // FIXME: should more than one AI package type be allowed? Check vanilla switch (content.mType) { case ESM::AI_Wander: return 0; @@ -1559,47 +1706,52 @@ namespace CSMWorld else return QVariant(); case 4: // wander idle + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: if (content.mType == ESM::AI_Wander) - { - return static_cast(content.mWander.mIdle[0]); // FIXME: - } + return static_cast(content.mWander.mIdle[subColIndex-4]); else return QVariant(); - case 5: // wander repeat + case 12: // wander repeat if (content.mType == ESM::AI_Wander) - return content.mWander.mShouldRepeat; + return content.mWander.mShouldRepeat != 0; else return QVariant(); - case 6: // activate name + case 13: // activate name if (content.mType == ESM::AI_Activate) return QString(content.mActivate.mName.toString().c_str()); else return QVariant(); - case 7: // target id + case 14: // target id if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return QString(content.mTarget.mId.toString().c_str()); else return QVariant(); - case 8: // target cell + case 15: // target cell if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return QString::fromUtf8(content.mCellName.c_str()); else return QVariant(); - case 9: + case 16: if (content.mType == ESM::AI_Travel) return content.mTravel.mX; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return content.mTarget.mX; else return QVariant(); - case 10: + case 17: if (content.mType == ESM::AI_Travel) return content.mTravel.mY; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return content.mTarget.mY; else return QVariant(); - case 11: + case 18: if (content.mType == ESM::AI_Travel) return content.mTravel.mZ; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) @@ -1629,11 +1781,12 @@ namespace CSMWorld case 0: // ai package type switch (value.toInt()) { - case 0: content.mType = ESM::AI_Wander; - case 1: content.mType = ESM::AI_Travel; - case 2: content.mType = ESM::AI_Follow; - case 3: content.mType = ESM::AI_Escort; - case 4: content.mType = ESM::AI_Activate; + case 0: content.mType = ESM::AI_Wander; break; + case 1: content.mType = ESM::AI_Travel; break; + case 2: content.mType = ESM::AI_Follow; break; + case 3: content.mType = ESM::AI_Escort; break; + case 4: content.mType = ESM::AI_Activate; break; + default: return; // return without saving } break; // always save @@ -1642,6 +1795,8 @@ namespace CSMWorld content.mWander.mDistance = static_cast(value.toInt()); else return; // return without saving + + break; // always save case 2: if (content.mType == ESM::AI_Wander || content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) @@ -1653,62 +1808,77 @@ namespace CSMWorld content.mWander.mTimeOfDay = static_cast(value.toInt()); else return; // return without saving + + break; // always save case 4: - if (content.mType == ESM::AI_Wander) - break; // FIXME: idle - else - return; // return without saving case 5: - if (content.mType == ESM::AI_Wander) - { - content.mWander.mShouldRepeat = static_cast(value.toInt()); - break; - } - case 6: // NAME32 - if (content.mType == ESM::AI_Activate) - { - content.mActivate.mName.assign(value.toString().toUtf8().constData()); - break; - } - else - return; // return without saving - case 7: // NAME32 - if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - { - content.mTarget.mId.assign(value.toString().toUtf8().constData()); - break; - } - else - return; // return without saving + case 6: + case 7: case 8: - if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - { - content.mCellName = std::string(value.toString().toUtf8().constData()); - break; - } - else - return; // return without saving case 9: - if (content.mType == ESM::AI_Travel) - content.mTravel.mZ = value.toFloat(); - else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - content.mTarget.mZ = value.toFloat(); - else - return; // return without saving case 10: - if (content.mType == ESM::AI_Travel) - content.mTravel.mZ = value.toFloat(); - else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - content.mTarget.mZ = value.toFloat(); - else - return; // return without saving case 11: + if (content.mType == ESM::AI_Wander) + content.mWander.mIdle[subColIndex-4] = static_cast(value.toInt()); + else + return; // return without saving + + break; // always save + case 12: + if (content.mType == ESM::AI_Wander) + content.mWander.mShouldRepeat = static_cast(value.toInt()); + else + return; // return without saving + + break; // always save + case 13: // NAME32 + if (content.mType == ESM::AI_Activate) + content.mActivate.mName.assign(value.toString().toUtf8().constData()); + else + return; // return without saving + + break; // always save + case 14: // NAME32 + if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + content.mTarget.mId.assign(value.toString().toUtf8().constData()); + else + return; // return without saving + + break; // always save + case 15: + if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + content.mCellName = std::string(value.toString().toUtf8().constData()); + else + return; // return without saving + + break; // always save + case 16: if (content.mType == ESM::AI_Travel) content.mTravel.mZ = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) content.mTarget.mZ = value.toFloat(); else return; // return without saving + + break; // always save + case 17: + if (content.mType == ESM::AI_Travel) + content.mTravel.mZ = value.toFloat(); + else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + content.mTarget.mZ = value.toFloat(); + else + return; // return without saving + + break; // always save + case 18: + if (content.mType == ESM::AI_Travel) + content.mTravel.mZ = value.toFloat(); + else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) + content.mTarget.mZ = value.toFloat(); + else + return; // return without saving + + break; // always save default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } @@ -1718,7 +1888,7 @@ namespace CSMWorld virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { - return 12; + return 19; } virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const @@ -1911,7 +2081,7 @@ namespace CSMWorld int index) const { if (column==mLevList.mLevList || column == mLevList.mNestedListLevList) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + return QVariant::fromValue(ColumnBase::TableEdit_Full); return BaseRefIdAdapter::getData (column, data, index); } diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 5495926b46..c4c8f8605d 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -71,6 +71,21 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.push_back (RefIdColumn (Columns::ColumnId_CoinValue, ColumnBase::Display_Integer)); inventoryColumns.mValue = &mColumns.back(); + IngredientColumns ingredientColumns (inventoryColumns); + mColumns.push_back (RefIdColumn (Columns::ColumnId_EffectList, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); + ingredientColumns.mEffects = &mColumns.back(); + std::map ingredientEffectsMap; + ingredientEffectsMap.insert(std::make_pair(UniversalId::Type_Ingredient, + new IngredEffectRefIdAdapter ())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), ingredientEffectsMap)); + mColumns.back().addColumn( + new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_IngredEffectId)); + mColumns.back().addColumn( + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); + mColumns.back().addColumn( + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); + // nested table PotionColumns potionColumns (inventoryColumns); mColumns.push_back (RefIdColumn (Columns::ColumnId_EffectList, @@ -83,9 +98,9 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_EffectSkill)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); + new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_EffectAttribute)); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_EffectRange, ColumnBase::Display_EffectRange)); mColumns.back().addColumn( @@ -93,9 +108,9 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_Duration, ColumnBase::Display_Integer)); // reuse from light mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_MinRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MinMagnitude, ColumnBase::Display_Integer)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_MaxRange, ColumnBase::Display_Integer)); // reuse from sound + new NestedChildColumn (Columns::ColumnId_MaxMagnitude, ColumnBase::Display_Integer)); EnchantableColumns enchantableColumns (inventoryColumns); @@ -113,8 +128,6 @@ CSMWorld::RefIdCollection::RefIdCollection() ActorColumns actorsColumns (nameColumns); - mColumns.push_back (RefIdColumn (Columns::ColumnId_Ai, ColumnBase::Display_Boolean)); - actorsColumns.mHasAi = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_AiHello, ColumnBase::Display_Integer)); actorsColumns.mHello = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_AiFlee, ColumnBase::Display_Integer)); @@ -195,10 +208,26 @@ CSMWorld::RefIdCollection::RefIdCollection() new RefIdColumn (Columns::ColumnId_AiDuration, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiWanderToD, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiWanderIdle, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Idle1, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_YesNo)); + new RefIdColumn (Columns::ColumnId_Idle2, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle3, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle4, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle5, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle6, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle7, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle8, CSMWorld::ColumnBase::Display_Integer)); + + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiActivateName, CSMWorld::ColumnBase::Display_String)); mColumns.back().addColumn( @@ -297,21 +326,10 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.push_back (RefIdColumn (Columns::ColumnId_CreatureType, ColumnBase::Display_CreatureType)); creatureColumns.mType = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::ColumnId_SoulPoints, ColumnBase::Display_Integer)); - creatureColumns.mSoul = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_Scale, ColumnBase::Display_Float)); creatureColumns.mScale = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_OriginalCreature, ColumnBase::Display_Creature)); creatureColumns.mOriginal = &mColumns.back(); - mColumns.push_back ( - RefIdColumn (Columns::ColumnId_CombatState, ColumnBase::Display_Integer)); - creatureColumns.mCombat = &mColumns.back(); - mColumns.push_back ( - RefIdColumn (Columns::ColumnId_MagicState, ColumnBase::Display_Integer)); - creatureColumns.mMagic = &mColumns.back(); - mColumns.push_back ( - RefIdColumn (Columns::ColumnId_StealthState, ColumnBase::Display_Integer)); - creatureColumns.mStealth = &mColumns.back(); static const struct { @@ -321,7 +339,6 @@ CSMWorld::RefIdCollection::RefIdCollection() { { Columns::ColumnId_Biped, ESM::Creature::Bipedal }, { Columns::ColumnId_HasWeapon, ESM::Creature::Weapon }, - { Columns::ColumnId_NoMovement, ESM::Creature::None }, { Columns::ColumnId_Swims, ESM::Creature::Swims }, { Columns::ColumnId_Flies, ESM::Creature::Flies }, { Columns::ColumnId_Walks, ESM::Creature::Walks }, @@ -351,6 +368,59 @@ CSMWorld::RefIdCollection::RefIdCollection() creatureColumns.mFlags.insert (std::make_pair (respawn, ESM::Creature::Respawn)); + // Nested table + mColumns.push_back(RefIdColumn (Columns::ColumnId_CreatureAttributes, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); + creatureColumns.mAttributes = &mColumns.back(); + std::map creaAttrMap; + creaAttrMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureAttributesRefIdAdapter())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), creaAttrMap)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Attribute, CSMWorld::ColumnBase::Display_Attribute, false, false)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_AttributeValue, CSMWorld::ColumnBase::Display_Integer)); + + // Nested table + mColumns.push_back(RefIdColumn (Columns::ColumnId_CreatureAttack, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); + creatureColumns.mAttacks = &mColumns.back(); + std::map attackMap; + attackMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureAttackRefIdAdapter())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), attackMap)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_CreatureAttack, CSMWorld::ColumnBase::Display_Integer, false, false)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_MinAttack, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_MaxAttack, CSMWorld::ColumnBase::Display_Integer)); + + // Nested list + mColumns.push_back(RefIdColumn (Columns::ColumnId_CreatureMisc, + ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); + creatureColumns.mMisc = &mColumns.back(); + std::map creaMiscMap; + creaMiscMap.insert(std::make_pair(UniversalId::Type_Creature, new CreatureMiscRefIdAdapter())); + mNestedAdapters.push_back (std::make_pair(&mColumns.back(), creaMiscMap)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Level, CSMWorld::ColumnBase::Display_Integer, + ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Health, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Mana, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Fatigue, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SoulPoints, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_CombatState, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_MagicState, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_StealthState, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Gold, CSMWorld::ColumnBase::Display_Integer)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_OpenSound, ColumnBase::Display_Sound)); const RefIdColumn *openSound = &mColumns.back(); @@ -381,7 +451,7 @@ CSMWorld::RefIdCollection::RefIdCollection() { Columns::ColumnId_Portable, ESM::Light::Carry }, { Columns::ColumnId_NegativeLight, ESM::Light::Negative }, { Columns::ColumnId_Flickering, ESM::Light::Flicker }, - { Columns::ColumnId_SlowFlickering, ESM::Light::Flicker }, + { Columns::ColumnId_SlowFlickering, ESM::Light::FlickerSlow }, { Columns::ColumnId_Pulsing, ESM::Light::Pulse }, { Columns::ColumnId_SlowPulsing, ESM::Light::PulseSlow }, { Columns::ColumnId_Fire, ESM::Light::Fire }, @@ -440,7 +510,7 @@ CSMWorld::RefIdCollection::RefIdCollection() attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter())); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), attrMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcAttributes, CSMWorld::ColumnBase::Display_String, false, false)); + new RefIdColumn (Columns::ColumnId_Attribute, CSMWorld::ColumnBase::Display_Attribute, false, false)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_Integer)); @@ -452,7 +522,7 @@ CSMWorld::RefIdCollection::RefIdCollection() skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter())); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), skillsMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcSkills, CSMWorld::ColumnBase::Display_String, false, false)); + new RefIdColumn (Columns::ColumnId_Skill, CSMWorld::ColumnBase::Display_SkillId, false, false)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_Integer)); @@ -464,15 +534,15 @@ CSMWorld::RefIdCollection::RefIdCollection() miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter())); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), miscMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcLevel, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Level, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcFactionID, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcHealth, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Health, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcMana, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Mana, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcFatigue, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Fatigue, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcDisposition, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( @@ -480,7 +550,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcRank, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcGold, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Gold, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcPersistence, CSMWorld::ColumnBase::Display_Boolean)); @@ -596,7 +666,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mAdapters.insert (std::make_pair (UniversalId::Type_Door, new DoorRefIdAdapter (nameColumns, openSound, closeSound))); mAdapters.insert (std::make_pair (UniversalId::Type_Ingredient, - new InventoryRefIdAdapter (UniversalId::Type_Ingredient, inventoryColumns))); + new IngredientRefIdAdapter (ingredientColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_CreatureLevelledList, new LevelledListRefIdAdapter ( UniversalId::Type_CreatureLevelledList, levListColumns))); @@ -767,61 +837,7 @@ const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord (int index) con void CSMWorld::RefIdCollection::load (ESM::ESMReader& reader, bool base, UniversalId::Type type) { - std::string id = reader.getHNOString ("NAME"); - - int index = searchId (id); - - if (reader.isNextSub ("DELE")) - { - reader.skipRecord(); - - if (index==-1) - { - // deleting a record that does not exist - - // ignore it for now - - /// \todo report the problem to the user - } - else if (base) - { - mData.erase (index, 1); - } - else - { - mData.getRecord (mData.globalToLocalIndex (index)).mState = RecordBase::State_Deleted; - } - } - else - { - if (index==-1) - { - // new record - int index = mData.getAppendIndex (type); - mData.appendRecord (type, id, base); - - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index); - - mData.load (localIndex, reader, base); - - mData.getRecord (localIndex).mState = - base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - } - else - { - // old record - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index); - - if (!base) - if (mData.getRecord (localIndex).mState==RecordBase::State_Erased) - throw std::logic_error ("attempt to access a deleted record"); - - mData.load (localIndex, reader, base); - - if (!base) - mData.getRecord (localIndex).mState = RecordBase::State_Modified; - } - } + mData.load(reader, base, type); } int CSMWorld::RefIdCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const @@ -914,3 +930,8 @@ const CSMWorld::NestedRefIdAdapterBase& CSMWorld::RefIdCollection::getNestedAdap } throw std::runtime_error("No such column in the nestedadapters"); } + +void CSMWorld::RefIdCollection::copyTo (int index, RefIdCollection& target) const +{ + mData.copyTo (index, target.mData); +} diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index 4d511d12da..de5a709c6e 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -138,6 +138,7 @@ namespace CSMWorld void save (int index, ESM::ESMWriter& writer) const; const RefIdData& getDataSet() const; //I can't figure out a better name for this one :( + void copyTo (int index, RefIdCollection& target) const; }; } diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index 7f5c25f368..2d8c9ac108 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -1,12 +1,22 @@ - #include "refiddata.hpp" #include - -#include +#include CSMWorld::RefIdDataContainerBase::~RefIdDataContainerBase() {} + +std::string CSMWorld::RefIdData::getRecordId(const CSMWorld::RefIdData::LocalIndex &index) const +{ + std::map::const_iterator found = + mRecordContainers.find (index.second); + + if (found == mRecordContainers.end()) + throw std::logic_error ("invalid local index type"); + + return found->second->getId(index.first); +} + CSMWorld::RefIdData::RefIdData() { mRecordContainers.insert (std::make_pair (UniversalId::Type_Activator, &mActivators)); @@ -161,15 +171,27 @@ int CSMWorld::RefIdData::getAppendIndex (UniversalId::Type type) const return index; } -void CSMWorld::RefIdData::load (const LocalIndex& index, ESM::ESMReader& reader, bool base) +void CSMWorld::RefIdData::load (ESM::ESMReader& reader, bool base, CSMWorld::UniversalId::Type type) { - std::map::iterator iter = - mRecordContainers.find (index.second); + std::map::iterator found = + mRecordContainers.find (type); - if (iter==mRecordContainers.end()) - throw std::logic_error ("invalid local index type"); + if (found == mRecordContainers.end()) + throw std::logic_error ("Invalid Referenceable ID type"); - iter->second->load (index.first, reader, base); + int index = found->second->load(reader, base); + if (index != -1) + { + LocalIndex localIndex = LocalIndex(index, type); + if (base && getRecord(localIndex).mState == RecordBase::State_Deleted) + { + erase(localIndex, 1); + } + else + { + mIndex[Misc::StringUtils::lowerCase(getRecordId(localIndex))] = localIndex; + } + } } void CSMWorld::RefIdData::erase (const LocalIndex& index, int count) @@ -345,7 +367,7 @@ const CSMWorld::RefIdDataContainer< ESM::Static >& CSMWorld::RefIdData::getStati return mStatics; } -void CSMWorld::RefIdData::insertRecord(CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id) +void CSMWorld::RefIdData::insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id) { std::map::iterator iter = mRecordContainers.find (type); @@ -358,3 +380,16 @@ void CSMWorld::RefIdData::insertRecord(CSMWorld::RecordBase& record, CSMWorld::U mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), LocalIndex (iter->second->getSize()-1, type))); } + +void CSMWorld::RefIdData::copyTo (int index, RefIdData& target) const +{ + LocalIndex localIndex = globalToLocalIndex (index); + + RefIdDataContainerBase *source = mRecordContainers.find (localIndex.second)->second; + + std::string id = source->getId (localIndex.first); + + std::auto_ptr newRecord (source->getRecord (localIndex.first).modifiedCopy()); + + target.insertRecord (*newRecord, localIndex.second, id); +} diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index 85d16a6eb2..59cad6a661 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -25,6 +25,8 @@ #include #include +#include + #include "record.hpp" #include "universalid.hpp" @@ -49,7 +51,8 @@ namespace CSMWorld virtual void insertRecord (RecordBase& record) = 0; - virtual void load (int index, ESM::ESMReader& reader, bool base) = 0; + virtual int load (ESM::ESMReader& reader, bool base) = 0; + ///< \return index of a loaded record or -1 if no record was loaded virtual void erase (int index, int count) = 0; @@ -73,7 +76,8 @@ namespace CSMWorld virtual void insertRecord (RecordBase& record); - virtual void load (int index, ESM::ESMReader& reader, bool base); + virtual int load (ESM::ESMReader& reader, bool base); + ///< \return index of a loaded record or -1 if no record was loaded virtual void erase (int index, int count); @@ -122,9 +126,58 @@ namespace CSMWorld } template - void RefIdDataContainer::load (int index, ESM::ESMReader& reader, bool base) + int RefIdDataContainer::load (ESM::ESMReader& reader, bool base) { - (base ? mContainer.at (index).mBase : mContainer.at (index).mModified).load (reader); + RecordT record; + bool isDeleted = false; + + record.load(reader, isDeleted); + + int index = 0; + int numRecords = static_cast(mContainer.size()); + for (; index < numRecords; ++index) + { + if (Misc::StringUtils::ciEqual(mContainer[index].get().mId, record.mId)) + { + break; + } + } + + if (isDeleted) + { + if (index == numRecords) + { + // deleting a record that does not exist + // ignore it for now + /// \todo report the problem to the user + return -1; + } + + // Flag the record as Deleted even for a base content file. + // RefIdData is responsible for its erasure. + mContainer[index].mState = RecordBase::State_Deleted; + } + else + { + if (index == numRecords) + { + appendRecord(record.mId, base); + if (base) + { + mContainer.back().mBase = record; + } + else + { + mContainer.back().mModified = record; + } + } + else if (!base) + { + mContainer[index].setModified(record); + } + } + + return index; } template @@ -145,19 +198,14 @@ namespace CSMWorld template void RefIdDataContainer::save (int index, ESM::ESMWriter& writer) const { - CSMWorld::RecordBase::State state = mContainer.at (index).mState; + Record record = mContainer.at(index); - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly) + if (record.isModified() || record.mState == RecordBase::State_Deleted) { - writer.startRecord (mContainer.at (index).mModified.sRecordId); - writer.writeHNCString ("NAME", getId (index)); - mContainer.at (index).mModified.save (writer); - writer.endRecord (mContainer.at (index).mModified.sRecordId); - } - else if (state==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + RecordT esmRecord = record.get(); + writer.startRecord(esmRecord.sRecordId); + esmRecord.save(writer, record.mState == RecordBase::State_Deleted); + writer.endRecord(esmRecord.sRecordId); } } @@ -198,6 +246,8 @@ namespace CSMWorld void erase (const LocalIndex& index, int count); ///< Must not spill over into another type. + std::string getRecordId(const LocalIndex &index) const; + public: RefIdData(); @@ -210,7 +260,8 @@ namespace CSMWorld void erase (int index, int count); - void insertRecord(CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id); + void insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, + const std::string& id); const RecordBase& getRecord (const LocalIndex& index) const; @@ -220,7 +271,7 @@ namespace CSMWorld int getAppendIndex (UniversalId::Type type) const; - void load (const LocalIndex& index, ESM::ESMReader& reader, bool base); + void load (ESM::ESMReader& reader, bool base, UniversalId::Type type); int getSize() const; @@ -252,11 +303,9 @@ namespace CSMWorld const RefIdDataContainer& getProbes() const; const RefIdDataContainer& getRepairs() const; const RefIdDataContainer& getStatics() const; + + void copyTo (int index, RefIdData& target) const; }; } #endif - - - - diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 42bde9c81a..6dbbac97fb 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -1,4 +1,3 @@ - #include "regionmap.hpp" #include @@ -169,7 +168,7 @@ void CSMWorld::RegionMap::updateRegions (const std::vector& regions { std::vector regions2 (regions); - std::for_each (regions2.begin(), regions2.end(), &Misc::StringUtils::lowerCase); + std::for_each (regions2.begin(), regions2.end(), Misc::StringUtils::lowerCaseInPlace); std::sort (regions2.begin(), regions2.end()); for (std::map::const_iterator iter (mMap.begin()); diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index 8c94398906..b31e211eec 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -1,65 +1,52 @@ - #include "resources.hpp" #include #include #include -#include +#include #include -CSMWorld::Resources::Resources (const std::string& baseDirectory, UniversalId::Type type, +CSMWorld::Resources::Resources (const VFS::Manager* vfs, const std::string& baseDirectory, UniversalId::Type type, const char * const *extensions) : mBaseDirectory (baseDirectory), mType (type) { int baseSize = mBaseDirectory.size(); - Ogre::StringVector resourcesGroups = - Ogre::ResourceGroupManager::getSingleton().getResourceGroups(); - - for (Ogre::StringVector::iterator iter (resourcesGroups.begin()); - iter!=resourcesGroups.end(); ++iter) + const std::map& index = vfs->getIndex(); + for (std::map::const_iterator it = index.begin(); it != index.end(); ++it) { - if (*iter=="General" || *iter=="Internal" || *iter=="Autodetect") + std::string filepath = it->first; + if (static_cast (filepath.size())begin()); - iter!=resources->end(); ++iter) + if (extensions) { - if (static_cast (iter->size())substr (0, baseSize)!=mBaseDirectory || - ((*iter)[baseSize]!='/' && (*iter)[baseSize]!='\\')) + std::string::size_type index = filepath.find_last_of ('.'); + + if (index==std::string::npos) continue; - if (extensions) - { - std::string::size_type index = iter->find_last_of ('.'); + std::string extension = filepath.substr (index+1); - if (index==std::string::npos) - continue; + int i = 0; - std::string extension = iter->substr (index+1); + for (; extensions[i]; ++i) + if (extensions[i]==extension) + break; - int i = 0; - - for (; extensions[i]; ++i) - if (extensions[i]==extension) - break; - - if (!extensions[i]) - continue; - } - - std::string file = iter->substr (baseSize+1); - mFiles.push_back (file); - std::replace (file.begin(), file.end(), '\\', '/'); - mIndex.insert (std::make_pair ( - Misc::StringUtils::lowerCase (file), static_cast (mFiles.size())-1)); + if (!extensions[i]) + continue; } + + std::string file = filepath.substr (baseSize+1); + mFiles.push_back (file); + std::replace (file.begin(), file.end(), '\\', '/'); + mIndex.insert (std::make_pair ( + Misc::StringUtils::lowerCase (file), static_cast (mFiles.size())-1)); } } diff --git a/apps/opencs/model/world/resources.hpp b/apps/opencs/model/world/resources.hpp index 9c1c76b6f2..d6998da9f0 100644 --- a/apps/opencs/model/world/resources.hpp +++ b/apps/opencs/model/world/resources.hpp @@ -7,6 +7,11 @@ #include "universalid.hpp" +namespace VFS +{ + class Manager; +} + namespace CSMWorld { class Resources @@ -19,7 +24,7 @@ namespace CSMWorld public: /// \param type Type of resources in this table. - Resources (const std::string& baseDirectory, UniversalId::Type type, + Resources (const VFS::Manager* vfs, const std::string& baseDirectory, UniversalId::Type type, const char * const *extensions = 0); int getSize() const; diff --git a/apps/opencs/model/world/resourcesmanager.cpp b/apps/opencs/model/world/resourcesmanager.cpp index deddd83b5e..016799be35 100644 --- a/apps/opencs/model/world/resourcesmanager.cpp +++ b/apps/opencs/model/world/resourcesmanager.cpp @@ -1,8 +1,12 @@ - #include "resourcesmanager.hpp" #include +CSMWorld::ResourcesManager::ResourcesManager() + : mVFS(NULL) +{ +} + void CSMWorld::ResourcesManager::addResources (const Resources& resources) { mResources.insert (std::make_pair (resources.getType(), resources)); @@ -10,16 +14,26 @@ void CSMWorld::ResourcesManager::addResources (const Resources& resources) resources)); } -void CSMWorld::ResourcesManager::listResources() +void CSMWorld::ResourcesManager::setVFS(const VFS::Manager *vfs) { - static const char * const sMeshTypes[] = { "nif", 0 }; + mVFS = vfs; + mResources.clear(); - addResources (Resources ("meshes", UniversalId::Type_Mesh, sMeshTypes)); - addResources (Resources ("icons", UniversalId::Type_Icon)); - addResources (Resources ("music", UniversalId::Type_Music)); - addResources (Resources ("sound", UniversalId::Type_SoundRes)); - addResources (Resources ("textures", UniversalId::Type_Texture)); - addResources (Resources ("videos", UniversalId::Type_Video)); + // maybe we could go over the osgDB::Registry to list all supported node formats + + static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", 0 }; + + addResources (Resources (vfs, "meshes", UniversalId::Type_Mesh, sMeshTypes)); + addResources (Resources (vfs, "icons", UniversalId::Type_Icon)); + addResources (Resources (vfs, "music", UniversalId::Type_Music)); + addResources (Resources (vfs, "sound", UniversalId::Type_SoundRes)); + addResources (Resources (vfs, "textures", UniversalId::Type_Texture)); + addResources (Resources (vfs, "videos", UniversalId::Type_Video)); +} + +const VFS::Manager* CSMWorld::ResourcesManager::getVFS() const +{ + return mVFS; } const CSMWorld::Resources& CSMWorld::ResourcesManager::get (UniversalId::Type type) const diff --git a/apps/opencs/model/world/resourcesmanager.hpp b/apps/opencs/model/world/resourcesmanager.hpp index 18861a6a5a..1ce06f2d3d 100644 --- a/apps/opencs/model/world/resourcesmanager.hpp +++ b/apps/opencs/model/world/resourcesmanager.hpp @@ -6,11 +6,17 @@ #include "universalid.hpp" #include "resources.hpp" +namespace VFS +{ + class Manager; +} + namespace CSMWorld { class ResourcesManager { std::map mResources; + const VFS::Manager* mVFS; private: @@ -18,8 +24,11 @@ namespace CSMWorld public: - /// Ask OGRE for a list of available resources. - void listResources(); + ResourcesManager(); + + const VFS::Manager* getVFS() const; + + void setVFS(const VFS::Manager* vfs); const Resources& get (UniversalId::Type type) const; }; diff --git a/apps/opencs/model/world/resourcetable.cpp b/apps/opencs/model/world/resourcetable.cpp index 2cd44781ab..5227ec3e63 100644 --- a/apps/opencs/model/world/resourcetable.cpp +++ b/apps/opencs/model/world/resourcetable.cpp @@ -1,4 +1,3 @@ - #include "resourcetable.hpp" #include diff --git a/apps/opencs/model/world/scope.cpp b/apps/opencs/model/world/scope.cpp index 6e4ce4c027..b026c34c32 100644 --- a/apps/opencs/model/world/scope.cpp +++ b/apps/opencs/model/world/scope.cpp @@ -1,4 +1,3 @@ - #include "scope.hpp" #include diff --git a/apps/opencs/model/world/scriptcontext.cpp b/apps/opencs/model/world/scriptcontext.cpp index a8c2c94526..344ae322e9 100644 --- a/apps/opencs/model/world/scriptcontext.cpp +++ b/apps/opencs/model/world/scriptcontext.cpp @@ -1,4 +1,3 @@ - #include "scriptcontext.hpp" #include @@ -40,8 +39,6 @@ char CSMWorld::ScriptContext::getGlobalType (const std::string& name) const std::pair CSMWorld::ScriptContext::getMemberType (const std::string& name, const std::string& id) const { - /// \todo invalidate locals cache on change to scripts - std::string id2 = Misc::StringUtils::lowerCase (id); int index = mData.getScripts().searchId (id2); @@ -96,7 +93,7 @@ bool CSMWorld::ScriptContext::isId (const std::string& name) const { mIds = mData.getIds(); - std::for_each (mIds.begin(), mIds.end(), &Misc::StringUtils::toLower); + std::for_each (mIds.begin(), mIds.end(), &Misc::StringUtils::lowerCaseInPlace); std::sort (mIds.begin(), mIds.end()); mIdsUpdated = true; @@ -121,3 +118,18 @@ void CSMWorld::ScriptContext::clear() mIdsUpdated = false; mLocals.clear(); } + +bool CSMWorld::ScriptContext::clearLocals (const std::string& script) +{ + std::map::iterator iter = + mLocals.find (Misc::StringUtils::lowerCase (script)); + + if (iter!=mLocals.end()) + { + mLocals.erase (iter); + mIdsUpdated = false; + return true; + } + + return false; +} diff --git a/apps/opencs/model/world/scriptcontext.hpp b/apps/opencs/model/world/scriptcontext.hpp index 29ee42645a..2cd59f070f 100644 --- a/apps/opencs/model/world/scriptcontext.hpp +++ b/apps/opencs/model/world/scriptcontext.hpp @@ -46,6 +46,9 @@ namespace CSMWorld void clear(); ///< Remove all cached data. + + /// \return Were there any locals that needed clearing? + bool clearLocals (const std::string& script); }; } diff --git a/apps/opencs/model/world/subcellcollection.hpp b/apps/opencs/model/world/subcellcollection.hpp index df1f8a12ea..496cb06430 100644 --- a/apps/opencs/model/world/subcellcollection.hpp +++ b/apps/opencs/model/world/subcellcollection.hpp @@ -20,7 +20,7 @@ namespace CSMWorld { const IdCollection& mCells; - virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader); + virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted); public: @@ -29,9 +29,10 @@ namespace CSMWorld template void SubCellCollection::loadRecord (ESXRecordT& record, - ESM::ESMReader& reader) + ESM::ESMReader& reader, + bool& isDeleted) { - record.load (reader, mCells); + record.load (reader, isDeleted, mCells); } template diff --git a/apps/opencs/model/world/tablemimedata.cpp b/apps/opencs/model/world/tablemimedata.cpp index d40e0c217c..1268a7389d 100644 --- a/apps/opencs/model/world/tablemimedata.cpp +++ b/apps/opencs/model/world/tablemimedata.cpp @@ -179,7 +179,7 @@ CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::Univers } } - throw std::runtime_error ("TableMimeData object does not hold object of the seeked type"); + throw std::runtime_error ("TableMimeData object does not hold object of the sought type"); } CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::ColumnBase::Display type) const @@ -201,7 +201,7 @@ CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::ColumnB } } - throw std::runtime_error ("TableMimeData object does not hold object of the seeked type"); + throw std::runtime_error ("TableMimeData object does not hold object of the sought type"); } bool CSMWorld::TableMimeData::fromDocument (const CSMDoc::Document& document) const @@ -264,6 +264,8 @@ namespace { CSMWorld::UniversalId::Type_Texture, CSMWorld::ColumnBase::Display_Texture }, { CSMWorld::UniversalId::Type_Video, CSMWorld::ColumnBase::Display_Video }, { CSMWorld::UniversalId::Type_Global, CSMWorld::ColumnBase::Display_GlobalVariable }, + { CSMWorld::UniversalId::Type_BodyPart, CSMWorld::ColumnBase::Display_BodyPart }, + { CSMWorld::UniversalId::Type_Enchantment, CSMWorld::ColumnBase::Display_Enchantment }, { CSMWorld::UniversalId::Type_None, CSMWorld::ColumnBase::Display_None } // end marker }; diff --git a/apps/opencs/model/world/tablemimedata.hpp b/apps/opencs/model/world/tablemimedata.hpp index 06d252435f..a42e975618 100644 --- a/apps/opencs/model/world/tablemimedata.hpp +++ b/apps/opencs/model/world/tablemimedata.hpp @@ -1,4 +1,3 @@ - #ifndef TABLEMIMEDATA_H #define TABLEMIMEDATA_H diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index e496fe79bb..77db2be108 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -1,4 +1,3 @@ - #include "universalid.hpp" #include @@ -56,6 +55,7 @@ namespace { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MagicEffects, "Magic Effects", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_StartScripts, "Start Scripts", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Meta Data Table", 0 }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; @@ -120,6 +120,7 @@ namespace { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Meta Data", 0 }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 0a9fa38473..e9104fc226 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -131,6 +131,8 @@ namespace CSMWorld Type_StartScripts, Type_StartScript, Type_Search, + Type_MetaDatas, + Type_MetaData, Type_RunLog }; diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index 6571ad7c81..620d853ac4 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -1,4 +1,3 @@ - #include "adjusterwidget.hpp" #include @@ -64,6 +63,7 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) QString message; mValid = (!name.isEmpty()); + bool warning = false; if (!mValid) { @@ -106,13 +106,14 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) { /// \todo add an user setting to make this an error. message += "

A file with the same name already exists. If you continue, it will be overwritten."; + warning = true; } } } mMessage->setText (message); mIcon->setPixmap (style()->standardIcon ( - mValid ? QStyle::SP_MessageBoxInformation : QStyle::SP_MessageBoxWarning). + mValid ? (warning ? QStyle::SP_MessageBoxWarning : QStyle::SP_MessageBoxInformation) : QStyle::SP_MessageBoxCritical). pixmap (QSize (16, 16))); emit stateChanged (mValid); diff --git a/apps/opencs/view/doc/filewidget.cpp b/apps/opencs/view/doc/filewidget.cpp index f18fe695a0..9e9acdfbe6 100644 --- a/apps/opencs/view/doc/filewidget.cpp +++ b/apps/opencs/view/doc/filewidget.cpp @@ -1,4 +1,3 @@ - #include "filewidget.hpp" #include @@ -17,7 +16,6 @@ CSVDoc::FileWidget::FileWidget (QWidget *parent) : QWidget (parent), mAddon (fal QHBoxLayout *layout = new QHBoxLayout (this); mInput = new QLineEdit (this); - mInput->setValidator (new QRegExpValidator(QRegExp("^[a-zA-Z0-9_-\\s]*$"))); layout->addWidget (mInput, 1); @@ -56,3 +54,11 @@ void CSVDoc::FileWidget::extensionLabelIsVisible(bool visible) { mType->setVisible(visible); } + +void CSVDoc::FileWidget::setName (const std::string& text) +{ + QString text2 = QString::fromUtf8 (text.c_str()); + + mInput->setText (text2); + textChanged (text2); +} diff --git a/apps/opencs/view/doc/filewidget.hpp b/apps/opencs/view/doc/filewidget.hpp index ff09d71a39..ab06f37f18 100644 --- a/apps/opencs/view/doc/filewidget.hpp +++ b/apps/opencs/view/doc/filewidget.hpp @@ -3,6 +3,8 @@ #include +#include + class QLabel; class QString; class QLineEdit; @@ -29,6 +31,8 @@ namespace CSVDoc void extensionLabelIsVisible(bool visible); + void setName (const std::string& text); + private slots: void textChanged (const QString& text); diff --git a/apps/opencs/view/doc/globaldebugprofilemenu.cpp b/apps/opencs/view/doc/globaldebugprofilemenu.cpp index b883813859..f0d9655dd0 100644 --- a/apps/opencs/view/doc/globaldebugprofilemenu.cpp +++ b/apps/opencs/view/doc/globaldebugprofilemenu.cpp @@ -1,4 +1,3 @@ - #include "globaldebugprofilemenu.hpp" #include diff --git a/apps/opencs/view/doc/loader.cpp b/apps/opencs/view/doc/loader.cpp index 30235d0f5c..713295d702 100644 --- a/apps/opencs/view/doc/loader.cpp +++ b/apps/opencs/view/doc/loader.cpp @@ -1,4 +1,3 @@ - #include "loader.hpp" #include diff --git a/apps/opencs/view/doc/newgame.cpp b/apps/opencs/view/doc/newgame.cpp index 32b4837285..b3e2a4f637 100644 --- a/apps/opencs/view/doc/newgame.cpp +++ b/apps/opencs/view/doc/newgame.cpp @@ -1,4 +1,3 @@ - #include "newgame.hpp" #include diff --git a/apps/opencs/view/doc/operation.cpp b/apps/opencs/view/doc/operation.cpp index 95cbf012d6..e5c1e7b89e 100644 --- a/apps/opencs/view/doc/operation.cpp +++ b/apps/opencs/view/doc/operation.cpp @@ -19,6 +19,7 @@ void CSVDoc::Operation::updateLabel (int threads) case CSMDoc::State_Saving: name = "saving"; break; case CSMDoc::State_Verifying: name = "verifying"; break; case CSMDoc::State_Searching: name = "searching"; break; + case CSMDoc::State_Merging: name = "merging"; break; } std::ostringstream stream; @@ -122,7 +123,7 @@ void CSVDoc::Operation::setBarColor (int type) bottomColor = "#9ECB2D"; //green gloss break; - case CSMDoc::State_Compiling: + case CSMDoc::State_Merging: topColor = "#F3E2C7"; midTopColor = "#C19E67"; diff --git a/apps/opencs/view/doc/runlogsubview.cpp b/apps/opencs/view/doc/runlogsubview.cpp index 1293969996..2b7182228b 100644 --- a/apps/opencs/view/doc/runlogsubview.cpp +++ b/apps/opencs/view/doc/runlogsubview.cpp @@ -1,4 +1,3 @@ - #include "runlogsubview.hpp" #include diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index 58a46c603b..67ff50dab9 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -1,4 +1,3 @@ - #include "startup.hpp" #include @@ -105,14 +104,16 @@ CSVDoc::StartupDialogue::StartupDialogue() : mWidth (0), mColumn (2) layout->addWidget (createButtons()); layout->addWidget (createTools()); - /// \todo remove this label once loading and saving are fully implemented - QLabel *warning = new QLabel ("WARNING:

OpenCS is in alpha stage.
The code for loading and saving is incomplete.
This version of OpenCS is only a preview.
Do NOT use it for real editing!
You will lose records both on loading and on saving.

Please note:
If you lose data and come to the OpenMW forum to complain,
we will mock you.
"); + /// \todo remove this label once we are feature complete and convinced that this thing is + /// working properly. + QLabel *warning = new QLabel ("WARNING: OpenMW-CS is in alpha stage.

The editor is not feature complete and not sufficiently tested.
In theory your data should be safe. But we strongly advice to make backups regularly if you are working with live data.
"); QFont font; font.setPointSize (12); font.setBold (true); warning->setFont (font); + warning->setWordWrap (true); layout->addWidget (warning, 1); diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp index f4f0c6afe8..67a8f8c705 100644 --- a/apps/opencs/view/doc/subview.cpp +++ b/apps/opencs/view/doc/subview.cpp @@ -16,7 +16,7 @@ bool CSVDoc::SubView::event (QEvent *event) emit closeRequest(); return true; } - + return QDockWidget::event (event); } @@ -38,13 +38,11 @@ void CSVDoc::SubView::setStatusBar (bool show) {} void CSVDoc::SubView::useHint (const std::string& hint) {} -void CSVDoc::SubView::updateUserSetting (const QString &, const QStringList &) -{} - void CSVDoc::SubView::setUniversalId (const CSMWorld::UniversalId& id) { mUniversalId = id; setWindowTitle (QString::fromUtf8(mUniversalId.toString().c_str())); + emit universalIdChanged (mUniversalId); } void CSVDoc::SubView::closeEvent (QCloseEvent *event) diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp index b323f9ed92..1c5f8a7866 100644 --- a/apps/opencs/view/doc/subview.hpp +++ b/apps/opencs/view/doc/subview.hpp @@ -52,8 +52,6 @@ namespace CSVDoc virtual std::string getTitle() const; - virtual void updateUserSetting (const QString& name, const QStringList& value); - private: void closeEvent (QCloseEvent *event); @@ -68,6 +66,8 @@ namespace CSVDoc void updateSubViewIndicies (SubView *view = 0); + void universalIdChanged (const CSMWorld::UniversalId& universalId); + protected slots: void closeRequest(); diff --git a/apps/opencs/view/doc/subviewfactory.cpp b/apps/opencs/view/doc/subviewfactory.cpp index 3137f7e324..82a8aeb054 100644 --- a/apps/opencs/view/doc/subviewfactory.cpp +++ b/apps/opencs/view/doc/subviewfactory.cpp @@ -1,4 +1,3 @@ - #include "subviewfactory.hpp" #include diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 64b066eb1e..3491c9de2b 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -15,7 +15,7 @@ #include #include "../../model/doc/document.hpp" -#include "../../model/settings/usersettings.hpp" +#include "../../model/prefs/state.hpp" #include "../../model/world/idtable.hpp" @@ -66,10 +66,18 @@ void CSVDoc::View::setupFileMenu() connect (mVerify, SIGNAL (triggered()), this, SLOT (verify())); file->addAction (mVerify); + mMerge = new QAction (tr ("Merge"), this); + connect (mMerge, SIGNAL (triggered()), this, SLOT (merge())); + file->addAction (mMerge); + QAction *loadErrors = new QAction (tr ("Load Error Log"), this); connect (loadErrors, SIGNAL (triggered()), this, SLOT (loadErrorLog())); file->addAction (loadErrors); + QAction *meta = new QAction (tr ("Meta Data"), this); + connect (meta, SIGNAL (triggered()), this, SLOT (addMetaDataSubView())); + file->addAction (meta); + QAction *close = new QAction (tr ("&Close"), this); connect (close, SIGNAL (triggered()), this, SLOT (close())); file->addAction(close); @@ -113,10 +121,9 @@ void CSVDoc::View::setupViewMenu() mShowStatusBar = new QAction (tr ("Show Status Bar"), this); mShowStatusBar->setCheckable (true); connect (mShowStatusBar, SIGNAL (toggled (bool)), this, SLOT (toggleShowStatusBar (bool))); - std::string showStatusBar = - CSMSettings::UserSettings::instance().settingValue("window/show-statusbar").toStdString(); - if(showStatusBar == "true") - mShowStatusBar->setChecked(true); + + mShowStatusBar->setChecked (CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); + view->addAction (mShowStatusBar); QAction *filters = new QAction (tr ("Filters"), this); @@ -325,9 +332,9 @@ void CSVDoc::View::updateTitle() if (mViewTotal>1) stream << " [" << (mViewIndex+1) << "/" << mViewTotal << "]"; - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; - bool hideTitle = userSettings.setting ("window/hide-subview", QString ("false"))=="true" && + bool hideTitle = windows["hide-subview"].isTrue() && mSubViews.size()==1 && !mSubViews.at (0)->isFloating(); if (hideTitle) @@ -338,19 +345,18 @@ void CSVDoc::View::updateTitle() void CSVDoc::View::updateSubViewIndicies(SubView *view) { + CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; + if(view && mSubViews.contains(view)) { mSubViews.removeOne(view); // adjust (reduce) the scroll area (even floating), except when it is "Scrollbar Only" - CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); - if(settings.settingValue ("window/mainwindow-scrollbar") == "Grow then Scroll") + if (windows["mainwindow-scrollbar"].toString() == "Grow then Scroll") updateScrollbar(); } - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); - - bool hideTitle = userSettings.setting ("window/hide-subview", QString ("false"))=="true" && + bool hideTitle = windows["hide-subview"].isTrue() && mSubViews.size()==1 && !mSubViews.at (0)->isFloating(); updateTitle(); @@ -389,27 +395,25 @@ void CSVDoc::View::updateActions() mGlobalDebugProfileMenu->updateActions (running); mStopDebug->setEnabled (running); + + mMerge->setEnabled (mDocument->getContentFiles().size()>1 && + !(mDocument->getState() & CSMDoc::State_Merging)); } CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews) : mViewManager (viewManager), mDocument (document), mViewIndex (totalViews-1), mViewTotal (totalViews), mScroll(0), mScrollbarOnly(false) { - int width = CSMSettings::UserSettings::instance().settingValue - ("window/default-width").toInt(); + CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; - int height = CSMSettings::UserSettings::instance().settingValue - ("window/default-height").toInt(); - - width = std::max(width, 300); - height = std::max(height, 300); + int width = std::max (windows["default-width"].toInt(), 300); + int height = std::max (windows["default-height"].toInt(), 300); resize (width, height); mSubViewWindow.setDockOptions (QMainWindow::AllowNestedDocks); - CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); - if(settings.settingValue ("window/mainwindow-scrollbar") == "Grow Only") + if (windows["mainwindow-scrollbar"].toString() == "Grow Only") { setCentralWidget (&mSubViewWindow); } @@ -424,6 +428,8 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to mOperations = new Operations; addDockWidget (Qt::BottomDockWidgetArea, mOperations); + setContextMenuPolicy(Qt::NoContextMenu); + updateTitle(); setupUi(); @@ -436,6 +442,9 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to mSubViewFactory.add (CSMWorld::UniversalId::Type_RunLog, new SubViewFactory); connect (mOperations, SIGNAL (abortOperation (int)), this, SLOT (abortOperation (int))); + + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); } CSVDoc::View::~View() @@ -467,6 +476,7 @@ void CSVDoc::View::updateDocumentState() static const int operations[] = { CSMDoc::State_Saving, CSMDoc::State_Verifying, CSMDoc::State_Searching, + CSMDoc::State_Merging, -1 // end marker }; @@ -489,14 +499,12 @@ void CSVDoc::View::updateProgress (int current, int max, int type, int threads) void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::string& hint) { - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; bool isReferenceable = id.getClass() == CSMWorld::UniversalId::Class_RefRecord; // User setting to reuse sub views (on a per top level view basis) - bool reuse = - userSettings.setting ("window/reuse", QString("true")) == "true" ? true : false; - if(reuse) + if (windows["reuse"].isTrue()) { foreach(SubView *sb, mSubViews) { @@ -524,8 +532,7 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin // // If the sub view limit setting is one, the sub view title bar is hidden and the // text in the main title bar is adjusted accordingly - int maxSubView = userSettings.setting("window/max-subviews", QString("256")).toInt(); - if(mSubViews.size() >= maxSubView) // create a new top level view + if(mSubViews.size() >= windows["max-subviews"].toInt()) // create a new top level view { mViewManager.addView(mDocument, id, hint); @@ -545,8 +552,8 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin view->setParent(this); mSubViews.append(view); // only after assert - int minWidth = userSettings.setting ("window/minimum-width", QString("325")).toInt(); - view->setMinimumWidth(minWidth); + int minWidth = windows["minimum-width"].toInt(); + view->setMinimumWidth (minWidth); view->setStatusBar (mShowStatusBar->isChecked()); @@ -561,13 +568,11 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin // should become visible) // - Move the scroll bar to the newly added subview // - CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); - QString mainwinScroll = settings.settingValue ("window/mainwindow-scrollbar"); - mScrollbarOnly = mainwinScroll.isEmpty() || mainwinScroll == "Scrollbar Only"; + mScrollbarOnly = windows["mainwindow-scrollbar"].toString() == "Scrollbar Only"; QDesktopWidget *dw = QApplication::desktop(); QRect rect; - if(settings.settingValue ("window/grow-limit") == "true") + if (windows["grow-limit"].isTrue()) rect = dw->screenGeometry(this); else rect = dw->screenGeometry(dw->screen(dw->screenNumber(this))); @@ -623,6 +628,45 @@ void CSVDoc::View::moveScrollBarToEnd(int min, int max) } } +void CSVDoc::View::settingChanged (const CSMPrefs::Setting *setting) +{ + if (*setting=="Windows/hide-subview") + updateSubViewIndicies (0); + else if (*setting=="Windows/mainwindow-scrollbar") + { + if (setting->toString()!="Grow Only") + { + if (mScroll) + { + if (setting->toString()=="Scrollbar Only") + { + mScrollbarOnly = true; + mSubViewWindow.setMinimumWidth(0); + } + else if (mScrollbarOnly) + { + mScrollbarOnly = false; + updateScrollbar(); + } + } + else + { + mScroll = new QScrollArea(this); + mScroll->setWidgetResizable(true); + mScroll->setWidget(&mSubViewWindow); + setCentralWidget(mScroll); + } + } + else if (mScroll) + { + mScroll->takeWidget(); + setCentralWidget (&mSubViewWindow); + mScroll->deleteLater(); + mScroll = 0; + } + } +} + void CSVDoc::View::newView() { mViewManager.addView (mDocument); @@ -813,6 +857,11 @@ void CSVDoc::View::addSearchSubView() addSubView (mDocument->newSearch()); } +void CSVDoc::View::addMetaDataSubView() +{ + addSubView (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_MetaData, "sys::meta")); +} + void CSVDoc::View::abortOperation (int type) { mDocument->abortOperation (type); @@ -841,59 +890,6 @@ void CSVDoc::View::resizeViewHeight (int height) resize (geometry().width(), height); } -void CSVDoc::View::updateUserSetting (const QString &name, const QStringList &list) -{ - if (name=="window/hide-subview") - updateSubViewIndicies (0); - - foreach (SubView *subView, mSubViews) - { - subView->updateUserSetting (name, list); - } - - if (name=="window/mainwindow-scrollbar") - { - if(list.at(0) != "Grow Only") - { - if (mScroll) - { - if (list.at(0).isEmpty() || list.at(0) == "Scrollbar Only") - { - mScrollbarOnly = true; - mSubViewWindow.setMinimumWidth(0); - } - else - { - if(!mScrollbarOnly) - return; - - mScrollbarOnly = false; - updateScrollbar(); - } - } - else - { - mScroll = new QScrollArea(this); - mScroll->setWidgetResizable(true); - mScroll->setWidget(&mSubViewWindow); - setCentralWidget(mScroll); - } - } - else - { - if (mScroll) - { - mScroll->takeWidget(); - setCentralWidget (&mSubViewWindow); - mScroll->deleteLater(); - mScroll = 0; - } - else - return; - } - } -} - void CSVDoc::View::toggleShowStatusBar (bool show) { foreach (QObject *view, mSubViewWindow.children()) @@ -925,10 +921,9 @@ void CSVDoc::View::stop() void CSVDoc::View::closeRequest (SubView *subView) { - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; - if (mSubViews.size()>1 || mViewTotal<=1 || - userSettings.setting ("window/hide-subview", QString ("false"))!="true") + if (mSubViews.size()>1 || mViewTotal<=1 || !windows["hide-subview"].isTrue()) { subView->deleteLater(); mSubViews.removeOne (subView); @@ -959,3 +954,8 @@ void CSVDoc::View::updateScrollbar() else mSubViewWindow.setMinimumWidth(0); } + +void CSVDoc::View::merge() +{ + emit mergeDocument (mDocument); +} diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 814dabc6b5..7d53042693 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -22,6 +22,11 @@ namespace CSMWorld class UniversalId; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSVDoc { class ViewManager; @@ -43,6 +48,7 @@ namespace CSVDoc QAction *mVerify; QAction *mShowStatusBar; QAction *mStopDebug; + QAction *mMerge; std::vector mEditingActions; Operations *mOperations; SubViewFactoryManager mSubViewFactory; @@ -82,8 +88,6 @@ namespace CSVDoc void exitApplication(); - void loadUserSettings(); - /// User preference function void resizeViewWidth (int width); @@ -114,9 +118,6 @@ namespace CSVDoc Operations *getOperations() const; - /// Function called by view manager when user preferences are updated - void updateEditorSetting (const QString &, const QString &); - signals: void newGameRequest(); @@ -129,6 +130,8 @@ namespace CSVDoc void editSettingsRequest(); + void mergeDocument (CSMDoc::Document *document); + public slots: void addSubView (const CSMWorld::UniversalId& id, const std::string& hint = ""); @@ -137,8 +140,6 @@ namespace CSVDoc void abortOperation (int type); - void updateUserSetting (const QString &, const QStringList &); - void updateTitle(); // called when subviews are added or removed @@ -146,6 +147,8 @@ namespace CSVDoc private slots: + void settingChanged (const CSMPrefs::Setting *setting); + void newView(); void save(); @@ -224,6 +227,8 @@ namespace CSVDoc void addSearchSubView(); + void addMetaDataSubView(); + void toggleShowStatusBar (bool show); void loadErrorLog(); @@ -235,6 +240,8 @@ namespace CSVDoc void closeRequest (SubView *subView); void moveScrollBarToEnd(int min, int max); + + void merge(); }; } diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 7aec001cf6..024636f34b 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -1,4 +1,3 @@ - #include "viewmanager.hpp" #include @@ -15,6 +14,8 @@ #include "../../model/world/universalid.hpp" #include "../../model/world/idcompletionmanager.hpp" +#include "../../model/prefs/state.hpp" + #include "../world/util.hpp" #include "../world/enumdelegate.hpp" #include "../world/vartypedelegate.hpp" @@ -23,8 +24,6 @@ #include "../world/idcompletiondelegate.hpp" #include "../world/colordelegate.hpp" -#include "../../model/settings/usersettings.hpp" - #include "view.hpp" void CSVDoc::ViewManager::updateIndices() @@ -97,16 +96,17 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_MeshType, CSMWorld::Columns::ColumnId_MeshType, false }, { CSMWorld::ColumnBase::Display_Gender, CSMWorld::Columns::ColumnId_Gender, true }, { CSMWorld::ColumnBase::Display_SoundGeneratorType, CSMWorld::Columns::ColumnId_SoundGeneratorType, false }, - { CSMWorld::ColumnBase::Display_School, CSMWorld::Columns::ColumnId_School, true }, - { CSMWorld::ColumnBase::Display_SkillImpact, CSMWorld::Columns::ColumnId_SkillImpact, true }, + { CSMWorld::ColumnBase::Display_School, CSMWorld::Columns::ColumnId_School, false }, + { CSMWorld::ColumnBase::Display_SkillId, CSMWorld::Columns::ColumnId_Skill, true }, { CSMWorld::ColumnBase::Display_EffectRange, CSMWorld::Columns::ColumnId_EffectRange, false }, { CSMWorld::ColumnBase::Display_EffectId, CSMWorld::Columns::ColumnId_EffectId, false }, { CSMWorld::ColumnBase::Display_PartRefType, CSMWorld::Columns::ColumnId_PartRefType, false }, { CSMWorld::ColumnBase::Display_AiPackageType, CSMWorld::Columns::ColumnId_AiPackageType, false }, - { CSMWorld::ColumnBase::Display_YesNo, CSMWorld::Columns::ColumnId_AiWanderRepeat, false }, { CSMWorld::ColumnBase::Display_InfoCondFunc, CSMWorld::Columns::ColumnId_InfoCondFunc, false }, { CSMWorld::ColumnBase::Display_InfoCondComp, CSMWorld::Columns::ColumnId_InfoCondComp, false }, - { CSMWorld::ColumnBase::Display_RaceSkill, CSMWorld::Columns::ColumnId_RaceSkill, true }, + { CSMWorld::ColumnBase::Display_IngredEffectId, CSMWorld::Columns::ColumnId_EffectId, true }, + { CSMWorld::ColumnBase::Display_EffectSkill, CSMWorld::Columns::ColumnId_Skill, false }, + { CSMWorld::ColumnBase::Display_EffectAttribute, CSMWorld::Columns::ColumnId_Attribute, false }, }; for (std::size_t i=0; itoggleStatusBar (showStatusBar == "true"); + view->toggleStatusBar (CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); view->show(); connect (view, SIGNAL (newGameRequest ()), this, SIGNAL (newGameRequest())); connect (view, SIGNAL (newAddonRequest ()), this, SIGNAL (newAddonRequest())); connect (view, SIGNAL (loadDocumentRequest ()), this, SIGNAL (loadDocumentRequest())); connect (view, SIGNAL (editSettingsRequest()), this, SIGNAL (editSettingsRequest())); - - connect (&CSMSettings::UserSettings::instance(), - SIGNAL (userSettingUpdated(const QString &, const QStringList &)), - view, - SLOT (updateUserSetting (const QString &, const QStringList &))); + connect (view, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SIGNAL (mergeDocument (CSMDoc::Document *))); updateIndices(); diff --git a/apps/opencs/view/doc/viewmanager.hpp b/apps/opencs/view/doc/viewmanager.hpp index cdd5ac7686..70431107f6 100644 --- a/apps/opencs/view/doc/viewmanager.hpp +++ b/apps/opencs/view/doc/viewmanager.hpp @@ -77,6 +77,8 @@ namespace CSVDoc void editSettingsRequest(); + void mergeDocument (CSMDoc::Document *document); + public slots: void exitApplication (CSVDoc::View *view); diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index bc7f9b5a16..600fa4f3bf 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -1,4 +1,3 @@ - #include "editwidget.hpp" #include @@ -6,14 +5,17 @@ #include #include "../../model/world/data.hpp" +#include "../../model/world/idtablebase.hpp" +#include "../../model/world/columns.hpp" CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) -: QLineEdit (parent), mParser (data) +: QLineEdit (parent), mParser (data), mIsEmpty(true) { mPalette = palette(); connect (this, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); - QAbstractItemModel *model = data.getTableModel (CSMWorld::UniversalId::Type_Filters); + const CSMWorld::IdTableBase *model = + static_cast (data.getTableModel (CSMWorld::UniversalId::Type_Filters)); connect (model, SIGNAL (dataChanged (const QModelIndex &, const QModelIndex&)), this, SLOT (filterDataChanged (const QModelIndex &, const QModelIndex&)), @@ -24,10 +26,23 @@ CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) connect (model, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (filterRowsInserted (const QModelIndex&, int, int)), Qt::QueuedConnection); + + mStateColumnIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Modification); + mDescColumnIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Description); } void CSVFilter::EditWidget::textChanged (const QString& text) { + //no need to parse and apply filter if it was empty and now is empty too. + //e.g. - we modifiing content of filter with already opened some other (big) tables. + if (text.length() == 0){ + if (mIsEmpty) + return; + else + mIsEmpty = true; + }else + mIsEmpty = false; + if (mParser.parse (text.toUtf8().constData())) { setPalette (mPalette); @@ -46,7 +61,9 @@ void CSVFilter::EditWidget::textChanged (const QString& text) void CSVFilter::EditWidget::filterDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { - textChanged (text()); + for (int i = topLeft.column(); i <= bottomRight.column(); ++i) + if (i != mStateColumnIndex && i != mDescColumnIndex) + textChanged (text()); } void CSVFilter::EditWidget::filterRowsRemoved (const QModelIndex& parent, int start, int end) diff --git a/apps/opencs/view/filter/editwidget.hpp b/apps/opencs/view/filter/editwidget.hpp index a0f9f8919a..f672877d94 100644 --- a/apps/opencs/view/filter/editwidget.hpp +++ b/apps/opencs/view/filter/editwidget.hpp @@ -25,6 +25,9 @@ namespace CSVFilter CSMFilter::Parser mParser; QPalette mPalette; + bool mIsEmpty; + int mStateColumnIndex; + int mDescColumnIndex; public: diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp index 7a42ef0a57..c6c6cc6ccc 100644 --- a/apps/opencs/view/filter/filterbox.cpp +++ b/apps/opencs/view/filter/filterbox.cpp @@ -1,4 +1,3 @@ - #include "filterbox.hpp" #include diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp index 97490d5083..2bf589215e 100644 --- a/apps/opencs/view/filter/recordfilterbox.cpp +++ b/apps/opencs/view/filter/recordfilterbox.cpp @@ -1,4 +1,3 @@ - #include "recordfilterbox.hpp" #include diff --git a/apps/opencs/view/prefs/dialogue.cpp b/apps/opencs/view/prefs/dialogue.cpp new file mode 100644 index 0000000000..f040926530 --- /dev/null +++ b/apps/opencs/view/prefs/dialogue.cpp @@ -0,0 +1,126 @@ + +#include "dialogue.hpp" + +#include +#include +#include +#include +#include +#include + +#include "../../model/prefs/state.hpp" + +#include "page.hpp" + +void CSVPrefs::Dialogue::buildCategorySelector (QSplitter *main) +{ + mList = new QListWidget (main); + mList->setMinimumWidth (50); + mList->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding); + + mList->setSelectionBehavior (QAbstractItemView::SelectItems); + + main->addWidget (mList); + + QFontMetrics metrics (QApplication::font()); + + int maxWidth = 1; + + for (CSMPrefs::State::Iterator iter = CSMPrefs::get().begin(); iter!=CSMPrefs::get().end(); + ++iter) + { + QString label = QString::fromUtf8 (iter->second.getKey().c_str()); + maxWidth = std::max (maxWidth, metrics.width (label)); + + mList->addItem (label); + } + + mList->setMaximumWidth (maxWidth + 10); + + connect (mList, SIGNAL (currentItemChanged (QListWidgetItem *, QListWidgetItem *)), + this, SLOT (selectionChanged (QListWidgetItem *, QListWidgetItem *))); +} + +void CSVPrefs::Dialogue::buildContentArea (QSplitter *main) +{ + mContent = new QStackedWidget (main); + mContent->setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Expanding); + + main->addWidget (mContent); +} + +CSVPrefs::PageBase *CSVPrefs::Dialogue::makePage (const std::string& key) +{ + // special case page code goes here + + return new Page (CSMPrefs::get()[key], mContent); +} + +CSVPrefs::Dialogue::Dialogue() +{ + setWindowTitle ("User Settings"); + + setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + + setMinimumSize (600, 400); + + QSplitter *main = new QSplitter (this); + + setCentralWidget (main); + buildCategorySelector (main); + buildContentArea (main); +} + +CSVPrefs::Dialogue::~Dialogue() +{ + if (isVisible()) + CSMPrefs::State::get().save(); +} + +void CSVPrefs::Dialogue::closeEvent (QCloseEvent *event) +{ + QMainWindow::closeEvent (event); + + CSMPrefs::State::get().save(); +} + +void CSVPrefs::Dialogue::show() +{ + if (QWidget *active = QApplication::activeWindow()) + { + // place at the centre of the window with focus + QSize size = active->size(); + move (active->geometry().x()+(size.width() - frameGeometry().width())/2, + active->geometry().y()+(size.height() - frameGeometry().height())/2); + } + else + { + // otherwise place at the centre of the screen + QPoint screenCenter = QApplication::desktop()->screenGeometry().center(); + move (screenCenter - QPoint(frameGeometry().width()/2, frameGeometry().height()/2)); + } + + QWidget::show(); +} + +void CSVPrefs::Dialogue::selectionChanged (QListWidgetItem *current, QListWidgetItem *previous) +{ + if (current) + { + std::string key = current->text().toUtf8().data(); + + for (int i=0; icount(); ++i) + { + PageBase& page = dynamic_cast (*mContent->widget (i)); + + if (page.getCategory().getKey()==key) + { + mContent->setCurrentIndex (i); + return; + } + } + + PageBase *page = makePage (key); + mContent->setCurrentIndex (mContent->addWidget (page)); + } +} diff --git a/apps/opencs/view/prefs/dialogue.hpp b/apps/opencs/view/prefs/dialogue.hpp new file mode 100644 index 0000000000..fc66892c8b --- /dev/null +++ b/apps/opencs/view/prefs/dialogue.hpp @@ -0,0 +1,50 @@ +#ifndef CSV_PREFS_DIALOGUE_H +#define CSV_PREFS_DIALOGUE_H + +#include + +class QSplitter; +class QListWidget; +class QStackedWidget; +class QListWidgetItem; + +namespace CSVPrefs +{ + class PageBase; + + class Dialogue : public QMainWindow + { + Q_OBJECT + + QListWidget *mList; + QStackedWidget *mContent; + + private: + + void buildCategorySelector (QSplitter *main); + + void buildContentArea (QSplitter *main); + + PageBase *makePage (const std::string& key); + + public: + + Dialogue(); + + virtual ~Dialogue(); + + protected: + + void closeEvent (QCloseEvent *event); + + public slots: + + void show(); + + private slots: + + void selectionChanged (QListWidgetItem *current, QListWidgetItem *previous); + }; +} + +#endif diff --git a/apps/opencs/view/prefs/page.cpp b/apps/opencs/view/prefs/page.cpp new file mode 100644 index 0000000000..c23e9f64fe --- /dev/null +++ b/apps/opencs/view/prefs/page.cpp @@ -0,0 +1,40 @@ + +#include "page.hpp" + +#include + +#include "../../model/prefs/setting.hpp" +#include "../../model/prefs/category.hpp" + +CSVPrefs::Page::Page (CSMPrefs::Category& category, QWidget *parent) +: PageBase (category, parent) +{ + QWidget *widget = new QWidget (parent); + mGrid = new QGridLayout (widget); + + for (CSMPrefs::Category::Iterator iter = category.begin(); iter!=category.end(); ++iter) + addSetting (*iter); + + setWidget (widget); +} + +void CSVPrefs::Page::addSetting (CSMPrefs::Setting *setting) +{ + std::pair widgets = setting->makeWidgets (this); + + int next = mGrid->rowCount(); + + if (widgets.first) + { + mGrid->addWidget (widgets.first, next, 0); + mGrid->addWidget (widgets.second, next, 1); + } + else if (widgets.second) + { + mGrid->addWidget (widgets.second, next, 0, 1, 2); + } + else + { + mGrid->addWidget (new QWidget (this), next, 0); + } +} diff --git a/apps/opencs/view/prefs/page.hpp b/apps/opencs/view/prefs/page.hpp new file mode 100644 index 0000000000..ce13e5d9b9 --- /dev/null +++ b/apps/opencs/view/prefs/page.hpp @@ -0,0 +1,29 @@ +#ifndef CSV_PREFS_PAGE_H +#define CSV_PREFS_PAGE_H + +#include "pagebase.hpp" + +class QGridLayout; + +namespace CSMPrefs +{ + class Setting; +} + +namespace CSVPrefs +{ + class Page : public PageBase + { + Q_OBJECT + + QGridLayout *mGrid; + + public: + + Page (CSMPrefs::Category& category, QWidget *parent); + + void addSetting (CSMPrefs::Setting *setting); + }; +} + +#endif diff --git a/apps/opencs/view/prefs/pagebase.cpp b/apps/opencs/view/prefs/pagebase.cpp new file mode 100644 index 0000000000..16684a69d4 --- /dev/null +++ b/apps/opencs/view/prefs/pagebase.cpp @@ -0,0 +1,13 @@ + +#include "pagebase.hpp" + +#include "../../model/prefs/category.hpp" + +CSVPrefs::PageBase::PageBase (CSMPrefs::Category& category, QWidget *parent) +: QScrollArea (parent), mCategory (category) +{} + +CSMPrefs::Category& CSVPrefs::PageBase::getCategory() +{ + return mCategory; +} diff --git a/apps/opencs/view/prefs/pagebase.hpp b/apps/opencs/view/prefs/pagebase.hpp new file mode 100644 index 0000000000..affe49f4a9 --- /dev/null +++ b/apps/opencs/view/prefs/pagebase.hpp @@ -0,0 +1,27 @@ +#ifndef CSV_PREFS_PAGEBASE_H +#define CSV_PREFS_PAGEBASE_H + +#include + +namespace CSMPrefs +{ + class Category; +} + +namespace CSVPrefs +{ + class PageBase : public QScrollArea + { + Q_OBJECT + + CSMPrefs::Category& mCategory; + + public: + + PageBase (CSMPrefs::Category& category, QWidget *parent); + + CSMPrefs::Category& getCategory(); + }; +} + +#endif diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 99658e1c8f..da40f7e7cb 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -1,8 +1,6 @@ - #include "cell.hpp" -#include -#include +#include #include #include @@ -11,7 +9,7 @@ #include "../../model/world/columns.hpp" #include "../../model/world/data.hpp" #include "../../model/world/refcollection.hpp" -#include "../world/physicssystem.hpp" +#include "../../model/world/cellcoordinates.hpp" #include "elements.hpp" #include "terrainstorage.hpp" @@ -34,7 +32,7 @@ bool CSVRender::Cell::addObjects (int start, int end) bool modified = false; const CSMWorld::RefCollection& collection = mData.getReferences(); - + for (int i=start; i<=end; ++i) { std::string cell = Misc::StringUtils::lowerCase (collection.getRecord (i).get().mCell); @@ -45,7 +43,7 @@ bool CSVRender::Cell::addObjects (int start, int end) { std::string id = Misc::StringUtils::lowerCase (collection.getRecord (i).get().mId); - mObjects.insert (std::make_pair (id, new Object (mData, mCellNode, id, false, mPhysics))); + mObjects.insert (std::make_pair (id, new Object (mData, mCellNode, id, false))); modified = true; } } @@ -53,52 +51,50 @@ bool CSVRender::Cell::addObjects (int start, int end) return modified; } -CSVRender::Cell::Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, - const std::string& id, boost::shared_ptr physics, const Ogre::Vector3& origin) -: mData (data), mId (Misc::StringUtils::lowerCase (id)), mPhysics(physics), mSceneMgr(sceneManager), mX(0), mY(0) +CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, + bool deleted) +: mData (data), mId (Misc::StringUtils::lowerCase (id)), mDeleted (deleted) { - mCellNode = sceneManager->getRootSceneNode()->createChildSceneNode(); - mCellNode->setPosition (origin); + std::pair result = CSMWorld::CellCoordinates::fromId (id); - CSMWorld::IdTable& references = dynamic_cast ( - *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + if (result.second) + mCoordinates = result.first; - int rows = references.rowCount(); + mCellNode = new osg::Group; + rootNode->addChild(mCellNode); - addObjects (0, rows-1); - - const CSMWorld::IdCollection& land = mData.getLand(); - int landIndex = land.searchId(mId); - if (landIndex != -1) + if (!mDeleted) { - const ESM::Land* esmLand = land.getRecord(mId).get().mLand.get(); - if(esmLand && esmLand->mDataTypes&ESM::Land::DATA_VHGT) - { - mTerrain.reset(new Terrain::TerrainGrid(sceneManager, new TerrainStorage(mData), Element_Terrain, true, - Terrain::Align_XY)); - mTerrain->loadCell(esmLand->mX, - esmLand->mY); + CSMWorld::IdTable& references = dynamic_cast ( + *mData.getTableModel (CSMWorld::UniversalId::Type_References)); - float verts = ESM::Land::LAND_SIZE; - float worldsize = ESM::Land::REAL_SIZE; - mX = esmLand->mX; - mY = esmLand->mY; - mPhysics->addHeightField(sceneManager, - esmLand->mLandData->mHeights, mX, mY, 0, worldsize / (verts-1), verts); + int rows = references.rowCount(); + + addObjects (0, rows-1); + + const CSMWorld::IdCollection& land = mData.getLand(); + int landIndex = land.searchId(mId); + if (landIndex != -1) + { + const ESM::Land& esmLand = land.getRecord(mId).get(); + + if (esmLand.getLandData (ESM::Land::DATA_VHGT)) + { + mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem().get(), NULL, new TerrainStorage(mData), Element_Terrain<<1)); + mTerrain->loadCell(esmLand.mX, + esmLand.mY); + } } } } CSVRender::Cell::~Cell() { - if (mTerrain.get()) - mPhysics->removeHeightField(mSceneMgr, mX, mY); - for (std::map::iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) delete iter->second; - mCellNode->getCreator()->destroySceneNode (mCellNode); + mCellNode->getParent(0)->removeChild(mCellNode); } bool CSVRender::Cell::referenceableDataChanged (const QModelIndex& topLeft, @@ -133,6 +129,9 @@ bool CSVRender::Cell::referenceableAboutToBeRemoved (const QModelIndex& parent, bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { + if (mDeleted) + return false; + CSMWorld::IdTable& references = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_References)); @@ -186,7 +185,7 @@ bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, for (std::map::iterator iter (ids.begin()); iter!=ids.end(); ++iter) { mObjects.insert (std::make_pair ( - iter->first, new Object (mData, mCellNode, iter->first, false, mPhysics))); + iter->first, new Object (mData, mCellNode, iter->first, false))); modified = true; } @@ -200,6 +199,9 @@ bool CSVRender::Cell::referenceAboutToBeRemoved (const QModelIndex& parent, int if (parent.isValid()) return false; + if (mDeleted) + return false; + CSMWorld::IdTable& references = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_References)); @@ -220,13 +222,57 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int if (parent.isValid()) return false; + if (mDeleted) + return false; + return addObjects (start, end); } -float CSVRender::Cell::getTerrainHeightAt(const Ogre::Vector3 &pos) const +void CSVRender::Cell::setSelection (int elementMask, Selection mode) { - if(mTerrain.get() != NULL) - return mTerrain->getHeightAt(pos); - else - return -std::numeric_limits::max(); + if (elementMask & Element_Reference) + { + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + { + bool selected = false; + + switch (mode) + { + case Selection_Clear: selected = false; break; + case Selection_All: selected = true; break; + case Selection_Invert: selected = !iter->second->getSelected(); break; + } + + iter->second->setSelected (selected); + } + } +} + +void CSVRender::Cell::setCellArrows (int mask) +{ + for (int i=0; i<4; ++i) + { + CellArrow::Direction direction = static_cast (1< -#include +#include #ifndef Q_MOC_RUN #include #endif #include "object.hpp" +#include "cellarrow.hpp" class QModelIndex; -namespace Ogre +namespace osg { - class SceneManager; - class SceneNode; + class Group; } namespace CSMWorld { class Data; -} - -namespace CSVWorld -{ - class PhysicsSystem; + class CellCoordinates; } namespace CSVRender @@ -39,13 +35,12 @@ namespace CSVRender { CSMWorld::Data& mData; std::string mId; - Ogre::SceneNode *mCellNode; + osg::ref_ptr mCellNode; std::map mObjects; std::auto_ptr mTerrain; - boost::shared_ptr mPhysics; - Ogre::SceneManager *mSceneMgr; - int mX; - int mY; + CSMWorld::CellCoordinates mCoordinates; + std::auto_ptr mCellArrows[4]; + bool mDeleted; /// Ignored if cell does not have an object with the given ID. /// @@ -59,8 +54,19 @@ namespace CSVRender public: - Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, const std::string& id, - boost::shared_ptr physics, const Ogre::Vector3& origin = Ogre::Vector3 (0, 0, 0)); + enum Selection + { + Selection_Clear, + Selection_All, + Selection_Invert + }; + + public: + + /// \note Deleted covers both cells that are deleted and cells that don't exist in + /// the first place. + Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, + bool deleted = false); ~Cell(); @@ -85,7 +91,14 @@ namespace CSVRender /// this cell? bool referenceAdded (const QModelIndex& parent, int start, int end); - float getTerrainHeightAt(const Ogre::Vector3 &pos) const; + void setSelection (int elementMask, Selection mode); + + void setCellArrows (int mask); + + /// Returns 0, 0 in case of an unpaged cell. + CSMWorld::CellCoordinates getCoordinates() const; + + bool isDeleted() const; }; } diff --git a/apps/opencs/view/render/cellarrow.cpp b/apps/opencs/view/render/cellarrow.cpp new file mode 100644 index 0000000000..1356aa0fbd --- /dev/null +++ b/apps/opencs/view/render/cellarrow.cpp @@ -0,0 +1,188 @@ + +#include "cellarrow.hpp" + +#include +#include +#include +#include +#include + +#include "elements.hpp" + +CSVRender::CellArrowTag::CellArrowTag (CellArrow *arrow) +: TagBase (Element_CellArrow), mArrow (arrow) +{} + +CSVRender::CellArrow *CSVRender::CellArrowTag::getCellArrow() const +{ + return mArrow; +} + +QString CSVRender::CellArrowTag::getToolTip (bool hideBasics) const +{ + QString text ("Direction: "); + + switch (mArrow->getDirection()) + { + case CellArrow::Direction_North: text += "North"; break; + case CellArrow::Direction_West: text += "West"; break; + case CellArrow::Direction_South: text += "South"; break; + case CellArrow::Direction_East: text += "East"; break; + } + + if (!hideBasics) + { + text += + "

" + "Modify which cells are shown" + "

  • Primary-Edit: Add cell in given direction
  • " + "
  • Secondary-Edit: Add cell and remove old cell
  • " + "
  • Shift Primary-Edit: Add cells in given direction
  • " + "
  • Shift Secondary-Edit: Add cells and remove old cells
  • " + "
"; + } + + return text; +} + + +void CSVRender::CellArrow::adjustTransform() +{ + // position + const int cellSize = 8192; + const int offset = cellSize / 2 + 800; + + int x = mCoordinates.getX()*cellSize + cellSize/2; + int y = mCoordinates.getY()*cellSize + cellSize/2; + + float xr = 0; + float yr = 0; + float zr = 0; + + float angle = osg::DegreesToRadians (90.0f); + + switch (mDirection) + { + case Direction_North: y += offset; xr = -angle; zr = angle; break; + case Direction_West: x -= offset; yr = -angle; break; + case Direction_South: y -= offset; xr = angle; zr = angle; break; + case Direction_East: x += offset; yr = angle; break; + }; + + mBaseNode->setPosition (osg::Vec3f (x, y, 0)); + + // orientation + osg::Quat xr2 (xr, osg::Vec3f (1,0,0)); + osg::Quat yr2 (yr, osg::Vec3f (0,1,0)); + osg::Quat zr2 (zr, osg::Vec3f (0,0,1)); + mBaseNode->setAttitude (zr2*yr2*xr2); +} + +void CSVRender::CellArrow::buildShape() +{ + osg::ref_ptr geometry (new osg::Geometry); + + const int arrowWidth = 4000; + const int arrowLength = 1500; + const int arrowHeight = 500; + + osg::Vec3Array *vertices = new osg::Vec3Array; + for (int i2=0; i2<2; ++i2) + for (int i=0; i<2; ++i) + { + float height = i ? -arrowHeight/2 : arrowHeight/2; + vertices->push_back (osg::Vec3f (height, -arrowWidth/2, 0)); + vertices->push_back (osg::Vec3f (height, arrowWidth/2, 0)); + vertices->push_back (osg::Vec3f (height, 0, arrowLength)); + } + + geometry->setVertexArray (vertices); + + osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + + // top + primitives->push_back (0); + primitives->push_back (1); + primitives->push_back (2); + + // bottom + primitives->push_back (5); + primitives->push_back (4); + primitives->push_back (3); + + // back + primitives->push_back (3+6); + primitives->push_back (4+6); + primitives->push_back (1+6); + + primitives->push_back (3+6); + primitives->push_back (1+6); + primitives->push_back (0+6); + + // sides + primitives->push_back (0+6); + primitives->push_back (2+6); + primitives->push_back (5+6); + + primitives->push_back (0+6); + primitives->push_back (5+6); + primitives->push_back (3+6); + + primitives->push_back (4+6); + primitives->push_back (5+6); + primitives->push_back (2+6); + + primitives->push_back (4+6); + primitives->push_back (2+6); + primitives->push_back (1+6); + + geometry->addPrimitiveSet (primitives); + + osg::Vec4Array *colours = new osg::Vec4Array; + + for (int i=0; i<6; ++i) + colours->push_back (osg::Vec4f (1.0f, 0.0f, 0.0f, 1.0f)); + for (int i=0; i<6; ++i) + colours->push_back (osg::Vec4f (0.8f, (i==2 || i==5) ? 0.6f : 0.4f, 0.0f, 1.0f)); + + geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable (geometry); + + mBaseNode->addChild (geode); +} + +CSVRender::CellArrow::CellArrow (osg::Group *cellNode, Direction direction, + const CSMWorld::CellCoordinates& coordinates) +: mDirection (direction), mParentNode (cellNode), mCoordinates (coordinates) +{ + mBaseNode = new osg::PositionAttitudeTransform; + + mBaseNode->setUserData (new CellArrowTag (this)); + + mParentNode->addChild (mBaseNode); + + // 0x1 reserved for separating cull and update visitors + mBaseNode->setNodeMask (Element_CellArrow<<1); + + adjustTransform(); + buildShape(); +} + +CSVRender::CellArrow::~CellArrow() +{ + mParentNode->removeChild (mBaseNode); +} + +CSMWorld::CellCoordinates CSVRender::CellArrow::getCoordinates() const +{ + return mCoordinates; +} + +CSVRender::CellArrow::Direction CSVRender::CellArrow::getDirection() const +{ + return mDirection; +} diff --git a/apps/opencs/view/render/cellarrow.hpp b/apps/opencs/view/render/cellarrow.hpp new file mode 100644 index 0000000000..4523561946 --- /dev/null +++ b/apps/opencs/view/render/cellarrow.hpp @@ -0,0 +1,74 @@ +#ifndef OPENCS_VIEW_CELLARROW_H +#define OPENCS_VIEW_CELLARROW_H + +#include "tagbase.hpp" + +#include + +#include "../../model/world/cellcoordinates.hpp" + +namespace osg +{ + class PositionAttitudeTransform; + class Group; +} + +namespace CSVRender +{ + class CellArrow; + + class CellArrowTag : public TagBase + { + CellArrow *mArrow; + + public: + + CellArrowTag (CellArrow *arrow); + + CellArrow *getCellArrow() const; + + virtual QString getToolTip (bool hideBasics) const; + }; + + + class CellArrow + { + public: + + enum Direction + { + Direction_North = 1, + Direction_West = 2, + Direction_South = 4, + Direction_East = 8 + }; + + private: + + // not implemented + CellArrow (const CellArrow&); + CellArrow& operator= (const CellArrow&); + + Direction mDirection; + osg::Group* mParentNode; + osg::ref_ptr mBaseNode; + CSMWorld::CellCoordinates mCoordinates; + + void adjustTransform(); + + void buildShape(); + + public: + + CellArrow (osg::Group *cellNode, Direction direction, + const CSMWorld::CellCoordinates& coordinates); + + ~CellArrow(); + + CSMWorld::CellCoordinates getCoordinates() const; + + Direction getDirection() const; + }; +} + +#endif diff --git a/apps/opencs/view/render/editmode.cpp b/apps/opencs/view/render/editmode.cpp index 9361030a30..b325e31fb0 100644 --- a/apps/opencs/view/render/editmode.cpp +++ b/apps/opencs/view/render/editmode.cpp @@ -1,8 +1,13 @@ - #include "editmode.hpp" +#include "tagbase.hpp" #include "worldspacewidget.hpp" +CSVRender::WorldspaceWidget& CSVRender::EditMode::getWorldspaceWidget() +{ + return *mWorldspaceWidget; +} + CSVRender::EditMode::EditMode (WorldspaceWidget *worldspaceWidget, const QIcon& icon, unsigned int mask, const QString& tooltip, QWidget *parent) : ModeButton (icon, tooltip, parent), mWorldspaceWidget (worldspaceWidget), mMask (mask) @@ -16,4 +21,52 @@ unsigned int CSVRender::EditMode::getInteractionMask() const void CSVRender::EditMode::activate (CSVWidget::SceneToolbar *toolbar) { mWorldspaceWidget->setInteractionMask (mMask); + mWorldspaceWidget->clearSelection (~mMask); } + +void CSVRender::EditMode::setEditLock (bool locked) +{ + +} + +void CSVRender::EditMode::primaryEditPressed (osg::ref_ptr tag) {} + +void CSVRender::EditMode::secondaryEditPressed (osg::ref_ptr tag) {} + +void CSVRender::EditMode::primarySelectPressed (osg::ref_ptr tag) {} + +void CSVRender::EditMode::secondarySelectPressed (osg::ref_ptr tag) {} + +bool CSVRender::EditMode::primaryEditStartDrag (osg::ref_ptr tag) +{ + return false; +} + +bool CSVRender::EditMode::secondaryEditStartDrag (osg::ref_ptr tag) +{ + return false; +} + +bool CSVRender::EditMode::primarySelectStartDrag (osg::ref_ptr tag) +{ + return false; +} + +bool CSVRender::EditMode::secondarySelectStartDrag (osg::ref_ptr tag) +{ + return false; +} + +void CSVRender::EditMode::drag (int diffX, int diffY, double speedFactor) {} + +void CSVRender::EditMode::dragCompleted() {} + +void CSVRender::EditMode::dragAborted() {} + +void CSVRender::EditMode::dragWheel (int diff, double speedFactor) {} + +void CSVRender::EditMode::dragEnterEvent (QDragEnterEvent *event) {} + +void CSVRender::EditMode::dropEvent (QDropEvent* event) {} + +void CSVRender::EditMode::dragMoveEvent (QDragMoveEvent *event) {} diff --git a/apps/opencs/view/render/editmode.hpp b/apps/opencs/view/render/editmode.hpp index c3192f8ea5..3ba97cf007 100644 --- a/apps/opencs/view/render/editmode.hpp +++ b/apps/opencs/view/render/editmode.hpp @@ -1,11 +1,18 @@ #ifndef CSV_RENDER_EDITMODE_H #define CSV_RENDER_EDITMODE_H +#include + #include "../widget/modebutton.hpp" +class QDragEnterEvent; +class QDropEvent; +class QDragMoveEvent; + namespace CSVRender { class WorldspaceWidget; + class TagBase; class EditMode : public CSVWidget::ModeButton { @@ -14,6 +21,10 @@ namespace CSVRender WorldspaceWidget *mWorldspaceWidget; unsigned int mMask; + protected: + + WorldspaceWidget& getWorldspaceWidget(); + public: EditMode (WorldspaceWidget *worldspaceWidget, const QIcon& icon, unsigned int mask, @@ -22,6 +33,65 @@ namespace CSVRender unsigned int getInteractionMask() const; virtual void activate (CSVWidget::SceneToolbar *toolbar); + + /// Default-implementation: Ignored. + virtual void setEditLock (bool locked); + + /// Default-implementation: Ignored. + virtual void primaryEditPressed (osg::ref_ptr tag); + + /// Default-implementation: Ignored. + virtual void secondaryEditPressed (osg::ref_ptr tag); + + /// Default-implementation: Ignored. + virtual void primarySelectPressed (osg::ref_ptr tag); + + /// Default-implementation: Ignored. + virtual void secondarySelectPressed (osg::ref_ptr tag); + + /// Default-implementation: ignore and return false + /// + /// \return Drag accepted? + virtual bool primaryEditStartDrag (osg::ref_ptr tag); + + /// Default-implementation: ignore and return false + /// + /// \return Drag accepted? + virtual bool secondaryEditStartDrag (osg::ref_ptr tag); + + /// Default-implementation: ignore and return false + /// + /// \return Drag accepted? + virtual bool primarySelectStartDrag (osg::ref_ptr tag); + + /// Default-implementation: ignore and return false + /// + /// \return Drag accepted? + virtual bool secondarySelectStartDrag (osg::ref_ptr tag); + + /// Default-implementation: ignored + virtual void drag (int diffX, int diffY, double speedFactor); + + /// Default-implementation: ignored + virtual void dragCompleted(); + + /// Default-implementation: ignored + /// + /// \note dragAborted will not be called, if the drag is aborted via changing + /// editing mode + virtual void dragAborted(); + + /// Default-implementation: ignored + virtual void dragWheel (int diff, double speedFactor); + + /// Default-implementation: ignored + virtual void dragEnterEvent (QDragEnterEvent *event); + + /// Default-implementation: ignored + virtual void dropEvent (QDropEvent* event); + + /// Default-implementation: ignored + virtual void dragMoveEvent (QDragMoveEvent *event); }; } diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp new file mode 100644 index 0000000000..a4d147bb4d --- /dev/null +++ b/apps/opencs/view/render/instancemode.cpp @@ -0,0 +1,57 @@ + +#include "instancemode.hpp" + +#include "../../model/prefs/state.hpp" + +#include "elements.hpp" +#include "object.hpp" +#include "worldspacewidget.hpp" + +CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) +: EditMode (worldspaceWidget, QIcon (":placeholder"), Element_Reference, "Instance editing", + parent) +{ + +} + +void CSVRender::InstanceMode::primaryEditPressed (osg::ref_ptr tag) +{ + if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) + primarySelectPressed (tag); +} + +void CSVRender::InstanceMode::secondaryEditPressed (osg::ref_ptr tag) +{ + if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) + secondarySelectPressed (tag); +} + +void CSVRender::InstanceMode::primarySelectPressed (osg::ref_ptr tag) +{ + getWorldspaceWidget().clearSelection (Element_Reference); + + if (tag) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + { + // hit an Object, select it + CSVRender::Object* object = objectTag->mObject; + object->setSelected (true); + return; + } + } +} + +void CSVRender::InstanceMode::secondarySelectPressed (osg::ref_ptr tag) +{ + if (tag) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + { + // hit an Object, toggle its selection state + CSVRender::Object* object = objectTag->mObject; + object->setSelected (!object->getSelected()); + return; + } + } +} diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp new file mode 100644 index 0000000000..66451bd996 --- /dev/null +++ b/apps/opencs/view/render/instancemode.hpp @@ -0,0 +1,26 @@ +#ifndef CSV_RENDER_INSTANCEMODE_H +#define CSV_RENDER_INSTANCEMODE_H + +#include "editmode.hpp" + +namespace CSVRender +{ + class InstanceMode : public EditMode + { + Q_OBJECT + + public: + + InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent = 0); + + virtual void primaryEditPressed (osg::ref_ptr tag); + + virtual void secondaryEditPressed (osg::ref_ptr tag); + + virtual void primarySelectPressed (osg::ref_ptr tag); + + virtual void secondarySelectPressed (osg::ref_ptr tag); + }; +} + +#endif diff --git a/apps/opencs/view/render/lighting.cpp b/apps/opencs/view/render/lighting.cpp index 3553ef58cc..8e068168f5 100644 --- a/apps/opencs/view/render/lighting.cpp +++ b/apps/opencs/view/render/lighting.cpp @@ -1,4 +1,5 @@ - #include "lighting.hpp" +#include + CSVRender::Lighting::~Lighting() {} diff --git a/apps/opencs/view/render/lighting.hpp b/apps/opencs/view/render/lighting.hpp index a1da9f7e38..a4315d02fa 100644 --- a/apps/opencs/view/render/lighting.hpp +++ b/apps/opencs/view/render/lighting.hpp @@ -1,10 +1,13 @@ #ifndef OPENCS_VIEW_LIGHTING_H #define OPENCS_VIEW_LIGHTING_H -namespace Ogre +#include + +namespace osg { - class SceneManager; - class ColourValue; + class Vec4f; + class LightSource; + class Group; } namespace CSVRender @@ -13,14 +16,19 @@ namespace CSVRender { public: + Lighting() : mRootNode(0) {} virtual ~Lighting(); - virtual void activate (Ogre::SceneManager *sceneManager, - const Ogre::ColourValue *defaultAmbient = 0) = 0; + virtual void activate (osg::Group* rootNode) = 0; virtual void deactivate() = 0; - virtual void setDefaultAmbient (const Ogre::ColourValue& colour) = 0; + virtual osg::Vec4f getAmbientColour(osg::Vec4f* defaultAmbient) = 0; + + protected: + + osg::ref_ptr mLightSource; + osg::Group* mRootNode; }; } diff --git a/apps/opencs/view/render/lightingbright.cpp b/apps/opencs/view/render/lightingbright.cpp index a342ab0936..07900d6b04 100644 --- a/apps/opencs/view/render/lightingbright.cpp +++ b/apps/opencs/view/render/lightingbright.cpp @@ -1,30 +1,34 @@ - #include "lightingbright.hpp" -#include +#include -CSVRender::LightingBright::LightingBright() : mSceneManager (0), mLight (0) {} +CSVRender::LightingBright::LightingBright() {} -void CSVRender::LightingBright::activate (Ogre::SceneManager *sceneManager, - const Ogre::ColourValue *defaultAmbient) +void CSVRender::LightingBright::activate (osg::Group* rootNode) { - mSceneManager = sceneManager; + mRootNode = rootNode; - mSceneManager->setAmbientLight (Ogre::ColourValue (1.0, 1.0, 1.0, 1)); + mLightSource = (new osg::LightSource); - mLight = mSceneManager->createLight(); - mLight->setType (Ogre::Light::LT_DIRECTIONAL); - mLight->setDirection (Ogre::Vector3 (0, 0, -1)); - mLight->setDiffuseColour (Ogre::ColourValue (1.0, 1.0, 1.0)); + osg::ref_ptr light (new osg::Light); + light->setAmbient(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + light->setPosition(osg::Vec4f(0.f, 0.f, 1.f, 0.f)); + light->setDiffuse(osg::Vec4f(1.f, 1.f, 1.f, 1.f)); + light->setSpecular(osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + light->setConstantAttenuation(1.f); + + mLightSource->setLight(light); + + mRootNode->addChild(mLightSource); } void CSVRender::LightingBright::deactivate() { - if (mLight) - { - mSceneManager->destroyLight (mLight); - mLight = 0; - } + if (mRootNode && mLightSource.get()) + mRootNode->removeChild(mLightSource); } -void CSVRender::LightingBright::setDefaultAmbient (const Ogre::ColourValue& colour) {} +osg::Vec4f CSVRender::LightingBright::getAmbientColour(osg::Vec4f* /*defaultAmbient*/) +{ + return osg::Vec4f(1.f, 1.f, 1.f, 1.f); +} diff --git a/apps/opencs/view/render/lightingbright.hpp b/apps/opencs/view/render/lightingbright.hpp index bc01899cb0..bc6422814c 100644 --- a/apps/opencs/view/render/lightingbright.hpp +++ b/apps/opencs/view/render/lightingbright.hpp @@ -3,28 +3,25 @@ #include "lighting.hpp" -namespace Ogre +namespace osg { class Light; + class Group; } namespace CSVRender { class LightingBright : public Lighting { - Ogre::SceneManager *mSceneManager; - Ogre::Light *mLight; - public: LightingBright(); - virtual void activate (Ogre::SceneManager *sceneManager, - const Ogre::ColourValue *defaultAmbient = 0); + virtual void activate (osg::Group* rootNode); virtual void deactivate(); - virtual void setDefaultAmbient (const Ogre::ColourValue& colour); + virtual osg::Vec4f getAmbientColour(osg::Vec4f* defaultAmbient); }; } diff --git a/apps/opencs/view/render/lightingday.cpp b/apps/opencs/view/render/lightingday.cpp index c5189ccfdf..a841edc639 100644 --- a/apps/opencs/view/render/lightingday.cpp +++ b/apps/opencs/view/render/lightingday.cpp @@ -1,36 +1,36 @@ - #include "lightingday.hpp" -#include +#include -CSVRender::LightingDay::LightingDay() : mSceneManager (0), mLight (0) {} +CSVRender::LightingDay::LightingDay(){} -void CSVRender::LightingDay::activate (Ogre::SceneManager *sceneManager, - const Ogre::ColourValue *defaultAmbient) +void CSVRender::LightingDay::activate (osg::Group* rootNode) { - mSceneManager = sceneManager; + mRootNode = rootNode; - if (defaultAmbient) - mSceneManager->setAmbientLight (*defaultAmbient); - else - mSceneManager->setAmbientLight (Ogre::ColourValue (0.7, 0.7, 0.7, 1)); + mLightSource = new osg::LightSource; - mLight = mSceneManager->createLight(); - mLight->setType (Ogre::Light::LT_DIRECTIONAL); - mLight->setDirection (Ogre::Vector3 (0, 0, -1)); - mLight->setDiffuseColour (Ogre::ColourValue (1, 1, 1)); + osg::ref_ptr light (new osg::Light); + light->setPosition(osg::Vec4f(0.f, 0.f, 1.f, 0.f)); + light->setAmbient(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + light->setDiffuse(osg::Vec4f(1.f, 1.f, 1.f, 1.f)); + light->setSpecular(osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + light->setConstantAttenuation(1.f); + + mLightSource->setLight(light); + mRootNode->addChild(mLightSource); } void CSVRender::LightingDay::deactivate() { - if (mLight) - { - mSceneManager->destroyLight (mLight); - mLight = 0; - } + if (mRootNode && mLightSource.get()) + mRootNode->removeChild(mLightSource); } -void CSVRender::LightingDay::setDefaultAmbient (const Ogre::ColourValue& colour) +osg::Vec4f CSVRender::LightingDay::getAmbientColour(osg::Vec4f *defaultAmbient) { - mSceneManager->setAmbientLight (colour); + if (defaultAmbient) + return *defaultAmbient; + else + return osg::Vec4f(0.7f, 0.7f, 0.7f, 1.f); } diff --git a/apps/opencs/view/render/lightingday.hpp b/apps/opencs/view/render/lightingday.hpp index 8638146e23..2dc3c02b6f 100644 --- a/apps/opencs/view/render/lightingday.hpp +++ b/apps/opencs/view/render/lightingday.hpp @@ -3,28 +3,19 @@ #include "lighting.hpp" -namespace Ogre -{ - class Light; -} - namespace CSVRender { class LightingDay : public Lighting { - Ogre::SceneManager *mSceneManager; - Ogre::Light *mLight; - public: LightingDay(); - virtual void activate (Ogre::SceneManager *sceneManager, - const Ogre::ColourValue *defaultAmbient = 0); + virtual void activate (osg::Group* rootNode); virtual void deactivate(); - virtual void setDefaultAmbient (const Ogre::ColourValue& colour); + virtual osg::Vec4f getAmbientColour(osg::Vec4f *defaultAmbient); }; } diff --git a/apps/opencs/view/render/lightingnight.cpp b/apps/opencs/view/render/lightingnight.cpp index 7d94dc9646..6f87d020b9 100644 --- a/apps/opencs/view/render/lightingnight.cpp +++ b/apps/opencs/view/render/lightingnight.cpp @@ -1,36 +1,37 @@ - #include "lightingnight.hpp" -#include +#include -CSVRender::LightingNight::LightingNight() : mSceneManager (0), mLight (0) {} +CSVRender::LightingNight::LightingNight() {} -void CSVRender::LightingNight::activate (Ogre::SceneManager *sceneManager, - const Ogre::ColourValue *defaultAmbient) +void CSVRender::LightingNight::activate (osg::Group* rootNode) { - mSceneManager = sceneManager; + mRootNode = rootNode; - if (defaultAmbient) - mSceneManager->setAmbientLight (*defaultAmbient); - else - mSceneManager->setAmbientLight (Ogre::ColourValue (0.2, 0.2, 0.2, 1)); + mLightSource = new osg::LightSource; - mLight = mSceneManager->createLight(); - mLight->setType (Ogre::Light::LT_DIRECTIONAL); - mLight->setDirection (Ogre::Vector3 (0, 0, -1)); - mLight->setDiffuseColour (Ogre::ColourValue (0.2, 0.2, 0.2)); + osg::ref_ptr light (new osg::Light); + light->setPosition(osg::Vec4f(0.f, 0.f, 1.f, 0.f)); + light->setAmbient(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + light->setDiffuse(osg::Vec4f(0.2f, 0.2f, 0.2f, 1.f)); + light->setSpecular(osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + light->setConstantAttenuation(1.f); + + mLightSource->setLight(light); + + mRootNode->addChild(mLightSource); } void CSVRender::LightingNight::deactivate() { - if (mLight) - { - mSceneManager->destroyLight (mLight); - mLight = 0; - } + if (mRootNode && mLightSource.get()) + mRootNode->removeChild(mLightSource); } -void CSVRender::LightingNight::setDefaultAmbient (const Ogre::ColourValue& colour) +osg::Vec4f CSVRender::LightingNight::getAmbientColour(osg::Vec4f *defaultAmbient) { - mSceneManager->setAmbientLight (colour); + if (defaultAmbient) + return *defaultAmbient; + else + return osg::Vec4f(0.2f, 0.2f, 0.2f, 1.f); } diff --git a/apps/opencs/view/render/lightingnight.hpp b/apps/opencs/view/render/lightingnight.hpp index 47d1d7ce82..dae2a8fa38 100644 --- a/apps/opencs/view/render/lightingnight.hpp +++ b/apps/opencs/view/render/lightingnight.hpp @@ -3,28 +3,18 @@ #include "lighting.hpp" -namespace Ogre -{ - class Light; -} - namespace CSVRender { class LightingNight : public Lighting { - Ogre::SceneManager *mSceneManager; - Ogre::Light *mLight; - public: LightingNight(); - virtual void activate (Ogre::SceneManager *sceneManager, - const Ogre::ColourValue *defaultAmbient = 0); - + virtual void activate (osg::Group* rootNode); virtual void deactivate(); - virtual void setDefaultAmbient (const Ogre::ColourValue& colour); + virtual osg::Vec4f getAmbientColour(osg::Vec4f *defaultAmbient); }; } diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp deleted file mode 100644 index 206820194d..0000000000 --- a/apps/opencs/view/render/mousestate.cpp +++ /dev/null @@ -1,463 +0,0 @@ -#include "mousestate.hpp" - -#include -#include -#include -#include - -#include -#include -#include - -#include "../../model/settings/usersettings.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/idtable.hpp" -#include "../../model/world/universalid.hpp" -#include "../world/physicssystem.hpp" - -#include "elements.hpp" -#include "worldspacewidget.hpp" - -namespace CSVRender -{ - // mouse picking - // FIXME: need to virtualise mouse buttons - // - // State machine: - // - // [default] mousePressEvent->check if the mouse is pointing at an object - // if yes, create collision planes then go to [grab] - // else check for terrain - // - // [grab] mouseReleaseEvent->if same button and new obj, go to [edit] - // mouseMoveEvent->if same button, go to [drag] - // other mouse events or buttons, go back to [default] (i.e. like 'cancel') - // - // [drag] mouseReleaseEvent->if same button, place the object at the new - // location, update the document then go to [edit] - // mouseMoveEvent->update position to the user based on ray to the collision - // planes and render the object at the new location, but do not update - // the document yet - // - // [edit] TODO, probably fine positional adjustments or rotations; clone/delete? - // - // - // press press (obj) - // [default] --------> [grab] <-------------------- [edit] - // ^ (obj) | | ------> [drag] -----> ^ - // | | | move ^ | release | - // | | | | | | - // | | | +-+ | - // | | | move | - // +----------------+ +--------------------------+ - // release release - // (same obj) (new obj) - // - // - - MouseState::MouseState(WorldspaceWidget *parent) - : mMouseState(Mouse_Default), mParent(parent), mPhysics(parent->mDocument.getPhysics()) - , mSceneManager(parent->getSceneManager()), mOldPos(0,0), mCurrentObj(""), mGrabbedSceneNode("") - , mMouseEventTimer(0), mPlane(0), mOrigObjPos(Ogre::Vector3()), mOrigMousePos(Ogre::Vector3()) - , mCurrentMousePos(Ogre::Vector3()), mOffset(0.0f), mIdTableModel(0), mColIndexPosX(0) - , mColIndexPosY(0), mColIndexPosZ(0) - { - const CSMWorld::RefCollection& references = mParent->mDocument.getData().getReferences(); - - mColIndexPosX = references.findColumnIndex(CSMWorld::Columns::ColumnId_PositionXPos); - mColIndexPosY = references.findColumnIndex(CSMWorld::Columns::ColumnId_PositionYPos); - mColIndexPosZ = references.findColumnIndex(CSMWorld::Columns::ColumnId_PositionZPos); - - mIdTableModel = static_cast( - mParent->mDocument.getData().getTableModel(CSMWorld::UniversalId::Type_Reference)); - - mMouseEventTimer = new QElapsedTimer(); - mMouseEventTimer->invalidate(); - - std::pair planeRes = planeAxis(); - mPlane = new Ogre::Plane(planeRes.first, 0); - Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().createPlane("mouse", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - *mPlane, - 300000,300000, // FIXME: use far clip dist? - 1,1, // segments - true, // normals - 1, // numTexCoordSets - 1,1, // uTile, vTile - planeRes.second // upVector - ); - } - - MouseState::~MouseState () - { - delete mMouseEventTimer; - delete mPlane; - } - - void MouseState::mouseMoveEvent (QMouseEvent *event) - { - switch(mMouseState) - { - case Mouse_Grab: - { - // check if min elapsed time to stop false detection of drag - if(!mMouseEventTimer->isValid() || !mMouseEventTimer->hasExpired(100)) // ms - break; - - mMouseEventTimer->invalidate(); - mMouseState = Mouse_Drag; - - /* FALL_THROUGH */ - } - case Mouse_Drag: - { - if(event->pos() != mOldPos) // TODO: maybe don't update less than a quantum? - { - mOldPos = event->pos(); - - // ray test against the plane to provide feedback to the user the - // relative movement of the object on the x-y plane - std::pair planeResult = mousePositionOnPlane(event->pos(), *mPlane); - if(planeResult.first) - { - if(mGrabbedSceneNode != "") - { - std::pair planeRes = planeAxis(); - Ogre::Vector3 pos = mOrigObjPos + planeRes.first*mOffset; - mSceneManager->getSceneNode(mGrabbedSceneNode)->setPosition(pos+planeResult.second-mOrigMousePos); - mCurrentMousePos = planeResult.second; - mPhysics->moveSceneNodes(mGrabbedSceneNode, pos+planeResult.second-mOrigMousePos); - updateSceneWidgets(); - } - } - } - break; - } - case Mouse_Edit: - case Mouse_Default: - { - break; // error event, ignore - } - /* NO_DEFAULT_CASE */ - } - } - - void MouseState::mousePressEvent (QMouseEvent *event) - { - switch(mMouseState) - { - case Mouse_Grab: - case Mouse_Drag: - { - break; - } - case Mouse_Edit: - case Mouse_Default: - { - if(event->buttons() & Qt::RightButton) - { - std::pair result = objectUnderCursor(event->x(), event->y()); - if(result.first == "") - break; - - mGrabbedSceneNode = result.first; - // ray test agaist the plane to get a starting position of the - // mouse in relation to the object position - std::pair planeRes = planeAxis(); - mPlane->redefine(planeRes.first, result.second); - std::pair planeResult = mousePositionOnPlane(event->pos(), *mPlane); - if(planeResult.first) - { - mOrigMousePos = planeResult.second; - mCurrentMousePos = planeResult.second; - mOffset = 0.0f; - } - - mOrigObjPos = mSceneManager->getSceneNode(mGrabbedSceneNode)->getPosition(); - mMouseEventTimer->start(); - - mMouseState = Mouse_Grab; - } - break; - } - /* NO_DEFAULT_CASE */ - } - } - - void MouseState::mouseReleaseEvent (QMouseEvent *event) - { - switch(mMouseState) - { - case Mouse_Grab: - { - std::pair result = objectUnderCursor(event->x(), event->y()); - if(result.first != "") - { - if(result.first == mCurrentObj) - { - // unselect object - mMouseState = Mouse_Default; - mCurrentObj = ""; - } - else - { - // select object - mMouseState = Mouse_Edit; - mCurrentObj = result.first; - - } - } - break; - } - case Mouse_Drag: - { - // final placement - std::pair planeResult = mousePositionOnPlane(event->pos(), *mPlane); - if(planeResult.first) - { - if(mGrabbedSceneNode != "") - { - std::pair planeRes = planeAxis(); - Ogre::Vector3 pos = mOrigObjPos+planeRes.first*mOffset+planeResult.second-mOrigMousePos; - // use the saved scene node name since the physics model has not moved yet - std::string referenceId = mPhysics->sceneNodeToRefId(mGrabbedSceneNode); - - mParent->mDocument.getUndoStack().beginMacro (QObject::tr("Move Object")); - mParent->mDocument.getUndoStack().push(new CSMWorld::ModifyCommand(*mIdTableModel, - mIdTableModel->getModelIndex(referenceId, mColIndexPosX), pos.x)); - mParent->mDocument.getUndoStack().push(new CSMWorld::ModifyCommand(*mIdTableModel, - mIdTableModel->getModelIndex(referenceId, mColIndexPosY), pos.y)); - mParent->mDocument.getUndoStack().push(new CSMWorld::ModifyCommand(*mIdTableModel, - mIdTableModel->getModelIndex(referenceId, mColIndexPosZ), pos.z)); - mParent->mDocument.getUndoStack().endMacro(); - - // FIXME: highlight current object? - //mCurrentObj = mGrabbedSceneNode; // FIXME: doesn't work? - mCurrentObj = ""; // whether the object is selected - - mMouseState = Mouse_Edit; - - // reset states - mCurrentMousePos = Ogre::Vector3(); // mouse pos to use in wheel event - mOrigMousePos = Ogre::Vector3(); // starting pos of mouse in world space - mOrigObjPos = Ogre::Vector3(); // starting pos of object in world space - mGrabbedSceneNode = ""; // id of the object - mOffset = 0.0f; // used for z-axis movement - mOldPos = QPoint(0, 0); // to calculate relative movement of mouse - } - } - break; - } - case Mouse_Edit: - case Mouse_Default: - { - // probably terrain, check - std::pair result = terrainUnderCursor(event->x(), event->y()); - if(result.first != "") - { - // FIXME: terrain editing goes here - } - break; - } - /* NO_DEFAULT_CASE */ - } - mMouseEventTimer->invalidate(); - } - - void MouseState::mouseDoubleClickEvent (QMouseEvent *event) - { - event->ignore(); - //mPhysics->toggleDebugRendering(mSceneManager); - //mParent->flagAsModified(); - } - - bool MouseState::wheelEvent (QWheelEvent *event) - { - switch(mMouseState) - { - case Mouse_Grab: - mMouseState = Mouse_Drag; - - /* FALL_THROUGH */ - case Mouse_Drag: - { - // move the object along the z axis during Mouse_Drag or Mouse_Grab - if (event->delta()) - { - // seems positive is up and negative is down - mOffset += (event->delta()/1); // FIXME: arbitrary number, make config option? - - std::pair planeRes = planeAxis(); - Ogre::Vector3 pos = mOrigObjPos + planeRes.first*mOffset; - mSceneManager->getSceneNode(mGrabbedSceneNode)->setPosition(pos+mCurrentMousePos-mOrigMousePos); - mPhysics->moveSceneNodes(mGrabbedSceneNode, pos+mCurrentMousePos-mOrigMousePos); - updateSceneWidgets(); - } - break; - } - case Mouse_Edit: - case Mouse_Default: - { - return false; - } - /* NO_DEFAULT_CASE */ - } - - return true; - } - - void MouseState::cancelDrag() - { - switch(mMouseState) - { - case Mouse_Grab: - case Mouse_Drag: - { - // cancel operation & return the object to the original position - mSceneManager->getSceneNode(mGrabbedSceneNode)->setPosition(mOrigObjPos); - // update all SceneWidgets and their SceneManagers - mPhysics->moveSceneNodes(mGrabbedSceneNode, mOrigObjPos); - updateSceneWidgets(); - - // reset states - mMouseState = Mouse_Default; - mCurrentMousePos = Ogre::Vector3(); - mOrigMousePos = Ogre::Vector3(); - mOrigObjPos = Ogre::Vector3(); - mGrabbedSceneNode = ""; - mCurrentObj = ""; - mOldPos = QPoint(0, 0); - mMouseEventTimer->invalidate(); - mOffset = 0.0f; - - break; - } - case Mouse_Edit: - case Mouse_Default: - { - break; - } - /* NO_DEFAULT_CASE */ - } - } - - //plane Z, upvector Y, mOffset z : x-y plane, wheel up/down - //plane Y, upvector X, mOffset y : y-z plane, wheel left/right - //plane X, upvector Y, mOffset x : x-z plane, wheel closer/further - std::pair MouseState::planeAxis() - { - const bool screenCoord = true; - Ogre::Vector3 dir = getCamera()->getDerivedDirection(); - - QString wheelDir = "Closer/Further"; - if(wheelDir == "Left/Right") - { - if(screenCoord) - return std::make_pair(getCamera()->getDerivedRight(), getCamera()->getDerivedUp()); - else - return std::make_pair(Ogre::Vector3::UNIT_Y, Ogre::Vector3::UNIT_Z); - } - else if(wheelDir == "Up/Down") - { - if(screenCoord) - return std::make_pair(getCamera()->getDerivedUp(), Ogre::Vector3(-dir.x, -dir.y, -dir.z)); - else - return std::make_pair(Ogre::Vector3::UNIT_Z, Ogre::Vector3::UNIT_X); - } - else - { - if(screenCoord) - return std::make_pair(Ogre::Vector3(-dir.x, -dir.y, -dir.z), getCamera()->getDerivedRight()); - else - return std::make_pair(Ogre::Vector3::UNIT_X, Ogre::Vector3::UNIT_Y); - } - } - - std::pair MouseState::mousePositionOnPlane(const QPoint &pos, const Ogre::Plane &plane) - { - // using a really small value seems to mess up with the projections - float nearClipDistance = getCamera()->getNearClipDistance(); // save existing - getCamera()->setNearClipDistance(10.0f); // arbitrary number - Ogre::Ray mouseRay = getCamera()->getCameraToViewportRay( - (float) pos.x() / getViewport()->getActualWidth(), - (float) pos.y() / getViewport()->getActualHeight()); - getCamera()->setNearClipDistance(nearClipDistance); // restore - std::pair planeResult = mouseRay.intersects(plane); - - if(planeResult.first) - return std::make_pair(true, mouseRay.getPoint(planeResult.second)); - else - return std::make_pair(false, Ogre::Vector3()); // should only happen if the plane is too small - } - - std::pair MouseState::terrainUnderCursor(const int mouseX, const int mouseY) - { - if(!getViewport()) - return std::make_pair("", Ogre::Vector3()); - - float x = (float) mouseX / getViewport()->getActualWidth(); - float y = (float) mouseY / getViewport()->getActualHeight(); - - std::pair result = mPhysics->castRay(x, y, mSceneManager, getCamera()); - if(result.first != "") - { - // FIXME: is there a better way to distinguish terrain from objects? - QString name = QString(result.first.c_str()); - if(name.contains(QRegExp("^HeightField"))) - { - return result; - } - } - - return std::make_pair("", Ogre::Vector3()); - } - - std::pair MouseState::objectUnderCursor(const int mouseX, const int mouseY) - { - if(!getViewport()) - return std::make_pair("", Ogre::Vector3()); - - float x = (float) mouseX / getViewport()->getActualWidth(); - float y = (float) mouseY / getViewport()->getActualHeight(); - - std::pair result = mPhysics->castRay(x, y, mSceneManager, getCamera()); - if(result.first != "") - { - // NOTE: anything not terrain is assumed to be an object - QString name = QString(result.first.c_str()); - if(!name.contains(QRegExp("^HeightField"))) - { - uint32_t visibilityMask = getViewport()->getVisibilityMask(); - bool ignoreObjects = !(visibilityMask & (uint32_t)CSVRender::Element_Reference); - - if(!ignoreObjects && mSceneManager->hasSceneNode(result.first)) - { - return result; - } - } - } - - return std::make_pair("", Ogre::Vector3()); - } - - void MouseState::updateSceneWidgets() - { - std::map sceneWidgets = mPhysics->sceneWidgets(); - - std::map::iterator iter = sceneWidgets.begin(); - for(; iter != sceneWidgets.end(); ++iter) - { - (*iter).second->updateScene(); - } - } - - Ogre::Camera *MouseState::getCamera() - { - return mParent->getCamera(); - } - - Ogre::Viewport *MouseState::getViewport() - { - return mParent->getCamera()->getViewport(); - } -} diff --git a/apps/opencs/view/render/mousestate.hpp b/apps/opencs/view/render/mousestate.hpp deleted file mode 100644 index 70e18427f3..0000000000 --- a/apps/opencs/view/render/mousestate.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef OPENCS_VIEW_MOUSESTATE_H -#define OPENCS_VIEW_MOUSESTATE_H - -#include -#include -#include -#include - -class QElapsedTimer; -class QMouseEvent; -class QWheelEvent; - -namespace Ogre -{ - class Plane; - class SceneManager; - class Camera; - class Viewport; -} - -namespace CSVWorld -{ - class PhysicsSystem; -} - -namespace CSMWorld -{ - class IdTable; -} - -namespace CSVRender -{ - class WorldspaceWidget; - - class MouseState - { - enum MouseStates - { - Mouse_Grab, - Mouse_Drag, - Mouse_Edit, - Mouse_Default - }; - MouseStates mMouseState; - - WorldspaceWidget *mParent; - boost::shared_ptr mPhysics; - Ogre::SceneManager *mSceneManager; // local copy - - QPoint mOldPos; - std::string mCurrentObj; - std::string mGrabbedSceneNode; - QElapsedTimer *mMouseEventTimer; - Ogre::Plane *mPlane; - Ogre::Vector3 mOrigObjPos; - Ogre::Vector3 mOrigMousePos; - Ogre::Vector3 mCurrentMousePos; - float mOffset; - - CSMWorld::IdTable *mIdTableModel; - int mColIndexPosX; - int mColIndexPosY; - int mColIndexPosZ; - - public: - - MouseState(WorldspaceWidget *parent); - ~MouseState(); - - void mouseMoveEvent (QMouseEvent *event); - void mousePressEvent (QMouseEvent *event); - void mouseReleaseEvent (QMouseEvent *event); - void mouseDoubleClickEvent (QMouseEvent *event); - bool wheelEvent (QWheelEvent *event); - - void cancelDrag(); - - private: - - std::pair mousePositionOnPlane(const QPoint &pos, const Ogre::Plane &plane); - std::pair terrainUnderCursor(const int mouseX, const int mouseY); - std::pair objectUnderCursor(const int mouseX, const int mouseY); - std::pair planeAxis(); - void updateSceneWidgets(); - - Ogre::Camera *getCamera(); // friend access - Ogre::Viewport *getViewport(); // friend access - }; -} - -#endif // OPENCS_VIEW_MOUSESTATE_H diff --git a/apps/opencs/view/render/navigation.cpp b/apps/opencs/view/render/navigation.cpp deleted file mode 100644 index 7057591041..0000000000 --- a/apps/opencs/view/render/navigation.cpp +++ /dev/null @@ -1,24 +0,0 @@ - -#include "navigation.hpp" - -float CSVRender::Navigation::getFactor (bool mouse) const -{ - float factor = mFastModeFactor; - - if (mouse) - factor /= 2; /// \todo make this configurable - - return factor; -} - -CSVRender::Navigation::Navigation() - : mFastModeFactor(1) -{ -} - -CSVRender::Navigation::~Navigation() {} - -void CSVRender::Navigation::setFastModeFactor (float factor) -{ - mFastModeFactor = factor; -} diff --git a/apps/opencs/view/render/navigation.hpp b/apps/opencs/view/render/navigation.hpp deleted file mode 100644 index ead8f3e135..0000000000 --- a/apps/opencs/view/render/navigation.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef OPENCS_VIEW_NAVIGATION_H -#define OPENCS_VIEW_NAVIGATION_H - -class QPoint; - -namespace Ogre -{ - class Camera; -} - -namespace CSVRender -{ - class Navigation - { - float mFastModeFactor; - - protected: - - float getFactor (bool mouse) const; - - public: - - Navigation(); - virtual ~Navigation(); - - void setFastModeFactor (float factor); - ///< Set currently applying fast mode factor. - - virtual bool activate (Ogre::Camera *camera) = 0; - ///< \return Update required? - - virtual bool wheelMoved (int delta) = 0; - ///< \return Update required? - - virtual bool mouseMoved (const QPoint& delta, int mode) = 0; - ///< \param mode: 0: default mouse key, 1: default mouse key and modifier key 1 - /// \return Update required? - - virtual bool handleMovementKeys (int vertical, int horizontal) = 0; - ///< \return Update required? - - virtual bool handleRollKeys (int delta) = 0; - ///< \return Update required? - }; -} - -#endif diff --git a/apps/opencs/view/render/navigation1st.cpp b/apps/opencs/view/render/navigation1st.cpp deleted file mode 100644 index 5d9a034688..0000000000 --- a/apps/opencs/view/render/navigation1st.cpp +++ /dev/null @@ -1,86 +0,0 @@ - -#include "navigation1st.hpp" - -#include - -#include - -CSVRender::Navigation1st::Navigation1st() : mCamera (0) {} - -bool CSVRender::Navigation1st::activate (Ogre::Camera *camera) -{ - mCamera = camera; - mCamera->setFixedYawAxis (true, Ogre::Vector3::UNIT_Z); - - Ogre::Radian pitch = mCamera->getOrientation().getPitch(); - - Ogre::Radian limit (Ogre::Math::PI/2-0.5); - - if (pitch>limit) - mCamera->pitch (-(pitch-limit)); - else if (pitch<-limit) - mCamera->pitch (pitch-limit); - - return true; -} - -bool CSVRender::Navigation1st::wheelMoved (int delta) -{ - mCamera->move (getFactor (true) * mCamera->getDirection() * delta); - return true; -} - -bool CSVRender::Navigation1st::mouseMoved (const QPoint& delta, int mode) -{ - if (mode==0) - { - // turn camera - if (delta.x()) - mCamera->yaw (Ogre::Degree (getFactor (true) * delta.x())); - - if (delta.y()) - { - Ogre::Radian oldPitch = mCamera->getOrientation().getPitch(); - float deltaPitch = getFactor (true) * delta.y(); - Ogre::Radian newPitch = oldPitch + Ogre::Degree (deltaPitch); - - if ((deltaPitch>0 && newPitchOgre::Radian(0.5))) - { - mCamera->pitch (Ogre::Degree (deltaPitch)); - } - } - - return true; - } - else if (mode==1) - { - // pan camera - if (delta.x()) - mCamera->move (getFactor (true) * mCamera->getDerivedRight() * delta.x()); - - if (delta.y()) - mCamera->move (getFactor (true) * -mCamera->getDerivedUp() * delta.y()); - - return true; - } - - return false; -} - -bool CSVRender::Navigation1st::handleMovementKeys (int vertical, int horizontal) -{ - if (vertical) - mCamera->move (getFactor (false) * mCamera->getDirection() * vertical); - - if (horizontal) - mCamera->move (getFactor (true) * mCamera->getDerivedRight() * horizontal); - - return true; -} - -bool CSVRender::Navigation1st::handleRollKeys (int delta) -{ - // we don't roll this way in 1st person mode - return false; -} diff --git a/apps/opencs/view/render/navigation1st.hpp b/apps/opencs/view/render/navigation1st.hpp deleted file mode 100644 index d1e09d2367..0000000000 --- a/apps/opencs/view/render/navigation1st.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef OPENCS_VIEW_NAVIGATION1ST_H -#define OPENCS_VIEW_NAVIGATION1ST_H - -#include "navigation.hpp" - -namespace CSVRender -{ - /// \brief First person-like camera controls - class Navigation1st : public Navigation - { - Ogre::Camera *mCamera; - - public: - - Navigation1st(); - - virtual bool activate (Ogre::Camera *camera); - ///< \return Update required? - - virtual bool wheelMoved (int delta); - ///< \return Update required? - - virtual bool mouseMoved (const QPoint& delta, int mode); - ///< \param mode: 0: default mouse key, 1: default mouse key and modifier key 1 - /// \return Update required? - - virtual bool handleMovementKeys (int vertical, int horizontal); - ///< \return Update required? - - virtual bool handleRollKeys (int delta); - ///< \return Update required? - }; -} - -#endif diff --git a/apps/opencs/view/render/navigationfree.cpp b/apps/opencs/view/render/navigationfree.cpp deleted file mode 100644 index 950e137a7d..0000000000 --- a/apps/opencs/view/render/navigationfree.cpp +++ /dev/null @@ -1,66 +0,0 @@ - -#include "navigationfree.hpp" - -#include - -#include - -CSVRender::NavigationFree::NavigationFree() : mCamera (0) {} - -bool CSVRender::NavigationFree::activate (Ogre::Camera *camera) -{ - mCamera = camera; - mCamera->setFixedYawAxis (false); - return false; -} - -bool CSVRender::NavigationFree::wheelMoved (int delta) -{ - mCamera->move (getFactor (true) * mCamera->getDirection() * delta); - return true; -} - -bool CSVRender::NavigationFree::mouseMoved (const QPoint& delta, int mode) -{ - if (mode==0) - { - // turn camera - if (delta.x()) - mCamera->yaw (Ogre::Degree (getFactor (true) * delta.x())); - - if (delta.y()) - mCamera->pitch (Ogre::Degree (getFactor (true) * delta.y())); - - return true; - } - else if (mode==1) - { - // pan camera - if (delta.x()) - mCamera->move (getFactor (true) * mCamera->getDerivedRight() * delta.x()); - - if (delta.y()) - mCamera->move (getFactor (true) * -mCamera->getDerivedUp() * delta.y()); - - return true; - } - - return false; -} - -bool CSVRender::NavigationFree::handleMovementKeys (int vertical, int horizontal) -{ - if (vertical) - mCamera->move (getFactor (false) * mCamera->getDerivedUp() * vertical); - - if (horizontal) - mCamera->move (getFactor (true) * mCamera->getDerivedRight() * horizontal); - - return true; -} - -bool CSVRender::NavigationFree::handleRollKeys (int delta) -{ - mCamera->roll (Ogre::Degree (getFactor (false) * delta)); - return true; -} diff --git a/apps/opencs/view/render/navigationfree.hpp b/apps/opencs/view/render/navigationfree.hpp deleted file mode 100644 index e30722f75d..0000000000 --- a/apps/opencs/view/render/navigationfree.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef OPENCS_VIEW_NAVIGATIONFREE_H -#define OPENCS_VIEW_NAVIGATIONFREE_H - -#include "navigation.hpp" - -namespace CSVRender -{ - /// \brief Free camera controls - class NavigationFree : public Navigation - { - Ogre::Camera *mCamera; - - public: - - NavigationFree(); - - virtual bool activate (Ogre::Camera *camera); - ///< \return Update required? - - virtual bool wheelMoved (int delta); - ///< \return Update required? - - virtual bool mouseMoved (const QPoint& delta, int mode); - ///< \param mode: 0: default mouse key, 1: default mouse key and modifier key 1 - /// \return Update required? - - virtual bool handleMovementKeys (int vertical, int horizontal); - ///< \return Update required? - - virtual bool handleRollKeys (int delta); - ///< \return Update required? - }; -} - -#endif diff --git a/apps/opencs/view/render/navigationorbit.cpp b/apps/opencs/view/render/navigationorbit.cpp deleted file mode 100644 index c5f3eda96f..0000000000 --- a/apps/opencs/view/render/navigationorbit.cpp +++ /dev/null @@ -1,100 +0,0 @@ - -#include "navigationorbit.hpp" - -#include - -#include - -void CSVRender::NavigationOrbit::rotateCamera (const Ogre::Vector3& diff) -{ - Ogre::Vector3 pos = mCamera->getPosition(); - - float distance = (pos-mCentre).length(); - - Ogre::Vector3 direction = (pos+diff)-mCentre; - direction.normalise(); - - mCamera->setPosition (mCentre + direction*distance); - mCamera->lookAt (mCentre); -} - -CSVRender::NavigationOrbit::NavigationOrbit() : mCamera (0), mCentre (0, 0, 0), mDistance (100) -{} - -bool CSVRender::NavigationOrbit::activate (Ogre::Camera *camera) -{ - mCamera = camera; - mCamera->setFixedYawAxis (false); - - if ((mCamera->getPosition()-mCentre).length()getPosition(); - direction.normalise(); - - if (direction.length()==0) - direction = Ogre::Vector3 (1, 0, 0); - - mCamera->setPosition (mCentre - direction * mDistance); - } - - mCamera->lookAt (mCentre); - - return true; -} - -bool CSVRender::NavigationOrbit::wheelMoved (int delta) -{ - Ogre::Vector3 diff = getFactor (true) * mCamera->getDirection() * delta; - - Ogre::Vector3 pos = mCamera->getPosition(); - - if (delta>0 && diff.length()>=(pos-mCentre).length()-mDistance) - { - pos = mCentre-(mCamera->getDirection() * mDistance); - } - else - { - pos += diff; - } - - mCamera->setPosition (pos); - - return true; -} - -bool CSVRender::NavigationOrbit::mouseMoved (const QPoint& delta, int mode) -{ - Ogre::Vector3 diff = - getFactor (true) * -mCamera->getDerivedRight() * delta.x() - + getFactor (true) * mCamera->getDerivedUp() * delta.y(); - - if (mode==0) - { - rotateCamera (diff); - return true; - } - else if (mode==1) - { - mCamera->move (diff); - mCentre += diff; - return true; - } - - return false; -} - -bool CSVRender::NavigationOrbit::handleMovementKeys (int vertical, int horizontal) -{ - rotateCamera ( - - getFactor (false) * -mCamera->getDerivedRight() * horizontal - + getFactor (false) * mCamera->getDerivedUp() * vertical); - - return true; -} - -bool CSVRender::NavigationOrbit::handleRollKeys (int delta) -{ - mCamera->roll (Ogre::Degree (getFactor (false) * delta)); - return true; -} diff --git a/apps/opencs/view/render/navigationorbit.hpp b/apps/opencs/view/render/navigationorbit.hpp deleted file mode 100644 index 7796eb9e7c..0000000000 --- a/apps/opencs/view/render/navigationorbit.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef OPENCS_VIEW_NAVIGATIONORBIT_H -#define OPENCS_VIEW_NAVIGATIONORBIT_H - -#include "navigation.hpp" - -#include - -namespace CSVRender -{ - /// \brief Orbiting camera controls - class NavigationOrbit : public Navigation - { - Ogre::Camera *mCamera; - Ogre::Vector3 mCentre; - int mDistance; - - void rotateCamera (const Ogre::Vector3& diff); - ///< Rotate camera around centre. - - public: - - NavigationOrbit(); - - virtual bool activate (Ogre::Camera *camera); - ///< \return Update required? - - virtual bool wheelMoved (int delta); - ///< \return Update required? - - virtual bool mouseMoved (const QPoint& delta, int mode); - ///< \param mode: 0: default mouse key, 1: default mouse key and modifier key 1 - /// \return Update required? - - virtual bool handleMovementKeys (int vertical, int horizontal); - ///< \return Update required? - - virtual bool handleRollKeys (int delta); - ///< \return Update required? - }; -} - -#endif diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 3607fb415d..c295a023a8 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -1,53 +1,63 @@ - #include "object.hpp" -#include -#include -#include +#include +#include + +#include +#include + +#include +#include +#include + +#include #include "../../model/world/data.hpp" #include "../../model/world/ref.hpp" #include "../../model/world/refidcollection.hpp" -#include "../world/physicssystem.hpp" +#include +#include #include "elements.hpp" -void CSVRender::Object::clearSceneNode (Ogre::SceneNode *node) +namespace { - for (Ogre::SceneNode::ObjectIterator iter = node->getAttachedObjectIterator(); - iter.hasMoreElements(); ) + + osg::ref_ptr createErrorCube() { - Ogre::MovableObject* object = dynamic_cast (iter.getNext()); - node->getCreator()->destroyMovableObject (object); + osg::ref_ptr shape(new osg::Box(osg::Vec3f(0,0,0), 50.f)); + osg::ref_ptr shapedrawable(new osg::ShapeDrawable); + shapedrawable->setShape(shape); + + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(shapedrawable); + return geode; } - for (Ogre::SceneNode::ChildNodeIterator iter = node->getChildIterator(); - iter.hasMoreElements(); ) - { - Ogre::SceneNode* childNode = dynamic_cast (iter.getNext()); - clearSceneNode (childNode); - node->getCreator()->destroySceneNode (childNode); - } } + +CSVRender::ObjectTag::ObjectTag (Object* object) +: TagBase (Element_Reference), mObject (object) +{} + +QString CSVRender::ObjectTag::getToolTip (bool hideBasics) const +{ + return QString::fromUtf8 (mObject->getReferenceableId().c_str()); +} + + void CSVRender::Object::clear() { - mObject.setNull(); - - if (mBase) - clearSceneNode (mBase); } void CSVRender::Object::update() { - if(!mObject.isNull()) - mPhysics->removePhysicsObject(mBase->getName()); - clear(); std::string model; - int error = 0; // 1 referemceanöe does not exist, 2 referenceable does not specify a mesh + int error = 0; // 1 referenceable does not exist, 2 referenceable does not specify a mesh const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); @@ -67,39 +77,29 @@ void CSVRender::Object::update() error = 2; } + mBaseNode->removeChildren(0, mBaseNode->getNumChildren()); + if (error) { - Ogre::Entity* entity = mBase->getCreator()->createEntity (Ogre::SceneManager::PT_CUBE); - entity->setMaterialName("BaseWhite"); /// \todo adjust material according to error - entity->setVisibilityFlags (Element_Reference); - - mBase->attachObject (entity); + mBaseNode->addChild(createErrorCube()); } else { - mObject = NifOgre::Loader::createObjects (mBase, "Meshes\\" + model); - mObject->setVisibilityFlags (Element_Reference); - - if (mPhysics && !mReferenceId.empty()) + try { - const CSMWorld::CellRef& reference = getReference(); + std::string path = "meshes\\" + model; - // position - Ogre::Vector3 position; - if (!mForceBaseToZero) - position = Ogre::Vector3(reference.mPos.pos[0], reference.mPos.pos[1], reference.mPos.pos[2]); - - // orientation - Ogre::Quaternion xr (Ogre::Radian (-reference.mPos.rot[0]), Ogre::Vector3::UNIT_X); - Ogre::Quaternion yr (Ogre::Radian (-reference.mPos.rot[1]), Ogre::Vector3::UNIT_Y); - Ogre::Quaternion zr (Ogre::Radian (-reference.mPos.rot[2]), Ogre::Vector3::UNIT_Z); - - mPhysics->addObject("meshes\\" + model, mBase->getName(), mReferenceId, reference.mScale, position, xr*yr*zr); + mResourceSystem->getSceneManager()->createInstance(path, mBaseNode); + } + catch (std::exception& e) + { + // TODO: use error marker mesh + std::cerr << e.what() << std::endl; } } } -void CSVRender::Object::adjust() +void CSVRender::Object::adjustTransform() { if (mReferenceId.empty()) return; @@ -107,21 +107,15 @@ void CSVRender::Object::adjust() const CSMWorld::CellRef& reference = getReference(); // position - if (!mForceBaseToZero) - mBase->setPosition (Ogre::Vector3 ( - reference.mPos.pos[0], reference.mPos.pos[1], reference.mPos.pos[2])); + mBaseNode->setPosition(mForceBaseToZero ? osg::Vec3() : osg::Vec3f(reference.mPos.pos[0], reference.mPos.pos[1], reference.mPos.pos[2])); // orientation - Ogre::Quaternion xr (Ogre::Radian (-reference.mPos.rot[0]), Ogre::Vector3::UNIT_X); + osg::Quat xr (-reference.mPos.rot[0], osg::Vec3f(1,0,0)); + osg::Quat yr (-reference.mPos.rot[1], osg::Vec3f(0,1,0)); + osg::Quat zr (-reference.mPos.rot[2], osg::Vec3f(0,0,1)); + mBaseNode->setAttitude(zr*yr*xr); - Ogre::Quaternion yr (Ogre::Radian (-reference.mPos.rot[1]), Ogre::Vector3::UNIT_Y); - - Ogre::Quaternion zr (Ogre::Radian (-reference.mPos.rot[2]), Ogre::Vector3::UNIT_Z); - - mBase->setOrientation (xr*yr*zr); - - // scale - mBase->setScale (reference.mScale, reference.mScale, reference.mScale); + mBaseNode->setScale(osg::Vec3(reference.mScale, reference.mScale, reference.mScale)); } const CSMWorld::CellRef& CSVRender::Object::getReference() const @@ -132,12 +126,20 @@ const CSMWorld::CellRef& CSVRender::Object::getReference() const return mData.getReferences().getRecord (mReferenceId).get(); } -CSVRender::Object::Object (const CSMWorld::Data& data, Ogre::SceneNode *cellNode, - const std::string& id, bool referenceable, boost::shared_ptr physics, - bool forceBaseToZero) -: mData (data), mBase (0), mForceBaseToZero (forceBaseToZero), mPhysics(physics) +CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, + const std::string& id, bool referenceable, bool forceBaseToZero) +: mData (data), mBaseNode(0), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero) { - mBase = cellNode->createChildSceneNode(); + mBaseNode = new osg::PositionAttitudeTransform; + mOutline = new osgFX::Scribe; + mOutline->addChild(mBaseNode); + + mBaseNode->setUserData(new ObjectTag(this)); + + parentNode->addChild(mBaseNode); + + // 0x1 reserved for separating cull and update visitors + mBaseNode->setNodeMask(Element_Reference<<1); if (referenceable) { @@ -149,21 +151,32 @@ CSVRender::Object::Object (const CSMWorld::Data& data, Ogre::SceneNode *cellNode mReferenceableId = getReference().mRefID; } + adjustTransform(); update(); - adjust(); } CSVRender::Object::~Object() { clear(); - if (mBase) - { - if(mPhysics) // preview may not have physics enabled - mPhysics->removeObject(mBase->getName()); + mParentNode->removeChild(mBaseNode); +} - mBase->getCreator()->destroySceneNode (mBase); - } +void CSVRender::Object::setSelected(bool selected) +{ + mSelected = selected; + + mParentNode->removeChild(mOutline); + mParentNode->removeChild(mBaseNode); + if (selected) + mParentNode->addChild(mOutline); + else + mParentNode->addChild(mBaseNode); +} + +bool CSVRender::Object::getSelected() const +{ + return mSelected; } bool CSVRender::Object::referenceableDataChanged (const QModelIndex& topLeft, @@ -175,8 +188,8 @@ bool CSVRender::Object::referenceableDataChanged (const QModelIndex& topLeft, if (index!=-1 && index>=topLeft.row() && index<=bottomRight.row()) { + adjustTransform(); update(); - adjust(); return true; } @@ -195,8 +208,8 @@ bool CSVRender::Object::referenceableAboutToBeRemoved (const QModelIndex& parent // Deletion of referenceable-type objects is handled outside of Object. if (!mReferenceId.empty()) { + adjustTransform(); update(); - adjust(); return true; } } @@ -219,6 +232,8 @@ bool CSVRender::Object::referenceDataChanged (const QModelIndex& topLeft, int columnIndex = references.findColumnIndex (CSMWorld::Columns::ColumnId_ReferenceableId); + adjustTransform(); + if (columnIndex>=topLeft.column() && columnIndex<=bottomRight.row()) { mReferenceableId = @@ -227,8 +242,6 @@ bool CSVRender::Object::referenceDataChanged (const QModelIndex& topLeft, update(); } - adjust(); - return true; } diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index c5a2b73c20..e7638e7a99 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -1,17 +1,31 @@ #ifndef OPENCS_VIEW_OBJECT_H #define OPENCS_VIEW_OBJECT_H +#include + #include -#ifndef Q_MOC_RUN -#include -#endif +#include +#include + +#include "tagbase.hpp" class QModelIndex; -namespace Ogre +namespace osg { - class SceneNode; + class PositionAttitudeTransform; + class Group; +} + +namespace osgFX +{ + class Scribe; +} + +namespace Resource +{ + class ResourceSystem; } namespace CSMWorld @@ -20,22 +34,34 @@ namespace CSMWorld struct CellRef; } -namespace CSVWorld -{ - class PhysicsSystem; -} - namespace CSVRender { + class Object; + + // An object to attach as user data to the osg::Node, allows us to get an Object back from a Node when we are doing a ray query + class ObjectTag : public TagBase + { + public: + + ObjectTag (Object* object); + + Object* mObject; + + virtual QString getToolTip (bool hideBasics) const; + }; + + class Object { const CSMWorld::Data& mData; std::string mReferenceId; std::string mReferenceableId; - Ogre::SceneNode *mBase; - NifOgre::ObjectScenePtr mObject; + osg::ref_ptr mBaseNode; + osg::ref_ptr mOutline; + bool mSelected; + osg::Group* mParentNode; + Resource::ResourceSystem* mResourceSystem; bool mForceBaseToZero; - boost::shared_ptr mPhysics; /// Not implemented Object (const Object&); @@ -43,32 +69,34 @@ namespace CSVRender /// Not implemented Object& operator= (const Object&); - /// Destroy all scene nodes and movable objects attached to node. - static void clearSceneNode (Ogre::SceneNode *node); - /// Remove object from node (includes deleting) void clear(); /// Update model + /// @note Make sure adjustTransform() was called first so world space particles get positioned correctly void update(); /// Adjust position, orientation and scale - void adjust(); + void adjustTransform(); /// Throws an exception if *this was constructed with referenceable const CSMWorld::CellRef& getReference() const; public: - Object (const CSMWorld::Data& data, Ogre::SceneNode *cellNode, + Object (CSMWorld::Data& data, osg::Group *cellNode, const std::string& id, bool referenceable, - boost::shared_ptr physics = boost::shared_ptr (), bool forceBaseToZero = false); /// \param forceBaseToZero If this is a reference ignore the coordinates and place /// it at 0, 0, 0 instead. ~Object(); + /// Mark the object as selected, selected objects show an outline effect + void setSelected(bool selected); + + bool getSelected() const; + /// \return Did this call result in a modification of the visual representation of /// this object? bool referenceableDataChanged (const QModelIndex& topLeft, diff --git a/apps/opencs/view/render/overlaymask.cpp b/apps/opencs/view/render/overlaymask.cpp deleted file mode 100644 index 09f020354d..0000000000 --- a/apps/opencs/view/render/overlaymask.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "overlaymask.hpp" - -#include -#include - -#include "textoverlay.hpp" -#include "../../model/world/cellcoordinates.hpp" - -namespace CSVRender -{ - -// ideas from http://www.ogre3d.org/forums/viewtopic.php?f=5&t=44828#p486334 -OverlayMask::OverlayMask(std::map &overlays, Ogre::Viewport* viewport) - : mTextOverlays(overlays), mViewport(viewport) -{ -} - -OverlayMask::~OverlayMask() -{ -} - -void OverlayMask::setViewport(Ogre::Viewport *viewport) -{ - mViewport = viewport; -} - -void OverlayMask::preViewportUpdate(const Ogre::RenderTargetViewportEvent &event) -{ - if(event.source == mViewport) - { - Ogre::OverlayManager &overlayMgr = Ogre::OverlayManager::getSingleton(); - for(Ogre::OverlayManager::OverlayMapIterator iter = overlayMgr.getOverlayIterator(); - iter.hasMoreElements();) - { - Ogre::Overlay* item = iter.getNext(); - for(Ogre::Overlay::Overlay2DElementsIterator it = item->get2DElementsIterator(); - it.hasMoreElements();) - { - Ogre::OverlayContainer* container = it.getNext(); - if(container) container->hide(); - } - } - - std::map::iterator it = mTextOverlays.begin(); - for(; it != mTextOverlays.end(); ++it) - { - it->second->show(true); - } - } -} - -} diff --git a/apps/opencs/view/render/overlaymask.hpp b/apps/opencs/view/render/overlaymask.hpp deleted file mode 100644 index ec050cac43..0000000000 --- a/apps/opencs/view/render/overlaymask.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef OPENCS_VIEW_OVERLAYMASK_H -#define OPENCS_VIEW_OVERLAYMASK_H - -#include - -namespace Ogre -{ - class Viewport; - class RendertargetViewportEvent; -} - -namespace CSMWorld -{ - class CellCoordinates; -} - -namespace CSVRender -{ - class TextOverlay; - - class OverlayMask : public Ogre::RenderTargetListener - { - - std::map &mTextOverlays; - Ogre::Viewport* mViewport; - - public: - - OverlayMask(std::map &overlays, - Ogre::Viewport* viewport); - - virtual ~OverlayMask(); - - void setViewport(Ogre::Viewport *viewport); - - protected: - - virtual void preViewportUpdate(const Ogre::RenderTargetViewportEvent &event); - }; -} - -#endif // OPENCS_VIEW_OVERLAYMASK_H diff --git a/apps/opencs/view/render/overlaysystem.cpp b/apps/opencs/view/render/overlaysystem.cpp deleted file mode 100644 index f565f5af06..0000000000 --- a/apps/opencs/view/render/overlaysystem.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "overlaysystem.hpp" - -#include - -#include - -namespace CSVRender -{ - OverlaySystem *OverlaySystem::mOverlaySystemInstance = 0; - - OverlaySystem::OverlaySystem() - { - assert(!mOverlaySystemInstance); - mOverlaySystemInstance = this; - mOverlaySystem = new Ogre::OverlaySystem(); - } - - OverlaySystem::~OverlaySystem() - { - delete mOverlaySystem; - } - - OverlaySystem &OverlaySystem::instance() - { - assert(mOverlaySystemInstance); - return *mOverlaySystemInstance; - } - - Ogre::OverlaySystem *OverlaySystem::get() - { - return mOverlaySystem; - } -} - diff --git a/apps/opencs/view/render/overlaysystem.hpp b/apps/opencs/view/render/overlaysystem.hpp deleted file mode 100644 index f8a78f3295..0000000000 --- a/apps/opencs/view/render/overlaysystem.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef OPENCS_VIEW_OVERLAYSYSTEM_H -#define OPENCS_VIEW_OVERLAYSYSTEM_H - -namespace Ogre -{ - class OverlaySystem; -} - -namespace CSVRender -{ - class OverlaySystem - { - Ogre::OverlaySystem *mOverlaySystem; - static OverlaySystem *mOverlaySystemInstance; - - public: - - OverlaySystem(); - ~OverlaySystem(); - static OverlaySystem &instance(); - - Ogre::OverlaySystem *get(); - }; -} - -#endif // OPENCS_VIEW_OVERLAYSYSTEM_H diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index cf9edb5483..dae5990e6f 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -1,21 +1,14 @@ - #include "pagedworldspacewidget.hpp" +#include #include #include +#include -#include -#include -#include -#include -#include -#include -#include +#include #include -#include "textoverlay.hpp" -#include "overlaymask.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/idtable.hpp" @@ -30,32 +23,19 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() { bool modified = false; - bool setCamera = false; + bool wasEmpty = mCells.empty(); const CSMWorld::IdCollection& cells = mDocument.getData().getCells(); { - // remove (or name/region modified) + // remove/update std::map::iterator iter (mCells.begin()); while (iter!=mCells.end()) { - int index = cells.searchId (iter->first.getId (mWorldspace)); - - if (!mSelection.has (iter->first) || index==-1 || - cells.getRecord (index).mState==CSMWorld::RecordBase::State_Deleted) + if (!mSelection.has (iter->first)) { - // delete overlays - std::map::iterator itOverlay = mTextOverlays.find(iter->first); - if(itOverlay != mTextOverlays.end()) - { - delete itOverlay->second; - mTextOverlays.erase(itOverlay); - } - - // destroy manual objects - getSceneManager()->destroyManualObject("manual"+iter->first.getId(mWorldspace)); - + // remove delete iter->second; mCells.erase (iter++); @@ -63,156 +43,85 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() } else { - // check if name or region field has changed - // FIXME: config setting - std::string name = cells.getRecord(index).get().mName; - std::string region = cells.getRecord(index).get().mRegion; + // update + int index = cells.searchId (iter->first.getId (mWorldspace)); - std::map::iterator it = mTextOverlays.find(iter->first); - if(it != mTextOverlays.end()) + bool deleted = index==-1 || + cells.getRecord (index).mState==CSMWorld::RecordBase::State_Deleted; + + if (deleted!=iter->second->isDeleted()) { - if(it->second->getDesc() != "") // previously had name - { - if(name != it->second->getDesc()) // new name - { - if(name != "") - it->second->setDesc(name); - else // name deleted, use region - it->second->setDesc(region); - it->second->update(); - } - } - else if(name != "") // name added - { - it->second->setDesc(name); - it->second->update(); - } - else if(region != it->second->getDesc()) // new region - { - it->second->setDesc(region); - it->second->update(); - } + modified = true; + + std::auto_ptr cell (new Cell (mDocument.getData(), mRootNode, + iter->first.getId (mWorldspace), deleted)); + + delete iter->second; + iter->second = cell.release(); + } + else if (!deleted) + { + // delete state has not changed -> just update + + // TODO check if name or region field has changed (cell marker) + // FIXME: config setting + //std::string name = cells.getRecord(index).get().mName; + //std::string region = cells.getRecord(index).get().mRegion; + modified = true; } + ++iter; } } } - if (mCells.begin()==mCells.end()) - setCamera = true; - // add for (CSMWorld::CellSelection::Iterator iter (mSelection.begin()); iter!=mSelection.end(); ++iter) { - int index = cells.searchId (iter->getId (mWorldspace)); - - if (index > 0 && cells.getRecord (index).mState!=CSMWorld::RecordBase::State_Deleted && - mCells.find (*iter)==mCells.end()) + if (mCells.find (*iter)==mCells.end()) { - Cell *cell = new Cell (mDocument.getData(), getSceneManager(), - iter->getId (mWorldspace), mDocument.getPhysics()); - mCells.insert (std::make_pair (*iter, cell)); - - float height = cell->getTerrainHeightAt(Ogre::Vector3( - ESM::Land::REAL_SIZE * iter->getX() + ESM::Land::REAL_SIZE/2, - ESM::Land::REAL_SIZE * iter->getY() + ESM::Land::REAL_SIZE/2, - 0)); - if (setCamera) - { - setCamera = false; - getCamera()->setPosition ( - ESM::Land::REAL_SIZE * iter->getX() + ESM::Land::REAL_SIZE/2, - ESM::Land::REAL_SIZE * iter->getY() + ESM::Land::REAL_SIZE/2, - height); - // better camera position at the start - getCamera()->move(getCamera()->getDirection() * -6000); // FIXME: config setting - } - - Ogre::ManualObject* manual = - getSceneManager()->createManualObject("manual" + iter->getId(mWorldspace)); - manual->begin("BaseWhite", Ogre::RenderOperation::OT_LINE_LIST); - // define start and end point (x, y, z) - manual-> position(ESM::Land::REAL_SIZE * iter->getX() + ESM::Land::REAL_SIZE/2, - ESM::Land::REAL_SIZE * iter->getY() + ESM::Land::REAL_SIZE/2, - height); - manual-> position(ESM::Land::REAL_SIZE * iter->getX() + ESM::Land::REAL_SIZE/2, - ESM::Land::REAL_SIZE * iter->getY() + ESM::Land::REAL_SIZE/2, - height+200); // FIXME: config setting - manual->end(); - manual->setBoundingBox(Ogre::AxisAlignedBox( - ESM::Land::REAL_SIZE * iter->getX() + ESM::Land::REAL_SIZE/2, - ESM::Land::REAL_SIZE * iter->getY() + ESM::Land::REAL_SIZE/2, - height, - ESM::Land::REAL_SIZE * iter->getX() + ESM::Land::REAL_SIZE/2, - ESM::Land::REAL_SIZE * iter->getY() + ESM::Land::REAL_SIZE/2, - height+200)); - getSceneManager()->getRootSceneNode()->createChildSceneNode()->attachObject(manual); - manual->setVisible(false); - - CSVRender::TextOverlay *textDisp = - new CSVRender::TextOverlay(manual, getCamera(), iter->getId(mWorldspace)); - textDisp->enable(true); - textDisp->setCaption(iter->getId(mWorldspace)); - std::string desc = cells.getRecord(index).get().mName; - if(desc == "") desc = cells.getRecord(index).get().mRegion; - textDisp->setDesc(desc); // FIXME: config setting - textDisp->update(); - mTextOverlays.insert(std::make_pair(*iter, textDisp)); - if(!mOverlayMask) - { - mOverlayMask = new OverlayMask(mTextOverlays, getViewport()); - addRenderTargetListener(mOverlayMask); - } - + addCellToScene (*iter); modified = true; } } + if (modified) + { + for (std::map::const_iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + { + int mask = 0; + + for (int i=CellArrow::Direction_North; i<=CellArrow::Direction_East; i *= 2) + { + CSMWorld::CellCoordinates coordinates (iter->second->getCoordinates()); + + switch (i) + { + case CellArrow::Direction_North: coordinates = coordinates.move (0, 1); break; + case CellArrow::Direction_West: coordinates = coordinates.move (-1, 0); break; + case CellArrow::Direction_South: coordinates = coordinates.move (0, -1); break; + case CellArrow::Direction_East: coordinates = coordinates.move (1, 0); break; + } + + if (!mSelection.has (coordinates)) + mask |= i; + } + + iter->second->setCellArrows (mask); + } + } + + /// \todo do not overwrite manipulator object + /// \todo move code to useViewHint function + if (modified && wasEmpty) + mView->setCameraManipulator(new osgGA::TrackballManipulator); + return modified; } -void CSVRender::PagedWorldspaceWidget::mousePressEvent (QMouseEvent *event) -{ - if(event->button() == Qt::RightButton) - { - std::map::iterator iter = mTextOverlays.begin(); - for(; iter != mTextOverlays.end(); ++iter) - { - if(mDisplayCellCoord && - iter->second->isEnabled() && iter->second->container().contains(event->x(), event->y())) - { - return; - } - } - } - WorldspaceWidget::mousePressEvent(event); -} - -void CSVRender::PagedWorldspaceWidget::mouseReleaseEvent (QMouseEvent *event) -{ - if(event->button() == Qt::RightButton) - { - std::map::iterator iter = mTextOverlays.begin(); - for(; iter != mTextOverlays.end(); ++iter) - { - if(mDisplayCellCoord && - iter->second->isEnabled() && iter->second->container().contains(event->x(), event->y())) - { - std::cout << "clicked: " << iter->second->getCaption() << std::endl; - return; - } - } - } - WorldspaceWidget::mouseReleaseEvent(event); -} - -void CSVRender::PagedWorldspaceWidget::mouseDoubleClickEvent (QMouseEvent *event) -{ - WorldspaceWidget::mouseDoubleClickEvent(event); -} - void CSVRender::PagedWorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { @@ -241,26 +150,74 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( "terrain-move"); } -void CSVRender::PagedWorldspaceWidget::updateOverlay() +void CSVRender::PagedWorldspaceWidget::handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift) { - if(getCamera()->getViewport()) + if (tag && tag->getElement()==Element_CellArrow) { - if((uint32_t)getCamera()->getViewport()->getVisibilityMask() - & (uint32_t)CSVRender::Element_CellMarker) - mDisplayCellCoord = true; - else - mDisplayCellCoord = false; - } - - if(!mTextOverlays.empty()) - { - std::map::iterator it = mTextOverlays.begin(); - for(; it != mTextOverlays.end(); ++it) + if (button=="p-edit" || button=="s-edit") { - it->second->enable(mDisplayCellCoord); - it->second->update(); + if (CellArrowTag *cellArrowTag = + dynamic_cast (tag.get())) + { + CellArrow *arrow = cellArrowTag->getCellArrow(); + + CSMWorld::CellCoordinates coordinates = arrow->getCoordinates(); + + CellArrow::Direction direction = arrow->getDirection(); + + int x = 0; + int y = 0; + + switch (direction) + { + case CellArrow::Direction_North: y = 1; break; + case CellArrow::Direction_West: x = -1; break; + case CellArrow::Direction_South: y = -1; break; + case CellArrow::Direction_East: x = 1; break; + } + + bool modified = false; + + if (shift) + { + if (button=="p-edit") + addCellSelection (x, y); + else + moveCellSelection (x, y); + + modified = true; + } + else + { + CSMWorld::CellCoordinates newCoordinates = coordinates.move (x, y); + + if (mCells.find (newCoordinates)==mCells.end()) + { + addCellToScene (newCoordinates); + mSelection.add (newCoordinates); + modified = true; + } + + if (button=="s-edit") + { + if (mCells.find (coordinates)!=mCells.end()) + { + removeCellFromScene (coordinates); + mSelection.remove (coordinates); + modified = true; + } + } + } + + if (modified) + adjustCells(); + + return; + } } } + + WorldspaceWidget::handleMouseClick (tag, button, shift); } void CSVRender::PagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, @@ -328,21 +285,89 @@ void CSVRender::PagedWorldspaceWidget::referenceAdded (const QModelIndex& parent std::string CSVRender::PagedWorldspaceWidget::getStartupInstruction() { - Ogre::Vector3 position = getCamera()->getPosition(); + osg::Vec3d eye, center, up; + mView->getCamera()->getViewMatrixAsLookAt(eye, center, up); + osg::Vec3d position = eye; std::ostringstream stream; stream << "player->position " - << position.x << ", " << position.y << ", " << position.z + << position.x() << ", " << position.y() << ", " << position.z() << ", 0"; return stream.str(); } +void CSVRender::PagedWorldspaceWidget::addCellToScene ( + const CSMWorld::CellCoordinates& coordinates) +{ + const CSMWorld::IdCollection& cells = mDocument.getData().getCells(); + + int index = cells.searchId (coordinates.getId (mWorldspace)); + + bool deleted = index==-1 || + cells.getRecord (index).mState==CSMWorld::RecordBase::State_Deleted; + + Cell *cell = new Cell (mDocument.getData(), mRootNode, coordinates.getId (mWorldspace), + deleted); + + mCells.insert (std::make_pair (coordinates, cell)); +} + +void CSVRender::PagedWorldspaceWidget::removeCellFromScene ( + const CSMWorld::CellCoordinates& coordinates) +{ + std::map::iterator iter = mCells.find (coordinates); + + if (iter!=mCells.end()) + { + delete iter->second; + mCells.erase (iter); + } +} + +void CSVRender::PagedWorldspaceWidget::addCellSelection (int x, int y) +{ + CSMWorld::CellSelection newSelection = mSelection; + newSelection.move (x, y); + + for (CSMWorld::CellSelection::Iterator iter (newSelection.begin()); iter!=newSelection.end(); + ++iter) + { + if (mCells.find (*iter)==mCells.end()) + { + addCellToScene (*iter); + mSelection.add (*iter); + } + } +} + +void CSVRender::PagedWorldspaceWidget::moveCellSelection (int x, int y) +{ + CSMWorld::CellSelection newSelection = mSelection; + newSelection.move (x, y); + + for (CSMWorld::CellSelection::Iterator iter (mSelection.begin()); iter!=mSelection.end(); + ++iter) + { + if (!newSelection.has (*iter)) + removeCellFromScene (*iter); + } + + for (CSMWorld::CellSelection::Iterator iter (newSelection.begin()); iter!=newSelection.end(); + ++iter) + { + if (!mSelection.has (*iter)) + addCellToScene (*iter); + } + + mSelection = newSelection; +} + CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget* parent, CSMDoc::Document& document) : WorldspaceWidget (document, parent), mDocument (document), mWorldspace ("std::default"), - mControlElements(NULL), mDisplayCellCoord(true), mOverlayMask(NULL) + mControlElements(NULL), mDisplayCellCoord(true) { QAbstractItemModel *cells = document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells); @@ -361,20 +386,6 @@ CSVRender::PagedWorldspaceWidget::~PagedWorldspaceWidget() iter!=mCells.end(); ++iter) { delete iter->second; - - getSceneManager()->destroyManualObject("manual"+iter->first.getId(mWorldspace)); - } - - for (std::map::iterator iter (mTextOverlays.begin()); - iter != mTextOverlays.end(); ++iter) - { - delete iter->second; - } - - if(mOverlayMask) - { - removeRenderTargetListener(mOverlayMask); - delete mOverlayMask; } } @@ -484,6 +495,15 @@ unsigned int CSVRender::PagedWorldspaceWidget::getVisibilityMask() const return WorldspaceWidget::getVisibilityMask() | mControlElements->getSelection(); } +void CSVRender::PagedWorldspaceWidget::clearSelection (int elementMask) +{ + for (std::map::iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + iter->second->setSelection (elementMask, Cell::Selection_Clear); + + flagAsModified(); +} + CSVWidget::SceneToolToggle *CSVRender::PagedWorldspaceWidget::makeControlVisibilitySelector ( CSVWidget::SceneToolbar *parent) { diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 3db6ee4edb..e983fddd55 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -28,8 +28,6 @@ namespace CSVRender std::string mWorldspace; CSVWidget::SceneToolToggle *mControlElements; bool mDisplayCellCoord; - std::map mTextOverlays; - OverlayMask *mOverlayMask; private: @@ -55,6 +53,20 @@ namespace CSVRender virtual std::string getStartupInstruction(); + /// \note Does not update the view or any cell marker + void addCellToScene (const CSMWorld::CellCoordinates& coordinates); + + /// \note Does not update the view or any cell marker + /// + /// \note Calling this function for a cell that is not in the selection is a no-op. + void removeCellFromScene (const CSMWorld::CellCoordinates& coordinates); + + /// \note Does not update the view or any cell marker + void addCellSelection (int x, int y); + + /// \note Does not update the view or any cell marker + void moveCellSelection (int x, int y); + public: PagedWorldspaceWidget (QWidget *parent, CSMDoc::Document& document); @@ -81,19 +93,16 @@ namespace CSVRender virtual unsigned int getVisibilityMask() const; + /// \param elementMask Elements to be affected by the clear operation + virtual void clearSelection (int elementMask); + protected: virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool); virtual void addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool); - virtual void updateOverlay(); - - virtual void mousePressEvent (QMouseEvent *event); - - virtual void mouseReleaseEvent (QMouseEvent *event); - - virtual void mouseDoubleClickEvent (QMouseEvent *event); + virtual void handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift); signals: diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index da18e7c895..a03b277d30 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -1,18 +1,15 @@ - #include "previewwidget.hpp" -#include -#include +#include #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, const std::string& id, bool referenceable, QWidget *parent) -: SceneWidget (parent), mData (data), - mObject (data, getSceneManager()->getRootSceneNode(), id, referenceable, boost::shared_ptr(), true) +: SceneWidget (data.getResourceSystem(), parent), mData (data), mObject(data, mRootNode, id, referenceable) { - setNavigation (&mOrbit); + mView->setCameraManipulator(new osgGA::TrackballManipulator); QAbstractItemModel *referenceables = mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables); diff --git a/apps/opencs/view/render/previewwidget.hpp b/apps/opencs/view/render/previewwidget.hpp index dd6a99c0f9..73f7dc810f 100644 --- a/apps/opencs/view/render/previewwidget.hpp +++ b/apps/opencs/view/render/previewwidget.hpp @@ -3,11 +3,15 @@ #include "scenewidget.hpp" -#include "navigationorbit.hpp" #include "object.hpp" class QModelIndex; +namespace VFS +{ + class Manager; +} + namespace CSMWorld { class Data; @@ -20,8 +24,7 @@ namespace CSVRender Q_OBJECT CSMWorld::Data& mData; - CSVRender::NavigationOrbit mOrbit; - Object mObject; + CSVRender::Object mObject; public: diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 55cf039fcd..b1300a991f 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -4,467 +4,249 @@ #include #include #include +#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include + +#include +#include #include "../widget/scenetoolmode.hpp" -#include "../../model/settings/usersettings.hpp" -#include "navigation.hpp" #include "lighting.hpp" -#include "overlaysystem.hpp" namespace CSVRender { - SceneWidget::SceneWidget(QWidget *parent) - : QWidget(parent) - , mCamera(NULL) - , mSceneMgr(NULL) - , mWindow(NULL) - , mViewport(NULL) - , mNavigation (0), mLighting (0), mUpdate (false), mKeyForward (false) - , mKeyBackward (false), mKeyLeft (false), mKeyRight (false) - , mKeyRollLeft (false), mKeyRollRight (false) - , mFast (false), mDragging (false), mMod1 (false) - , mFastFactor (4) - , mDefaultAmbient (0, 0, 0, 0), mHasDefaultAmbient (false) - { - setAttribute(Qt::WA_PaintOnScreen); - setAttribute(Qt::WA_NoSystemBackground); - setFocusPolicy (Qt::StrongFocus); +RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f) + : QWidget(parent, f) + , mRootNode(0) +{ - mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); + osgViewer::CompositeViewer& viewer = CompositeViewer::get(); - mSceneMgr->setAmbientLight (Ogre::ColourValue (0,0,0,1)); + osg::DisplaySettings* ds = osg::DisplaySettings::instance().get(); + //ds->setNumMultiSamples(8); - mCamera = mSceneMgr->createCamera("foo"); + osg::ref_ptr traits = new osg::GraphicsContext::Traits; + traits->windowName = ""; + traits->windowDecoration = true; + traits->x = 0; + traits->y = 0; + traits->width = width(); + traits->height = height(); + traits->doubleBuffer = true; + traits->alpha = ds->getMinimumNumAlphaBits(); + traits->stencil = ds->getMinimumNumStencilBits(); + traits->sampleBuffers = ds->getMultiSamples(); + traits->samples = ds->getNumMultiSamples(); + // Doesn't make much sense as we're running on demand updates, and there seems to be a bug with the refresh rate when running multiple QGLWidgets + traits->vsync = false; - mCamera->setPosition (300, 0, 0); - mCamera->lookAt (0, 0, 0); - mCamera->setNearClipDistance (0.1); + mView = new osgViewer::View; - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + osg::ref_ptr window = new osgQt::GraphicsWindowQt(traits.get()); + QLayout* layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(window->getGLWidget()); + setLayout(layout); - float farClipDist = userSettings.setting("3d-render/far-clip-distance", QString("300000")).toFloat(); - mCamera->setFarClipDistance (farClipDist); + // Pass events through this widget first + window->getGLWidget()->installEventFilter(this); - mFastFactor = userSettings.setting("scene-input/fast-factor", QString("4")).toInt(); + mView->getCamera()->setGraphicsContext(window); + mView->getCamera()->setClearColor( osg::Vec4(0.2, 0.2, 0.6, 1.0) ); + mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); + mView->getCamera()->setProjectionMatrixAsPerspective(30.0f, static_cast(traits->width)/static_cast(traits->height), 1.0f, 10000.0f ); - mCamera->roll (Ogre::Degree (90)); + mRootNode = new osg::Group; - setLighting (&mLightingDay); + mView->getCamera()->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); + mView->getCamera()->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); - mOverlaySystem = OverlaySystem::instance().get(); - mSceneMgr->addRenderQueueListener(mOverlaySystem); + mView->setSceneData(mRootNode); - QTimer *timer = new QTimer (this); + // Press S to reveal profiling stats + mView->addEventHandler(new osgViewer::StatsHandler); - connect (timer, SIGNAL (timeout()), this, SLOT (update())); + mView->getCamera()->setCullMask(~(0x1)); + + viewer.addView(mView); + viewer.setDone(false); + viewer.realize(); +} + +RenderWidget::~RenderWidget() +{ + CompositeViewer::get().removeView(mView); +} + +void RenderWidget::flagAsModified() +{ + mView->requestRedraw(); +} + +void RenderWidget::setVisibilityMask(int mask) +{ + // 0x1 reserved for separating cull and update visitors + mView->getCamera()->setCullMask(mask<<1); +} + +bool RenderWidget::eventFilter(QObject* obj, QEvent* event) +{ + // handle event in this widget, is there a better way to do this? + if (event->type() == QEvent::MouseButtonPress) + mousePressEvent(static_cast(event)); + if (event->type() == QEvent::MouseButtonRelease) + mouseReleaseEvent(static_cast(event)); + if (event->type() == QEvent::MouseMove) + mouseMoveEvent(static_cast(event)); + if (event->type() == QEvent::KeyPress) + keyPressEvent(static_cast(event)); + if (event->type() == QEvent::KeyRelease) + keyReleaseEvent(static_cast(event)); + if (event->type() == QEvent::Wheel) + wheelEvent(static_cast(event)); + + // Always pass the event on to GLWidget, i.e. to OSG event queue + return QObject::eventFilter(obj, event); +} + +// -------------------------------------------------- + +CompositeViewer::CompositeViewer() + : mSimulationTime(0.0) +{ +#if QT_VERSION >= 0x050000 + // Qt5 is currently crashing and reporting "Cannot make QOpenGLContext current in a different thread" when the viewer is run multi-threaded, this is regression from Qt4 + osgViewer::ViewerBase::ThreadingModel threadingModel = osgViewer::ViewerBase::SingleThreaded; +#else + osgViewer::ViewerBase::ThreadingModel threadingModel = osgViewer::ViewerBase::DrawThreadPerContext; +#endif + + setThreadingModel(threadingModel); + + // disable the default setting of viewer.done() by pressing Escape. + setKeyEventSetsDone(0); + + // Only render when the camera position changed, or content flagged dirty + //setRunFrameScheme(osgViewer::ViewerBase::ON_DEMAND); + setRunFrameScheme(osgViewer::ViewerBase::CONTINUOUS); + + connect( &mTimer, SIGNAL(timeout()), this, SLOT(update()) ); + mTimer.start( 10 ); +} + +CompositeViewer &CompositeViewer::get() +{ + static CompositeViewer sThis; + return sThis; +} + +void CompositeViewer::update() +{ + mSimulationTime += mFrameTimer.time_s(); + mFrameTimer.setStartTick(); + frame(mSimulationTime); +} + +// --------------------------------------------------- + +SceneWidget::SceneWidget(boost::shared_ptr resourceSystem, QWidget *parent, Qt::WindowFlags f) + : RenderWidget(parent, f) + , mResourceSystem(resourceSystem) + , mLighting(NULL) + , mHasDefaultAmbient(false) +{ + // we handle lighting manually + mView->setLightingMode(osgViewer::View::NO_LIGHT); + + setLighting(&mLightingDay); + + /// \todo make shortcut configurable + QShortcut *focusToolbar = new QShortcut (Qt::Key_T, this, 0, 0, Qt::WidgetWithChildrenShortcut); + connect (focusToolbar, SIGNAL (activated()), this, SIGNAL (focusToolbarRequest())); +} + +SceneWidget::~SceneWidget() +{ + // Since we're holding on to the scene templates past the existance of this graphics context, we'll need to manually release the created objects + mResourceSystem->getSceneManager()->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); +} + +void SceneWidget::setLighting(Lighting *lighting) +{ + if (mLighting) + mLighting->deactivate(); + + mLighting = lighting; + mLighting->activate (mRootNode); + + osg::Vec4f ambient = mLighting->getAmbientColour(mHasDefaultAmbient ? &mDefaultAmbient : 0); + setAmbient(ambient); + + flagAsModified(); +} + +void SceneWidget::setAmbient(const osg::Vec4f& ambient) +{ + osg::ref_ptr stateset = new osg::StateSet; + osg::ref_ptr lightmodel = new osg::LightModel; + lightmodel->setAmbientIntensity(ambient); + stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); + stateset->setMode(GL_LIGHT0, osg::StateAttribute::ON); + stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON); + mRootNode->setStateSet(stateset); +} + +void SceneWidget::selectLightingMode (const std::string& mode) +{ + if (mode=="day") + setLighting (&mLightingDay); + else if (mode=="night") + setLighting (&mLightingNight); + else if (mode=="bright") + setLighting (&mLightingBright); +} + +CSVWidget::SceneToolMode *SceneWidget::makeLightingSelector (CSVWidget::SceneToolbar *parent) +{ + CSVWidget::SceneToolMode *tool = new CSVWidget::SceneToolMode (parent, "Lighting Mode"); + + /// \todo replace icons + tool->addButton (":scenetoolbar/day", "day", + "Day" + "
  • Cell specific ambient in interiors
  • " + "
  • Low ambient in exteriors
  • " + "
  • Strong directional light source
  • " + "
  • This mode closely resembles day time in-game
"); + tool->addButton (":scenetoolbar/night", "night", + "Night" + "
  • Cell specific ambient in interiors
  • " + "
  • Low ambient in exteriors
  • " + "
  • Weak directional light source
  • " + "
  • This mode closely resembles night time in-game
"); + tool->addButton (":scenetoolbar/bright", "bright", + "Bright" + "
  • Maximum ambient
  • " + "
  • Strong directional light source
"); + + connect (tool, SIGNAL (modeChanged (const std::string&)), + this, SLOT (selectLightingMode (const std::string&))); + + return tool; +} + +void SceneWidget::setDefaultAmbient (const osg::Vec4f& colour) +{ + mDefaultAmbient = colour; + mHasDefaultAmbient = true; + + setAmbient(mLighting->getAmbientColour(&mDefaultAmbient)); +} - int timerStart = userSettings.setting("scene-input/timer", QString("20")).toInt(); - timer->start (timerStart); - - /// \todo make shortcut configurable - QShortcut *focusToolbar = new QShortcut (Qt::Key_T, this, 0, 0, Qt::WidgetWithChildrenShortcut); - connect (focusToolbar, SIGNAL (activated()), this, SIGNAL (focusToolbarRequest())); - } - - CSVWidget::SceneToolMode *SceneWidget::makeLightingSelector (CSVWidget::SceneToolbar *parent) - { - CSVWidget::SceneToolMode *tool = new CSVWidget::SceneToolMode (parent, "Lighting Mode"); - - /// \todo replace icons - tool->addButton (":scenetoolbar/day", "day", - "Day" - "
  • Cell specific ambient in interiors
  • " - "
  • Low ambient in exteriors
  • " - "
  • Strong directional light source/lir>" - "
  • This mode closely resembles day time in-game
"); - tool->addButton (":scenetoolbar/night", "night", - "Night" - "
  • Cell specific ambient in interiors
  • " - "
  • Low ambient in exteriors
  • " - "
  • Weak directional light source
  • " - "
  • This mode closely resembles night time in-game
"); - tool->addButton (":scenetoolbar/bright", "bright", - "Bright" - "
  • Maximum ambient
  • " - "
  • Strong directional light source
"); - - connect (tool, SIGNAL (modeChanged (const std::string&)), - this, SLOT (selectLightingMode (const std::string&))); - - return tool; - } - - void SceneWidget::setDefaultAmbient (const Ogre::ColourValue& colour) - { - mDefaultAmbient = colour; - mHasDefaultAmbient = true; - - if (mLighting) - mLighting->setDefaultAmbient (colour); - } - - void SceneWidget::updateOgreWindow() - { - if (mWindow) - { - Ogre::Root::getSingleton().destroyRenderTarget(mWindow); - mWindow = NULL; - } - - std::stringstream windowHandle; -#ifdef WIN32 - windowHandle << Ogre::StringConverter::toString((uintptr_t)(this->winId())); -#else - windowHandle << this->winId(); -#endif - std::stringstream windowTitle; - static int count=0; - windowTitle << ++count; - - Ogre::NameValuePairList params; - - params.insert(std::make_pair("externalWindowHandle", windowHandle.str())); - params.insert(std::make_pair("title", windowTitle.str())); - - std::string antialiasing = - CSMSettings::UserSettings::instance().settingValue("3d-render/antialiasing").toStdString(); - if(antialiasing == "MSAA 16") antialiasing = "16"; - else if(antialiasing == "MSAA 8") antialiasing = "8"; - else if(antialiasing == "MSAA 4") antialiasing = "4"; - else if(antialiasing == "MSAA 2") antialiasing = "2"; - else antialiasing = "0"; - params.insert(std::make_pair("FSAA", antialiasing)); - - params.insert(std::make_pair("vsync", "false")); // TODO setting -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - params.insert(std::make_pair("macAPI", "cocoa")); - params.insert(std::make_pair("macAPICocoaUseNSView", "true")); -#endif - - mWindow = Ogre::Root::getSingleton().createRenderWindow(windowTitle.str(), this->width(), this->height(), false, ¶ms); - - mViewport = mWindow->addViewport (mCamera); - mViewport->setBackgroundColour (Ogre::ColourValue (0.3,0.3,0.3,1)); - - Ogre::Real aspectRatio = Ogre::Real(width()) / Ogre::Real(height()); - mCamera->setAspectRatio(aspectRatio); - } - - SceneWidget::~SceneWidget() - { - if (mWindow) - Ogre::Root::getSingleton().destroyRenderTarget (mWindow); - - if (mSceneMgr) - mSceneMgr->removeRenderQueueListener (mOverlaySystem); - - if (mSceneMgr) - Ogre::Root::getSingleton().destroySceneManager (mSceneMgr); - - } - - void SceneWidget::setVisibilityMask (unsigned int mask) - { - mViewport->setVisibilityMask (mask); - } - - void SceneWidget::setNavigation (Navigation *navigation) - { - if ((mNavigation = navigation)) - { - mNavigation->setFastModeFactor (mFast ? mFastFactor : 1); - if (mNavigation->activate (mCamera)) - mUpdate = true; - } - } - - void SceneWidget::addRenderTargetListener(Ogre::RenderTargetListener *listener) - { - mWindow->addListener(listener); - } - - void SceneWidget::removeRenderTargetListener(Ogre::RenderTargetListener *listener) - { - mWindow->removeListener(listener); - } - - Ogre::Viewport *SceneWidget::getViewport() - { - if (!mWindow) - updateOgreWindow(); - - return mViewport; - } - - Ogre::SceneManager *SceneWidget::getSceneManager() - { - return mSceneMgr; - } - - Ogre::Camera *SceneWidget::getCamera() - { - return mCamera; - } - - void SceneWidget::flagAsModified() - { - mUpdate = true; - } - - void SceneWidget::paintEvent(QPaintEvent* e) - { - if (!mWindow) - updateOgreWindow(); - - mWindow->update(); - e->accept(); - } - - QPaintEngine* SceneWidget::paintEngine() const - { - // We don't want another paint engine to get in the way. - // So we return nothing. - return NULL; - } - - void SceneWidget::resizeEvent(QResizeEvent *e) - { - if (!mWindow) - return; - - const QSize &newSize = e->size(); - - // TODO: Fix Ogre to handle this more consistently (fixed in 1.9) -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX - mWindow->resize(newSize.width(), newSize.height()); -#else - mWindow->windowMovedOrResized(); -#endif - - Ogre::Real aspectRatio = Ogre::Real(newSize.width()) / Ogre::Real(newSize.height()); - mCamera->setAspectRatio(aspectRatio); - } - - bool SceneWidget::event(QEvent *e) - { - if (e->type() == QEvent::WinIdChange) - { - // I haven't actually seen this happen yet. - if (mWindow) - updateOgreWindow(); - } - return QWidget::event(e); - } - - void SceneWidget::keyPressEvent (QKeyEvent *event) - { - switch (event->key()) - { - case Qt::Key_W: mKeyForward = true; break; - case Qt::Key_S: mKeyBackward = true; break; - case Qt::Key_A: mKeyLeft = true; break; - case Qt::Key_D: mKeyRight = true; break; - case Qt::Key_Q: mKeyRollLeft = true; break; - case Qt::Key_E: mKeyRollRight = true; break; - case Qt::Key_Control: mMod1 = true; break; - - case Qt::Key_Shift: - - mFast = true; - - if (mNavigation) - mNavigation->setFastModeFactor (mFastFactor); - - break; - - default: QWidget::keyPressEvent (event); - } - } - - void SceneWidget::keyReleaseEvent (QKeyEvent *event) - { - switch (event->key()) - { - case Qt::Key_W: mKeyForward = false; break; - case Qt::Key_S: mKeyBackward = false; break; - case Qt::Key_A: mKeyLeft = false; break; - case Qt::Key_D: mKeyRight = false; break; - case Qt::Key_Q: mKeyRollLeft = false; break; - case Qt::Key_E: mKeyRollRight = false; break; - case Qt::Key_Control: mMod1 = false; break; - - case Qt::Key_Shift: - - mFast = false; - - if (mNavigation) - mNavigation->setFastModeFactor (1); - - break; - - default: QWidget::keyReleaseEvent (event); - } - } - - void SceneWidget::wheelEvent (QWheelEvent *event) - { - if (mNavigation) - if (event->delta()) - if (mNavigation->wheelMoved (event->delta())) - mUpdate = true; - } - - void SceneWidget::leaveEvent (QEvent *event) - { - mDragging = false; - } - - void SceneWidget::mouseMoveEvent (QMouseEvent *event) - { - if (event->buttons() & Qt::LeftButton) - { - if (mDragging) - { - QPoint diff = mOldPos-event->pos(); - mOldPos = event->pos(); - - if (mNavigation) - if (mNavigation->mouseMoved (diff, mMod1 ? 1 : 0)) - mUpdate = true; - } - else - { - mDragging = true; - mOldPos = event->pos(); - } - } - } - - void SceneWidget::mouseReleaseEvent (QMouseEvent *event) - { - if (!(event->buttons() & Qt::LeftButton)) - mDragging = false; - } - - void SceneWidget::focusOutEvent (QFocusEvent *event) - { - mKeyForward = false; - mKeyBackward = false; - mKeyLeft = false; - mKeyRight = false; - mFast = false; - mMod1 = false; - - QWidget::focusOutEvent (event); - } - - void SceneWidget::update() - { - if (mNavigation) - { - int horizontal = 0; - int vertical = 0; - - if (mKeyForward && !mKeyBackward) - vertical = 1; - else if (!mKeyForward && mKeyBackward) - vertical = -1; - - if (mKeyLeft && !mKeyRight) - horizontal = -1; - else if (!mKeyLeft && mKeyRight) - horizontal = 1; - - if (horizontal || vertical) - if (mNavigation->handleMovementKeys (vertical, horizontal)) - mUpdate = true; - - int roll = 0; - - if (mKeyRollLeft && !mKeyRollRight) - roll = 1; - else if (!mKeyRollLeft && mKeyRollRight) - roll = -1; - - if (roll) - if (mNavigation->handleRollKeys (roll)) - mUpdate = true; - - } - - if (mUpdate && mWindow) - { - mUpdate = false; - mWindow->update(); - updateOverlay(); - } - } - - void SceneWidget::updateScene() - { - flagAsModified(); - } - - void SceneWidget::updateOverlay() - { } - - void SceneWidget::setLighting (Lighting *lighting) - { - if (mLighting) - mLighting->deactivate(); - - mLighting = lighting; - mLighting->activate (mSceneMgr, mHasDefaultAmbient ? &mDefaultAmbient : 0); - - if (mWindow) - mWindow->update(); - } - - void SceneWidget::selectLightingMode (const std::string& mode) - { - if (mode=="day") - setLighting (&mLightingDay); - else if (mode=="night") - setLighting (&mLightingNight); - else if (mode=="bright") - setLighting (&mLightingBright); - } - - void SceneWidget::updateUserSetting (const QString &key, const QStringList &list) - { - if(key.contains(QRegExp("^\\b(Objects|Shader|Scene)", Qt::CaseInsensitive))) - flagAsModified(); - - if(key == "3d-render/far-clip-distance" && !list.empty()) - { - if(mCamera->getFarClipDistance() != list.at(0).toFloat()) - mCamera->setFarClipDistance(list.at(0).toFloat()); - } - - // minimise unnecessary ogre window creation by updating only when there is a change - if(key == "3d-render/antialiasing") - { - unsigned int aa = mWindow->getFSAA(); - unsigned int antialiasing = 0; - if(!list.empty()) - { - if(list.at(0) == "MSAA 16") antialiasing = 16; - else if(list.at(0) == "MSAA 8") antialiasing = 8; - else if(list.at(0) == "MSAA 4") antialiasing = 4; - else if(list.at(0) == "MSAA 2") antialiasing = 2; - } - if(aa != antialiasing) - updateOgreWindow(); - } - } } diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 699d6a7a56..63da01a45b 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -2,21 +2,25 @@ #define OPENCS_VIEW_SCENEWIDGET_H #include +#include -#include +#include #include "lightingday.hpp" #include "lightingnight.hpp" #include "lightingbright.hpp" -namespace Ogre +#include +#include + +namespace Resource { - class Camera; - class SceneManager; - class RenderWindow; - class Viewport; - class OverlaySystem; - class RenderTargetListener; + class ResourceSystem; +} + +namespace osg +{ + class Group; } namespace CSVWidget @@ -27,114 +31,91 @@ namespace CSVWidget namespace CSVRender { - class Navigation; class Lighting; - class SceneWidget : public QWidget + class RenderWidget : public QWidget { Q_OBJECT - public: + public: + RenderWidget(QWidget* parent = 0, Qt::WindowFlags f = 0); + virtual ~RenderWidget(); - SceneWidget(QWidget *parent); - virtual ~SceneWidget(); + void flagAsModified(); - QPaintEngine* paintEngine() const; + void setVisibilityMask(int mask); - CSVWidget::SceneToolMode *makeLightingSelector (CSVWidget::SceneToolbar *parent); - ///< \attention The created tool is not added to the toolbar (via addTool). Doing that - /// is the responsibility of the calling function. + bool eventFilter(QObject *, QEvent *); - virtual void setVisibilityMask (unsigned int mask); + protected: - virtual void updateScene(); + osg::ref_ptr mView; - protected: + osg::Group* mRootNode; - void setNavigation (Navigation *navigation); - ///< \attention The ownership of \a navigation is not transferred to *this. + QTimer mTimer; + }; - void addRenderTargetListener(Ogre::RenderTargetListener *listener); + // Extension of RenderWidget to support lighting mode selection & toolbar + class SceneWidget : public RenderWidget + { + Q_OBJECT + public: + SceneWidget(boost::shared_ptr resourceSystem, QWidget* parent = 0, Qt::WindowFlags f = 0); + virtual ~SceneWidget(); - void removeRenderTargetListener(Ogre::RenderTargetListener *listener); + CSVWidget::SceneToolMode *makeLightingSelector (CSVWidget::SceneToolbar *parent); + ///< \attention The created tool is not added to the toolbar (via addTool). Doing that + /// is the responsibility of the calling function. - Ogre::Viewport *getViewport(); + void setDefaultAmbient (const osg::Vec4f& colour); + ///< \note The actual ambient colour may differ based on lighting settings. - Ogre::SceneManager *getSceneManager(); + protected: + void setLighting (Lighting *lighting); + ///< \attention The ownership of \a lighting is not transferred to *this. - Ogre::Camera *getCamera(); + void setAmbient(const osg::Vec4f& ambient); - void flagAsModified(); + boost::shared_ptr mResourceSystem; - void setDefaultAmbient (const Ogre::ColourValue& colour); - ///< \note The actual ambient colour may differ based on lighting settings. + Lighting* mLighting; - virtual void updateOverlay(); + osg::Vec4f mDefaultAmbient; + bool mHasDefaultAmbient; + LightingDay mLightingDay; + LightingNight mLightingNight; + LightingBright mLightingBright; - virtual void mouseReleaseEvent (QMouseEvent *event); + private slots: - virtual void mouseMoveEvent (QMouseEvent *event); - - void wheelEvent (QWheelEvent *event); - - void keyPressEvent (QKeyEvent *event); - - private: - void paintEvent(QPaintEvent* e); - void resizeEvent(QResizeEvent* e); - bool event(QEvent* e); - - void keyReleaseEvent (QKeyEvent *event); - - void focusOutEvent (QFocusEvent *event); - - void leaveEvent (QEvent *event); - - void updateOgreWindow(); - - void setLighting (Lighting *lighting); - ///< \attention The ownership of \a lighting is not transferred to *this. - - Ogre::Camera* mCamera; - Ogre::SceneManager* mSceneMgr; - Ogre::RenderWindow* mWindow; - Ogre::Viewport *mViewport; - Ogre::OverlaySystem *mOverlaySystem; - - Navigation *mNavigation; - Lighting *mLighting; - bool mUpdate; - bool mKeyForward; - bool mKeyBackward; - bool mKeyLeft; - bool mKeyRight; - bool mKeyRollLeft; - bool mKeyRollRight; - bool mFast; - bool mDragging; - bool mMod1; - QPoint mOldPos; - int mFastFactor; - Ogre::ColourValue mDefaultAmbient; - bool mHasDefaultAmbient; - LightingDay mLightingDay; - LightingNight mLightingNight; - LightingBright mLightingBright; - - public slots: - - void updateUserSetting (const QString &key, const QStringList &list); - - private slots: - - void update(); - - void selectLightingMode (const std::string& mode); + void selectLightingMode (const std::string& mode); signals: - void focusToolbarRequest(); + void focusToolbarRequest(); }; + + + // There are rendering glitches when using multiple Viewer instances, work around using CompositeViewer with multiple views + class CompositeViewer : public QObject, public osgViewer::CompositeViewer + { + Q_OBJECT + public: + CompositeViewer(); + + static CompositeViewer& get(); + + QTimer mTimer; + + private: + osg::Timer mFrameTimer; + double mSimulationTime; + + public slots: + void update(); + }; + } #endif diff --git a/apps/opencs/view/render/tagbase.cpp b/apps/opencs/view/render/tagbase.cpp new file mode 100644 index 0000000000..79412c1321 --- /dev/null +++ b/apps/opencs/view/render/tagbase.cpp @@ -0,0 +1,14 @@ + +#include "tagbase.hpp" + +CSVRender::TagBase::TagBase (Elements element) : mElement (element) {} + +CSVRender::Elements CSVRender::TagBase::getElement() const +{ + return mElement; +} + +QString CSVRender::TagBase::getToolTip (bool hideBasics) const +{ + return ""; +} diff --git a/apps/opencs/view/render/tagbase.hpp b/apps/opencs/view/render/tagbase.hpp new file mode 100644 index 0000000000..9f169c3b1a --- /dev/null +++ b/apps/opencs/view/render/tagbase.hpp @@ -0,0 +1,27 @@ +#ifndef OPENCS_VIEW_TAGBASE_H +#define OPENCS_VIEW_TAGBASE_H + +#include + +#include + +#include "elements.hpp" + +namespace CSVRender +{ + class TagBase : public osg::Referenced + { + Elements mElement; + + public: + + TagBase (Elements element); + + Elements getElement() const; + + virtual QString getToolTip (bool hideBasics) const; + + }; +} + +#endif diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index a14eea5dd6..2be4efd73e 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -4,11 +4,12 @@ namespace CSVRender { TerrainStorage::TerrainStorage(const CSMWorld::Data &data) - : mData(data) + : ESMTerrain::Storage(data.getResourceSystem()->getVFS()) + , mData(data) { } - ESM::Land* TerrainStorage::getLand(int cellX, int cellY) + const ESM::Land* TerrainStorage::getLand(int cellX, int cellY) { std::ostringstream stream; stream << "#" << cellX << " " << cellY; @@ -19,19 +20,24 @@ namespace CSVRender if (index == -1) return NULL; - ESM::Land* land = mData.getLand().getRecord(index).get().mLand.get(); + const ESM::Land& land = mData.getLand().getRecord(index).get(); int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX; - if (!land->isDataLoaded(mask)) - land->loadData(mask); - return land; + land.loadData (mask); + return &land; } const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) { - std::ostringstream stream; - stream << index << "_" << plugin; + int numRecords = mData.getLandTextures().getSize(); - return &mData.getLandTextures().getRecord(stream.str()).get(); + for (int i=0; imIndex == index && ltex->mPluginIndex == plugin) + return ltex; + } + + return NULL; } void TerrainStorage::getBounds(float &minX, float &maxX, float &minY, float &maxY) diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 97782ad17e..16b0f3ec7a 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -18,7 +18,7 @@ namespace CSVRender private: const CSMWorld::Data& mData; - virtual ESM::Land* getLand (int cellX, int cellY); + virtual const ESM::Land* getLand (int cellX, int cellY); virtual const ESM::LandTexture* getLandTexture(int index, short plugin); virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); diff --git a/apps/opencs/view/render/textoverlay.cpp b/apps/opencs/view/render/textoverlay.cpp deleted file mode 100644 index c41d5f3180..0000000000 --- a/apps/opencs/view/render/textoverlay.cpp +++ /dev/null @@ -1,359 +0,0 @@ -#include "textoverlay.hpp" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace CSVRender -{ - -// Things to do: -// - configurable font size in pixels (automatically calulate everything else from it) -// - configurable texture to use -// - try material script -// - decide whether to use QPaint (http://www.ogre3d.org/tikiwiki/Ogre+overlays+using+Qt) - -// http://www.ogre3d.org/tikiwiki/ObjectTextDisplay -// http://www.ogre3d.org/tikiwiki/MovableTextOverlay -// http://www.ogre3d.org/tikiwiki/Creating+dynamic+textures -// http://www.ogre3d.org/tikiwiki/ManualObject -TextOverlay::TextOverlay(const Ogre::MovableObject* obj, const Ogre::Camera* camera, const Ogre::String& id) - : mOverlay(0), mCaption(""), mDesc(""), mObj(obj), mCamera(camera), mFontHeight(16), mId(id) - , mEnabled(true), mOnScreen(false), mInstance(0) // FIXME: make font height configurable -{ - if(id == "" || !camera || !obj) - throw std::runtime_error("TextOverlay could not be created."); - - // setup font - Ogre::FontManager &fontMgr = Ogre::FontManager::getSingleton(); - if (fontMgr.resourceExists("DejaVuLGC")) - mFont = fontMgr.getByName("DejaVuLGC","General"); - else - { - mFont = fontMgr.create("DejaVuLGC","General"); - mFont->setType(Ogre::FT_TRUETYPE); - mFont->setSource("DejaVuLGCSansMono.ttf"); - mFont->setTrueTypeSize(mFontHeight); - mFont->setTrueTypeResolution(96); - } - if(!mFont.isNull()) - mFont->load(); - else - throw std::runtime_error("TextOverlay font not loaded."); - - // setup overlay - Ogre::OverlayManager &overlayMgr = Ogre::OverlayManager::getSingleton(); - mOverlay = overlayMgr.getByName("CellIDPanel"+mId+Ogre::StringConverter::toString(mInstance)); - // FIXME: this logic is badly broken as it is possible to delete an earlier instance - while(mOverlay != NULL) - { - mInstance++; - mOverlay = overlayMgr.getByName("CellIDPanel"+mId+Ogre::StringConverter::toString(mInstance)); - } - mOverlay = overlayMgr.create("CellIDPanel"+mId+Ogre::StringConverter::toString(mInstance)); - - // create texture - Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().getByName("DynamicTransBlue"); - if(texture.isNull()) - { - texture = Ogre::TextureManager::getSingleton().createManual( - "DynamicTransBlue", // name - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, // type - 8, 8, // width & height - 0, // number of mipmaps - Ogre::PF_BYTE_BGRA, // pixel format - Ogre::TU_DEFAULT); // usage; should be TU_DYNAMIC_WRITE_ONLY_DISCARDABLE for - // textures updated very often (e.g. each frame) - - Ogre::HardwarePixelBufferSharedPtr pixelBuffer = texture->getBuffer(); - pixelBuffer->lock(Ogre::HardwareBuffer::HBL_NORMAL); - const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock(); - - Ogre::uint8* pDest = static_cast(pixelBox.data); - - // Fill in some pixel data. This will give a semi-transparent blue, - // but this is of course dependent on the chosen pixel format. - for (size_t j = 0; j < 8; j++) - { - for(size_t i = 0; i < 8; i++) - { - *pDest++ = 255; // B - *pDest++ = 0; // G - *pDest++ = 0; // R - *pDest++ = 63; // A - } - - pDest += pixelBox.getRowSkip() * Ogre::PixelUtil::getNumElemBytes(pixelBox.format); - } - pixelBuffer->unlock(); - } - - // setup material for containers - Ogre::MaterialPtr mQuadMaterial = Ogre::MaterialManager::getSingleton().getByName( - "TransOverlayMaterial"); - if(mQuadMaterial.isNull()) - { - Ogre::MaterialPtr mQuadMaterial = Ogre::MaterialManager::getSingleton().create( - "TransOverlayMaterial", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true ); - Ogre::Pass *pass = mQuadMaterial->getTechnique( 0 )->getPass( 0 ); - pass->setLightingEnabled( false ); - pass->setDepthWriteEnabled( false ); - pass->setSceneBlending( Ogre::SBT_TRANSPARENT_ALPHA ); - - Ogre::TextureUnitState *tex = pass->createTextureUnitState("MyCustomState", 0); - tex->setTextureName("DynamicTransBlue"); - tex->setTextureFiltering( Ogre::TFO_ANISOTROPIC ); - mQuadMaterial->load(); - } - - mContainer = static_cast(overlayMgr.createOverlayElement( - "Panel", "container"+mId +"#"+Ogre::StringConverter::toString(mInstance))); - mContainer->setMaterialName("TransOverlayMaterial"); - mOverlay->add2D(mContainer); - - // setup text area overlay element - mElement = static_cast(overlayMgr.createOverlayElement( - "TextArea", "text"+mId +"#"+Ogre::StringConverter::toString(mInstance))); - mElement->setMetricsMode(Ogre::GMM_RELATIVE); - mElement->setDimensions(1.0, 1.0); - mElement->setMetricsMode(Ogre::GMM_PIXELS); - mElement->setPosition(2*fontHeight()/3, 1.3*fontHeight()/3); // 1.3 & 2 = fudge factor - - mElement->setFontName("DejaVuLGC"); - mElement->setCharHeight(fontHeight()); // NOTE: seems that this is required as well as font->setTrueTypeSize() - mElement->setHorizontalAlignment(Ogre::GHA_LEFT); - //mElement->setColour(Ogre::ColourValue(1.0, 1.0, 1.0)); // R, G, B - mElement->setColour(Ogre::ColourValue(1.0, 1.0, 0)); // yellow - - mContainer->addChild(mElement); - mOverlay->show(); -} - -void TextOverlay::getScreenCoordinates(const Ogre::Vector3& position, Ogre::Real& x, Ogre::Real& y) -{ - Ogre::Vector3 hcsPosition = mCamera->getProjectionMatrix() * (mCamera->getViewMatrix() * position); - - x = 1.0f - ((hcsPosition.x * 0.5f) + 0.5f); // 0 <= x <= 1 // left := 0,right := 1 - y = ((hcsPosition.y * 0.5f) + 0.5f); // 0 <= y <= 1 // bottom := 0,top := 1 -} - -void TextOverlay::getMinMaxEdgesOfAABBIn2D(float& MinX, float& MinY, float& MaxX, float& MaxY, - bool top) -{ - MinX = 0, MinY = 0, MaxX = 0, MaxY = 0; - float X[4]; // the 2D dots of the AABB in screencoordinates - float Y[4]; - - if(!mObj->isInScene()) - return; - - const Ogre::AxisAlignedBox &AABB = mObj->getWorldBoundingBox(true); // the AABB of the target - Ogre::Vector3 cornersOfAABB[4]; - if(top) - { - cornersOfAABB[0] = AABB.getCorner(Ogre::AxisAlignedBox::FAR_LEFT_TOP); - cornersOfAABB[1] = AABB.getCorner(Ogre::AxisAlignedBox::FAR_RIGHT_TOP); - cornersOfAABB[2] = AABB.getCorner(Ogre::AxisAlignedBox::NEAR_LEFT_TOP); - cornersOfAABB[3] = AABB.getCorner(Ogre::AxisAlignedBox::NEAR_RIGHT_TOP); - } - else - { - cornersOfAABB[0] = AABB.getCorner(Ogre::AxisAlignedBox::FAR_LEFT_BOTTOM); - cornersOfAABB[1] = AABB.getCorner(Ogre::AxisAlignedBox::FAR_RIGHT_BOTTOM); - cornersOfAABB[2] = AABB.getCorner(Ogre::AxisAlignedBox::NEAR_LEFT_BOTTOM); - cornersOfAABB[3] = AABB.getCorner(Ogre::AxisAlignedBox::NEAR_RIGHT_BOTTOM); - } - - //The normal vector of the plane. This points directly infront of the camera. - Ogre::Vector3 cameraPlainNormal = mCamera->getDerivedOrientation().zAxis(); - - //the plane that devides the space before and behind the camera. - Ogre::Plane CameraPlane = Ogre::Plane(cameraPlainNormal, mCamera->getDerivedPosition()); - - for (int i = 0; i < 4; i++) - { - X[i] = 0; - Y[i] = 0; - - getScreenCoordinates(cornersOfAABB[i],X[i],Y[i]); // transfor into 2d dots - - if (CameraPlane.getSide(cornersOfAABB[i]) == Ogre::Plane::NEGATIVE_SIDE) - { - if (i == 0) // accept the first set of values, no matter how bad it might be. - { - MinX = X[i]; - MinY = Y[i]; - MaxX = X[i]; - MaxY = Y[i]; - } - else // now compare if you get "better" values - { - if (MinX > X[i]) MinX = X[i]; - if (MinY > Y[i]) MinY = Y[i]; - if (MaxX < X[i]) MaxX = X[i]; - if (MaxY < Y[i]) MaxY = Y[i]; - } - } - else - { - MinX = 0; - MinY = 0; - MaxX = 0; - MaxY = 0; - break; - } - } -} - -TextOverlay::~TextOverlay() -{ - Ogre::OverlayManager::OverlayMapIterator iter = Ogre::OverlayManager::getSingleton().getOverlayIterator(); - if(!iter.hasMoreElements()) - mOverlay->hide(); - - Ogre::OverlayManager *overlayMgr = Ogre::OverlayManager::getSingletonPtr(); - mContainer->removeChild("text"+mId+"#"+Ogre::StringConverter::toString(mInstance)); - mOverlay->remove2D(mContainer); - - if(!iter.hasMoreElements()) - overlayMgr->destroy(mOverlay); -} - -void TextOverlay::show(bool show) -{ - if(show && mOnScreen) - mContainer->show(); - else - mContainer->hide(); -} - -void TextOverlay::enable(bool enable) -{ - if(enable == mOverlay->isVisible()) - return; - - mEnabled = enable; - if(enable) - mOverlay->show(); - else - mOverlay->hide(); -} - -bool TextOverlay::isEnabled() -{ - return mEnabled; -} - -void TextOverlay::setCaption(const Ogre::String& text) -{ - if(mCaption == text) - return; - - mCaption = text; - mElement->setCaption(text); -} - -void TextOverlay::setDesc(const Ogre::String& text) -{ - if(mDesc == text) - return; - - mDesc = text; - mElement->setCaption(mCaption + ((text == "") ? "" : ("\n" + text))); -} - -Ogre::FontPtr TextOverlay::getFont() -{ - return mFont; -} - -int TextOverlay::textWidth() -{ - float captionWidth = 0; - float descWidth = 0; - - for(Ogre::String::const_iterator i = mCaption.begin(); i < mCaption.end(); ++i) - { - if(*i == 0x0020) - captionWidth += getFont()->getGlyphAspectRatio(0x0030); - else - captionWidth += getFont()->getGlyphAspectRatio(*i); - } - - for(Ogre::String::const_iterator i = mDesc.begin(); i < mDesc.end(); ++i) - { - if(*i == 0x0020) - descWidth += getFont()->getGlyphAspectRatio(0x0030); - else - descWidth += getFont()->getGlyphAspectRatio(*i); - } - - captionWidth *= fontHeight(); - descWidth *= fontHeight(); - - return (int) std::max(captionWidth, descWidth); -} - -int TextOverlay::fontHeight() -{ - return mFontHeight; -} - -void TextOverlay::update() -{ - float min_x, max_x, min_y, max_y; - getMinMaxEdgesOfAABBIn2D(min_x, min_y, max_x, max_y, false); - - if ((min_x>0.0) && (max_x<1.0) && (min_y>0.0) && (max_y<1.0)) - { - mOnScreen = true; - mContainer->show(); - } - else - { - mOnScreen = false; - mContainer->hide(); - return; - } - - getMinMaxEdgesOfAABBIn2D(min_x, min_y, max_x, max_y); - - Ogre::OverlayManager &overlayMgr = Ogre::OverlayManager::getSingleton(); - float viewportWidth = std::max(overlayMgr.getViewportWidth(), 1); // zero at the start - float viewportHeight = std::max(overlayMgr.getViewportHeight(), 1); // zero at the start - - int width = fontHeight()*2/3 + textWidth() + fontHeight()*2/3; // add margins - int height = fontHeight()/3 + fontHeight() + fontHeight()/3; - if(mDesc != "") - height = fontHeight()/3 + 2*fontHeight() + fontHeight()/3; - - float relTextWidth = width / viewportWidth; - float relTextHeight = height / viewportHeight; - - float posX = 1 - (min_x + max_x + relTextWidth)/2; - float posY = 1 - max_y - (relTextHeight-fontHeight()/3/viewportHeight); - - mContainer->setMetricsMode(Ogre::GMM_RELATIVE); - mContainer->setPosition(posX, posY); - mContainer->setDimensions(relTextWidth, relTextHeight); - - mPos = QRect(posX*viewportWidth, posY*viewportHeight, width, height); -} - -QRect TextOverlay::container() -{ - return mPos; -} - -} diff --git a/apps/opencs/view/render/textoverlay.hpp b/apps/opencs/view/render/textoverlay.hpp deleted file mode 100644 index dbb347e567..0000000000 --- a/apps/opencs/view/render/textoverlay.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef OPENCS_VIEW_TEXTOVERLAY_H -#define OPENCS_VIEW_TEXTOVERLAY_H - -#include - -#include -#include - -namespace Ogre -{ - class MovableObject; - class Camera; - class Font; - class Overlay; - class OverlayContainer; - class TextAreaOverlayElement; -} - -namespace CSVRender -{ - - class TextOverlay - { - Ogre::Overlay* mOverlay; - Ogre::OverlayContainer* mContainer; - Ogre::TextAreaOverlayElement* mElement; - Ogre::String mCaption; - Ogre::String mDesc; - - const Ogre::MovableObject* mObj; - const Ogre::Camera* mCamera; - Ogre::FontPtr mFont; - int mFontHeight; // in pixels - Ogre::String mId; - QRect mPos; - - bool mEnabled; - bool mOnScreen; - int mInstance; - - Ogre::FontPtr getFont(); - int textWidth(); - int fontHeight(); - void getScreenCoordinates(const Ogre::Vector3& position, Ogre::Real& x, Ogre::Real& y); - void getMinMaxEdgesOfAABBIn2D(float& MinX, float& MinY, float& MaxX, float& MaxY, - bool top = true); - - public: - - TextOverlay(const Ogre::MovableObject* obj, const Ogre::Camera* camera, const Ogre::String &id); - virtual ~TextOverlay(); - - void enable(bool enable); // controlled from scene widget toolbar visibility mask - void show(bool show); // for updating from render target listener - bool isEnabled(); - void setCaption(const Ogre::String& text); - void setDesc(const Ogre::String& text); - void update(); - QRect container(); // for detection of mouse click on the overlay - Ogre::String getCaption() { return mCaption; } // FIXME: debug - Ogre::String getDesc() { return mDesc; } - }; - -} - -#endif // OPENCS_VIEW_TEXTOVERLAY_H diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 383382938d..68f068daca 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -1,13 +1,13 @@ - #include "unpagedworldspacewidget.hpp" #include -#include -#include +#include #include +#include + #include "../../model/doc/document.hpp" #include "../../model/world/data.hpp" @@ -24,8 +24,8 @@ void CSVRender::UnpagedWorldspaceWidget::update() const CSMWorld::Record& record = dynamic_cast&> (mCellsModel->getRecord (mCellId)); - Ogre::ColourValue colour; - colour.setAsABGR (record.get().mAmbi.mAmbient); + osg::Vec4f colour = SceneUtil::colourFromRGB(record.get().mAmbi.mAmbient); + setDefaultAmbient (colour); /// \todo deal with mSunlight and mFog/mForDensity @@ -49,7 +49,9 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& update(); - mCell.reset (new Cell (document.getData(), getSceneManager(), mCellId, document.getPhysics())); + mCell.reset (new Cell (document.getData(), mRootNode, mCellId)); + + mView->setCameraManipulator(new osgGA::TrackballManipulator); } void CSVRender::UnpagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, @@ -91,7 +93,8 @@ bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vectorgetId(); - mCell.reset (new Cell (getDocument().getData(), getSceneManager(), mCellId, getDocument().getPhysics())); + + mCell.reset (new Cell (getDocument().getData(), mRootNode, mCellId)); update(); emit cellChanged(*data.begin()); @@ -99,6 +102,12 @@ bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vectorsetSelection (elementMask, Cell::Selection_Clear); + flagAsModified(); +} + void CSVRender::UnpagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { @@ -163,13 +172,15 @@ void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons ( std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() { - Ogre::Vector3 position = getCamera()->getPosition(); + osg::Vec3d eye, center, up; + mView->getCamera()->getViewMatrixAsLookAt(eye, center, up); + osg::Vec3d position = eye; std::ostringstream stream; stream << "player->positionCell " - << position.x << ", " << position.y << ", " << position.z + << position.x() << ", " << position.y() << ", " << position.z() << ", 0, \"" << mCellId << "\""; return stream.str(); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index d01c3e7667..9e6fa97f43 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -43,6 +43,9 @@ namespace CSVRender virtual bool handleDrop (const std::vector& data, DropType type); + /// \param elementMask Elements to be affected by the clear operation + virtual void clearSelection (int elementMask); + private: virtual void referenceableDataChanged (const QModelIndex& topLeft, diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index ba0ec84460..f0d3986414 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -1,11 +1,7 @@ - #include "worldspacewidget.hpp" #include - -#include -#include -#include +#include #include #include @@ -13,22 +9,32 @@ #include #include #include +#include +#include + +#include +#include + +#include #include "../../model/world/universalid.hpp" #include "../../model/world/idtable.hpp" +#include "../../model/prefs/state.hpp" + #include "../widget/scenetoolmode.hpp" #include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolrun.hpp" -#include "../world/physicssystem.hpp" - +#include "object.hpp" #include "elements.hpp" #include "editmode.hpp" +#include "instancemode.hpp" CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) -: SceneWidget (parent), mSceneElements(0), mRun(0), mDocument(document), mPhysics(boost::shared_ptr()), mMouse(0), - mInteractionMask (0) +: SceneWidget (document.getData().getResourceSystem(), parent), mSceneElements(0), mRun(0), mDocument(document), + mInteractionMask (0), mEditMode (0), mLocked (false), mDragging (false), + mToolTipPos (-1, -1) { setAcceptDrops(true); @@ -60,32 +66,51 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg connect (debugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (debugProfileAboutToBeRemoved (const QModelIndex&, int, int))); - mPhysics = document.getPhysics(); // create physics if one doesn't exist - mPhysics->addSceneManager(getSceneManager(), this); - mMouse = new MouseState(this); + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + CSMPrefs::get()["3D Scene Input"].update(); + CSMPrefs::get()["Tooltips"].update(); + + mToolTipDelayTimer.setSingleShot (true); + connect (&mToolTipDelayTimer, SIGNAL (timeout()), this, SLOT (showToolTip())); } CSVRender::WorldspaceWidget::~WorldspaceWidget () { - delete mMouse; - mPhysics->removeSceneManager(getSceneManager()); +} + +void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setting) +{ + if (storeMappingSetting (setting)) + return; + + if (*setting=="3D Scene Input/drag-factor") + mDragFactor = setting->toDouble(); + else if (*setting=="3D Scene Input/drag-wheel-factor") + mDragWheelFactor = setting->toDouble(); + else if (*setting=="3D Scene Input/drag-shift-factor") + mDragShiftFactor = setting->toDouble(); + else if (*setting=="Tooltips/scene-delay") + mToolTipDelay = setting->toInt(); + else if (*setting=="Tooltips/scene") + mShowToolTips = setting->isTrue(); } void CSVRender::WorldspaceWidget::selectNavigationMode (const std::string& mode) { if (mode=="1st") - setNavigation (&m1st); + mView->setCameraManipulator(new osgGA::FirstPersonManipulator); else if (mode=="free") - setNavigation (&mFree); + mView->setCameraManipulator(new osgGA::FirstPersonManipulator); else if (mode=="orbit") - setNavigation (&mOrbit); + mView->setCameraManipulator(new osgGA::OrbitManipulator); } void CSVRender::WorldspaceWidget::useViewHint (const std::string& hint) {} void CSVRender::WorldspaceWidget::selectDefaultNavigationMode() { - setNavigation (&m1st); + mView->setCameraManipulator(new osgGA::FirstPersonManipulator); } CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( @@ -184,11 +209,14 @@ CSVWidget::SceneToolRun *CSVRender::WorldspaceWidget::makeRunTool ( CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeEditModeSelector ( CSVWidget::SceneToolbar *parent) { - CSVWidget::SceneToolMode *tool = new CSVWidget::SceneToolMode (parent, "Edit Mode"); + mEditMode = new CSVWidget::SceneToolMode (parent, "Edit Mode"); - addEditModeSelectorButtons (tool); + addEditModeSelectorButtons (mEditMode); - return tool; + connect (mEditMode, SIGNAL (modeChanged (const std::string&)), + this, SLOT (editModeChanged (const std::string&))); + + return mEditMode; } CSVRender::WorldspaceWidget::DropType CSVRender::WorldspaceWidget::getDropType ( @@ -260,6 +288,11 @@ unsigned int CSVRender::WorldspaceWidget::getInteractionMask() const return mInteractionMask & getVisibilityMask(); } +void CSVRender::WorldspaceWidget::setEditLock (bool locked) +{ + dynamic_cast (*mEditMode->getCurrent()).setEditLock (locked); +} + void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { @@ -271,9 +304,7 @@ void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons ( void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool) { /// \todo replace EditMode with suitable subclasses - tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Element_Reference, "Instance editing"), - "object"); + tool->addButton (new InstanceMode (this, tool), "object"); tool->addButton ( new EditMode (this, QIcon (":placeholder"), Element_Pathgrid, "Pathgrid editing"), "pathgrid"); @@ -286,12 +317,136 @@ CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument() void CSVRender::WorldspaceWidget::dragEnterEvent (QDragEnterEvent* event) { - event->accept(); + const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped + return; + + if (mime->fromDocument (mDocument)) + { + if (mime->holdsType (CSMWorld::UniversalId::Type_Cell) || + mime->holdsType (CSMWorld::UniversalId::Type_Cell_Missing) || + mime->holdsType (CSMWorld::UniversalId::Type_DebugProfile)) + { + // These drops are handled through the subview object. + event->accept(); + } + else + dynamic_cast (*mEditMode->getCurrent()).dragEnterEvent (event); + } } void CSVRender::WorldspaceWidget::dragMoveEvent(QDragMoveEvent *event) { - event->accept(); + const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped + return; + + if (mime->fromDocument (mDocument)) + { + if (mime->holdsType (CSMWorld::UniversalId::Type_Cell) || + mime->holdsType (CSMWorld::UniversalId::Type_Cell_Missing) || + mime->holdsType (CSMWorld::UniversalId::Type_DebugProfile)) + { + // These drops are handled through the subview object. + event->accept(); + } + else + dynamic_cast (*mEditMode->getCurrent()).dragMoveEvent (event); + } +} + +bool CSVRender::WorldspaceWidget::storeMappingSetting (const CSMPrefs::Setting *setting) +{ + if (setting->getParent()->getKey()!="3D Scene Input") + return false; + + static const char * const sMappingSettings[] = + { + "p-navi", "s-navi", + "p-edit", "s-edit", + "p-select", "s-select", + 0 + }; + + for (int i=0; sMappingSettings[i]; ++i) + if (setting->getKey()==sMappingSettings[i]) + { + QString value = QString::fromUtf8 (setting->toString().c_str()); + + Qt::MouseButton button = Qt::NoButton; + + if (value.endsWith ("Left Mouse-Button")) + button = Qt::LeftButton; + else if (value.endsWith ("Right Mouse-Button")) + button = Qt::RightButton; + else if (value.endsWith ("Middle Mouse-Button")) + button = Qt::MiddleButton; + else + return false; + + bool ctrl = value.startsWith ("Ctrl-"); + + mButtonMapping[std::make_pair (button, ctrl)] = sMappingSettings[i]; + return true; + } + + return false; +} + +osg::ref_ptr CSVRender::WorldspaceWidget::mousePick (const QPoint& localPos) +{ + // (0,0) is considered the lower left corner of an OpenGL window + int x = localPos.x(); + int y = height() - localPos.y(); + + osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::Intersector::WINDOW, x, y)); + + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); + osgUtil::IntersectionVisitor visitor(intersector); + + visitor.setTraversalMask(getInteractionMask() << 1); + + mView->getCamera()->accept(visitor); + + for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); + it != intersector->getIntersections().end(); ++it) + { + osgUtil::LineSegmentIntersector::Intersection intersection = *it; + + // reject back-facing polygons + osg::Vec3f normal = intersection.getWorldIntersectNormal(); + normal = osg::Matrix::transform3x3(normal, mView->getCamera()->getViewMatrix()); + if (normal.z() < 0) + continue; + + for (std::vector::iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) + { + osg::Node* node = *it; + if (osg::ref_ptr tag = dynamic_cast(node->getUserData())) + return tag; + } + +// ignoring terrain for now + // must be terrain, report coordinates +// std::cout << "Terrain hit at " << intersection.getWorldIntersectPoint().x() << " " << intersection.getWorldIntersectPoint().y() << std::endl; +// return; + } + + return osg::ref_ptr(); +} + +std::string CSVRender::WorldspaceWidget::mapButton (QMouseEvent *event) +{ + std::pair phyiscal ( + event->button(), event->modifiers() & Qt::ControlModifier); + + std::map, std::string>::const_iterator iter = + mButtonMapping.find (phyiscal); + + if (iter!=mButtonMapping.end()) + return iter->second; + + return ""; } void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) @@ -302,8 +457,15 @@ void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) if (mime->fromDocument (mDocument)) { - emit dataDropped(mime->getData()); - } //not handling drops from different documents at the moment + if (mime->holdsType (CSMWorld::UniversalId::Type_Cell) || + mime->holdsType (CSMWorld::UniversalId::Type_Cell_Missing) || + mime->holdsType (CSMWorld::UniversalId::Type_DebugProfile)) + { + emit dataDropped(mime->getData()); + } + else + dynamic_cast (*mEditMode->getCurrent()).dropEvent (event); + } } void CSVRender::WorldspaceWidget::runRequest (const std::string& profile) @@ -358,6 +520,26 @@ void CSVRender::WorldspaceWidget::debugProfileAboutToBeRemoved (const QModelInde } } +void CSVRender::WorldspaceWidget::editModeChanged (const std::string& id) +{ + dynamic_cast (*mEditMode->getCurrent()).setEditLock (mLocked); + mDragging = false; +} + +void CSVRender::WorldspaceWidget::showToolTip() +{ + if (mShowToolTips) + { + QPoint pos = QCursor::pos(); + + if (osg::ref_ptr tag = mousePick (mapFromGlobal (pos))) + { + bool hideBasics = CSMPrefs::get()["Tooltips"]["scene-hide-basic"].isTrue(); + QToolTip::showText (pos, tag->getToolTip (hideBasics), this); + } + } +} + void CSVRender::WorldspaceWidget::elementSelectionChanged() { setVisibilityMask (getVisibilityMask()); @@ -371,57 +553,162 @@ void CSVRender::WorldspaceWidget::updateOverlay() void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) { - if(event->buttons() & Qt::RightButton) + if (!mDragging) { - mMouse->mouseMoveEvent(event); + if (mDragMode.empty()) + { + if (event->globalPos()!=mToolTipPos) + { + mToolTipPos = event->globalPos(); + + if (mShowToolTips) + mToolTipDelayTimer.start (mToolTipDelay); + } + } + else if (mDragMode=="p-navi" || mDragMode=="s-navi") + { + + } + else if (mDragMode=="p-edit" || mDragMode=="s-edit" || mDragMode=="p-select" || mDragMode=="s-select") + { + osg::ref_ptr tag = mousePick (event->pos()); + + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + + if (mDragMode=="p-edit") + mDragging = editMode.primaryEditStartDrag (tag); + else if (mDragMode=="s-edit") + mDragging = editMode.secondaryEditStartDrag (tag); + else if (mDragMode=="p-select") + mDragging = editMode.primarySelectStartDrag (tag); + else if (mDragMode=="s-select") + mDragging = editMode.secondarySelectStartDrag (tag); + + if (mDragging) + { +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + mDragX = event->localPos().x(); + mDragY = height() - event->localPos().y(); +#else + mDragX = event->posF().x(); + mDragY = height() - event->posF().y(); +#endif + } + } + } + else + { + int diffX = event->x() - mDragX; + int diffY = (height() - event->y()) - mDragY; + + mDragX = event->x(); + mDragY = height() - event->y(); + + double factor = mDragFactor; + + if (event->modifiers() & Qt::ShiftModifier) + factor *= mDragShiftFactor; + + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + + editMode.drag (diffX, diffY, factor); } - SceneWidget::mouseMoveEvent(event); } void CSVRender::WorldspaceWidget::mousePressEvent (QMouseEvent *event) { - if(event->buttons() & Qt::RightButton) - { - mMouse->mousePressEvent(event); - } - //SceneWidget::mousePressEvent(event); + std::string button = mapButton (event); + + if (!mDragging) + mDragMode = button; } void CSVRender::WorldspaceWidget::mouseReleaseEvent (QMouseEvent *event) { - if(event->button() == Qt::RightButton) + std::string button = mapButton (event); + + if (mDragging) { - if(!getViewport()) + if (mDragMode=="p-navi" || mDragMode=="s-navi") { - SceneWidget::mouseReleaseEvent(event); - return; + + } + else if (mDragMode=="p-edit" || mDragMode=="s-edit" || + mDragMode=="p-select" || mDragMode=="s-select") + { + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + + editMode.dragCompleted(); + mDragging = false; } - mMouse->mouseReleaseEvent(event); } - SceneWidget::mouseReleaseEvent(event); + else + { + if (button=="p-navi" || button=="s-navi") + { + + } + else if (button=="p-edit" || button=="s-edit" || + button=="p-select" || button=="s-select") + { + osg::ref_ptr tag = mousePick (event->pos()); + + handleMouseClick (tag, button, event->modifiers() & Qt::ShiftModifier); + } + } + + mDragMode.clear(); } void CSVRender::WorldspaceWidget::mouseDoubleClickEvent (QMouseEvent *event) { if(event->button() == Qt::RightButton) { - mMouse->mouseDoubleClickEvent(event); + //mMouse->mouseDoubleClickEvent(event); } - //SceneWidget::mouseDoubleClickEvent(event); } void CSVRender::WorldspaceWidget::wheelEvent (QWheelEvent *event) { - if(!mMouse->wheelEvent(event)) - SceneWidget::wheelEvent(event); + if (mDragging) + { + double factor = mDragWheelFactor; + + if (event->modifiers() & Qt::ShiftModifier) + factor *= mDragShiftFactor; + + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + + editMode.dragWheel (event->delta(), factor); + } } void CSVRender::WorldspaceWidget::keyPressEvent (QKeyEvent *event) { if(event->key() == Qt::Key_Escape) { - mMouse->cancelDrag(); + if (mDragging) + { + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + + editMode.dragAborted(); + mDragging = false; + } } else - SceneWidget::keyPressEvent(event); + RenderWidget::keyPressEvent(event); +} + +void CSVRender::WorldspaceWidget::handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift) +{ + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + + if (button=="p-edit") + editMode.primaryEditPressed (tag); + else if (button=="s-edit") + editMode.secondaryEditPressed (tag); + else if (button=="p-select") + editMode.primarySelectPressed (tag); + else if (button=="s-select") + editMode.secondarySelectPressed (tag); } diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index b19197e36b..54376cee45 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -1,16 +1,22 @@ #ifndef OPENCS_VIEW_WORLDSPACEWIDGET_H #define OPENCS_VIEW_WORLDSPACEWIDGET_H +#include + #include -#include "scenewidget.hpp" -#include "mousestate.hpp" +#include -#include "navigation1st.hpp" -#include "navigationfree.hpp" -#include "navigationorbit.hpp" -#include -#include +#include "../../model/doc/document.hpp" +#include "../../model/world/tablemimedata.hpp" + +#include "scenewidget.hpp" +#include "elements.hpp" + +namespace CSMPrefs +{ + class Setting; +} namespace CSMWorld { @@ -25,26 +31,33 @@ namespace CSVWidget class SceneToolRun; } -namespace CSVWorld -{ - class PhysicsSystem; -} - namespace CSVRender { + class TagBase; + class CellArrow; + class WorldspaceWidget : public SceneWidget { Q_OBJECT - CSVRender::Navigation1st m1st; - CSVRender::NavigationFree mFree; - CSVRender::NavigationOrbit mOrbit; CSVWidget::SceneToolToggle2 *mSceneElements; CSVWidget::SceneToolRun *mRun; CSMDoc::Document& mDocument; - boost::shared_ptr mPhysics; - MouseState *mMouse; unsigned int mInteractionMask; + std::map, std::string> mButtonMapping; + CSVWidget::SceneToolMode *mEditMode; + bool mLocked; + std::string mDragMode; + bool mDragging; + int mDragX; + int mDragY; + double mDragFactor; + double mDragWheelFactor; + double mDragShiftFactor; + QTimer mToolTipDelayTimer; + QPoint mToolTipPos; + bool mShowToolTips; + int mToolTipDelay; public: @@ -107,14 +120,19 @@ namespace CSVRender /// marked for interaction. unsigned int getInteractionMask() const; + virtual void setEditLock (bool locked); + + CSMDoc::Document& getDocument(); + + /// \param elementMask Elements to be affected by the clear operation + virtual void clearSelection (int elementMask) = 0; + protected: virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool); virtual void addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool); - CSMDoc::Document& getDocument(); - virtual void updateOverlay(); virtual void mouseMoveEvent (QMouseEvent *event); @@ -124,6 +142,9 @@ namespace CSVRender virtual void wheelEvent (QWheelEvent *event); virtual void keyPressEvent (QKeyEvent *event); + virtual void handleMouseClick (osg::ref_ptr tag, const std::string& button, + bool shift); + private: void dragEnterEvent(QDragEnterEvent *event); @@ -132,10 +153,19 @@ namespace CSVRender void dragMoveEvent(QDragMoveEvent *event); + /// \return Is \a key a button mapping setting? (ignored otherwise) + bool storeMappingSetting (const CSMPrefs::Setting *setting); + + osg::ref_ptr mousePick (const QPoint& localPos); + + std::string mapButton (QMouseEvent *event); + virtual std::string getStartupInstruction() = 0; private slots: + void settingChanged (const CSMPrefs::Setting *setting); + void selectNavigationMode (const std::string& mode); virtual void referenceableDataChanged (const QModelIndex& topLeft, @@ -158,6 +188,9 @@ namespace CSVRender void debugProfileAboutToBeRemoved (const QModelIndex& parent, int start, int end); + void editModeChanged (const std::string& id); + + void showToolTip(); protected slots: diff --git a/apps/opencs/view/settings/booleanview.cpp b/apps/opencs/view/settings/booleanview.cpp deleted file mode 100644 index 8c759cabb0..0000000000 --- a/apps/opencs/view/settings/booleanview.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include -#include - -#include -#include -#include - -#include - -#include "booleanview.hpp" -#include "../../model/settings/setting.hpp" - -CSVSettings::BooleanView::BooleanView (CSMSettings::Setting *setting, - Page *parent) - : View (setting, parent), mType(setting->type()) -{ - foreach (const QString &value, setting->declaredValues()) - { - QAbstractButton *button = 0; - - switch (mType) - { - case CSMSettings::Type_CheckBox: { - if(mButtons.empty()) // show only one for checkboxes - { - button = new QCheckBox (value, this); - button->setChecked (setting->defaultValues().at(0) == "true" ? true : false); - - // special visual treatment option for checkboxes - if(setting->specialValueText() != "") { - Frame::setTitle(""); - button->setText(setting->specialValueText()); - } - } - } - break; - - case CSMSettings::Type_RadioButton: - button = new QRadioButton (value, this); - break; - - default: - break; - } - - if(button && (mType != CSMSettings::Type_CheckBox || mButtons.empty())) - { - connect (button, SIGNAL (clicked (bool)), - this, SLOT (slotToggled (bool))); - - button->setObjectName (value); - - addWidget (button); - - mButtons[value] = button; - } - } -} - -void CSVSettings::BooleanView::slotToggled (bool state) -{ - //test only for true to avoid multiple selection updates with radiobuttons - if (!isMultiValue() && !state) - return; - - QStringList values; - - foreach (QString key, mButtons.keys()) - { - // checkbox values are true/false unlike radio buttons - if(mType == CSMSettings::Type_CheckBox) - values.append(mButtons.value(key)->isChecked() ? "true" : "false"); - else - { - if (mButtons.value(key)->isChecked()) - values.append (key); - } - } - setSelectedValues (values, false); - - View::updateView(); -} - -void CSVSettings::BooleanView::updateView (bool signalUpdate) const -{ - - QStringList values = selectedValues(); - - foreach (const QString &buttonName, mButtons.keys()) - { - QAbstractButton *button = mButtons[buttonName]; - - //if the value is not found in the list, the widget is checked false - bool buttonValue = values.contains(buttonName); - - //skip if the butotn value will not change - if (button->isChecked() == buttonValue) - continue; - - //disable autoexclusive if it's enabled and we're setting - //the button value to false - bool switchExclusive = (!buttonValue && button->autoExclusive()); - - if (switchExclusive) - button->setAutoExclusive (false); - - button->setChecked (buttonValue); - - if (switchExclusive) - button->setAutoExclusive(true); - } - View::updateView (signalUpdate); -} - -CSVSettings::BooleanView *CSVSettings::BooleanViewFactory::createView - (CSMSettings::Setting *setting, - Page *parent) -{ - return new BooleanView (setting, parent); -} diff --git a/apps/opencs/view/settings/booleanview.hpp b/apps/opencs/view/settings/booleanview.hpp deleted file mode 100644 index 53198234a5..0000000000 --- a/apps/opencs/view/settings/booleanview.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef CSVSETTINGS_BOOLEANVIEW_HPP -#define CSVSETTINGS_BOOLEANVIEW_HPP - -#include -#include - -#include "view.hpp" -#include "../../model/settings/support.hpp" - -class QStringListModel; - -namespace CSVSettings -{ - class BooleanView : public View - { - Q_OBJECT - - QMap mButtons; - enum CSMSettings::SettingType mType; - - public: - explicit BooleanView (CSMSettings::Setting *setting, - Page *parent); - - protected: - void updateView (bool signalUpdate = true) const; - - private slots: - void slotToggled (bool state); - }; - - class BooleanViewFactory : public QObject, public IViewFactory - { - Q_OBJECT - - public: - explicit BooleanViewFactory (QWidget *parent = 0) - : QObject (parent) - {} - - BooleanView *createView (CSMSettings::Setting *setting, - Page *parent); - }; -} -#endif // CSVSETTINGS_BOOLEANVIEW_HPP diff --git a/apps/opencs/view/settings/dialog.cpp b/apps/opencs/view/settings/dialog.cpp deleted file mode 100644 index 38eb7bbc7f..0000000000 --- a/apps/opencs/view/settings/dialog.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#include "dialog.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "../../model/settings/usersettings.hpp" - -#include "page.hpp" - - -CSVSettings::Dialog::Dialog(QMainWindow *parent) - : SettingWindow (parent), mStackedWidget (0), mDebugMode (false) -{ - setWindowTitle(QString::fromUtf8 ("User Settings")); - - setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - - setMinimumSize (600, 400); - - setupDialog(); - - connect (mPageListWidget, - SIGNAL (currentItemChanged(QListWidgetItem*, QListWidgetItem*)), - this, - SLOT (slotChangePage (QListWidgetItem*, QListWidgetItem*))); -} - -void CSVSettings::Dialog::slotChangePage - (QListWidgetItem *cur, QListWidgetItem *prev) -{ - mStackedWidget->changePage - (mPageListWidget->row (cur), mPageListWidget->row (prev)); -} - -void CSVSettings::Dialog::setupDialog() -{ - QSplitter *centralWidget = new QSplitter (this); - centralWidget->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - - setCentralWidget (centralWidget); - - buildPageListWidget (centralWidget); - buildStackedWidget (centralWidget); -} - -void CSVSettings::Dialog::buildPages() -{ - SettingWindow::createPages (); - - QFontMetrics fm (QApplication::font()); - - int maxWidth = 1; - - foreach (Page *page, SettingWindow::pages()) - { - maxWidth = std::max (maxWidth, fm.width(page->getLabel())); - - new QListWidgetItem (page->getLabel(), mPageListWidget); - - mStackedWidget->addWidget (page); - } - - mPageListWidget->setMaximumWidth (maxWidth + 10); - - resize (mStackedWidget->sizeHint()); -} - -void CSVSettings::Dialog::buildPageListWidget (QSplitter *centralWidget) -{ - mPageListWidget = new QListWidget (centralWidget); - mPageListWidget->setMinimumWidth(50); - mPageListWidget->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding); - - mPageListWidget->setSelectionBehavior (QAbstractItemView::SelectItems); - - centralWidget->addWidget(mPageListWidget); -} - -void CSVSettings::Dialog::buildStackedWidget (QSplitter *centralWidget) -{ - mStackedWidget = new ResizeableStackedWidget (centralWidget); - mStackedWidget->setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Expanding); - - centralWidget->addWidget (mStackedWidget); -} - -void CSVSettings::Dialog::closeEvent (QCloseEvent *event) -{ - //SettingWindow::closeEvent() must be called first to ensure - //model is updated - SettingWindow::closeEvent (event); - - saveSettings(); -} - -void CSVSettings::Dialog::show() -{ - if (pages().isEmpty()) - { - buildPages(); - setViewValues(); - } - - QWidget *currView = QApplication::activeWindow(); - if(currView) - { - // place at the center of the window with focus - QSize size = currView->size(); - move(currView->geometry().x()+(size.width() - frameGeometry().width())/2, - currView->geometry().y()+(size.height() - frameGeometry().height())/2); - } - else - { - // something's gone wrong, place at the center of the screen - QPoint screenCenter = QApplication::desktop()->screenGeometry().center(); - move(screenCenter - QPoint(frameGeometry().width()/2, - frameGeometry().height()/2)); - } - QWidget::show(); -} diff --git a/apps/opencs/view/settings/dialog.hpp b/apps/opencs/view/settings/dialog.hpp deleted file mode 100644 index e3a3f575ac..0000000000 --- a/apps/opencs/view/settings/dialog.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef CSVSETTINGS_DIALOG_H -#define CSVSETTINGS_DIALOG_H - -#include "settingwindow.hpp" -#include "resizeablestackedwidget.hpp" - -class QStackedWidget; -class QListWidget; -class QListWidgetItem; -class QSplitter; - -namespace CSVSettings { - - class Page; - - class Dialog : public SettingWindow - { - Q_OBJECT - - QListWidget *mPageListWidget; - ResizeableStackedWidget *mStackedWidget; - bool mDebugMode; - - public: - - explicit Dialog (QMainWindow *parent = 0); - - protected: - - /// Settings are written on close - void closeEvent (QCloseEvent *event); - - void setupDialog(); - - private: - - void buildPages(); - void buildPageListWidget (QSplitter *centralWidget); - void buildStackedWidget (QSplitter *centralWidget); - - public slots: - - void show(); - - private slots: - - void slotChangePage (QListWidgetItem *, QListWidgetItem *); - }; -} -#endif // CSVSETTINGS_DIALOG_H diff --git a/apps/opencs/view/settings/frame.cpp b/apps/opencs/view/settings/frame.cpp deleted file mode 100644 index 454d3fefa4..0000000000 --- a/apps/opencs/view/settings/frame.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include "frame.hpp" - -#include - -const QString CSVSettings::Frame::sInvisibleBoxStyle = - QString::fromUtf8("Frame { border:2px; padding: 2px; margin: 2px;}"); - -CSVSettings::Frame::Frame (bool isVisible, const QString &title, - QWidget *parent) - : QGroupBox (title, parent), mIsHorizontal (true), - mLayout (new SettingLayout()) -{ - setFlat (true); - mVisibleBoxStyle = styleSheet(); - - if (!isVisible) - { - // must be Page, not a View - setStyleSheet (sInvisibleBoxStyle); - } - - setLayout (mLayout); -} - -void CSVSettings::Frame::hideWidgets() -{ - for (int i = 0; i < children().size(); i++) - { - QObject *obj = children().at(i); - - Frame *widgFrame = dynamic_cast (obj); - - if (widgFrame) - { - widgFrame->hideWidgets(); - continue; - } - - QWidget *widg = static_cast (obj); - if (widg->property("sizePolicy").isValid()) - widg->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding); - } - - layout()->activate(); - setFixedSize(minimumSizeHint()); - -} - -void CSVSettings::Frame::showWidgets() -{ - for (int i = 0; i < children().size(); i++) - { - QObject *obj = children().at(i); - - Frame *widgFrame = dynamic_cast (obj); - - if (widgFrame) - { - widgFrame->showWidgets(); - continue; - } - - QWidget *widg = static_cast (obj); - - if (widg->property("sizePolicy").isValid()) - { - widg->setSizePolicy - (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - } - } - layout()->activate(); - setFixedSize(minimumSizeHint()); -} - -void CSVSettings::Frame::addWidget (QWidget *widget, int row, int column, - int rowSpan, int columnSpan) -{ - if (row == -1) - row = getNextRow(); - - if (column == -1) - column = getNextColumn(); - - mLayout->addWidget (widget, row, column, rowSpan, columnSpan); - //, Qt::AlignLeft | Qt::AlignTop); - - widget->setSizePolicy (QSizePolicy::Ignored, QSizePolicy::Ignored); -} - -int CSVSettings::Frame::getNextRow () const -{ - int row = mLayout->rowCount(); - - if (mIsHorizontal && row > 0) - row--; - - return row; -} - -int CSVSettings::Frame::getNextColumn () const -{ - int column = 0; - - if (mIsHorizontal) - column = mLayout->columnCount(); - - return column; -} diff --git a/apps/opencs/view/settings/frame.hpp b/apps/opencs/view/settings/frame.hpp deleted file mode 100644 index bbb92f34f7..0000000000 --- a/apps/opencs/view/settings/frame.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef CSVSETTINGS_FRAME_HPP -#define CSVSETTINGS_FRAME_HPP - -#include -#include -#include -#include "../../model/settings/support.hpp" - -namespace CSVSettings -{ - class SettingLayout : public QGridLayout - { - public: - explicit SettingLayout (QWidget *parent = 0) - : QGridLayout (parent) - { - setContentsMargins(0,0,0,0); - setAlignment(Qt::AlignLeft | Qt::AlignTop); - } - }; - - /// Custom implementation of QGroupBox to act as a base for view classes - class Frame : public QGroupBox - { - static const QString sInvisibleBoxStyle; - - QString mVisibleBoxStyle; - - bool mIsHorizontal; - - SettingLayout *mLayout; - - public: - explicit Frame (bool isVisible, const QString &title = "", - QWidget *parent = 0); - - ///Adds a widget to the grid layout, setting the position - ///relative to the last added widgets, or absolutely for positive - ///row / column values - void addWidget (QWidget *widget, int row = -1, int column = -1, - int rowSpan = 1, int columnSpan = 1); - - ///Force the grid to lay out in horizontal or vertical alignments - void setHLayout() { mIsHorizontal = true; } - void setVLayout() { mIsHorizontal = false; } - - ///show / hide widgets (when stacked widget page changes) - void showWidgets(); - void hideWidgets(); - - private: - - ///functions which return the index for the next layout row / column - int getNextColumn() const; - int getNextRow() const; - - }; -} - -#endif // CSVSETTINGS_FRAME_HPP diff --git a/apps/opencs/view/settings/listview.cpp b/apps/opencs/view/settings/listview.cpp deleted file mode 100644 index 0876b39820..0000000000 --- a/apps/opencs/view/settings/listview.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include "listview.hpp" -#include "../../model/settings/setting.hpp" - -#include -#include -#include - -CSVSettings::ListView::ListView(CSMSettings::Setting *setting, - Page *parent) - : View(setting, parent), mAbstractItemView (0), mComboBox (0) -{ - QWidget *widget = - buildWidget(setting->isMultiLine(), setting->widgetWidth()); - - addWidget (widget, setting->viewRow(), setting->viewColumn()); - - if (mComboBox) - buildComboBoxModel(); - - else if (mAbstractItemView) - buildAbstractItemViewModel(); -} - -void CSVSettings::ListView::buildComboBoxModel() -{ - mComboBox->setModel (dataModel()); - mComboBox->setModelColumn (0); - mComboBox->view()->setSelectionModel (selectionModel()); - - int curIdx = -1; - - if (!selectionModel()->selection().isEmpty()) - curIdx = selectionModel()->selectedIndexes().at(0).row(); - - mComboBox->setCurrentIndex (curIdx); - - connect (mComboBox, SIGNAL(currentIndexChanged(int)), - this, SLOT(emitItemViewUpdate(int))); -} - -void CSVSettings::ListView::buildAbstractItemViewModel() -{ - mAbstractItemView->setModel (dataModel()); - mAbstractItemView->setSelectionModel (selectionModel()); - - //connection needs to go here for list view update to signal to - //the outside -} - -void CSVSettings::ListView::emitItemViewUpdate (int idx) -{ - updateView(); -} - -QWidget *CSVSettings::ListView::buildWidget(bool isMultiLine, int width) -{ - QWidget *widget = 0; - - if (isMultiLine) - { - mAbstractItemView = new QListView (this); - widget = mAbstractItemView; - - if (width > 0) - widget->setFixedWidth (widgetWidth (width)); - } - else - { - mComboBox = new QComboBox (this); - widget = mComboBox; - - if (width > 0) - mComboBox->setMinimumContentsLength (width); - } - - return widget; -} - -void CSVSettings::ListView::showEvent ( QShowEvent * event ) -{ - View::showEvent (event); -} - -void CSVSettings::ListView::updateView (bool signalUpdate) const -{ - QStringList values = selectedValues(); - - if (mComboBox) - { - int idx = -1; - - if (values.size() > 0) - idx = (mComboBox->findText(values.at(0))); - - mComboBox->setCurrentIndex (idx); - } - - View::updateView (signalUpdate); -} - -CSVSettings::ListView *CSVSettings::ListViewFactory::createView - (CSMSettings::Setting *setting, - Page *parent) -{ - return new ListView(setting, parent); -} diff --git a/apps/opencs/view/settings/listview.hpp b/apps/opencs/view/settings/listview.hpp deleted file mode 100644 index c2860d769a..0000000000 --- a/apps/opencs/view/settings/listview.hpp +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef CSVSETTINGS_LISTVIEW_HPP -#define CSVSETTINGS_LISTVIEW_HPP - -#include "view.hpp" - - -class QStringListModel; -class QComboBox; -class QAbstractItemView; - -namespace CSVSettings -{ - class ListView : public View - { - Q_OBJECT - - QAbstractItemView *mAbstractItemView; - QComboBox *mComboBox; - - public: - explicit ListView (CSMSettings::Setting *setting, - Page *parent); - - protected: - - void updateView (bool signalUpdate = true) const; - void showEvent ( QShowEvent * event ); - - ///Receives signal from widget and signals viwUpdated() - void slotTextEdited (QString value); - - private: - - ///Helper function to construct a model for an AbstractItemView - void buildAbstractItemViewModel(); - - ///Helper function to construct a model for a combobox - void buildComboBoxModel(); - - ///Helper function to build the view widget - QWidget *buildWidget (bool isMultiLine, int width); - - private slots: - - ///Receives updates from single-select widgets (like combobox) and - ///signals viewUpdated with the selected values. - void emitItemViewUpdate (int idx); - }; - - class ListViewFactory : public QObject, public IViewFactory - { - Q_OBJECT - - public: - explicit ListViewFactory (QWidget *parent = 0) - : QObject (parent) - {} - - ListView *createView (CSMSettings::Setting *setting, - Page *parent); - }; -} -#endif // CSVSETTINGS_LISTVIEW_HPP diff --git a/apps/opencs/view/settings/page.cpp b/apps/opencs/view/settings/page.cpp deleted file mode 100644 index c009cdd7a5..0000000000 --- a/apps/opencs/view/settings/page.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "page.hpp" - -#include - -#include "view.hpp" -#include "booleanview.hpp" -#include "textview.hpp" -#include "listview.hpp" -#include "rangeview.hpp" - -#include "../../model/settings/usersettings.hpp" -#include "../../model/settings/connector.hpp" -#include "../../model/settings/support.hpp" - -#include "settingwindow.hpp" - -QMap - CSVSettings::Page::mViewFactories; - -CSVSettings::Page::Page (const QString &pageName, QList settingList, - SettingWindow *parent, const QString& label) -: Frame(false, "", parent), mParent(parent), mIsEditorPage (false), mLabel (label) -{ - setObjectName (pageName); - - if (mViewFactories.size() == 0) - buildFactories(); - - setVLayout(); - setupViews (settingList); -} - -void CSVSettings::Page::setupViews - (QList &settingList) -{ - foreach (CSMSettings::Setting *setting, settingList) - addView (setting); -} - -void CSVSettings::Page::addView (CSMSettings::Setting *setting) -{ - if (setting->viewType() == ViewType_Undefined) - { - if(setting->specialValueText() != "") - { - // hack to put a label - addWidget(new QLabel(setting->specialValueText()), - setting->viewRow(), setting->viewColumn(), - setting->rowSpan(), setting->columnSpan()); - return; - } - else - return; - } - - View *view = mViewFactories[setting->viewType()]->createView(setting, this); - - if (!view) - return; - - mViews.append (view); - - addWidget (view, setting->viewRow(), setting->viewColumn(), - setting->rowSpan(), setting->columnSpan() ); - - //if this page is an editor page, connect each of it's views up to the - //UserSettings singleton for signaling back to OpenCS - if (setting->isEditorSetting()) { - connect (view, SIGNAL (viewUpdated(const QString&, const QStringList&)), - &CSMSettings::UserSettings::instance(), - SLOT (updateUserSetting (const QString &, const QStringList &))); - } -} - -CSVSettings::View *CSVSettings::Page::findView (const QString &page, - const QString &setting) const -{ - - //if this is not the page we're looking for, - //appeal to the parent setting window to find the appropriate view - if (page != objectName()) - return mParent->findView (page, setting); - - //otherwise, return the matching view - for (int i = 0; i < mViews.size(); i++) - { - View *view = mViews.at(i); - - if (view->parentPage()->objectName() != page) - continue; - - if (view->objectName() == setting) - return view; - } - - return 0; -} - -void CSVSettings::Page::buildFactories() -{ - mViewFactories[ViewType_Boolean] = new BooleanViewFactory (this); - mViewFactories[ViewType_Text] = new TextViewFactory (this); - mViewFactories[ViewType_List] = new ListViewFactory (this); - mViewFactories[ViewType_Range] = new RangeViewFactory (this); -} - -QString CSVSettings::Page::getLabel() const -{ - return mLabel; -} diff --git a/apps/opencs/view/settings/page.hpp b/apps/opencs/view/settings/page.hpp deleted file mode 100644 index caf2eec3fe..0000000000 --- a/apps/opencs/view/settings/page.hpp +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef CSVSETTINGS_PAGE_HPP -#define CSVSETTINGS_PAGE_HPP - -#include -#include -#include - -#include "frame.hpp" - -#include "../../model/settings/support.hpp" - -namespace CSMSettings { class Setting; } - -namespace CSVSettings -{ - class View; - class IViewFactory; - class SettingWindow; - - class Page : public Frame - { - Q_OBJECT - - QList mViews; - SettingWindow *mParent; - static QMap mViewFactories; - bool mIsEditorPage; - QString mLabel; - - public: - Page (const QString &pageName, QList settingList, - SettingWindow *parent, const QString& label); - - ///Creates a new view based on the passed setting and adds it to - ///the page. - void addView (CSMSettings::Setting *setting); - - ///Iterates the views created for this page based on the passed setting - ///and returns it. - View *findView (const QString &page, const QString &setting) const; - - ///returns the list of views associated with the page - const QList &views () const { return mViews; } - - QString getLabel() const; - - private: - - ///Creates views based on the passed setting list - void setupViews (QList &settingList); - - ///Creates factory objects for view construction - void buildFactories(); - }; -} -#endif // CSVSETTINGS_PAGE_HPP diff --git a/apps/opencs/view/settings/rangeview.cpp b/apps/opencs/view/settings/rangeview.cpp deleted file mode 100644 index 5893c5d0da..0000000000 --- a/apps/opencs/view/settings/rangeview.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "rangeview.hpp" -#include "spinbox.hpp" -#include "../../model/settings/setting.hpp" -#include "../../model/settings/support.hpp" - -CSVSettings::RangeView::RangeView (CSMSettings::Setting *setting, - Page *parent) - : View (setting, parent), mRangeWidget (0), mRangeType (setting->type()) -{ - - mRangeWidget = 0; - - if (isMultiValue()) - return; - - switch (mRangeType) - { - case CSMSettings::Type_SpinBox: - case CSMSettings::Type_DoubleSpinBox: - buildSpinBox (setting); - break; - - case CSMSettings::Type_Dial: - case CSMSettings::Type_Slider: - buildSlider (setting); - break; - - default: - break; - } - - if(mRangeWidget) - { - mRangeWidget->setFixedWidth (widgetWidth (setting->widgetWidth())); - mRangeWidget->setObjectName (setting->name()); - } - - addWidget (mRangeWidget); -} - -void CSVSettings::RangeView::buildSlider (CSMSettings::Setting *setting) -{ - switch (setting->type()) - { - case CSMSettings::Type_Slider: - mRangeWidget = new QSlider (Qt::Horizontal, this); - mRangeWidget->setProperty ("tickInterval", setting->tickInterval()); - - if (setting->ticksAbove()) - { - if (setting->ticksBelow()) - mRangeWidget->setProperty ("tickPosition", QSlider::TicksBothSides); - else - mRangeWidget->setProperty ("tickPosition", QSlider::TicksAbove); - } - else if (setting->ticksBelow()) - mRangeWidget->setProperty ("tickPosition", QSlider::TicksBelow); - else - mRangeWidget->setProperty ("tickPosition", QSlider::NoTicks); - - break; - - case CSMSettings::Type_Dial: - mRangeWidget = new QDial (this); - mRangeWidget->setProperty ("wrapping", setting->wrapping()); - mRangeWidget->setProperty ("notchesVisible", - (setting->ticksAbove() || setting->ticksBelow())); - break; - - default: - break; - } - - if(mRangeWidget) - { - mRangeWidget->setProperty ("minimum", setting->minimum()); - mRangeWidget->setProperty ("maximum", setting->maximum()); - mRangeWidget->setProperty ("tracking", false); - mRangeWidget->setProperty ("singleStep", setting->singleStep()); - - connect (mRangeWidget, SIGNAL (valueChanged (int)), - this, SLOT (slotUpdateView (int))); - } -} - -void CSVSettings::RangeView::buildSpinBox (CSMSettings::Setting *setting) -{ - SpinBox *sb = 0; - - switch (setting->type()) - { - case CSMSettings::Type_SpinBox: - - sb = new SpinBox (this); - - if (!setting->declaredValues().isEmpty()) - sb->setValueList (setting->declaredValues()); - - mRangeWidget = sb; - - connect (mRangeWidget, SIGNAL (valueChanged (int)), - this, SLOT (slotUpdateView (int))); - break; - - case CSMSettings::Type_DoubleSpinBox: - mRangeWidget = new QDoubleSpinBox (this); - - connect (mRangeWidget, SIGNAL (valueChanged (double)), - this, SLOT (slotUpdateView (double))); - break; - - default: - return; - } - - //min / max values are set automatically in AlphaSpinBox - if (setting->declaredValues().isEmpty()) - { - mRangeWidget->setProperty ("minimum", setting->minimum()); - mRangeWidget->setProperty ("maximum", setting->maximum()); - mRangeWidget->setProperty ("singleStep", setting->singleStep()); - } - - mRangeWidget->setProperty ("prefix", setting->prefix()); - mRangeWidget->setProperty ("suffix", setting->suffix()); - mRangeWidget->setProperty ("wrapping", setting->wrapping()); - dynamic_cast (mRangeWidget)->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); - - if(setting->type() == CSMSettings::Type_SpinBox && setting->declaredValues().isEmpty()) - dynamic_cast (mRangeWidget)->setValue (setting->defaultValues().at(0).toInt()); -} - -void CSVSettings::RangeView::slotUpdateView (int value) -{ - QString textValue = ""; - QStringList list; - - switch (mRangeType) - { - case CSMSettings::Type_SpinBox: - list = static_cast (mRangeWidget)->valueList(); - if (!list.isEmpty()) - textValue = list.at(value); - break; - - default: - break; - } - - if (textValue.isEmpty()) - textValue = QVariant (value).toString(); - - setSelectedValue (textValue, false); - - View::updateView(); -} - -void CSVSettings::RangeView::slotUpdateView (double value) -{ - setSelectedValue (QVariant(value).toString(), false); - - View::updateView(); -} - -void CSVSettings::RangeView::updateView (bool signalUpdate) const -{ - QString value; - - if (!selectedValues().isEmpty()) - value = selectedValues().at(0); - - switch (mRangeType) - { - case CSMSettings::Type_SpinBox: - static_cast (mRangeWidget)->setValue (value); - break; - - case CSMSettings::Type_DoubleSpinBox: - static_cast (mRangeWidget)->setValue (value.toDouble()); - break; - - case CSMSettings::Type_Slider: - case CSMSettings::Type_Dial: - mRangeWidget->setProperty ("value", value.toInt()); - mRangeWidget->setProperty ("sliderPosition", value.toInt()); - break; - - default: - break; - - } - - View::updateView (signalUpdate); -} - -CSVSettings::RangeView *CSVSettings::RangeViewFactory::createView - (CSMSettings::Setting *setting, - Page *parent) -{ - return new RangeView (setting, parent); -} diff --git a/apps/opencs/view/settings/rangeview.hpp b/apps/opencs/view/settings/rangeview.hpp deleted file mode 100644 index 2ab343f1f9..0000000000 --- a/apps/opencs/view/settings/rangeview.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef CSVSETTINGS_RANGEVIEW_HPP -#define CSVSETTINGS_RANGEVIEW_HPP - -#include "view.hpp" -#include "../../model/settings/support.hpp" - -class QStringListModel; -class QAbstractSpinBox; - -namespace CSVSettings -{ - class RangeView : public View - { - Q_OBJECT - - QWidget *mRangeWidget; - CSMSettings::SettingType mRangeType; - - public: - explicit RangeView (CSMSettings::Setting *setting, - Page *parent); - - protected: - - ///virtual function called through View - void updateView (bool signalUpdate = true) const; - - ///construct a slider-based view - void buildSlider (CSMSettings::Setting *setting); - - ///construct a spinbox-based view - void buildSpinBox (CSMSettings::Setting *setting); - - private slots: - - ///responds to valueChanged signals - void slotUpdateView (int value); - void slotUpdateView (double value); - - }; - - class RangeViewFactory : public QObject, public IViewFactory - { - Q_OBJECT - - public: - explicit RangeViewFactory (QWidget *parent = 0) - : QObject (parent) - {} - - RangeView *createView (CSMSettings::Setting *setting, - Page *parent); - }; -} -#endif // CSVSETTINGS_RANGEVIEW_HPP diff --git a/apps/opencs/view/settings/resizeablestackedwidget.cpp b/apps/opencs/view/settings/resizeablestackedwidget.cpp deleted file mode 100644 index 0e87a25062..0000000000 --- a/apps/opencs/view/settings/resizeablestackedwidget.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "resizeablestackedwidget.hpp" -#include "page.hpp" - -#include - -CSVSettings::ResizeableStackedWidget::ResizeableStackedWidget(QWidget *parent) : - QStackedWidget(parent) -{} - -void CSVSettings::ResizeableStackedWidget::addWidget(QWidget* pWidget) -{ - QStackedWidget::addWidget(pWidget); -} - -void CSVSettings::ResizeableStackedWidget::changePage - (int current, int previous) -{ - if (current == previous) - return; - - Page *prevPage = 0; - Page *curPage = 0; - - if (previous > -1) - prevPage = static_cast (widget (previous)); - - if (current > -1) - curPage = static_cast (widget (current)); - - if (prevPage) - prevPage->hideWidgets(); - - if (curPage) - curPage->showWidgets(); - - layout()->activate(); - - setCurrentIndex (current); -} diff --git a/apps/opencs/view/settings/resizeablestackedwidget.hpp b/apps/opencs/view/settings/resizeablestackedwidget.hpp deleted file mode 100644 index 2d0c71a23a..0000000000 --- a/apps/opencs/view/settings/resizeablestackedwidget.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef CSVSETTINGS_RESIZEABLESTACKEDWIDGET_HPP -#define CSVSETTINGS_RESIZEABLESTACKEDWIDGET_HPP - -#include - -class QListWidgetItem; - -namespace CSVSettings -{ - class ResizeableStackedWidget : public QStackedWidget - { - Q_OBJECT - - public: - explicit ResizeableStackedWidget(QWidget *parent = 0); - - ///add a widget to the stacked widget - void addWidget(QWidget* pWidget); - - ///called whenever the stacked widget page is changed - void changePage (int, int); - }; -} - -#endif // CSVSETTINGS_RESIZEABLESTACKEDWIDGET_HPP diff --git a/apps/opencs/view/settings/settingwindow.cpp b/apps/opencs/view/settings/settingwindow.cpp deleted file mode 100644 index 76ea9dc4fe..0000000000 --- a/apps/opencs/view/settings/settingwindow.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include -#include - -#include "../../model/settings/setting.hpp" -#include "../../model/settings/connector.hpp" -#include "../../model/settings/usersettings.hpp" -#include "settingwindow.hpp" -#include "page.hpp" -#include "view.hpp" - -CSVSettings::SettingWindow::SettingWindow(QWidget *parent) - : QMainWindow(parent), mModel(NULL) -{} - -void CSVSettings::SettingWindow::createPages() -{ - CSMSettings::SettingPageMap pageMap = mModel->settingPageMap(); - - QList connectedSettings; - - foreach (const QString &pageName, pageMap.keys()) - { - QList pageSettings = pageMap.value (pageName).second; - - mPages.append (new Page (pageName, pageSettings, this, pageMap.value (pageName).first)); - - for (int i = 0; i < pageSettings.size(); i++) - { - CSMSettings::Setting *setting = pageSettings.at(i); - - if (!setting->proxyLists().isEmpty()) - connectedSettings.append (setting); - } - } - - if (!connectedSettings.isEmpty()) - createConnections(connectedSettings); -} - -void CSVSettings::SettingWindow::createConnections - (const QList &list) -{ - foreach (const CSMSettings::Setting *setting, list) - { - View *masterView = findView (setting->page(), setting->name()); - - CSMSettings::Connector *connector = - new CSMSettings::Connector (masterView, this); - - connect (masterView, - SIGNAL (viewUpdated(const QString &, const QStringList &)), - connector, - SLOT (slotUpdateSlaves()) - ); - - const CSMSettings::ProxyValueMap &proxyMap = setting->proxyLists(); - - foreach (const QString &key, proxyMap.keys()) - { - QStringList keyPair = key.split('/'); - - if (keyPair.size() != 2) - continue; - - View *slaveView = findView (keyPair.at(0), keyPair.at(1)); - - if (!slaveView) - { - qWarning () << "Unable to create connection for view " - << key; - continue; - } - - QList proxyList = proxyMap.value (key); - connector->addSlaveView (slaveView, proxyList); - - connect (slaveView, - SIGNAL (viewUpdated(const QString &, const QStringList &)), - connector, - SLOT (slotUpdateMaster())); - } - } -} - -void CSVSettings::SettingWindow::setViewValues() -{ - //iterate each page and view, setting their definitions - //if they exist in the model - foreach (const Page *page, mPages) - { - foreach (const View *view, page->views()) - { - //testing beforehand prevents overwriting a proxy setting - if (!mModel->hasSettingDefinitions (view->viewKey())) - continue; - - QStringList defs = mModel->definitions (view->viewKey()); - - view->setSelectedValues(defs); - } - } -} - -CSVSettings::View *CSVSettings::SettingWindow::findView - (const QString &pageName, const QString &setting) -{ - foreach (const Page *page, mPages) - { - if (page->objectName() == pageName) - return page->findView (pageName, setting); - } - return 0; -} - -void CSVSettings::SettingWindow::saveSettings() -{ - //setting the definition in the model automatically syncs with the file - foreach (const Page *page, mPages) - { - foreach (const View *view, page->views()) - { - if (!view->serializable()) - continue; - - mModel->setDefinitions (view->viewKey(), view->selectedValues()); - } - } - - mModel->saveDefinitions(); -} - diff --git a/apps/opencs/view/settings/settingwindow.hpp b/apps/opencs/view/settings/settingwindow.hpp deleted file mode 100644 index 11bceee96b..0000000000 --- a/apps/opencs/view/settings/settingwindow.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef CSVSETTINGS_SETTINGWINDOW_HPP -#define CSVSETTINGS_SETTINGWINDOW_HPP - -#include -#include - -#include "../../model/settings/support.hpp" - -namespace CSMSettings { - class Setting; - class UserSettings; -} - -namespace CSVSettings { - - class Page; - class View; - - typedef QList PageList; - - class SettingWindow : public QMainWindow - { - Q_OBJECT - - PageList mPages; - CSMSettings::UserSettings *mModel; - - public: - explicit SettingWindow(QWidget *parent = 0); - - ///retrieve a reference to a view based on it's page and setting name - View *findView (const QString &pageName, const QString &setting); - - ///set the model the view uses (instance of UserSettings) - void setModel (CSMSettings::UserSettings &model) { mModel = &model; } - - protected: - - ///construct the pages to be displayed in the dialog - void createPages(); - - ///return the list of constructed pages - const PageList &pages() const { return mPages; } - - ///save settings from the GUI to file - void saveSettings(); - - ///sets the defined values for the views that have been created - void setViewValues(); - - private: - - ///create connections between settings (used for proxy settings) - void createConnections (const QList &list); - }; -} - -#endif // CSVSETTINGS_SETTINGWINDOW_HPP diff --git a/apps/opencs/view/settings/spinbox.cpp b/apps/opencs/view/settings/spinbox.cpp deleted file mode 100644 index 043107bb76..0000000000 --- a/apps/opencs/view/settings/spinbox.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "spinbox.hpp" - -#include - -CSVSettings::SpinBox::SpinBox(QWidget *parent) - : QSpinBox(parent), mValueList(QStringList()) -{ - setRange (0, 0); -} - -QString CSVSettings::SpinBox::textFromValue(int val) const -{ - if (mValueList.isEmpty()) - return QVariant (val).toString(); - - QString value; - - if (val < mValueList.size()) - value = mValueList.at (val); - - return value; -} - -int CSVSettings::SpinBox::valueFromText(const QString &text) const -{ - if (mValueList.isEmpty()) - return text.toInt(); // TODO: assumed integer, untested error handling for alpha types - - if (mValueList.contains (text)) - return mValueList.indexOf(text); - - return -1; -} - -void CSVSettings::SpinBox::setValue (const QString &value) -{ - if (!mValueList.isEmpty()) - { - lineEdit()->setText (value); - QSpinBox::setValue(valueFromText(value)); - } - else - QSpinBox::setValue (value.toInt()); -} - -void CSVSettings::SpinBox::setValueList (const QStringList &list) -{ - mValueList = list; - setMaximum (list.size() - 1); -} diff --git a/apps/opencs/view/settings/spinbox.hpp b/apps/opencs/view/settings/spinbox.hpp deleted file mode 100644 index e887e8c937..0000000000 --- a/apps/opencs/view/settings/spinbox.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef CSVSETTINGS_SPINBOX_HPP -#define CSVSETTINGS_SPINBOX_HPP - -#include -#include -#include - -namespace CSVSettings -{ - class SpinBox : public QSpinBox - { - Q_OBJECT - - QStringList mValueList; - - public: - explicit SpinBox(QWidget *parent = 0); - - ///set the value displayed in the spin box - void setValue (const QString &value); - - ///set the stringlist that's used as a list of pre-defined values - ///to be displayed as the user scrolls - void setValueList (const QStringList &list); - - ///returns the pre-defined value list. - const QStringList &valueList() const { return mValueList; } - - protected: - - ///converts an index value to corresponding text to be displayed - QString textFromValue (int val) const; - - ///converts a text value to a corresponding index - int valueFromText (const QString &text) const; - }; -} -#endif // CSVSETTINGS_SPINBOX_HPP diff --git a/apps/opencs/view/settings/textview.cpp b/apps/opencs/view/settings/textview.cpp deleted file mode 100644 index a6ab657fe2..0000000000 --- a/apps/opencs/view/settings/textview.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include -#include - -#include "textview.hpp" -#include "../../model/settings/setting.hpp" - -CSVSettings::TextView::TextView(CSMSettings::Setting *setting, Page *parent) - : View (setting, parent), mDelimiter (setting->delimiter()) - -{ - if (setting->isMultiLine()) - mTextWidget = new QTextEdit ("", this); - else - mTextWidget = new QLineEdit ("", this); - - if (setting->widgetWidth() > 0) - mTextWidget->setFixedWidth (widgetWidth (setting->widgetWidth())); - - connect (mTextWidget, SIGNAL (textEdited (QString)), - this, SLOT (slotTextEdited (QString))); - - addWidget (mTextWidget, setting->viewRow(), setting->viewColumn()); -} - -bool CSVSettings::TextView::isEquivalent - (const QString &lhs, const QString &rhs) const -{ - return (lhs.trimmed() == rhs.trimmed()); -} - -void CSVSettings::TextView::slotTextEdited (QString value) -{ - QStringList values = value.split (mDelimiter, QString::SkipEmptyParts); - - QStringList returnValues; - - foreach (const QString &splitValue, values) - returnValues.append (splitValue.trimmed()); - - setSelectedValues (returnValues, false); - - View::updateView(); -} - -void CSVSettings::TextView::updateView(bool signalUpdate) const -{ - QString values = selectedValues().join (mDelimiter); - - if (isEquivalent (mTextWidget->property("text").toString(), values)) - return; - - mTextWidget->setProperty("text", values); - - View::updateView (signalUpdate); -} - -CSVSettings::TextView *CSVSettings::TextViewFactory::createView - (CSMSettings::Setting *setting, - Page *parent) -{ - return new TextView (setting, parent); -} - diff --git a/apps/opencs/view/settings/textview.hpp b/apps/opencs/view/settings/textview.hpp deleted file mode 100644 index f4cd03d2f6..0000000000 --- a/apps/opencs/view/settings/textview.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef CSVSETTINGS_TEXTVIEW_HPP -#define CSVSETTINGS_TEXTVIEW_HPP - -#include "view.hpp" -#include "../../model/settings/setting.hpp" - -namespace CSVSettings -{ - class TextView : public View - { - Q_OBJECT - - QWidget *mTextWidget; - - QString mDelimiter; - - public: - explicit TextView (CSMSettings::Setting *setting, - Page *parent = 0); - - protected: - - /// virtual function called through View - void updateView (bool signalUpdate = true) const; - - protected slots: - - ///Receives updates to the widget for signalling - void slotTextEdited (QString value); - - private: - - ///Comparison function that returns true if the trimmed() strings - ///are equal - bool isEquivalent (const QString &lhs, const QString &rhs) const; - }; - - class TextViewFactory : public QObject, public IViewFactory - { - Q_OBJECT - - public: - explicit TextViewFactory (QWidget *parent = 0) - : QObject (parent) - {} - - TextView *createView (CSMSettings::Setting *setting, - Page *parent); - }; -} -#endif // CSVSETTINGS_TEXTVIEW_HPP diff --git a/apps/opencs/view/settings/view.cpp b/apps/opencs/view/settings/view.cpp deleted file mode 100644 index 21cf55fddd..0000000000 --- a/apps/opencs/view/settings/view.cpp +++ /dev/null @@ -1,222 +0,0 @@ -#include -#include -#include -#include -#include - -#include "view.hpp" -#include "../../model/settings/support.hpp" -#include "../../model/settings/setting.hpp" -#include "page.hpp" - -CSVSettings::View::View(CSMSettings::Setting *setting, - Page *parent) - - : Frame(true, setting->getLabel(), parent), - mParentPage (parent), mDataModel(0), - mHasFixedValues (!setting->declaredValues().isEmpty()), - mIsMultiValue (setting->isMultiValue()), - mViewKey (setting->page() + '/' + setting->name()), - mSerializable (setting->serializable()) -{ - if (!setting->getToolTip().isEmpty()) - setToolTip (setting->getToolTip()); - - setObjectName (setting->name()); - buildView(); - buildModel (setting); - // apply stylesheet to view's frame if exists - if(setting->styleSheet() != "") - Frame::setStyleSheet (setting->styleSheet()); -} - -void CSVSettings::View::buildModel (const CSMSettings::Setting *setting) -{ - QStringList values = setting->defaultValues(); - - if (mHasFixedValues) - buildFixedValueModel (setting->declaredValues()); - else - buildUpdatableValueModel (values); - - mSelectionModel = new QItemSelectionModel (mDataModel, this); - - setSelectedValues (values, false); -} - -void CSVSettings::View::buildFixedValueModel (const QStringList &values) -{ - //fixed value models are simple string list models, since they are read-only - mDataModel = new QStringListModel (values, this); -} - -void CSVSettings::View::buildUpdatableValueModel (const QStringList &values) -{ - //updateable models are standard item models because they support - //replacing entire columns - QList itemList; - - foreach (const QString &value, values) - itemList.append (new QStandardItem(value)); - - QStandardItemModel *model = new QStandardItemModel (this); - model->appendColumn (itemList); - - mDataModel = model; -} - -void CSVSettings::View::buildView() -{ - setFlat (true); - setHLayout(); -} - -int CSVSettings::View::currentIndex () const -{ - if (selectedValues().isEmpty()) - return -1; - - QString currentValue = selectedValues().at(0); - - for (int i = 0; i < mDataModel->rowCount(); i++) - if (value(i) == currentValue) - return i; - - return -1; -} - -void CSVSettings::View::refresh() const -{ - select (mSelectionModel->selection()); - updateView(); -} - -int CSVSettings::View::rowCount() const -{ - return mDataModel->rowCount(); -} - -void CSVSettings::View::select (const QItemSelection &selection) const -{ - mSelectionModel->clear(); - mSelectionModel->select(selection, QItemSelectionModel::Select); -} - -QStringList CSVSettings::View::selectedValues() const -{ - QStringList selValues; - - foreach (const QModelIndex &idx, mSelectionModel->selectedIndexes()) - selValues.append (value(idx.row())); - - return selValues; -} - -void CSVSettings::View::setSelectedValue (const QString &value, - bool doViewUpdate, bool signalUpdate) -{ - setSelectedValues (QStringList() << value, doViewUpdate, signalUpdate); -} - -void CSVSettings::View::setSelectedValues (const QStringList &list, - bool doViewUpdate, bool signalUpdate) const -{ - QItemSelection selection; - - if (stringListsMatch (list, selectedValues())) - return; - - if (!mHasFixedValues) - { - QStandardItemModel *model = - static_cast (mDataModel); - - model->clear(); - model->appendColumn (toStandardItemList (list)); - - for (int i = 0; i < model->rowCount(); i++) - { - QModelIndex idx = model->index(i, 0); - selection.append (QItemSelectionRange (idx, idx)); - } - } - else - { - for (int i = 0; i < mDataModel->rowCount(); i++) - { - if (list.contains(value(i))) - { - QModelIndex idx = mDataModel->index(i, 0); - selection.append(QItemSelectionRange (idx, idx)); - } - } - } - select (selection); - - //update the view if the selection was set from the model side, not by the - //user - if (doViewUpdate) - updateView (signalUpdate); -} - -void CSVSettings::View::showEvent ( QShowEvent * event ) -{ - refresh(); -} - -bool CSVSettings::View::stringListsMatch ( - const QStringList &list1, - const QStringList &list2) const -{ - //returns a "sloppy" match, verifying that each list contains all the same - //items, though not necessarily in the same order. - - if (list1.size() != list2.size()) - return false; - - QStringList tempList(list2); - - //iterate each value in the list, removing one occurrence of the value in - //the other list. If no corresponding value is found, test fails - foreach (const QString &value, list1) - { - if (!tempList.contains(value)) - return false; - - tempList.removeOne(value); - } - return true; -} - -QList CSVSettings::View::toStandardItemList - (const QStringList &list) const -{ - QList itemList; - - foreach (const QString &value, list) - itemList.append (new QStandardItem (value)); - - return itemList; -} - -void CSVSettings::View::updateView (bool signalUpdate) const -{ - if (signalUpdate) - emit viewUpdated(viewKey(), selectedValues()); -} - -QString CSVSettings::View::value (int row) const -{ - if (row > -1 && row < mDataModel->rowCount()) - return mDataModel->data (mDataModel->index(row, 0)).toString(); - - return QString(); -} - -int CSVSettings::View::widgetWidth(int characterCount) const -{ - QString widthToken = QString().fill ('m', characterCount); - QFontMetrics fm (QApplication::font()); - - return (fm.width (widthToken)); -} diff --git a/apps/opencs/view/settings/view.hpp b/apps/opencs/view/settings/view.hpp deleted file mode 100644 index 84ad62759e..0000000000 --- a/apps/opencs/view/settings/view.hpp +++ /dev/null @@ -1,160 +0,0 @@ -#ifndef CSVSETTINGS_VIEW_HPP -#define CSVSETTINGS_VIEW_HPP - -#include -#include - -#include "frame.hpp" -#include "../../model/settings/support.hpp" - -class QGroupBox; -class QStringList; -class QStandardItem; -class QItemSelection; -class QAbstractItemModel; -class QItemSelectionModel; - -namespace CSMSettings { class Setting; } - -namespace CSVSettings -{ - class Page; - - class View : public Frame - { - Q_OBJECT - - ///Pointer to the owning Page instance - Page *mParentPage; - - ///Pointer to the selection model for the view - QItemSelectionModel *mSelectionModel; - - ///Pointer to the data model for the view's selection model - QAbstractItemModel *mDataModel; - - ///State indicating whether or not the setting has a pre-defined list - ///of values, limiting possible definitions - bool mHasFixedValues; - - ///State indicating whether the view will allow multiple values - bool mIsMultiValue; - - ///'pagename.settingname' form of the view's id - QString mViewKey; - - ///indicates whether or not the setting is written to file - bool mSerializable; - - public: - - explicit View (CSMSettings::Setting *setting, Page *parent); - - ///Returns the index / row of the passed value, -1 if not found. - int currentIndex () const; - - ///Returns the number of rows in the view's data model - int rowCount() const; - - ///Returns bool indicating the data in this view should / should not - ///be serialized to a config file - bool serializable() const { return mSerializable; } - - ///Returns a pointer to the view's owning parent page - const Page *parentPage() const { return mParentPage; } - - ///Returns the selected items in the selection model as a QStringList - QStringList selectedValues() const; - - ///Sets the selected items in the selection model based on passed list. - ///Bools allow opt-out of updating the view - ///or signaling the view was updatedto avoid viscious cylcing. - void setSelectedValues (const QStringList &values, - bool updateView = true, - bool signalUpdate = true) const; - - void setSelectedValue (const QString &value, - bool updateView = true, - bool signalUpdate = true); - - - ///Returns the value of the data model at the specified row - QString value (int row) const; - - QString viewKey() const { return mViewKey; } - - protected: - - /// Returns the model which provides data for the selection model - QAbstractItemModel *dataModel() { return mDataModel; } - - ///Accessor function for subclasses - bool isMultiValue() { return mIsMultiValue; } - - ///Returns the view selection model - QItemSelectionModel *selectionModel() { return mSelectionModel;} - - ///Global callback for basic view initialization - void showEvent ( QShowEvent * event ); - - ///Virtual for updating a specific View subclass - ///bool indicates whether viewUpdated() signal is emitted - virtual void updateView (bool signalUpdate = true) const; - - ///Returns the pixel width corresponding to the specified number of - ///characters. - int widgetWidth(int characterCount) const; - - private: - - ///Constructs the view layout - void buildView(); - - ///Constructs the data and selection models - void buildModel (const CSMSettings::Setting *setting); - - ///In cases where the view has a pre-defined list of possible values, - ///a QStringListModel is created using those values. - ///View changes operate on the selection model only. - void buildFixedValueModel (const QStringList &definitions); - - ///In cases where the view does not have a pre-defined list of possible - ///values, a QStandardItemModel is created, containing the actual - ///setting definitions. View changes first update the data in the - ///model to match the data in the view. The selection model always - ///selects all values. - void buildUpdatableValueModel (const QStringList &definitions); - - ///Refreshes the view - void refresh() const; - - ///Convenince function for selection model's select() method. Also - ///clears out the model beforehand to ensure complete selection. - void select (const QItemSelection &selection) const; - - ///Compares two string lists "loosely", ensuring that all values in - ///one list are contained entirely in the other, and that neither list - ///has more values than the other. List order is not considered. - bool stringListsMatch (const QStringList &list1, - const QStringList &list2) const; - - ///Converts a string list to a list of QStandardItem pointers. - QList toStandardItemList(const QStringList &) const; - - signals: - - ///Signals that the view has been changed. - void viewUpdated(const QString &, const QStringList &) const; - - }; - - class IViewFactory - { - public: - - ///Creation interface for view factories - virtual View *createView (CSMSettings::Setting *setting, - Page *parent) = 0; - }; -} -#endif // CSVSETTINGS_VIEW_HPP diff --git a/apps/opencs/view/tools/merge.cpp b/apps/opencs/view/tools/merge.cpp new file mode 100644 index 0000000000..566a5ee062 --- /dev/null +++ b/apps/opencs/view/tools/merge.cpp @@ -0,0 +1,142 @@ + +#include "merge.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../../model/doc/document.hpp" +#include "../../model/doc/documentmanager.hpp" + +#include "../doc/filewidget.hpp" +#include "../doc/adjusterwidget.hpp" + +void CSVTools::Merge::keyPressEvent (QKeyEvent *event) +{ + if (event->key()==Qt::Key_Escape) + { + event->accept(); + cancel(); + } + else + QWidget::keyPressEvent (event); +} + +CSVTools::Merge::Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent) +: QWidget (parent), mDocument (0), mDocumentManager (documentManager) +{ + setWindowTitle ("Merge Content Files into a new Game File"); + + QVBoxLayout *mainLayout = new QVBoxLayout; + setLayout (mainLayout); + + QSplitter *splitter = new QSplitter (Qt::Horizontal, this); + + mainLayout->addWidget (splitter, 1); + + // left panel (files to be merged) + QWidget *left = new QWidget (this); + left->setContentsMargins (0, 0, 0, 0); + splitter->addWidget (left); + + QVBoxLayout *leftLayout = new QVBoxLayout; + left->setLayout (leftLayout); + + leftLayout->addWidget (new QLabel ("Files to be merged", this)); + + mFiles = new QListWidget (this); + leftLayout->addWidget (mFiles, 1); + + // right panel (new game file) + QWidget *right = new QWidget (this); + right->setContentsMargins (0, 0, 0, 0); + splitter->addWidget (right); + + QVBoxLayout *rightLayout = new QVBoxLayout; + rightLayout->setAlignment (Qt::AlignTop); + right->setLayout (rightLayout); + + rightLayout->addWidget (new QLabel ("New game file", this)); + + mNewFile = new CSVDoc::FileWidget (this); + mNewFile->setType (false); + mNewFile->extensionLabelIsVisible (true); + rightLayout->addWidget (mNewFile); + + mAdjuster = new CSVDoc::AdjusterWidget (this); + + rightLayout->addWidget (mAdjuster); + + connect (mNewFile, SIGNAL (nameChanged (const QString&, bool)), + mAdjuster, SLOT (setName (const QString&, bool))); + connect (mAdjuster, SIGNAL (stateChanged (bool)), this, SLOT (stateChanged (bool))); + + // buttons + QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Cancel, Qt::Horizontal, this); + + connect (buttons->button (QDialogButtonBox::Cancel), SIGNAL (clicked()), this, SLOT (cancel())); + + mOkay = new QPushButton ("Merge", this); + connect (mOkay, SIGNAL (clicked()), this, SLOT (accept())); + mOkay->setDefault (true); + buttons->addButton (mOkay, QDialogButtonBox::AcceptRole); + + mainLayout->addWidget (buttons); +} + +void CSVTools::Merge::configure (CSMDoc::Document *document) +{ + mDocument = document; + + mNewFile->setName (""); + + // content files + while (mFiles->count()) + delete mFiles->takeItem (0); + + std::vector files = document->getContentFiles(); + + for (std::vector::const_iterator iter (files.begin()); + iter!=files.end(); ++iter) + mFiles->addItem (QString::fromUtf8 (iter->filename().string().c_str())); +} + +void CSVTools::Merge::setLocalData (const boost::filesystem::path& localData) +{ + mAdjuster->setLocalData (localData); +} + +CSMDoc::Document *CSVTools::Merge::getDocument() const +{ + return mDocument; +} + +void CSVTools::Merge::cancel() +{ + mDocument = 0; + hide(); +} + +void CSVTools::Merge::accept() +{ + if ((mDocument->getState() & CSMDoc::State_Merging)==0) + { + std::vector< boost::filesystem::path > files (1, mAdjuster->getPath()); + + std::auto_ptr target ( + mDocumentManager.makeDocument (files, files[0], true)); + + mDocument->runMerge (target); + + hide(); + } +} + +void CSVTools::Merge::stateChanged (bool valid) +{ + mOkay->setEnabled (valid); +} diff --git a/apps/opencs/view/tools/merge.hpp b/apps/opencs/view/tools/merge.hpp new file mode 100644 index 0000000000..d4ed7e34b8 --- /dev/null +++ b/apps/opencs/view/tools/merge.hpp @@ -0,0 +1,61 @@ +#ifndef CSV_TOOLS_REPORTTABLE_H +#define CSV_TOOLS_REPORTTABLE_H + +#include + +#include + +class QPushButton; +class QListWidget; + +namespace CSMDoc +{ + class Document; + class DocumentManager; +} + +namespace CSVDoc +{ + class FileWidget; + class AdjusterWidget; +} + +namespace CSVTools +{ + class Merge : public QWidget + { + Q_OBJECT + + CSMDoc::Document *mDocument; + QPushButton *mOkay; + QListWidget *mFiles; + CSVDoc::FileWidget *mNewFile; + CSVDoc::AdjusterWidget *mAdjuster; + CSMDoc::DocumentManager& mDocumentManager; + + void keyPressEvent (QKeyEvent *event); + + public: + + Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent = 0); + + /// Configure dialogue for a new merge + void configure (CSMDoc::Document *document); + + void setLocalData (const boost::filesystem::path& localData); + + CSMDoc::Document *getDocument() const; + + public slots: + + void cancel(); + + private slots: + + void accept(); + + void stateChanged (bool valid); + }; +} + +#endif diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp index 492874c01b..c7712f29cf 100644 --- a/apps/opencs/view/tools/reportsubview.cpp +++ b/apps/opencs/view/tools/reportsubview.cpp @@ -1,15 +1,25 @@ - #include "reportsubview.hpp" #include "reporttable.hpp" CSVTools::ReportSubView::ReportSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: CSVDoc::SubView (id) +: CSVDoc::SubView (id), mDocument (document), mRefreshState (0) { - setWidget (mTable = new ReportTable (document, id, false, this)); + if (id.getType()==CSMWorld::UniversalId::Type_VerificationResults) + mRefreshState = CSMDoc::State_Verifying; + + setWidget (mTable = new ReportTable (document, id, false, mRefreshState, this)); connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&))); + + if (mRefreshState==CSMDoc::State_Verifying) + { + connect (mTable, SIGNAL (refreshRequest()), this, SLOT (refreshRequest())); + + connect (&document, SIGNAL (stateChanged (int, CSMDoc::Document *)), + mTable, SLOT (stateChanged (int, CSMDoc::Document *))); + } } void CSVTools::ReportSubView::setEditLock (bool locked) @@ -17,7 +27,14 @@ void CSVTools::ReportSubView::setEditLock (bool locked) // ignored. We don't change document state anyway. } -void CSVTools::ReportSubView::updateUserSetting (const QString &name, const QStringList &list) +void CSVTools::ReportSubView::refreshRequest() { - mTable->updateUserSetting (name, list); + if (!(mDocument.getState() & mRefreshState)) + { + if (mRefreshState==CSMDoc::State_Verifying) + { + mTable->clear(); + mDocument.verify (getUniversalId()); + } + } } diff --git a/apps/opencs/view/tools/reportsubview.hpp b/apps/opencs/view/tools/reportsubview.hpp index 7e8a08e3cd..9f43efdac8 100644 --- a/apps/opencs/view/tools/reportsubview.hpp +++ b/apps/opencs/view/tools/reportsubview.hpp @@ -20,6 +20,8 @@ namespace CSVTools Q_OBJECT ReportTable *mTable; + CSMDoc::Document& mDocument; + int mRefreshState; public: @@ -27,7 +29,9 @@ namespace CSVTools virtual void setEditLock (bool locked); - virtual void updateUserSetting (const QString &, const QStringList &); + private slots: + + void refreshRequest(); }; } diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index e530e1159e..bfc002933d 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -1,4 +1,3 @@ - #include "reporttable.hpp" #include @@ -11,9 +10,12 @@ #include #include #include +#include #include "../../model/tools/reportmodel.hpp" +#include "../../model/prefs/state.hpp" + #include "../../view/world/idtypedelegate.hpp" namespace CSVTools @@ -23,7 +25,7 @@ namespace CSVTools public: RichTextDelegate (QObject *parent = 0); - + virtual void paint(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; }; @@ -63,7 +65,7 @@ void CSVTools::ReportTable::contextMenuEvent (QContextMenuEvent *event) for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { - QString hint = mModel->data (mModel->index (iter->row(), 2)).toString(); + QString hint = mProxyModel->data (mProxyModel->index (iter->row(), 2)).toString(); if (!hint.isEmpty() && hint[0]=='R') { @@ -74,9 +76,11 @@ void CSVTools::ReportTable::contextMenuEvent (QContextMenuEvent *event) if (found) menu.addAction (mReplaceAction); - } - + + if (mRefreshAction) + menu.addAction (mRefreshAction); + menu.exec (event->globalPos()); } @@ -104,14 +108,14 @@ void CSVTools::ReportTable::mouseDoubleClickEvent (QMouseEvent *event) event->accept(); return; } - + switch (iter->second) { case Action_None: event->accept(); break; - + case Action_Edit: event->accept(); @@ -134,8 +138,10 @@ void CSVTools::ReportTable::mouseDoubleClickEvent (QMouseEvent *event) } CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, - const CSMWorld::UniversalId& id, bool richTextDescription, QWidget *parent) -: CSVWorld::DragRecordTable (document, parent), mModel (document.getReport (id)) + const CSMWorld::UniversalId& id, bool richTextDescription, int refreshState, + QWidget *parent) +: CSVWorld::DragRecordTable (document, parent), mModel (document.getReport (id)), + mRefreshAction (0), mRefreshState (refreshState) { #if QT_VERSION >= QT_VERSION_CHECK(5,0,0) horizontalHeader()->setSectionResizeMode (QHeaderView::Interactive); @@ -148,7 +154,10 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); - setModel (mModel); + mProxyModel = new QSortFilterProxyModel (this); + mProxyModel->setSourceModel (mModel); + + setModel (mProxyModel); setColumnHidden (2, true); mIdTypeDelegate = CSVWorld::IdTypeDelegateFactory().makeDelegate (0, @@ -158,7 +167,7 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, if (richTextDescription) setItemDelegateForColumn (mModel->columnCount()-1, new RichTextDelegate (this)); - + mShowAction = new QAction (tr ("Show"), this); connect (mShowAction, SIGNAL (triggered()), this, SLOT (showSelection())); addAction (mShowAction); @@ -171,9 +180,21 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, connect (mReplaceAction, SIGNAL (triggered()), this, SIGNAL (replaceRequest())); addAction (mReplaceAction); + if (mRefreshState) + { + mRefreshAction = new QAction (tr ("Refresh"), this); + mRefreshAction->setEnabled (!(mDocument.getState() & mRefreshState)); + connect (mRefreshAction, SIGNAL (triggered()), this, SIGNAL (refreshRequest())); + addAction (mRefreshAction); + } + mDoubleClickActions.insert (std::make_pair (Qt::NoModifier, Action_Edit)); mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier, Action_Remove)); - mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_EditAndRemove)); + mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_EditAndRemove)); + + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + CSMPrefs::get()["Reports"].update(); } std::vector CSVTools::ReportTable::getDraggedRecords() const @@ -185,46 +206,12 @@ std::vector CSVTools::ReportTable::getDraggedRecords() co for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { - ids.push_back (mModel->getUniversalId (iter->row())); + ids.push_back (mModel->getUniversalId (mProxyModel->mapToSource (*iter).row())); } return ids; } -void CSVTools::ReportTable::updateUserSetting (const QString& name, const QStringList& list) -{ - mIdTypeDelegate->updateUserSetting (name, list); - - QString base ("report-input/double"); - if (name.startsWith (base)) - { - QString modifierString = name.mid (base.size()); - Qt::KeyboardModifiers modifiers = 0; - - if (modifierString=="-s") - modifiers = Qt::ShiftModifier; - else if (modifierString=="-c") - modifiers = Qt::ControlModifier; - else if (modifierString=="-sc") - modifiers = Qt::ShiftModifier | Qt::ControlModifier; - - DoubleClickAction action = Action_None; - - QString value = list.at (0); - - if (value=="Edit") - action = Action_Edit; - else if (value=="Remove") - action = Action_Remove; - else if (value=="Edit And Remove") - action = Action_EditAndRemove; - - mDoubleClickActions[modifiers] = action; - - return; - } -} - std::vector CSVTools::ReportTable::getReplaceIndices (bool selection) const { std::vector indices; @@ -233,13 +220,22 @@ std::vector CSVTools::ReportTable::getReplaceIndices (bool selection) const { QModelIndexList selectedRows = selectionModel()->selectedRows(); + std::vector rows; + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) { - QString hint = mModel->data (mModel->index (iter->row(), 2)).toString(); + rows.push_back (mProxyModel->mapToSource (*iter).row()); + } + + std::sort (rows.begin(), rows.end()); + + for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) + { + QString hint = mModel->data (mModel->index (*iter, 2)).toString(); if (!hint.isEmpty() && hint[0]=='R') - indices.push_back (iter->row()); + indices.push_back (*iter); } } else @@ -260,25 +256,73 @@ void CSVTools::ReportTable::flagAsReplaced (int index) { mModel->flagAsReplaced (index); } - + +void CSVTools::ReportTable::settingChanged (const CSMPrefs::Setting *setting) +{ + if (setting->getParent()->getKey()=="Reports") + { + QString base ("double"); + QString key = setting->getKey().c_str(); + if (key.startsWith (base)) + { + QString modifierString = key.mid (base.size()); + Qt::KeyboardModifiers modifiers = 0; + + if (modifierString=="-s") + modifiers = Qt::ShiftModifier; + else if (modifierString=="-c") + modifiers = Qt::ControlModifier; + else if (modifierString=="-sc") + modifiers = Qt::ShiftModifier | Qt::ControlModifier; + + DoubleClickAction action = Action_None; + + std::string value = setting->toString(); + + if (value=="Edit") + action = Action_Edit; + else if (value=="Remove") + action = Action_Remove; + else if (value=="Edit And Remove") + action = Action_EditAndRemove; + + mDoubleClickActions[modifiers] = action; + + return; + } + } + else if (*setting=="Records/type-format") + mIdTypeDelegate->settingChanged (setting); +} + void CSVTools::ReportTable::showSelection() { QModelIndexList selectedRows = selectionModel()->selectedRows(); for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) - emit editRequest (mModel->getUniversalId (iter->row()), mModel->getHint (iter->row())); + { + int row = mProxyModel->mapToSource (*iter).row(); + emit editRequest (mModel->getUniversalId (row), mModel->getHint (row)); + } } void CSVTools::ReportTable::removeSelection() { QModelIndexList selectedRows = selectionModel()->selectedRows(); - std::reverse (selectedRows.begin(), selectedRows.end()); + std::vector rows; - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + for (QModelIndexList::iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) - mModel->removeRows (iter->row(), 1); + { + rows.push_back (mProxyModel->mapToSource (*iter).row()); + } + + std::sort (rows.begin(), rows.end()); + + for (std::vector::const_reverse_iterator iter (rows.rbegin()); iter!=rows.rend(); ++iter) + mProxyModel->removeRows (*iter, 1); selectionModel()->clear(); } @@ -287,3 +331,9 @@ void CSVTools::ReportTable::clear() { mModel->clear(); } + +void CSVTools::ReportTable::stateChanged (int state, CSMDoc::Document *document) +{ + if (mRefreshAction) + mRefreshAction->setEnabled (!(state & mRefreshState)); +} diff --git a/apps/opencs/view/tools/reporttable.hpp b/apps/opencs/view/tools/reporttable.hpp index 95ab07cbbf..88936d3c3b 100644 --- a/apps/opencs/view/tools/reporttable.hpp +++ b/apps/opencs/view/tools/reporttable.hpp @@ -6,12 +6,18 @@ #include "../world/dragrecordtable.hpp" class QAction; +class QSortFilterProxyModel; namespace CSMTools { class ReportModel; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSVWorld { class CommandDelegate; @@ -30,13 +36,16 @@ namespace CSVTools Action_Remove, Action_EditAndRemove }; - + + QSortFilterProxyModel *mProxyModel; CSMTools::ReportModel *mModel; CSVWorld::CommandDelegate *mIdTypeDelegate; QAction *mShowAction; QAction *mRemoveAction; QAction *mReplaceAction; + QAction *mRefreshAction; std::map mDoubleClickActions; + int mRefreshState; private: @@ -49,33 +58,45 @@ namespace CSVTools public: /// \param richTextDescription Use rich text in the description column. + /// \param refreshState Document state to check for refresh function. If value is + /// 0 no refresh function exists. If the document current has the specified state + /// the refresh function is disabled. ReportTable (CSMDoc::Document& document, const CSMWorld::UniversalId& id, - bool richTextDescription, QWidget *parent = 0); + bool richTextDescription, int refreshState = 0, QWidget *parent = 0); virtual std::vector getDraggedRecords() const; - void updateUserSetting (const QString& name, const QStringList& list); - void clear(); - // Return indices of rows that are suitable for replacement. - // - // \param selection Only list selected rows. + /// Return indices of rows that are suitable for replacement. + /// + /// \param selection Only list selected rows. + /// + /// \return rows in the original model std::vector getReplaceIndices (bool selection) const; + /// \param index row in the original model void flagAsReplaced (int index); private slots: + void settingChanged (const CSMPrefs::Setting *setting); + void showSelection(); void removeSelection(); + public slots: + + void stateChanged (int state, CSMDoc::Document *document); + signals: void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); void replaceRequest(); + + void refreshRequest(); }; } diff --git a/apps/opencs/view/tools/searchbox.cpp b/apps/opencs/view/tools/searchbox.cpp index 1307c1aab1..d980447603 100644 --- a/apps/opencs/view/tools/searchbox.cpp +++ b/apps/opencs/view/tools/searchbox.cpp @@ -1,4 +1,3 @@ - #include "searchbox.hpp" #include diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index dc670af40a..493defa5ad 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -1,4 +1,3 @@ - #include "searchsubview.hpp" #include @@ -7,7 +6,7 @@ #include "../../model/tools/search.hpp" #include "../../model/tools/reportmodel.hpp" #include "../../model/world/idtablebase.hpp" -#include "../../model/settings/usersettings.hpp" +#include "../../model/prefs/state.hpp" #include "reporttable.hpp" #include "searchbox.hpp" @@ -16,7 +15,7 @@ void CSVTools::SearchSubView::replace (bool selection) { if (mLocked) return; - + std::vector indices = mTable->getReplaceIndices (selection); std::string replace = mSearchBox.getReplaceText(); @@ -24,12 +23,11 @@ void CSVTools::SearchSubView::replace (bool selection) const CSMTools::ReportModel& model = dynamic_cast (*mTable->model()); - bool autoDelete = CSMSettings::UserSettings::instance().setting ( - "search/auto-delete", QString ("true"))=="true"; + bool autoDelete = CSMPrefs::get()["Search & Replace"]["auto-delete"].isTrue(); CSMTools::Search search (mSearch); CSMWorld::IdTableBase *currentTable = 0; - + // We are running through the indices in reverse order to avoid messing up multiple results // in a single string. for (std::vector::const_reverse_iterator iter (indices.rbegin()); iter!=indices.rend(); ++iter) @@ -46,7 +44,7 @@ void CSVTools::SearchSubView::replace (bool selection) search.configure (table); currentTable = table; } - + std::string hint = model.getHint (*iter); if (search.verify (mDocument, table, id, hint)) @@ -63,7 +61,7 @@ void CSVTools::SearchSubView::replace (bool selection) void CSVTools::SearchSubView::showEvent (QShowEvent *event) { CSVDoc::SubView::showEvent (event); - mSearchBox.focus(); + mSearchBox.focus(); } CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) @@ -71,25 +69,23 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc: { QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - layout->addWidget (&mSearchBox); - + layout->addWidget (mTable = new ReportTable (document, id, true), 2); QWidget *widget = new QWidget; - + widget->setLayout (layout); setWidget (widget); stateChanged (document.getState(), &document); - + connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&))); connect (mTable, SIGNAL (replaceRequest()), this, SLOT (replaceRequest())); - + connect (&document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (stateChanged (int, CSMDoc::Document *))); @@ -105,11 +101,6 @@ void CSVTools::SearchSubView::setEditLock (bool locked) mSearchBox.setEditLock (locked); } -void CSVTools::SearchSubView::updateUserSetting (const QString &name, const QStringList &list) -{ - mTable->updateUserSetting (name, list); -} - void CSVTools::SearchSubView::stateChanged (int state, CSMDoc::Document *document) { mSearchBox.setSearchMode (!(state & CSMDoc::State_Searching)); @@ -117,14 +108,11 @@ void CSVTools::SearchSubView::stateChanged (int state, CSMDoc::Document *documen void CSVTools::SearchSubView::startSearch (const CSMTools::Search& search) { - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); - - int paddingBefore = userSettings.setting ("search/char-before", QString ("5")).toInt(); - int paddingAfter = userSettings.setting ("search/char-after", QString ("5")).toInt(); + CSMPrefs::Category& settings = CSMPrefs::get()["Search & Replace"]; mSearch = search; - mSearch.setPadding (paddingBefore, paddingAfter); - + mSearch.setPadding (settings["char-before"].toInt(), settings["char-after"].toInt()); + mTable->clear(); mDocument.runSearch (getUniversalId(), mSearch); } diff --git a/apps/opencs/view/tools/searchsubview.hpp b/apps/opencs/view/tools/searchsubview.hpp index 2e96b98b50..ac0a5a762d 100644 --- a/apps/opencs/view/tools/searchsubview.hpp +++ b/apps/opencs/view/tools/searchsubview.hpp @@ -36,15 +36,13 @@ namespace CSVTools protected: void showEvent (QShowEvent *event); - + public: SearchSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); virtual void setEditLock (bool locked); - virtual void updateUserSetting (const QString &, const QStringList &); - private slots: void stateChanged (int state, CSMDoc::Document *document); diff --git a/apps/opencs/view/tools/subviews.cpp b/apps/opencs/view/tools/subviews.cpp index 8a343ebe86..8c3d6d50ea 100644 --- a/apps/opencs/view/tools/subviews.cpp +++ b/apps/opencs/view/tools/subviews.cpp @@ -1,4 +1,3 @@ - #include "subviews.hpp" #include "../doc/subviewfactoryimp.hpp" diff --git a/apps/opencs/view/widget/droplineedit.cpp b/apps/opencs/view/widget/droplineedit.cpp new file mode 100644 index 0000000000..2ca3064613 --- /dev/null +++ b/apps/opencs/view/widget/droplineedit.cpp @@ -0,0 +1,41 @@ +#include "droplineedit.hpp" + +#include + +#include "../../model/world/tablemimedata.hpp" +#include "../../model/world/universalid.hpp" + +#include "../world/dragdroputils.hpp" + +CSVWidget::DropLineEdit::DropLineEdit(CSMWorld::ColumnBase::Display type, QWidget *parent) + : QLineEdit(parent), + mDropType(type) +{ + setAcceptDrops(true); +} + +void CSVWidget::DropLineEdit::dragEnterEvent(QDragEnterEvent *event) +{ + if (CSVWorld::DragDropUtils::canAcceptData(*event, mDropType)) + { + event->acceptProposedAction(); + } +} + +void CSVWidget::DropLineEdit::dragMoveEvent(QDragMoveEvent *event) +{ + if (CSVWorld::DragDropUtils::canAcceptData(*event, mDropType)) + { + event->accept(); + } +} + +void CSVWidget::DropLineEdit::dropEvent(QDropEvent *event) +{ + if (CSVWorld::DragDropUtils::canAcceptData(*event, mDropType)) + { + CSMWorld::UniversalId id = CSVWorld::DragDropUtils::getAcceptedData(*event, mDropType); + setText(QString::fromUtf8(id.getId().c_str())); + emit tableMimeDataDropped(id, CSVWorld::DragDropUtils::getTableMimeData(*event)->getDocumentPtr()); + } +} diff --git a/apps/opencs/view/widget/droplineedit.hpp b/apps/opencs/view/widget/droplineedit.hpp new file mode 100644 index 0000000000..60832e71bc --- /dev/null +++ b/apps/opencs/view/widget/droplineedit.hpp @@ -0,0 +1,41 @@ +#ifndef CSV_WIDGET_DROPLINEEDIT_HPP +#define CSV_WIDGET_DROPLINEEDIT_HPP + +#include + +#include "../../model/world/columnbase.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class TableMimeData; + class UniversalId; +} + +namespace CSVWidget +{ + class DropLineEdit : public QLineEdit + { + Q_OBJECT + + CSMWorld::ColumnBase::Display mDropType; + ///< The accepted Display type for this LineEdit. + + public: + DropLineEdit(CSMWorld::ColumnBase::Display type, QWidget *parent = 0); + + protected: + void dragEnterEvent(QDragEnterEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dropEvent(QDropEvent *event); + + signals: + void tableMimeDataDropped(const CSMWorld::UniversalId &id, const CSMDoc::Document *document); + }; +} + +#endif diff --git a/apps/opencs/view/widget/modebutton.cpp b/apps/opencs/view/widget/modebutton.cpp index 56896b4220..7c62f6bb16 100644 --- a/apps/opencs/view/widget/modebutton.cpp +++ b/apps/opencs/view/widget/modebutton.cpp @@ -1,4 +1,3 @@ - #include "modebutton.hpp" CSVWidget::ModeButton::ModeButton (const QIcon& icon, const QString& tooltip, QWidget *parent) diff --git a/apps/opencs/view/widget/pushbutton.cpp b/apps/opencs/view/widget/pushbutton.cpp index 1baeb7ca27..424aaf68a0 100644 --- a/apps/opencs/view/widget/pushbutton.cpp +++ b/apps/opencs/view/widget/pushbutton.cpp @@ -1,4 +1,3 @@ - #include "pushbutton.hpp" #include diff --git a/apps/opencs/view/widget/scenetool.cpp b/apps/opencs/view/widget/scenetool.cpp index b8e9f895f4..796b985678 100644 --- a/apps/opencs/view/widget/scenetool.cpp +++ b/apps/opencs/view/widget/scenetool.cpp @@ -1,4 +1,3 @@ - #include "scenetool.hpp" #include diff --git a/apps/opencs/view/widget/scenetoolbar.cpp b/apps/opencs/view/widget/scenetoolbar.cpp index f7023b31f3..b2e988dc9a 100644 --- a/apps/opencs/view/widget/scenetoolbar.cpp +++ b/apps/opencs/view/widget/scenetoolbar.cpp @@ -1,4 +1,3 @@ - #include "scenetoolbar.hpp" #include diff --git a/apps/opencs/view/widget/scenetoolmode.cpp b/apps/opencs/view/widget/scenetoolmode.cpp index 39e051c485..a93bb05567 100644 --- a/apps/opencs/view/widget/scenetoolmode.cpp +++ b/apps/opencs/view/widget/scenetoolmode.cpp @@ -1,4 +1,3 @@ - #include "scenetoolmode.hpp" #include @@ -72,6 +71,11 @@ void CSVWidget::SceneToolMode::addButton (ModeButton *button, const std::string& } } +CSVWidget::ModeButton *CSVWidget::SceneToolMode::getCurrent() +{ + return mCurrent; +} + void CSVWidget::SceneToolMode::selected() { std::map::const_iterator iter = diff --git a/apps/opencs/view/widget/scenetoolmode.hpp b/apps/opencs/view/widget/scenetoolmode.hpp index e6f462cf89..6828a22691 100644 --- a/apps/opencs/view/widget/scenetoolmode.hpp +++ b/apps/opencs/view/widget/scenetoolmode.hpp @@ -41,6 +41,9 @@ namespace CSVWidget /// The ownership of \a button is transferred to *this. void addButton (ModeButton *button, const std::string& id); + /// Will return a 0-pointer only if the mode does not have any buttons yet. + ModeButton *getCurrent(); + signals: void modeChanged (const std::string& id); diff --git a/apps/opencs/view/widget/scenetoolrun.cpp b/apps/opencs/view/widget/scenetoolrun.cpp index 4c9eb676e8..1e2d44e7a1 100644 --- a/apps/opencs/view/widget/scenetoolrun.cpp +++ b/apps/opencs/view/widget/scenetoolrun.cpp @@ -1,4 +1,3 @@ - #include "scenetoolrun.hpp" #include diff --git a/apps/opencs/view/widget/scenetooltoggle.cpp b/apps/opencs/view/widget/scenetooltoggle.cpp index 07c448e453..d7251882a7 100644 --- a/apps/opencs/view/widget/scenetooltoggle.cpp +++ b/apps/opencs/view/widget/scenetooltoggle.cpp @@ -1,4 +1,3 @@ - #include "scenetooltoggle.hpp" #include diff --git a/apps/opencs/view/widget/scenetooltoggle2.cpp b/apps/opencs/view/widget/scenetooltoggle2.cpp index 313e519cb4..e0431476eb 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.cpp +++ b/apps/opencs/view/widget/scenetooltoggle2.cpp @@ -1,4 +1,3 @@ - #include "scenetooltoggle2.hpp" #include diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp index cdeee56559..2a710a9400 100644 --- a/apps/opencs/view/world/cellcreator.cpp +++ b/apps/opencs/view/world/cellcreator.cpp @@ -1,4 +1,3 @@ - #include "cellcreator.hpp" #include @@ -8,6 +7,9 @@ #include #include +#include "../../model/world/commands.hpp" +#include "../../model/world/idtree.hpp" + std::string CSVWorld::CellCreator::getId() const { if (mType->currentIndex()==0) @@ -20,6 +22,15 @@ std::string CSVWorld::CellCreator::getId() const return stream.str(); } +void CSVWorld::CellCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const +{ + CSMWorld::IdTree *model = dynamic_cast(getData().getTableModel(getCollectionId())); + Q_ASSERT(model != NULL); + int parentIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Cell); + int index = model->findNestedColumnIndex(parentIndex, CSMWorld::Columns::ColumnId_Interior); + command.addNestedValue(parentIndex, index, mType->currentIndex() == 0); +} + CSVWorld::CellCreator::CellCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) : GenericCreator (data, undoStack, id) @@ -95,9 +106,16 @@ void CSVWorld::CellCreator::cloneMode(const std::string& originId, } } - -void CSVWorld::CellCreator::toggleWidgets(bool active) +std::string CSVWorld::CellCreator::getErrors() const { - CSVWorld::GenericCreator::toggleWidgets(active); - mType->setEnabled(active); + std::string errors; + if (mType->currentIndex() == 0) + { + errors = GenericCreator::getErrors(); + } + else if (getData().hasId(getId())) + { + errors = "The Exterior Cell is already exist"; + } + return errors; } diff --git a/apps/opencs/view/world/cellcreator.hpp b/apps/opencs/view/world/cellcreator.hpp index db9fbf8a34..6c682c6cd4 100644 --- a/apps/opencs/view/world/cellcreator.hpp +++ b/apps/opencs/view/world/cellcreator.hpp @@ -23,17 +23,22 @@ namespace CSVWorld virtual std::string getId() const; + /// Allow subclasses to add additional data to \a command. + virtual void configureCreateCommand(CSMWorld::CreateCommand& command) const; + public: CellCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); virtual void reset(); - virtual void toggleWidgets(bool active = true); - virtual void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type); + virtual std::string getErrors() const; + ///< Return formatted error descriptions for the current state of the creator. if an empty + /// string is returned, there is no error. + private slots: void setType (int index); diff --git a/apps/opencs/view/world/creator.cpp b/apps/opencs/view/world/creator.cpp index 7a8c8d48f0..7a93339c5b 100644 --- a/apps/opencs/view/world/creator.cpp +++ b/apps/opencs/view/world/creator.cpp @@ -1,4 +1,3 @@ - #include "creator.hpp" #include diff --git a/apps/opencs/view/world/datadisplaydelegate.cpp b/apps/opencs/view/world/datadisplaydelegate.cpp index 72f45a18c1..51d7137ec5 100644 --- a/apps/opencs/view/world/datadisplaydelegate.cpp +++ b/apps/opencs/view/world/datadisplaydelegate.cpp @@ -1,5 +1,6 @@ #include "datadisplaydelegate.hpp" -#include "../../model/settings/usersettings.hpp" + +#include "../../model/prefs/state.hpp" #include #include @@ -8,8 +9,8 @@ CSVWorld::DataDisplayDelegate::DataDisplayDelegate(const ValueList &values, const IconList &icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, - const QString &pageName, - const QString &settingName, + const std::string &pageName, + const std::string &settingName, QObject *parent) : EnumDelegate (values, dispatcher, document, parent), mDisplayMode (Mode_TextOnly), mIcons (icons), mIconSize (QSize(16, 16)), @@ -18,10 +19,8 @@ CSVWorld::DataDisplayDelegate::DataDisplayDelegate(const ValueList &values, { buildPixmaps(); - QString value = - CSMSettings::UserSettings::instance().settingValue (mSettingKey); - - updateDisplayMode(value); + if (!pageName.empty()) + updateDisplayMode (CSMPrefs::get()[pageName][settingName].toString()); } void CSVWorld::DataDisplayDelegate::buildPixmaps () @@ -52,7 +51,7 @@ void CSVWorld::DataDisplayDelegate::setTextLeftOffset(int offset) QSize CSVWorld::DataDisplayDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize size = EnumDelegate::sizeHint(option, index); - + int valueIndex = getValueIndex(index); if (valueIndex != -1) { @@ -105,7 +104,7 @@ void CSVWorld::DataDisplayDelegate::paintIcon (QPainter *painter, const QStyleOp textRect.setLeft(iconRect.right() + mTextLeftOffset); textRect.setRight(option.rect.right() - mHorizontalMargin); - QString text = option.fontMetrics.elidedText(mValues.at(index).second, + QString text = option.fontMetrics.elidedText(mValues.at(index).second, option.textElideMode, textRect.width()); QApplication::style()->drawItemText(painter, @@ -118,19 +117,7 @@ void CSVWorld::DataDisplayDelegate::paintIcon (QPainter *painter, const QStyleOp QApplication::style()->drawItemPixmap(painter, iconRect, Qt::AlignCenter, mPixmaps.at(index).second); } -void CSVWorld::DataDisplayDelegate::updateUserSetting (const QString &name, - const QStringList &list) -{ - if (list.isEmpty()) - return; - - QString value = list.at(0); - - if (name == mSettingKey) - updateDisplayMode (value); -} - -void CSVWorld::DataDisplayDelegate::updateDisplayMode (const QString &mode) +void CSVWorld::DataDisplayDelegate::updateDisplayMode (const std::string &mode) { if (mode == "Icon and Text") mDisplayMode = Mode_IconAndText; @@ -146,6 +133,13 @@ CSVWorld::DataDisplayDelegate::~DataDisplayDelegate() { } +void CSVWorld::DataDisplayDelegate::settingChanged (const CSMPrefs::Setting *setting) +{ + if (*setting==mSettingKey) + updateDisplayMode (setting->toString()); +} + + void CSVWorld::DataDisplayDelegateFactory::add (int enumValue, QString enumName, QString iconFilename) { mIcons.push_back (std::make_pair(enumValue, QIcon(iconFilename))); @@ -158,5 +152,3 @@ CSVWorld::CommandDelegate *CSVWorld::DataDisplayDelegateFactory::makeDelegate ( { return new DataDisplayDelegate (mValues, mIcons, dispatcher, document, "", "", parent); } - - diff --git a/apps/opencs/view/world/datadisplaydelegate.hpp b/apps/opencs/view/world/datadisplaydelegate.hpp index e565a3469e..cde109fd4c 100755 --- a/apps/opencs/view/world/datadisplaydelegate.hpp +++ b/apps/opencs/view/world/datadisplaydelegate.hpp @@ -4,10 +4,13 @@ #include #include "enumdelegate.hpp" +namespace CSMPrefs +{ + class Setting; +} + namespace CSVWorld { - - class DataDisplayDelegate : public EnumDelegate { public: @@ -34,12 +37,12 @@ namespace CSVWorld int mHorizontalMargin; int mTextLeftOffset; - QString mSettingKey; + std::string mSettingKey; public: DataDisplayDelegate (const ValueList & values, const IconList & icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, - const QString &pageName, const QString &settingName, QObject *parent); + const std::string& pageName, const std::string& settingName, QObject *parent); ~DataDisplayDelegate(); @@ -53,13 +56,10 @@ namespace CSVWorld /// offset the horizontal position of the text from the right edge of the icon. Default is 8 pixels. void setTextLeftOffset (int offset); - ///update the display mode for the delegate - void updateUserSetting (const QString &name, const QStringList &list); - private: /// update the display mode based on a passed string - void updateDisplayMode (const QString &); + void updateDisplayMode (const std::string &); /// custom paint function for painting the icon. Mode_IconAndText and Mode_Icon only. void paintIcon (QPainter *painter, const QStyleOptionViewItem &option, int i) const; @@ -67,6 +67,7 @@ namespace CSVWorld /// rebuild the list of pixmaps from the provided icons (called when icon size is changed) void buildPixmaps(); + virtual void settingChanged (const CSMPrefs::Setting *setting); }; class DataDisplayDelegateFactory : public EnumDelegateFactory diff --git a/apps/opencs/view/world/dialoguecreator.cpp b/apps/opencs/view/world/dialoguecreator.cpp index 3d451ed2dd..7c6fb2e81f 100644 --- a/apps/opencs/view/world/dialoguecreator.cpp +++ b/apps/opencs/view/world/dialoguecreator.cpp @@ -1,4 +1,3 @@ - #include "dialoguecreator.hpp" #include diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 5d9eda3f24..25bd8e8ee4 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -17,10 +17,9 @@ #include #include #include -#include -#include #include #include +#include #include "../../model/world/nestedtableproxymodel.hpp" #include "../../model/world/columnbase.hpp" @@ -33,12 +32,16 @@ #include "../../model/world/commands.hpp" #include "../../model/doc/document.hpp" +#include "../../model/prefs/state.hpp" + #include "../widget/coloreditor.hpp" +#include "../widget/droplineedit.hpp" #include "recordstatusdelegate.hpp" #include "util.hpp" #include "tablebottombox.hpp" #include "nestedtable.hpp" +#include "recordbuttonbar.hpp" /* ==============================NotEditableSubDelegate========================================== */ @@ -63,16 +66,24 @@ void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QMo } } + CSMWorld::Columns::ColumnId columnId = static_cast ( + mTable->getColumnId (index.column())); + if (QVariant::String == v.type()) { label->setText(v.toString()); } - else //else we are facing enums + else if (CSMWorld::Columns::hasEnums (columnId)) { int data = v.toInt(); - std::vector enumNames (CSMWorld::Columns::getEnums (static_cast (mTable->getColumnId (index.column())))); + std::vector enumNames (CSMWorld::Columns::getEnums (columnId)); + label->setText(QString::fromUtf8(enumNames.at(data).c_str())); } + else + { + label->setText (v.toString()); + } } void CSVWorld::NotEditableSubDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const @@ -94,7 +105,9 @@ QWidget* CSVWorld::NotEditableSubDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { - return new QLabel(parent); + QLabel *label = new QLabel(parent); + label->setTextInteractionFlags (Qt::TextSelectableByMouse); + return label; } /* @@ -129,52 +142,6 @@ QWidget* CSVWorld::DialogueDelegateDispatcherProxy::getEditor() const return mEditor; } -void CSVWorld::DialogueDelegateDispatcherProxy::tableMimeDataDropped(const std::vector& data, const CSMDoc::Document* document) -{ - QLineEdit* lineEdit = qobject_cast(mEditor); - { - if (!lineEdit || !mIndexWrapper.get()) - { - return; - } - } - for (unsigned i = 0; i < data.size(); ++i) - { - CSMWorld::UniversalId::Type type = data[i].getType(); - if (mDisplay == CSMWorld::ColumnBase::Display_Referenceable) - { - if (type == CSMWorld::UniversalId::Type_Activator - || type == CSMWorld::UniversalId::Type_Potion - || type == CSMWorld::UniversalId::Type_Apparatus - || type == CSMWorld::UniversalId::Type_Armor - || type == CSMWorld::UniversalId::Type_Book - || type == CSMWorld::UniversalId::Type_Clothing - || type == CSMWorld::UniversalId::Type_Container - || type == CSMWorld::UniversalId::Type_Creature - || type == CSMWorld::UniversalId::Type_Door - || type == CSMWorld::UniversalId::Type_Ingredient - || type == CSMWorld::UniversalId::Type_CreatureLevelledList - || type == CSMWorld::UniversalId::Type_ItemLevelledList - || type == CSMWorld::UniversalId::Type_Light - || type == CSMWorld::UniversalId::Type_Lockpick - || type == CSMWorld::UniversalId::Type_Miscellaneous - || type == CSMWorld::UniversalId::Type_Npc - || type == CSMWorld::UniversalId::Type_Probe - || type == CSMWorld::UniversalId::Type_Repair - || type == CSMWorld::UniversalId::Type_Static - || type == CSMWorld::UniversalId::Type_Weapon) - { - type = CSMWorld::UniversalId::Type_Referenceable; - } - } - if (mDisplay == CSMWorld::TableMimeData::convertEnums(type)) - { - emit tableMimeDataDropped(mEditor, mIndexWrapper->mIndex, data[i], document); - emit editorDataCommited(mEditor, mIndexWrapper->mIndex, mDisplay); - break; - } - } -} /* ==============================DialogueDelegateDispatcher========================================== */ @@ -306,16 +273,12 @@ QWidget* CSVWorld::DialogueDelegateDispatcher::makeEditor(CSMWorld::ColumnBase:: // NOTE: For each entry in CSVWorld::CommandDelegate::createEditor() a corresponding entry // is required here - if (qobject_cast(editor)) + if (qobject_cast(editor)) { connect(editor, SIGNAL(editingFinished()), proxy, SLOT(editorDataCommited())); - connect(editor, SIGNAL(tableMimeDataDropped(const std::vector&, const CSMDoc::Document*)), - proxy, SLOT(tableMimeDataDropped(const std::vector&, const CSMDoc::Document*))); - - connect(proxy, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*)), - this, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*))); - + connect(editor, SIGNAL(tableMimeDataDropped(const CSMWorld::UniversalId&, const CSMDoc::Document*)), + proxy, SLOT(editorDataCommited())); } else if (qobject_cast(editor)) { @@ -356,10 +319,141 @@ CSVWorld::DialogueDelegateDispatcher::~DialogueDelegateDispatcher() } } + +CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display) + : QObject(widget), + mWidget(widget), + mIdType(CSMWorld::TableMimeData::convertEnums(display)) +{ + Q_ASSERT(mWidget != NULL); + Q_ASSERT(CSMWorld::ColumnBase::isId(display)); + Q_ASSERT(mIdType != CSMWorld::UniversalId::Type_None); + + mWidget->setContextMenuPolicy(Qt::CustomContextMenu); + connect(mWidget, + SIGNAL(customContextMenuRequested(const QPoint &)), + this, + SLOT(showContextMenu(const QPoint &))); + + mEditIdAction = new QAction(this); + connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editIdRequest())); + + QLineEdit *lineEdit = qobject_cast(mWidget); + if (lineEdit != NULL) + { + mContextMenu = lineEdit->createStandardContextMenu(); + } + else + { + mContextMenu = new QMenu(mWidget); + } +} + +void CSVWorld::IdContextMenu::excludeId(const std::string &id) +{ + mExcludedIds.insert(id); +} + +QString CSVWorld::IdContextMenu::getWidgetValue() const +{ + QLineEdit *lineEdit = qobject_cast(mWidget); + QLabel *label = qobject_cast(mWidget); + + QString value = ""; + if (lineEdit != NULL) + { + value = lineEdit->text(); + } + else if (label != NULL) + { + value = label->text(); + } + return value; +} + +void CSVWorld::IdContextMenu::addEditIdActionToMenu(const QString &text) +{ + mEditIdAction->setText(text); + if (mContextMenu->actions().isEmpty()) + { + mContextMenu->addAction(mEditIdAction); + } + else if (mContextMenu->actions().first() != mEditIdAction) + { + QAction *action = mContextMenu->actions().first(); + mContextMenu->insertAction(action, mEditIdAction); + mContextMenu->insertSeparator(action); + } +} + +void CSVWorld::IdContextMenu::removeEditIdActionFromMenu() +{ + if (mContextMenu->actions().isEmpty()) + { + return; + } + + if (mContextMenu->actions().first() == mEditIdAction) + { + mContextMenu->removeAction(mEditIdAction); + if (!mContextMenu->actions().isEmpty() && mContextMenu->actions().first()->isSeparator()) + { + mContextMenu->removeAction(mContextMenu->actions().first()); + } + } +} + +void CSVWorld::IdContextMenu::showContextMenu(const QPoint &pos) +{ + QString value = getWidgetValue(); + bool isExcludedId = mExcludedIds.find(value.toUtf8().constData()) != mExcludedIds.end(); + if (!value.isEmpty() && !isExcludedId) + { + addEditIdActionToMenu("Edit '" + value + "'"); + } + else + { + removeEditIdActionFromMenu(); + } + + if (!mContextMenu->actions().isEmpty()) + { + mContextMenu->exec(mWidget->mapToGlobal(pos)); + } +} + +void CSVWorld::IdContextMenu::editIdRequest() +{ + CSMWorld::UniversalId editId(mIdType, getWidgetValue().toUtf8().constData()); + emit editIdRequest(editId, ""); +} + /* =============================================================EditWidget===================================================== */ +void CSVWorld::EditWidget::createEditorContextMenu(QWidget *editor, + CSMWorld::ColumnBase::Display display, + int currentRow) const +{ + Q_ASSERT(editor != NULL); + + if (CSMWorld::ColumnBase::isId(display) && + CSMWorld::TableMimeData::convertEnums(display) != CSMWorld::UniversalId::Type_None) + { + int idColumn = mTable->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + QString id = mTable->data(mTable->index(currentRow, idColumn)).toString(); + + IdContextMenu *menu = new IdContextMenu(editor, display); + // Current ID is already opened, so no need to create Edit 'ID' action for it + menu->excludeId(id.toUtf8().constData()); + connect(menu, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), + this, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); + } +} + CSVWorld::EditWidget::~EditWidget() { for (unsigned i = 0; i < mNestedModels.size(); ++i) @@ -386,9 +480,6 @@ mCommandDispatcher (commandDispatcher), mDocument (document) { remake (row); - - connect(mDispatcher, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*)), - this, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*))); } void CSVWorld::EditWidget::remake(int row) @@ -474,16 +565,27 @@ void CSVWorld::EditWidget::remake(int row) static_cast (mTable->data (mTable->index (row, typeColumn)).toInt()), mTable->data (mTable->index (row, idColumn)).toString().toUtf8().constData()); - NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this); - table->resizeColumnsToContents(); + bool editable = true; + bool fixedRows = false; + QVariant v = mTable->index(row, i).data(); + if (v.canConvert()) + { + assert (QString(v.typeName()) == "CSMWorld::ColumnBase::TableEditModes"); - if(mTable->index(row, i).data().type() == QVariant::UserType) + if (v.value() == CSMWorld::ColumnBase::TableEdit_None) + editable = false; + else if (v.value() == CSMWorld::ColumnBase::TableEdit_FixedRows) + fixedRows = true; + } + + NestedTable* table = + new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows); + table->resizeColumnsToContents(); + if (!editable) { table->setEditTriggers(QAbstractItemView::NoEditTriggers); table->setEnabled(false); } - else - table->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::CurrentChanged); int rows = mTable->rowCount(mTable->index(row, i)); int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0); @@ -495,11 +597,16 @@ void CSVWorld::EditWidget::remake(int row) new QLabel (mTable->headerData (i, Qt::Horizontal, Qt::DisplayRole).toString(), mMainWidget); label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); - if(mTable->index(row, i).data().type() == QVariant::UserType) + if(!editable) label->setEnabled(false); tablesLayout->addWidget(label); tablesLayout->addWidget(table); + + connect(table, + SIGNAL(editRequest(const CSMWorld::UniversalId &, const std::string &)), + this, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &))); } else if (!(flags & CSMWorld::ColumnBase::Flag_Dialogue_List)) { @@ -533,6 +640,8 @@ void CSVWorld::EditWidget::remake(int row) editor->setEnabled(false); label->setEnabled(false); } + + createEditorContextMenu(editor, display, row); } } else @@ -584,6 +693,8 @@ void CSVWorld::EditWidget::remake(int row) editor->setEnabled(false); label->setEnabled(false); } + + createEditorContextMenu(editor, display, row); } } mNestedTableMapper->setCurrentModelIndex(tree->index(0, 0, tree->index(row, i))); @@ -600,17 +711,37 @@ void CSVWorld::EditWidget::remake(int row) this->setWidgetResizable(true); } -/* -==============================DialogueSubView========================================== -*/ -CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, - const CreatorFactoryBase& creatorFactory, bool sorting) : +QVBoxLayout& CSVWorld::SimpleDialogueSubView::getMainLayout() +{ + return *mMainLayout; +} + +CSMWorld::IdTable& CSVWorld::SimpleDialogueSubView::getTable() +{ + return *mTable; +} + +CSMWorld::CommandDispatcher& CSVWorld::SimpleDialogueSubView::getCommandDispatcher() +{ + return mCommandDispatcher; +} + +CSVWorld::EditWidget& CSVWorld::SimpleDialogueSubView::getEditWidget() +{ + return *mEditWidget; +} + +bool CSVWorld::SimpleDialogueSubView::isLocked() const +{ + return mLocked; +} + +CSVWorld::SimpleDialogueSubView::SimpleDialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), mEditWidget(0), mMainLayout(NULL), mTable(dynamic_cast(document.getData().getTableModel(id))), - mUndoStack(document.getUndoStack()), mLocked(false), mDocument(document), mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) @@ -618,171 +749,37 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM connect(mTable, SIGNAL(dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT(dataChanged(const QModelIndex&))); connect(mTable, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), this, SLOT(rowsAboutToBeRemoved(const QModelIndex&, int, int))); - changeCurrentId(id.getId()); + updateCurrentId(); QWidget *mainWidget = new QWidget(this); - QHBoxLayout *buttonsLayout = new QHBoxLayout; - QToolButton* prevButton = new QToolButton(mainWidget); - prevButton->setIcon(QIcon(":/go-previous.png")); - prevButton->setToolTip ("Switch to previous record"); - QToolButton* nextButton = new QToolButton(mainWidget); - nextButton->setIcon(QIcon(":/go-next.png")); - nextButton->setToolTip ("Switch to next record"); - buttonsLayout->addWidget(prevButton, 0); - buttonsLayout->addWidget(nextButton, 1); - buttonsLayout->addStretch(2); - - QToolButton* cloneButton = new QToolButton(mainWidget); - cloneButton->setIcon(QIcon(":/edit-clone.png")); - cloneButton->setToolTip ("Clone record"); - QToolButton* addButton = new QToolButton(mainWidget); - addButton->setIcon(QIcon(":/add.png")); - addButton->setToolTip ("Add new record"); - QToolButton* deleteButton = new QToolButton(mainWidget); - deleteButton->setIcon(QIcon(":/edit-delete.png")); - deleteButton->setToolTip ("Delete record"); - QToolButton* revertButton = new QToolButton(mainWidget); - revertButton->setIcon(QIcon(":/edit-undo.png")); - revertButton->setToolTip ("Revert record"); - - if (mTable->getFeatures() & CSMWorld::IdTable::Feature_Preview) - { - QToolButton* previewButton = new QToolButton(mainWidget); - previewButton->setIcon(QIcon(":/edit-preview.png")); - previewButton->setToolTip ("Open a preview of this record"); - buttonsLayout->addWidget(previewButton); - connect(previewButton, SIGNAL(clicked()), this, SLOT(showPreview())); - } - - if (mTable->getFeatures() & CSMWorld::IdTable::Feature_View) - { - QToolButton* viewButton = new QToolButton(mainWidget); - viewButton->setIcon(QIcon(":/cell.png")); - viewButton->setToolTip ("Open a scene view of the cell this record is located in"); - buttonsLayout->addWidget(viewButton); - connect(viewButton, SIGNAL(clicked()), this, SLOT(viewRecord())); - } - - buttonsLayout->addWidget(cloneButton); - buttonsLayout->addWidget(addButton); - buttonsLayout->addWidget(deleteButton); - buttonsLayout->addWidget(revertButton); - - connect(nextButton, SIGNAL(clicked()), this, SLOT(nextId())); - connect(prevButton, SIGNAL(clicked()), this, SLOT(prevId())); - connect(cloneButton, SIGNAL(clicked()), this, SLOT(cloneRequest())); - connect(revertButton, SIGNAL(clicked()), &mCommandDispatcher, SLOT(executeRevert())); - connect(deleteButton, SIGNAL(clicked()), &mCommandDispatcher, SLOT(executeDelete())); - mMainLayout = new QVBoxLayout(mainWidget); + setWidget (mainWidget); + + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); mEditWidget = new EditWidget(mainWidget, - mTable->getModelIndex(mCurrentId, 0).row(), mTable, mCommandDispatcher, document, false); - connect(mEditWidget, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*)), - this, SLOT(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*))); + mTable->getModelIndex(getUniversalId().getId(), idColumn).row(), mTable, mCommandDispatcher, document, false); mMainLayout->addWidget(mEditWidget); mEditWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - mMainLayout->addWidget (mBottom = new TableBottomBox (creatorFactory, document, id, this)); + dataChanged(mTable->getModelIndex (getUniversalId().getId(), idColumn)); - mBottom->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); - - connect(mBottom, SIGNAL(requestFocus(const std::string&)), this, SLOT(requestFocus(const std::string&))); - - connect(addButton, SIGNAL(clicked()), mBottom, SLOT(createRequest())); - - if(!mBottom->canCreateAndDelete()) - { - cloneButton->setDisabled (true); - addButton->setDisabled (true); - deleteButton->setDisabled (true); - } - - dataChanged(mTable->getModelIndex (mCurrentId, 0)); - mMainLayout->addLayout (buttonsLayout); - setWidget (mainWidget); + connect(mEditWidget, + SIGNAL(editIdRequest(const CSMWorld::UniversalId &, const std::string &)), + this, + SIGNAL(focusId(const CSMWorld::UniversalId &, const std::string &))); } -void CSVWorld::DialogueSubView::prevId () +void CSVWorld::SimpleDialogueSubView::setEditLock (bool locked) { - int newRow = mTable->getModelIndex(mCurrentId, 0).row() - 1; - - if (newRow < 0) - { - return; - } - while (newRow >= 0) - { - QModelIndex newIndex(mTable->index(newRow, 0)); - - if (!newIndex.isValid()) - { - return; - } - - CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (newRow, 1)).toInt()); - if (!(state == CSMWorld::RecordBase::State_Deleted || state == CSMWorld::RecordBase::State_Erased)) - { - mEditWidget->remake(newRow); - - setUniversalId(CSMWorld::UniversalId (static_cast (mTable->data (mTable->index (newRow, 2)).toInt()), - mTable->data (mTable->index (newRow, 0)).toString().toUtf8().constData())); - - changeCurrentId(std::string(mTable->data (mTable->index (newRow, 0)).toString().toUtf8().constData())); - - mEditWidget->setDisabled(mLocked); - - return; - } - --newRow; - } -} - -void CSVWorld::DialogueSubView::nextId () -{ - int newRow = mTable->getModelIndex(mCurrentId, 0).row() + 1; - - if (newRow >= mTable->rowCount()) - { - return; - } - - while (newRow < mTable->rowCount()) - { - QModelIndex newIndex(mTable->index(newRow, 0)); - - if (!newIndex.isValid()) - { - return; - } - - CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (newRow, 1)).toInt()); - if (!(state == CSMWorld::RecordBase::State_Deleted)) - { - mEditWidget->remake(newRow); - - setUniversalId(CSMWorld::UniversalId (static_cast (mTable->data (mTable->index (newRow, 2)).toInt()), - mTable->data (mTable->index (newRow, 0)).toString().toUtf8().constData())); - - changeCurrentId(std::string(mTable->data (mTable->index (newRow, 0)).toString().toUtf8().constData())); - - mEditWidget->setDisabled(mLocked); - - return; - } - ++newRow; - } -} - -void CSVWorld::DialogueSubView::setEditLock (bool locked) -{ - if (!mEditWidget) // hack to indicate that mCurrentId is no longer valid + if (!mEditWidget) // hack to indicate that getUniversalId().getId() is no longer valid return; + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); mLocked = locked; - QModelIndex currentIndex(mTable->getModelIndex(mCurrentId, 0)); + QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); if (currentIndex.isValid()) { @@ -795,9 +792,10 @@ void CSVWorld::DialogueSubView::setEditLock (bool locked) } -void CSVWorld::DialogueSubView::dataChanged (const QModelIndex & index) +void CSVWorld::SimpleDialogueSubView::dataChanged (const QModelIndex & index) { - QModelIndex currentIndex(mTable->getModelIndex(mCurrentId, 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); if (currentIndex.isValid() && (index.parent().isValid() ? index.parent().row() : index.row()) == currentIndex.row()) @@ -828,11 +826,17 @@ void CSVWorld::DialogueSubView::dataChanged (const QModelIndex & index) } } -void CSVWorld::DialogueSubView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +void CSVWorld::SimpleDialogueSubView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { - QModelIndex currentIndex(mTable->getModelIndex(mCurrentId, 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex(mTable->getModelIndex(getUniversalId().getId(), idColumn)); - if (currentIndex.isValid() && currentIndex.row() >= start && currentIndex.row() <= end) + if (!currentIndex.isValid()) + { + return; + } + + if (currentIndex.parent() == parent && currentIndex.row() >= start && currentIndex.row() <= end) { if(mEditWidget) { @@ -843,60 +847,130 @@ void CSVWorld::DialogueSubView::rowsAboutToBeRemoved(const QModelIndex &parent, } } -void CSVWorld::DialogueSubView::tableMimeDataDropped (QWidget* editor, - const QModelIndex& index, - const CSMWorld::UniversalId& id, - const CSMDoc::Document* document) +void CSVWorld::SimpleDialogueSubView::updateCurrentId() { - if (document == &mDocument) + std::vector selection; + selection.push_back (getUniversalId().getId()); + mCommandDispatcher.setSelection(selection); +} + + +void CSVWorld::DialogueSubView::addButtonBar() +{ + if (mButtons) + return; + + mButtons = new RecordButtonBar (getUniversalId(), getTable(), mBottom, + &getCommandDispatcher(), this); + + getMainLayout().insertWidget (1, mButtons); + + // connections + connect (mButtons, SIGNAL (showPreview()), this, SLOT (showPreview())); + connect (mButtons, SIGNAL (viewRecord()), this, SLOT (viewRecord())); + connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); + + connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), + mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); +} + +CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, + CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) +: SimpleDialogueSubView (id, document), mButtons (0) +{ + // bottom box + mBottom = new TableBottomBox (creatorFactory, document, id, this); + + connect (mBottom, SIGNAL (requestFocus (const std::string&)), + this, SLOT (requestFocus (const std::string&))); + + // layout + getMainLayout().addWidget (mBottom); + + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + CSMPrefs::get()["ID Dialogues"].update(); +} + +void CSVWorld::DialogueSubView::setEditLock (bool locked) +{ + SimpleDialogueSubView::setEditLock (locked); + + if (mButtons) + mButtons->setEditLock (locked); +} + +void CSVWorld::DialogueSubView::settingChanged (const CSMPrefs::Setting *setting) +{ + if (*setting=="ID Dialogues/toolbar") { - qobject_cast(editor)->setText(id.getId().c_str()); + if (setting->isTrue()) + { + addButtonBar(); + } + else if (mButtons) + { + getMainLayout().removeWidget (mButtons); + delete mButtons; + mButtons = 0; + } } } -void CSVWorld::DialogueSubView::requestFocus (const std::string& id) -{ - changeCurrentId(id); - - mEditWidget->remake(mTable->getModelIndex (id, 0).row()); -} - -void CSVWorld::DialogueSubView::cloneRequest () -{ - mBottom->cloneRequest(mCurrentId, static_cast(mTable->data(mTable->getModelIndex(mCurrentId, 2)).toInt())); -} - void CSVWorld::DialogueSubView::showPreview () { - QModelIndex currentIndex(mTable->getModelIndex(mCurrentId, 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), idColumn)); if (currentIndex.isValid() && - mTable->getFeatures() & CSMWorld::IdTable::Feature_Preview && - currentIndex.row() < mTable->rowCount()) + getTable().getFeatures() & CSMWorld::IdTable::Feature_Preview && + currentIndex.row() < getTable().rowCount()) { - emit focusId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Preview, mCurrentId), ""); + emit focusId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Preview, getUniversalId().getId()), ""); } } void CSVWorld::DialogueSubView::viewRecord () { - QModelIndex currentIndex(mTable->getModelIndex (mCurrentId, 0)); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex currentIndex (getTable().getModelIndex (getUniversalId().getId(), idColumn)); if (currentIndex.isValid() && - currentIndex.row() < mTable->rowCount()) + currentIndex.row() < getTable().rowCount()) { - std::pair params = mTable->view (currentIndex.row()); + std::pair params = getTable().view (currentIndex.row()); if (params.first.getType()!=CSMWorld::UniversalId::Type_None) emit focusId (params.first, params.second); } } -void CSVWorld::DialogueSubView::changeCurrentId (const std::string& newId) +void CSVWorld::DialogueSubView::switchToRow (int row) { - std::vector selection; - mCurrentId = std::string(newId); + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + std::string id = getTable().data (getTable().index (row, idColumn)).toString().toUtf8().constData(); - selection.push_back(mCurrentId); - mCommandDispatcher.setSelection(selection); + int typeColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); + CSMWorld::UniversalId::Type type = static_cast ( + getTable().data (getTable().index (row, typeColumn)).toInt()); + + setUniversalId (CSMWorld::UniversalId (type, id)); + updateCurrentId(); + + getEditWidget().remake (row); + + int stateColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + CSMWorld::RecordBase::State state = static_cast ( + getTable().data (getTable().index (row, stateColumn)).toInt()); + + getEditWidget().setDisabled (isLocked() || state==CSMWorld::RecordBase::State_Deleted); +} + +void CSVWorld::DialogueSubView::requestFocus (const std::string& id) +{ + int idColumn = getTable().findColumnIndex (CSMWorld::Columns::ColumnId_Id); + QModelIndex index = getTable().getModelIndex (id, idColumn); + + if (index.isValid()) + switchToRow (index.row()); } diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index 6cbd8ad778..bd7116ba2e 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -1,6 +1,7 @@ #ifndef CSV_WORLD_DIALOGUESUBVIEW_H #define CSV_WORLD_DIALOGUESUBVIEW_H +#include #include #include @@ -11,12 +12,14 @@ #include "../../model/world/columnbase.hpp" #include "../../model/world/commanddispatcher.hpp" +#include "../../model/world/universalid.hpp" class QDataWidgetMapper; class QSize; class QEvent; class QLabel; class QVBoxLayout; +class QMenu; namespace CSMWorld { @@ -24,6 +27,11 @@ namespace CSMWorld class NestedTableProxyModel; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSMDoc { class Document; @@ -86,18 +94,12 @@ namespace CSVWorld public slots: void editorDataCommited(); void setIndex(const QModelIndex& index); - void tableMimeDataDropped(const std::vector& data, - const CSMDoc::Document* document); signals: void editorDataCommited(QWidget* editor, const QModelIndex& index, CSMWorld::ColumnBase::Display display); - void tableMimeDataDropped(QWidget* editor, const QModelIndex& index, - const CSMWorld::UniversalId& id, - const CSMDoc::Document* document); - }; class DialogueDelegateDispatcher : public QAbstractItemDelegate @@ -153,11 +155,36 @@ namespace CSVWorld private slots: void editorDataCommited(QWidget* editor, const QModelIndex& index, CSMWorld::ColumnBase::Display display); + }; - signals: - void tableMimeDataDropped(QWidget* editor, const QModelIndex& index, - const CSMWorld::UniversalId& id, - const CSMDoc::Document* document); + /// A context menu with "Edit 'ID'" action for editors in the dialogue subview + class IdContextMenu : public QObject + { + Q_OBJECT + + QWidget *mWidget; + CSMWorld::UniversalId::Type mIdType; + std::set mExcludedIds; + ///< A list of IDs that should not have the Edit 'ID' action. + + QMenu *mContextMenu; + QAction *mEditIdAction; + + QString getWidgetValue() const; + void addEditIdActionToMenu(const QString &text); + void removeEditIdActionFromMenu(); + + public: + IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Display display); + + void excludeId(const std::string &id); + + private slots: + void showContextMenu(const QPoint &pos); + void editIdRequest(); + + signals: + void editIdRequest(const CSMWorld::UniversalId &id, const std::string &hint); }; class EditWidget : public QScrollArea @@ -173,6 +200,9 @@ namespace CSVWorld CSMDoc::Document& mDocument; std::vector mNestedModels; //Plain, raw C pointers, deleted in the dtor + void createEditorContextMenu(QWidget *editor, + CSMWorld::ColumnBase::Display display, + int currentRow) const; public: EditWidget (QWidget *parent, int row, CSMWorld::IdTable* table, @@ -184,59 +214,79 @@ namespace CSVWorld void remake(int row); signals: - void tableMimeDataDropped(QWidget* editor, const QModelIndex& index, - const CSMWorld::UniversalId& id, - const CSMDoc::Document* document); + void editIdRequest(const CSMWorld::UniversalId &id, const std::string &hint); }; - class DialogueSubView : public CSVDoc::SubView + class SimpleDialogueSubView : public CSVDoc::SubView { - Q_OBJECT + Q_OBJECT - EditWidget* mEditWidget; - QVBoxLayout* mMainLayout; - CSMWorld::IdTable* mTable; - QUndoStack& mUndoStack; - std::string mCurrentId; - bool mLocked; - const CSMDoc::Document& mDocument; - TableBottomBox* mBottom; - CSMWorld::CommandDispatcher mCommandDispatcher; + EditWidget* mEditWidget; + QVBoxLayout* mMainLayout; + CSMWorld::IdTable* mTable; + bool mLocked; + const CSMDoc::Document& mDocument; + CSMWorld::CommandDispatcher mCommandDispatcher; + + protected: + + QVBoxLayout& getMainLayout(); + + CSMWorld::IdTable& getTable(); + + CSMWorld::CommandDispatcher& getCommandDispatcher(); + + EditWidget& getEditWidget(); + + void updateCurrentId(); + + bool isLocked() const; public: - DialogueSubView (const CSMWorld::UniversalId& id, - CSMDoc::Document& document, - const CreatorFactoryBase& creatorFactory, - bool sorting = false); + SimpleDialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); virtual void setEditLock (bool locked); + private slots: + + void dataChanged(const QModelIndex & index); + ///\brief we need to care for deleting currently edited record + + void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + }; + + class RecordButtonBar; + + class DialogueSubView : public SimpleDialogueSubView + { + Q_OBJECT + + TableBottomBox* mBottom; + RecordButtonBar *mButtons; + private: - void changeCurrentId(const std::string& newCurrent); + + void addButtonBar(); + + public: + + DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, + const CreatorFactoryBase& creatorFactory, bool sorting = false); + + virtual void setEditLock (bool locked); private slots: - void nextId(); - - void prevId(); + void settingChanged (const CSMPrefs::Setting *setting); void showPreview(); void viewRecord(); - void cloneRequest(); - - void dataChanged(const QModelIndex & index); - ///\brief we need to care for deleting currently edited record - - void tableMimeDataDropped(QWidget* editor, const QModelIndex& index, - const CSMWorld::UniversalId& id, - const CSMDoc::Document* document); + void switchToRow (int row); void requestFocus (const std::string& id); - - void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); }; } diff --git a/apps/opencs/view/world/dragdroputils.cpp b/apps/opencs/view/world/dragdroputils.cpp new file mode 100644 index 0000000000..7f3974e539 --- /dev/null +++ b/apps/opencs/view/world/dragdroputils.cpp @@ -0,0 +1,26 @@ +#include "dragdroputils.hpp" + +#include + +#include "../../model/world/tablemimedata.hpp" + +const CSMWorld::TableMimeData *CSVWorld::DragDropUtils::getTableMimeData(const QDropEvent &event) +{ + return dynamic_cast(event.mimeData()); +} + +bool CSVWorld::DragDropUtils::canAcceptData(const QDropEvent &event, CSMWorld::ColumnBase::Display type) +{ + const CSMWorld::TableMimeData *data = getTableMimeData(event); + return data != NULL && data->holdsType(type); +} + +CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent &event, + CSMWorld::ColumnBase::Display type) +{ + if (canAcceptData(event, type)) + { + return getTableMimeData(event)->returnMatching(type); + } + return CSMWorld::UniversalId::Type_None; +} diff --git a/apps/opencs/view/world/dragdroputils.hpp b/apps/opencs/view/world/dragdroputils.hpp new file mode 100644 index 0000000000..d1d780708e --- /dev/null +++ b/apps/opencs/view/world/dragdroputils.hpp @@ -0,0 +1,29 @@ +#ifndef CSV_WORLD_DRAGDROPUTILS_HPP +#define CSV_WORLD_DRAGDROPUTILS_HPP + +#include "../../model/world/columnbase.hpp" + +class QDropEvent; + +namespace CSMWorld +{ + class TableMimeData; + class UniversalId; +} + +namespace CSVWorld +{ + namespace DragDropUtils + { + const CSMWorld::TableMimeData *getTableMimeData(const QDropEvent &event); + + bool canAcceptData(const QDropEvent &event, CSMWorld::ColumnBase::Display type); + ///< Checks whether the \a event contains a valid CSMWorld::TableMimeData that holds the \a type + + CSMWorld::UniversalId getAcceptedData(const QDropEvent &event, CSMWorld::ColumnBase::Display type); + ///< Gets the accepted data from the \a event using the \a type + ///< \return Type_None if the \a event data doesn't holds the \a type + } +} + +#endif diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index 2a1ae1f401..a5f933283c 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -1,12 +1,24 @@ +#include "dragrecordtable.hpp" + #include #include +#include "../../model/doc/document.hpp" + #include "../../model/world/tablemimedata.hpp" -#include "dragrecordtable.hpp" +#include "../../model/world/commands.hpp" + +#include "dragdroputils.hpp" void CSVWorld::DragRecordTable::startDragFromTable (const CSVWorld::DragRecordTable& table) { - CSMWorld::TableMimeData* mime = new CSMWorld::TableMimeData (table.getDraggedRecords(), mDocument); + std::vector records = table.getDraggedRecords(); + if (records.empty()) + { + return; + } + + CSMWorld::TableMimeData* mime = new CSMWorld::TableMimeData (records, mDocument); if (mime) { @@ -21,7 +33,9 @@ CSVWorld::DragRecordTable::DragRecordTable (CSMDoc::Document& document, QWidget* QTableView(parent), mDocument(document), mEditLock(false) -{} +{ + setAcceptDrops(true); +} void CSVWorld::DragRecordTable::setEditLock (bool locked) { @@ -35,5 +49,49 @@ void CSVWorld::DragRecordTable::dragEnterEvent(QDragEnterEvent *event) void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent *event) { - event->accept(); + QModelIndex index = indexAt(event->pos()); + if (CSVWorld::DragDropUtils::canAcceptData(*event, getIndexDisplayType(index))) + { + if (index.flags() & Qt::ItemIsEditable) + { + event->accept(); + return; + } + } + event->ignore(); +} + +void CSVWorld::DragRecordTable::dropEvent(QDropEvent *event) +{ + QModelIndex index = indexAt(event->pos()); + CSMWorld::ColumnBase::Display display = getIndexDisplayType(index); + if (CSVWorld::DragDropUtils::canAcceptData(*event, display)) + { + const CSMWorld::TableMimeData *data = CSVWorld::DragDropUtils::getTableMimeData(*event); + if (data->fromDocument(mDocument)) + { + CSMWorld::UniversalId id = CSVWorld::DragDropUtils::getAcceptedData(*event, display); + QVariant newIndexData = QString::fromUtf8(id.getId().c_str()); + QVariant oldIndexData = index.data(Qt::EditRole); + if (newIndexData != oldIndexData) + { + mDocument.getUndoStack().push(new CSMWorld::ModifyCommand(*model(), index, newIndexData)); + } + } + } +} + +CSMWorld::ColumnBase::Display CSVWorld::DragRecordTable::getIndexDisplayType(const QModelIndex &index) const +{ + Q_ASSERT(model() != NULL); + + if (index.isValid()) + { + QVariant display = model()->headerData(index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display); + if (display.isValid()) + { + return static_cast(display.toInt()); + } + } + return CSMWorld::ColumnBase::Display_None; } diff --git a/apps/opencs/view/world/dragrecordtable.hpp b/apps/opencs/view/world/dragrecordtable.hpp index 48f9e25280..560864ba5a 100644 --- a/apps/opencs/view/world/dragrecordtable.hpp +++ b/apps/opencs/view/world/dragrecordtable.hpp @@ -4,6 +4,8 @@ #include #include +#include "../../model/world/columnbase.hpp" + class QWidget; class QAction; @@ -38,6 +40,11 @@ namespace CSVWorld void dragEnterEvent(QDragEnterEvent *event); void dragMoveEvent(QDragMoveEvent *event); + + void dropEvent(QDropEvent *event); + + private: + CSMWorld::ColumnBase::Display getIndexDisplayType(const QModelIndex &index) const; }; } diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index 2190a62c69..e582e3356d 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -1,4 +1,3 @@ - #include "enumdelegate.hpp" #include diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp new file mode 100644 index 0000000000..2cf6222a6e --- /dev/null +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -0,0 +1,239 @@ +#include "extendedcommandconfigurator.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "../../model/doc/document.hpp" + +#include "../../model/world/commanddispatcher.hpp" +#include "../../model/world/data.hpp" + +CSVWorld::ExtendedCommandConfigurator::ExtendedCommandConfigurator(CSMDoc::Document &document, + const CSMWorld::UniversalId &id, + QWidget *parent) + : QWidget(parent), + mNumUsedCheckBoxes(0), + mNumChecked(0), + mMode(Mode_None), + mData(document.getData()), + mEditLock(false) +{ + mCommandDispatcher = new CSMWorld::CommandDispatcher(document, id, this); + + connect(&mData, SIGNAL(idListChanged()), this, SLOT(dataIdListChanged())); + + mPerformButton = new QPushButton(this); + mPerformButton->setDefault(true); + mPerformButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + connect(mPerformButton, SIGNAL(clicked(bool)), this, SLOT(performExtendedCommand())); + + mCancelButton = new QPushButton("Cancel", this); + mCancelButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + connect(mCancelButton, SIGNAL(clicked(bool)), this, SIGNAL(done())); + + mTypeGroup = new QGroupBox(this); + + QGridLayout *groupLayout = new QGridLayout(mTypeGroup); + groupLayout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + mTypeGroup->setLayout(groupLayout); + + QHBoxLayout *mainLayout = new QHBoxLayout(this); + mainLayout->setSizeConstraint(QLayout::SetNoConstraint); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->addWidget(mTypeGroup); + mainLayout->addWidget(mPerformButton); + mainLayout->addWidget(mCancelButton); +} + +void CSVWorld::ExtendedCommandConfigurator::configure(CSVWorld::ExtendedCommandConfigurator::Mode mode, + const std::vector &selectedIds) +{ + mMode = mode; + if (mMode != Mode_None) + { + mPerformButton->setText((mMode == Mode_Delete) ? "Extended Delete" : "Extended Revert"); + mSelectedIds = selectedIds; + mCommandDispatcher->setSelection(mSelectedIds); + + setupCheckBoxes(mCommandDispatcher->getExtendedTypes()); + setupGroupLayout(); + lockWidgets(mEditLock); + } +} + +void CSVWorld::ExtendedCommandConfigurator::setEditLock(bool locked) +{ + if (mEditLock != locked) + { + mEditLock = locked; + lockWidgets(mEditLock); + } +} + +void CSVWorld::ExtendedCommandConfigurator::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + setupGroupLayout(); +} + +void CSVWorld::ExtendedCommandConfigurator::setupGroupLayout() +{ + if (mMode == Mode_None) + { + return; + } + + int groupWidth = mTypeGroup->geometry().width(); + QGridLayout *layout = qobject_cast(mTypeGroup->layout()); + + // Find the optimal number of rows to place the checkboxes within the available space + int divider = 1; + do + { + while (layout->itemAt(0) != NULL) + { + layout->removeItem(layout->itemAt(0)); + } + + int counter = 0; + int itemsPerRow = mNumUsedCheckBoxes / divider; + CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); + for (; current != end; ++current) + { + if (counter < mNumUsedCheckBoxes) + { + int row = counter / itemsPerRow; + int column = counter - (counter / itemsPerRow) * itemsPerRow; + layout->addWidget(current->first, row, column); + } + ++counter; + } + divider *= 2; + } + while (groupWidth < mTypeGroup->sizeHint().width() && divider <= mNumUsedCheckBoxes); +} + +void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vector &types) +{ + // Make sure that we have enough checkboxes + int numTypes = static_cast(types.size()); + int numCheckBoxes = static_cast(mTypeCheckBoxes.size()); + if (numTypes > numCheckBoxes) + { + for (int i = numTypes - numCheckBoxes; i > 0; --i) + { + QCheckBox *checkBox = new QCheckBox(mTypeGroup); + connect(checkBox, SIGNAL(stateChanged(int)), this, SLOT(checkBoxStateChanged(int))); + mTypeCheckBoxes.insert(std::make_pair(checkBox, CSMWorld::UniversalId::Type_None)); + } + } + + // Set up the checkboxes + int counter = 0; + CheckBoxMap::iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::iterator end = mTypeCheckBoxes.end(); + for (; current != end; ++current) + { + if (counter < numTypes) + { + CSMWorld::UniversalId type = types[counter]; + current->first->setText(QString::fromUtf8(type.getTypeName().c_str())); + current->first->setChecked(true); + current->second = type; + ++counter; + } + else + { + current->first->hide(); + } + } + mNumChecked = mNumUsedCheckBoxes = numTypes; +} + +void CSVWorld::ExtendedCommandConfigurator::lockWidgets(bool locked) +{ + mPerformButton->setEnabled(!mEditLock && mNumChecked > 0); + + CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); + for (int i = 0; current != end && i < mNumUsedCheckBoxes; ++current, ++i) + { + current->first->setEnabled(!mEditLock); + } +} + +void CSVWorld::ExtendedCommandConfigurator::performExtendedCommand() +{ + std::vector types; + + CheckBoxMap::const_iterator current = mTypeCheckBoxes.begin(); + CheckBoxMap::const_iterator end = mTypeCheckBoxes.end(); + for (; current != end; ++current) + { + if (current->first->isChecked()) + { + types.push_back(current->second); + } + } + + mCommandDispatcher->setExtendedTypes(types); + if (mMode == Mode_Delete) + { + mCommandDispatcher->executeExtendedDelete(); + } + else + { + mCommandDispatcher->executeExtendedRevert(); + } + emit done(); +} + +void CSVWorld::ExtendedCommandConfigurator::checkBoxStateChanged(int state) +{ + switch (state) + { + case Qt::Unchecked: + --mNumChecked; + break; + case Qt::Checked: + ++mNumChecked; + break; + case Qt::PartiallyChecked: // Not used + break; + } + + mPerformButton->setEnabled(mNumChecked > 0); +} + +void CSVWorld::ExtendedCommandConfigurator::dataIdListChanged() +{ + bool idsRemoved = false; + for (int i = 0; i < static_cast(mSelectedIds.size()); ++i) + { + if (!mData.hasId(mSelectedIds[i])) + { + std::swap(mSelectedIds[i], mSelectedIds.back()); + mSelectedIds.pop_back(); + idsRemoved = true; + --i; + } + } + + // If all selected IDs were removed, cancel the configurator + if (mSelectedIds.empty()) + { + emit done(); + return; + } + + if (idsRemoved) + { + mCommandDispatcher->setSelection(mSelectedIds); + } +} diff --git a/apps/opencs/view/world/extendedcommandconfigurator.hpp b/apps/opencs/view/world/extendedcommandconfigurator.hpp new file mode 100644 index 0000000000..641b4a5241 --- /dev/null +++ b/apps/opencs/view/world/extendedcommandconfigurator.hpp @@ -0,0 +1,78 @@ +#ifndef CSVWORLD_EXTENDEDCOMMANDCONFIGURATOR_HPP +#define CSVWORLD_EXTENDEDCOMMANDCONFIGURATOR_HPP + +#include + +#include + +#include "../../model/world/universalid.hpp" + +class QPushButton; +class QGroupBox; +class QCheckBox; +class QLabel; +class QHBoxLayout; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher; + class Data; +} + +namespace CSVWorld +{ + class ExtendedCommandConfigurator : public QWidget + { + Q_OBJECT + + public: + enum Mode { Mode_None, Mode_Delete, Mode_Revert }; + + private: + typedef std::map CheckBoxMap; + + QPushButton *mPerformButton; + QPushButton *mCancelButton; + QGroupBox *mTypeGroup; + CheckBoxMap mTypeCheckBoxes; + int mNumUsedCheckBoxes; + int mNumChecked; + + Mode mMode; + CSMWorld::CommandDispatcher *mCommandDispatcher; + CSMWorld::Data &mData; + std::vector mSelectedIds; + + bool mEditLock; + + void setupGroupLayout(); + void setupCheckBoxes(const std::vector &types); + void lockWidgets(bool locked); + + public: + ExtendedCommandConfigurator(CSMDoc::Document &document, + const CSMWorld::UniversalId &id, + QWidget *parent = 0); + + void configure(Mode mode, const std::vector &selectedIds); + void setEditLock(bool locked); + + protected: + virtual void resizeEvent(QResizeEvent *event); + + private slots: + void performExtendedCommand(); + void checkBoxStateChanged(int state); + void dataIdListChanged(); + + signals: + void done(); + }; +} + +#endif diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index c641096087..df77399417 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -1,4 +1,3 @@ - #include "genericcreator.hpp" #include @@ -48,6 +47,16 @@ std::string CSVWorld::GenericCreator::getId() const return mId->text().toUtf8().constData(); } +std::string CSVWorld::GenericCreator::getIdValidatorResult() const +{ + std::string errors; + + if (!mId->hasAcceptableInput()) + errors = mValidator->getError(); + + return errors; +} + void CSVWorld::GenericCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const {} void CSVWorld::GenericCreator::pushCommand (std::auto_ptr command, @@ -161,6 +170,8 @@ CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undo connect (mCreate, SIGNAL (clicked (bool)), this, SLOT (create())); connect (mId, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); + + connect (&mData, SIGNAL (idListChanged()), this, SLOT (dataIdListChanged())); } void CSVWorld::GenericCreator::setEditLock (bool locked) @@ -291,3 +302,12 @@ void CSVWorld::GenericCreator::scopeChanged (int index) update(); updateNamespace(); } + +void CSVWorld::GenericCreator::dataIdListChanged() +{ + // If the original ID of cloned record was removed, cancel the creator + if (mCloneMode && !mData.hasId(mClonedId)) + { + emit done(); + } +} diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp index 0d2a40486f..f63c451092 100644 --- a/apps/opencs/view/world/genericcreator.hpp +++ b/apps/opencs/view/world/genericcreator.hpp @@ -60,6 +60,8 @@ namespace CSVWorld virtual std::string getId() const; + virtual std::string getIdValidatorResult() const; + /// Allow subclasses to add additional data to \a command. virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; @@ -113,6 +115,8 @@ namespace CSVWorld void create(); void scopeChanged (int index); + + void dataIdListChanged(); }; } diff --git a/apps/opencs/view/world/idcompletiondelegate.cpp b/apps/opencs/view/world/idcompletiondelegate.cpp index 056026471b..970490828f 100644 --- a/apps/opencs/view/world/idcompletiondelegate.cpp +++ b/apps/opencs/view/world/idcompletiondelegate.cpp @@ -2,6 +2,8 @@ #include "../../model/world/idcompletionmanager.hpp" +#include "../widget/droplineedit.hpp" + CSVWorld::IdCompletionDelegate::IdCompletionDelegate(CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) @@ -26,7 +28,7 @@ QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, } CSMWorld::IdCompletionManager &completionManager = getDocument().getIdCompletionManager(); - DropLineEdit *editor = new DropLineEdit(parent); + CSVWidget::DropLineEdit *editor = new CSVWidget::DropLineEdit(display, parent); editor->setCompleter(completionManager.getCompleter(display).get()); return editor; } diff --git a/apps/opencs/view/world/idtypedelegate.cpp b/apps/opencs/view/world/idtypedelegate.cpp index 34c8d12cd7..ee35e07d41 100755 --- a/apps/opencs/view/world/idtypedelegate.cpp +++ b/apps/opencs/view/world/idtypedelegate.cpp @@ -5,7 +5,7 @@ CSVWorld::IdTypeDelegate::IdTypeDelegate (const ValueList &values, const IconList &icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) : DataDisplayDelegate (values, icons, dispatcher, document, - "records", "type-format", + "Records", "type-format", parent) {} diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp index 13b05d2d17..1092d72171 100644 --- a/apps/opencs/view/world/idvalidator.cpp +++ b/apps/opencs/view/world/idvalidator.cpp @@ -1,4 +1,3 @@ - #include "idvalidator.hpp" #include diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index 916427fc24..1139afd691 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -1,10 +1,8 @@ - #include "infocreator.hpp" #include #include -#include #include #include @@ -17,6 +15,8 @@ #include "../../model/world/idtable.hpp" #include "../../model/world/idcompletionmanager.hpp" +#include "../widget/droplineedit.hpp" + std::string CSVWorld::InfoCreator::getId() const { std::string id = Misc::StringUtils::lowerCase (mTopic->text().toUtf8().constData()); @@ -48,12 +48,12 @@ CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, QLabel *label = new QLabel ("Topic", this); insertBeforeButtons (label, false); - mTopic = new QLineEdit (this); CSMWorld::ColumnBase::Display displayType = CSMWorld::ColumnBase::Display_Topic; if (getCollectionId().getType() == CSMWorld::UniversalId::Type_JournalInfos) { displayType = CSMWorld::ColumnBase::Display_Journal; } + mTopic = new CSVWidget::DropLineEdit(displayType, this); mTopic->setCompleter(completionManager.getCompleter(displayType).get()); insertBeforeButtons (mTopic, true); diff --git a/apps/opencs/view/world/infocreator.hpp b/apps/opencs/view/world/infocreator.hpp index 1928004bb9..d131e3fac5 100644 --- a/apps/opencs/view/world/infocreator.hpp +++ b/apps/opencs/view/world/infocreator.hpp @@ -3,21 +3,24 @@ #include "genericcreator.hpp" -class QLineEdit; - namespace CSMWorld { class InfoCollection; class IdCompletionManager; } +namespace CSVWidget +{ + class DropLineEdit; +} + namespace CSVWorld { class InfoCreator : public GenericCreator { Q_OBJECT - QLineEdit *mTopic; + CSVWidget::DropLineEdit *mTopic; virtual std::string getId() const; diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index 112873cb9a..23d5664396 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -1,21 +1,28 @@ #include "nestedtable.hpp" -#include "../../model/world/nestedtableproxymodel.hpp" -#include "../../model/world/universalid.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/commanddispatcher.hpp" -#include "util.hpp" #include #include #include #include +#include "../../model/world/nestedtableproxymodel.hpp" +#include "../../model/world/universalid.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/commanddispatcher.hpp" + +#include "tableeditidaction.hpp" +#include "util.hpp" + CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, - QWidget* parent) - : QTableView(parent), - mUndoStack(document.getUndoStack()), + QWidget* parent, + bool editable, + bool fixedRows) + : DragRecordTable(document, parent), + mAddNewRowAction(NULL), + mRemoveRowAction(NULL), + mEditIdAction(NULL), mModel(model) { mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); @@ -47,53 +54,78 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, setModel(model); - setAcceptDrops(true); + if (editable) + { + if (!fixedRows) + { + mAddNewRowAction = new QAction (tr ("Add new row"), this); - mAddNewRowAction = new QAction (tr ("Add new row"), this); + connect(mAddNewRowAction, SIGNAL(triggered()), + this, SLOT(addNewRowActionTriggered())); - connect(mAddNewRowAction, SIGNAL(triggered()), - this, SLOT(addNewRowActionTriggered())); + mRemoveRowAction = new QAction (tr ("Remove row"), this); - mRemoveRowAction = new QAction (tr ("Remove row"), this); + connect(mRemoveRowAction, SIGNAL(triggered()), + this, SLOT(removeRowActionTriggered())); + } - connect(mRemoveRowAction, SIGNAL(triggered()), - this, SLOT(removeRowActionTriggered())); + mEditIdAction = new TableEditIdAction(*this, this); + connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editCell())); + } } -void CSVWorld::NestedTable::dragEnterEvent(QDragEnterEvent *event) -{ -} - -void CSVWorld::NestedTable::dragMoveEvent(QDragMoveEvent *event) +std::vector CSVWorld::NestedTable::getDraggedRecords() const { + // No drag support for nested tables + return std::vector(); } void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) { + if (!mEditIdAction) + return; + QModelIndexList selectedRows = selectionModel()->selectedRows(); QMenu menu(this); - if (selectionModel()->selectedRows().size() == 1) - menu.addAction(mRemoveRowAction); + int currentRow = rowAt(event->y()); + int currentColumn = columnAt(event->x()); + if (mEditIdAction->isValidIdCell(currentRow, currentColumn)) + { + mEditIdAction->setCell(currentRow, currentColumn); + menu.addAction(mEditIdAction); + menu.addSeparator(); + } - menu.addAction(mAddNewRowAction); + if (mAddNewRowAction && mRemoveRowAction) + { + if (selectionModel()->selectedRows().size() == 1) + menu.addAction(mRemoveRowAction); + + menu.addAction(mAddNewRowAction); + } menu.exec (event->globalPos()); } void CSVWorld::NestedTable::removeRowActionTriggered() { - mUndoStack.push(new CSMWorld::DeleteNestedCommand(*(mModel->model()), - mModel->getParentId(), - selectionModel()->selectedRows().begin()->row(), - mModel->getParentColumn())); + mDocument.getUndoStack().push(new CSMWorld::DeleteNestedCommand(*(mModel->model()), + mModel->getParentId(), + selectionModel()->selectedRows().begin()->row(), + mModel->getParentColumn())); } void CSVWorld::NestedTable::addNewRowActionTriggered() { - mUndoStack.push(new CSMWorld::AddNestedCommand(*(mModel->model()), - mModel->getParentId(), - selectionModel()->selectedRows().size(), - mModel->getParentColumn())); + mDocument.getUndoStack().push(new CSMWorld::AddNestedCommand(*(mModel->model()), + mModel->getParentId(), + selectionModel()->selectedRows().size(), + mModel->getParentColumn())); +} + +void CSVWorld::NestedTable::editCell() +{ + emit editRequest(mEditIdAction->getCurrentId(), ""); } diff --git a/apps/opencs/view/world/nestedtable.hpp b/apps/opencs/view/world/nestedtable.hpp index 5db977942a..765060ea5b 100644 --- a/apps/opencs/view/world/nestedtable.hpp +++ b/apps/opencs/view/world/nestedtable.hpp @@ -1,10 +1,10 @@ #ifndef CSV_WORLD_NESTEDTABLE_H #define CSV_WORLD_NESTEDTABLE_H -#include #include -class QUndoStack; +#include "dragrecordtable.hpp" + class QAction; class QContextMenuEvent; @@ -22,13 +22,15 @@ namespace CSMDoc namespace CSVWorld { - class NestedTable : public QTableView + class TableEditIdAction; + + class NestedTable : public DragRecordTable { Q_OBJECT QAction *mAddNewRowAction; QAction *mRemoveRowAction; - QUndoStack& mUndoStack; + TableEditIdAction *mEditIdAction; CSMWorld::NestedTableProxyModel* mModel; CSMWorld::CommandDispatcher *mDispatcher; @@ -36,12 +38,11 @@ namespace CSVWorld NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, - QWidget* parent = NULL); + QWidget* parent = NULL, + bool editable = true, + bool fixedRows = false); - protected: - void dragEnterEvent(QDragEnterEvent *event); - - void dragMoveEvent(QDragMoveEvent *event); + virtual std::vector getDraggedRecords() const; private: void contextMenuEvent (QContextMenuEvent *event); @@ -50,6 +51,11 @@ namespace CSVWorld void removeRowActionTriggered(); void addNewRowActionTriggered(); + + void editCell(); + + signals: + void editRequest(const CSMWorld::UniversalId &id, const std::string &hint); }; } diff --git a/apps/opencs/view/world/physicssystem.cpp b/apps/opencs/view/world/physicssystem.cpp deleted file mode 100644 index 2cbe17dcfa..0000000000 --- a/apps/opencs/view/world/physicssystem.cpp +++ /dev/null @@ -1,324 +0,0 @@ -#include "physicssystem.hpp" - -#include - -#include -#include -#include - -#include -#include -#include "../../model/settings/usersettings.hpp" -#include "../render/elements.hpp" - -namespace CSVWorld -{ - PhysicsSystem::PhysicsSystem() - { - // Create physics. shapeLoader is deleted by the physic engine - NifBullet::ManualBulletShapeLoader* shapeLoader = new NifBullet::ManualBulletShapeLoader(true); - mEngine = new OEngine::Physic::PhysicEngine(shapeLoader); - } - - PhysicsSystem::~PhysicsSystem() - { - delete mEngine; - } - - // looks up the scene manager based on the scene node name (inefficient) - // NOTE: referenceId is assumed to be unique per document - // NOTE: searching is done here rather than after rayTest, hence slower to load but - // faster to find (guessing, not verified w/ perf test) - void PhysicsSystem::addObject(const std::string &mesh, - const std::string &sceneNodeName, const std::string &referenceId, float scale, - const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, bool placeable) - { - Ogre::SceneManager *sceneManager = findSceneManager(sceneNodeName); - if(sceneManager) - { - // update maps (NOTE: sometimes replaced) - mSceneNodeToRefId[sceneNodeName] = referenceId; - mSceneNodeToMesh[sceneNodeName] = mesh; - mRefIdToSceneNode[referenceId][sceneManager] = sceneNodeName; - } - else - { - std::cerr << "Attempt to add an object without a corresponding SceneManager: " - + referenceId + " : " + sceneNodeName << std::endl; - return; - } - - // update physics, only one physics model per referenceId - if(mEngine->getRigidBody(referenceId, true) == NULL) - { - mEngine->createAndAdjustRigidBody(mesh, - referenceId, scale, position, rotation, - 0, // scaledBoxTranslation - 0, // boxRotation - true, // raycasting - placeable); - } - } - - // normal delete (e.g closing a scene subview or ~Object()) - // the scene node is destroyed so the mappings should be removed - // - // TODO: should think about using some kind of reference counting within RigidBody - void PhysicsSystem::removeObject(const std::string &sceneNodeName) - { - std::string referenceId = mSceneNodeToRefId[sceneNodeName]; - - if(referenceId != "") - { - mSceneNodeToRefId.erase(sceneNodeName); - mSceneNodeToMesh.erase(sceneNodeName); - - // find which SceneManager has this object - Ogre::SceneManager *sceneManager = findSceneManager(sceneNodeName); - if(!sceneManager) - { - std::cerr << "Attempt to remove an object without a corresponding SceneManager: " - + sceneNodeName << std::endl; - return; - } - - // illustration: erase the object "K" from the object map - // - // RidigBody SubView Ogre - // --------------- -------------- ------------- - // ReferenceId "A" (SceneManager X SceneNode "J") - // (SceneManager Y SceneNode "K") <--- erase - // (SceneManager Z SceneNode "L") - // - // ReferenceId "B" (SceneManager X SceneNode "M") - // (SceneManager Y SceneNode "N") <--- notice not deleted - // (SceneManager Z SceneNode "O") - std::map >::iterator itRef = - mRefIdToSceneNode.begin(); - for(; itRef != mRefIdToSceneNode.end(); ++itRef) - { - if((*itRef).second.find(sceneManager) != (*itRef).second.end()) - { - (*itRef).second.erase(sceneManager); - break; - } - } - - // check whether the physics model should be deleted - if(mRefIdToSceneNode.find(referenceId) == mRefIdToSceneNode.end()) - { - mEngine->removeRigidBody(referenceId); - mEngine->deleteRigidBody(referenceId); - } - } - } - - // Object::clear() is called when reference data is changed. It clears all - // contents of the SceneNode and removes the physics object - // - // A new physics object will be created and assigned to this sceneNodeName by - // Object::update() - void PhysicsSystem::removePhysicsObject(const std::string &sceneNodeName) - { - std::string referenceId = mSceneNodeToRefId[sceneNodeName]; - - if(referenceId != "") - { - mEngine->removeRigidBody(referenceId); - mEngine->deleteRigidBody(referenceId); - } - } - - void PhysicsSystem::replaceObject(const std::string &sceneNodeName, float scale, - const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, bool placeable) - { - std::string referenceId = mSceneNodeToRefId[sceneNodeName]; - std::string mesh = mSceneNodeToMesh[sceneNodeName]; - - if(referenceId != "") - { - // delete the physics object - mEngine->removeRigidBody(referenceId); - mEngine->deleteRigidBody(referenceId); - - // create a new physics object - mEngine->createAndAdjustRigidBody(mesh, referenceId, scale, position, rotation, - 0, 0, true, placeable); - - // update other scene managers if they have the referenceId - // FIXME: rotation or scale not updated - moveSceneNodeImpl(sceneNodeName, referenceId, position); - } - } - - // FIXME: adjustRigidBody() seems to lose objects, work around by deleting and recreating objects - void PhysicsSystem::moveObject(const std::string &sceneNodeName, - const Ogre::Vector3 &position, const Ogre::Quaternion &rotation) - { - mEngine->adjustRigidBody(mEngine->getRigidBody(sceneNodeName, true /*raycasting*/), - position, rotation); - } - - void PhysicsSystem::moveSceneNodeImpl(const std::string sceneNodeName, - const std::string referenceId, const Ogre::Vector3 &position) - { - std::map::const_iterator iter = mSceneWidgets.begin(); - for(; iter != mSceneWidgets.end(); ++iter) - { - std::string name = refIdToSceneNode(referenceId, (*iter).first); - if(name != sceneNodeName && (*iter).first->hasSceneNode(name)) - { - (*iter).first->getSceneNode(name)->setPosition(position); - } - } - } - - void PhysicsSystem::moveSceneNodes(const std::string sceneNodeName, const Ogre::Vector3 &position) - { - moveSceneNodeImpl(sceneNodeName, sceneNodeToRefId(sceneNodeName), position); - } - - void PhysicsSystem::addHeightField(Ogre::SceneManager *sceneManager, - float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts) - { - std::string name = "HeightField_" - + QString::number(x).toStdString() + "_" + QString::number(y).toStdString(); - - if(mTerrain.find(name) == mTerrain.end()) - mEngine->addHeightField(heights, x, y, yoffset, triSize, sqrtVerts); - - mTerrain.insert(std::pair(name, sceneManager)); - } - - void PhysicsSystem::removeHeightField(Ogre::SceneManager *sceneManager, int x, int y) - { - std::string name = "HeightField_" - + QString::number(x).toStdString() + "_" + QString::number(y).toStdString(); - - if(mTerrain.count(name) == 1) - mEngine->removeHeightField(x, y); - - std::multimap::iterator iter = mTerrain.begin(); - for(; iter != mTerrain.end(); ++iter) - { - if((*iter).second == sceneManager) - { - mTerrain.erase(iter); - break; - } - } - } - - // sceneMgr: to lookup the scene node name from the object's referenceId - // camera: primarily used to get the visibility mask for the viewport - // - // returns the found object's scene node name and its position in the world space - // - // WARNING: far clip distance is a global setting, if it changes in future - // this method will need to be updated - std::pair PhysicsSystem::castRay(float mouseX, - float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera) - { - // NOTE: there could be more than one camera for the scene manager - // TODO: check whether camera belongs to sceneMgr - if(!sceneMgr || !camera || !camera->getViewport()) - return std::make_pair("", Ogre::Vector3(0,0,0)); // FIXME: this should be an exception - - // using a really small value seems to mess up with the projections - float nearClipDistance = camera->getNearClipDistance(); // save existing - camera->setNearClipDistance(10.0f); // arbitrary number - Ogre::Ray ray = camera->getCameraToViewportRay(mouseX, mouseY); - camera->setNearClipDistance(nearClipDistance); // restore - - Ogre::Vector3 from = ray.getOrigin(); - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); - float farClipDist = userSettings.setting("Scene/far clip distance", QString("300000")).toFloat(); - Ogre::Vector3 to = ray.getPoint(farClipDist); - - btVector3 _from, _to; - _from = btVector3(from.x, from.y, from.z); - _to = btVector3(to.x, to.y, to.z); - - uint32_t visibilityMask = camera->getViewport()->getVisibilityMask(); - bool ignoreHeightMap = !(visibilityMask & (uint32_t)CSVRender::Element_Terrain); - bool ignoreObjects = !(visibilityMask & (uint32_t)CSVRender::Element_Reference); - - Ogre::Vector3 norm; // not used - std::pair result = - mEngine->rayTest(_from, _to, !ignoreObjects, ignoreHeightMap, &norm); - - // result.first is the object's referenceId - if(result.first == "") - return std::make_pair("", Ogre::Vector3(0,0,0)); - else - { - std::string name = refIdToSceneNode(result.first, sceneMgr); - if(name == "") - name = result.first; - else - name = refIdToSceneNode(result.first, sceneMgr); - - return std::make_pair(name, ray.getPoint(farClipDist*result.second)); - } - } - - std::string PhysicsSystem::refIdToSceneNode(std::string referenceId, Ogre::SceneManager *sceneMgr) - { - return mRefIdToSceneNode[referenceId][sceneMgr]; - } - - std::string PhysicsSystem::sceneNodeToRefId(std::string sceneNodeName) - { - return mSceneNodeToRefId[sceneNodeName]; - } - - void PhysicsSystem::addSceneManager(Ogre::SceneManager *sceneMgr, CSVRender::SceneWidget *sceneWidget) - { - mSceneWidgets[sceneMgr] = sceneWidget; - - mEngine->createDebugDraw(sceneMgr); - } - - std::map PhysicsSystem::sceneWidgets() - { - return mSceneWidgets; - } - - void PhysicsSystem::removeSceneManager(Ogre::SceneManager *sceneMgr) - { - mEngine->removeDebugDraw(sceneMgr); - - mSceneWidgets.erase(sceneMgr); - } - - Ogre::SceneManager *PhysicsSystem::findSceneManager(std::string sceneNodeName) - { - std::map::const_iterator iter = mSceneWidgets.begin(); - for(; iter != mSceneWidgets.end(); ++iter) - { - if((*iter).first->hasSceneNode(sceneNodeName)) - { - return (*iter).first; - } - } - - return NULL; - } - - void PhysicsSystem::toggleDebugRendering(Ogre::SceneManager *sceneMgr) - { - // FIXME: should check if sceneMgr is in the list - if(!sceneMgr) - return; - - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); - if(!(userSettings.setting("debug/mouse-picking", QString("false")) == "true" ? true : false)) - { - std::cerr << "Turn on mouse-picking debug option to see collision shapes." << std::endl; - return; - } - - mEngine->toggleDebugRendering(sceneMgr); - mEngine->stepDebug(sceneMgr); - } -} diff --git a/apps/opencs/view/world/physicssystem.hpp b/apps/opencs/view/world/physicssystem.hpp deleted file mode 100644 index 0036bf769d..0000000000 --- a/apps/opencs/view/world/physicssystem.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef CSV_WORLD_PHYSICSSYSTEM_H -#define CSV_WORLD_PHYSICSSYSTEM_H - -#include -#include - -namespace Ogre -{ - class Vector3; - class Quaternion; - class SceneManager; - class Camera; -} - -namespace OEngine -{ - namespace Physic - { - class PhysicEngine; - } -} - -namespace CSVRender -{ - class SceneWidget; -} - -namespace CSVWorld -{ - class PhysicsSystem - { - std::map mSceneNodeToRefId; - std::map > mRefIdToSceneNode; - std::map mSceneNodeToMesh; - std::map mSceneWidgets; - OEngine::Physic::PhysicEngine* mEngine; - std::multimap mTerrain; - - public: - - PhysicsSystem(); - ~PhysicsSystem(); - - void addSceneManager(Ogre::SceneManager *sceneMgr, CSVRender::SceneWidget * scene); - - void removeSceneManager(Ogre::SceneManager *sceneMgr); - - void addObject(const std::string &mesh, - const std::string &sceneNodeName, const std::string &referenceId, float scale, - const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, - bool placeable=false); - - void removeObject(const std::string &sceneNodeName); - void removePhysicsObject(const std::string &sceneNodeName); - - void replaceObject(const std::string &sceneNodeName, - float scale, const Ogre::Vector3 &position, - const Ogre::Quaternion &rotation, bool placeable=false); - - void moveObject(const std::string &sceneNodeName, - const Ogre::Vector3 &position, const Ogre::Quaternion &rotation); - - void moveSceneNodes(const std::string sceneNodeName, const Ogre::Vector3 &position); - - void addHeightField(Ogre::SceneManager *sceneManager, - float* heights, int x, int y, float yoffset, float triSize, float sqrtVerts); - - void removeHeightField(Ogre::SceneManager *sceneManager, int x, int y); - - void toggleDebugRendering(Ogre::SceneManager *sceneMgr); - - // return the object's SceneNode name and position for the given SceneManager - std::pair castRay(float mouseX, - float mouseY, Ogre::SceneManager *sceneMgr, Ogre::Camera *camera); - - std::string sceneNodeToRefId(std::string sceneNodeName); - - // for multi-scene manager per physics engine - std::map sceneWidgets(); - - private: - - void moveSceneNodeImpl(const std::string sceneNodeName, - const std::string referenceId, const Ogre::Vector3 &position); - - void updateSelectionHighlight(std::string sceneNode, const Ogre::Vector3 &position); - - std::string refIdToSceneNode(std::string referenceId, Ogre::SceneManager *sceneMgr); - - Ogre::SceneManager *findSceneManager(std::string sceneNodeName); - }; -} - -#endif // CSV_WORLD_PHYSICSSYSTEM_H diff --git a/apps/opencs/view/world/previewsubview.cpp b/apps/opencs/view/world/previewsubview.cpp index 1c2d6b95c7..f3312bb208 100644 --- a/apps/opencs/view/world/previewsubview.cpp +++ b/apps/opencs/view/world/previewsubview.cpp @@ -1,4 +1,3 @@ - #include "previewsubview.hpp" #include @@ -13,8 +12,6 @@ CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDo { QHBoxLayout *layout = new QHBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - if (document.getData().getReferenceables().searchId (id.getId())==-1) { std::string referenceableId = diff --git a/apps/opencs/view/world/recordbuttonbar.cpp b/apps/opencs/view/world/recordbuttonbar.cpp new file mode 100644 index 0000000000..40a24bf658 --- /dev/null +++ b/apps/opencs/view/world/recordbuttonbar.cpp @@ -0,0 +1,206 @@ +#include "recordbuttonbar.hpp" + +#include +#include + +#include "../../model/world/idtable.hpp" +#include "../../model/world/commanddispatcher.hpp" + +#include "../../model/prefs/state.hpp" + +#include "../world/tablebottombox.hpp" + +void CSVWorld::RecordButtonBar::updateModificationButtons() +{ + bool createAndDeleteDisabled = !mBottom || !mBottom->canCreateAndDelete() || mLocked; + + mCloneButton->setDisabled (createAndDeleteDisabled); + mAddButton->setDisabled (createAndDeleteDisabled); + + bool commandDisabled = !mCommandDispatcher || mLocked; + + mRevertButton->setDisabled (commandDisabled); + mDeleteButton->setDisabled (commandDisabled || createAndDeleteDisabled); +} + +void CSVWorld::RecordButtonBar::updatePrevNextButtons() +{ + int rows = mTable.rowCount(); + + if (rows<=1) + { + mPrevButton->setDisabled (true); + mNextButton->setDisabled (true); + } + else if (CSMPrefs::get()["General Input"]["cycle"].isTrue()) + { + mPrevButton->setDisabled (false); + mNextButton->setDisabled (false); + } + else + { + int row = mTable.getModelIndex (mId.getId(), 0).row(); + + mPrevButton->setDisabled (row<=0); + mNextButton->setDisabled (row>=rows-1); + } +} + +CSVWorld::RecordButtonBar::RecordButtonBar (const CSMWorld::UniversalId& id, + CSMWorld::IdTable& table, TableBottomBox *bottomBox, + CSMWorld::CommandDispatcher *commandDispatcher, QWidget *parent) +: QWidget (parent), mId (id), mTable (table), mBottom (bottomBox), + mCommandDispatcher (commandDispatcher), mLocked (false) +{ + QHBoxLayout *buttonsLayout = new QHBoxLayout; + buttonsLayout->setContentsMargins (0, 0, 0, 0); + + // left section + mPrevButton = new QToolButton (this); + mPrevButton->setIcon(QIcon(":/go-previous.png")); + mPrevButton->setToolTip ("Switch to previous record"); + buttonsLayout->addWidget (mPrevButton, 0); + + mNextButton = new QToolButton (this); + mNextButton->setIcon(QIcon(":/go-next.png")); + mNextButton->setToolTip ("Switch to next record"); + buttonsLayout->addWidget (mNextButton, 1); + + buttonsLayout->addStretch(2); + + // optional buttons of the right section + if (mTable.getFeatures() & CSMWorld::IdTable::Feature_Preview) + { + QToolButton* previewButton = new QToolButton (this); + previewButton->setIcon(QIcon(":/edit-preview.png")); + previewButton->setToolTip ("Open a preview of this record"); + buttonsLayout->addWidget(previewButton); + connect (previewButton, SIGNAL(clicked()), this, SIGNAL (showPreview())); + } + + if (mTable.getFeatures() & CSMWorld::IdTable::Feature_View) + { + QToolButton* viewButton = new QToolButton (this); + viewButton->setIcon(QIcon(":/cell.png")); + viewButton->setToolTip ("Open a scene view of the cell this record is located in"); + buttonsLayout->addWidget(viewButton); + connect (viewButton, SIGNAL(clicked()), this, SIGNAL (viewRecord())); + } + + // right section + mCloneButton = new QToolButton (this); + mCloneButton->setIcon(QIcon(":/edit-clone.png")); + mCloneButton->setToolTip ("Clone record"); + buttonsLayout->addWidget(mCloneButton); + + mAddButton = new QToolButton (this); + mAddButton->setIcon(QIcon(":/add.png")); + mAddButton->setToolTip ("Add new record"); + buttonsLayout->addWidget(mAddButton); + + mDeleteButton = new QToolButton (this); + mDeleteButton->setIcon(QIcon(":/edit-delete.png")); + mDeleteButton->setToolTip ("Delete record"); + buttonsLayout->addWidget(mDeleteButton); + + mRevertButton = new QToolButton (this); + mRevertButton->setIcon(QIcon(":/edit-undo.png")); + mRevertButton->setToolTip ("Revert record"); + buttonsLayout->addWidget(mRevertButton); + + setLayout (buttonsLayout); + + // connections + if(mBottom && mBottom->canCreateAndDelete()) + { + connect (mAddButton, SIGNAL (clicked()), mBottom, SLOT (createRequest())); + connect (mCloneButton, SIGNAL (clicked()), this, SLOT (cloneRequest())); + } + + connect (mNextButton, SIGNAL (clicked()), this, SLOT (nextId())); + connect (mPrevButton, SIGNAL (clicked()), this, SLOT (prevId())); + + if (mCommandDispatcher) + { + connect (mRevertButton, SIGNAL (clicked()), mCommandDispatcher, SLOT (executeRevert())); + connect (mDeleteButton, SIGNAL (clicked()), mCommandDispatcher, SLOT (executeDelete())); + } + + connect (&mTable, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (rowNumberChanged (const QModelIndex&, int, int))); + connect (&mTable, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), + this, SLOT (rowNumberChanged (const QModelIndex&, int, int))); + + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + + updateModificationButtons(); + updatePrevNextButtons(); +} + +void CSVWorld::RecordButtonBar::setEditLock (bool locked) +{ + mLocked = locked; + updateModificationButtons(); +} + +void CSVWorld::RecordButtonBar::universalIdChanged (const CSMWorld::UniversalId& id) +{ + mId = id; + updatePrevNextButtons(); +} + +void CSVWorld::RecordButtonBar::settingChanged (const CSMPrefs::Setting *setting) +{ + if (*setting=="General Input/cycle") + updatePrevNextButtons(); +} + +void CSVWorld::RecordButtonBar::cloneRequest() +{ + if (mBottom) + { + int typeColumn = mTable.findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); + + QModelIndex typeIndex = mTable.getModelIndex (mId.getId(), typeColumn); + CSMWorld::UniversalId::Type type = static_cast ( + mTable.data (typeIndex).toInt()); + + mBottom->cloneRequest (mId.getId(), type); + } +} + +void CSVWorld::RecordButtonBar::nextId() +{ + int newRow = mTable.getModelIndex (mId.getId(), 0).row() + 1; + + if (newRow >= mTable.rowCount()) + { + if (CSMPrefs::get()["General Input"]["cycle"].isTrue()) + newRow = 0; + else + return; + } + + emit switchToRow (newRow); +} + +void CSVWorld::RecordButtonBar::prevId() +{ + int newRow = mTable.getModelIndex (mId.getId(), 0).row() - 1; + + if (newRow < 0) + { + if (CSMPrefs::get()["General Input"]["cycle"].isTrue()) + newRow = mTable.rowCount()-1; + else + return; + } + + emit switchToRow (newRow); +} + +void CSVWorld::RecordButtonBar::rowNumberChanged (const QModelIndex& parent, int start, int end) +{ + updatePrevNextButtons(); +} diff --git a/apps/opencs/view/world/recordbuttonbar.hpp b/apps/opencs/view/world/recordbuttonbar.hpp new file mode 100644 index 0000000000..fbee066cea --- /dev/null +++ b/apps/opencs/view/world/recordbuttonbar.hpp @@ -0,0 +1,92 @@ +#ifndef CSV_WORLD_RECORDBUTTONBAR_H +#define CSV_WORLD_RECORDBUTTONBAR_H + +#include + +#include "../../model/world/universalid.hpp" + +class QToolButton; +class QModelIndex; + +namespace CSMWorld +{ + class IdTable; + class CommandDispatcher; +} + +namespace CSMPrefs +{ + class Setting; +} + +namespace CSVWorld +{ + class TableBottomBox; + + /// \brief Button bar for use in dialogue-type subviews + /// + /// Contains the following buttons: + /// - next/prev + /// - clone + /// - add + /// - delete + /// - revert + /// - preview (optional) + /// - view (optional) + class RecordButtonBar : public QWidget + { + Q_OBJECT + + CSMWorld::UniversalId mId; + CSMWorld::IdTable& mTable; + TableBottomBox *mBottom; + CSMWorld::CommandDispatcher *mCommandDispatcher; + QToolButton *mPrevButton; + QToolButton *mNextButton; + QToolButton *mCloneButton; + QToolButton *mAddButton; + QToolButton *mDeleteButton; + QToolButton *mRevertButton; + bool mLocked; + + private: + + void updateModificationButtons(); + + void updatePrevNextButtons(); + + public: + + RecordButtonBar (const CSMWorld::UniversalId& id, + CSMWorld::IdTable& table, TableBottomBox *bottomBox = 0, + CSMWorld::CommandDispatcher *commandDispatcher = 0, QWidget *parent = 0); + + void setEditLock (bool locked); + + public slots: + + void universalIdChanged (const CSMWorld::UniversalId& id); + + private slots: + + void settingChanged (const CSMPrefs::Setting *setting); + + void cloneRequest(); + + void nextId(); + + void prevId(); + + void rowNumberChanged (const QModelIndex& parent, int start, int end); + + signals: + + void showPreview(); + + void viewRecord(); + + void switchToRow (int row); + }; +} + +#endif diff --git a/apps/opencs/view/world/recordstatusdelegate.cpp b/apps/opencs/view/world/recordstatusdelegate.cpp index 58a5c01774..12d5453391 100644 --- a/apps/opencs/view/world/recordstatusdelegate.cpp +++ b/apps/opencs/view/world/recordstatusdelegate.cpp @@ -4,14 +4,13 @@ #include #include -#include "../../model/settings/usersettings.hpp" #include "../../model/world/columns.hpp" CSVWorld::RecordStatusDelegate::RecordStatusDelegate(const ValueList& values, const IconList & icons, CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) : DataDisplayDelegate (values, icons, dispatcher, document, - "records", "status-format", + "Records", "status-format", parent) {} diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index e8055ed317..1357ca46f0 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -1,4 +1,3 @@ - #include "referenceablecreator.hpp" #include diff --git a/apps/opencs/view/world/referencecreator.cpp b/apps/opencs/view/world/referencecreator.cpp index 81efb17ab6..73ca62e025 100644 --- a/apps/opencs/view/world/referencecreator.cpp +++ b/apps/opencs/view/world/referencecreator.cpp @@ -1,8 +1,6 @@ - #include "referencecreator.hpp" #include -#include #include "../../model/doc/document.hpp" @@ -12,6 +10,8 @@ #include "../../model/world/idtable.hpp" #include "../../model/world/idcompletionmanager.hpp" +#include "../widget/droplineedit.hpp" + std::string CSVWorld::ReferenceCreator::getId() const { return mId; @@ -80,7 +80,7 @@ CSVWorld::ReferenceCreator::ReferenceCreator (CSMWorld::Data& data, QUndoStack& QLabel *label = new QLabel ("Cell", this); insertBeforeButtons (label, false); - mCell = new QLineEdit (this); + mCell = new CSVWidget::DropLineEdit(CSMWorld::ColumnBase::Display_Cell, this); mCell->setCompleter(completionManager.getCompleter(CSMWorld::ColumnBase::Display_Cell).get()); insertBeforeButtons (mCell, true); diff --git a/apps/opencs/view/world/referencecreator.hpp b/apps/opencs/view/world/referencecreator.hpp index 7f56143c9f..c230d01268 100644 --- a/apps/opencs/view/world/referencecreator.hpp +++ b/apps/opencs/view/world/referencecreator.hpp @@ -3,13 +3,16 @@ #include "genericcreator.hpp" -class QLineEdit; - namespace CSMWorld { class IdCompletionManager; } +namespace CSVWidget +{ + class DropLineEdit; +} + namespace CSVWorld { @@ -17,7 +20,7 @@ namespace CSVWorld { Q_OBJECT - QLineEdit *mCell; + CSVWidget::DropLineEdit *mCell; std::string mId; private: diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index bc96b0952a..49764bd178 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -1,4 +1,3 @@ - #include "regionmap.hpp" #include diff --git a/apps/opencs/view/world/regionmapsubview.cpp b/apps/opencs/view/world/regionmapsubview.cpp index 411e24e754..996d1dc8b8 100644 --- a/apps/opencs/view/world/regionmapsubview.cpp +++ b/apps/opencs/view/world/regionmapsubview.cpp @@ -1,4 +1,3 @@ - #include "regionmapsubview.hpp" #include "regionmap.hpp" diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 397d249296..44fe94d840 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -1,4 +1,3 @@ - #include "scenesubview.hpp" #include @@ -16,6 +15,7 @@ #include "../render/pagedworldspacewidget.hpp" #include "../render/unpagedworldspacewidget.hpp" +#include "../render/editmode.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolmode.hpp" @@ -31,8 +31,6 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D { QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - layout->addWidget (mBottom = new TableBottomBox (NullCreatorFactory(), document, id, this), 0); mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); @@ -124,20 +122,14 @@ CSVWidget::SceneToolbar* CSVWorld::SceneSubView::makeToolbar (CSVRender::Worldsp CSVWidget::SceneToolRun *runTool = widget->makeRunTool (toolbar); toolbar->addTool (runTool); - CSVWidget::SceneToolMode *editModeTool = widget->makeEditModeSelector (toolbar); - toolbar->addTool (editModeTool); + toolbar->addTool (widget->makeEditModeSelector (toolbar)); return toolbar; } void CSVWorld::SceneSubView::setEditLock (bool locked) { - -} - -void CSVWorld::SceneSubView::updateEditorSetting(const QString &settingName, const QString &settingValue) -{ - + mScene->setEditLock (locked); } void CSVWorld::SceneSubView::setStatusBar (bool show) @@ -250,8 +242,6 @@ void CSVWorld::SceneSubView::replaceToolbarAndWorldspace (CSVRender::WorldspaceW mToolbar = toolbar; connect (mScene, SIGNAL (focusToolbarRequest()), mToolbar, SLOT (setFocus())); - connect (this, SIGNAL (updateSceneUserSetting(const QString &, const QStringList &)), - mScene, SLOT (updateUserSetting(const QString &, const QStringList &))); connect (mToolbar, SIGNAL (focusSceneRequest()), mScene, SLOT (setFocus())); mLayout->addWidget (mToolbar, 0); @@ -260,8 +250,3 @@ void CSVWorld::SceneSubView::replaceToolbarAndWorldspace (CSVRender::WorldspaceW mScene->selectDefaultNavigationMode(); setFocusProxy (mScene); } - -void CSVWorld::SceneSubView::updateUserSetting (const QString &key, const QStringList &list) -{ - emit updateSceneUserSetting(key, list); -} diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index fc45347d0b..0f18e8c303 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -27,6 +27,7 @@ namespace CSVRender namespace CSVWidget { class SceneToolbar; + class SceneToolMode; } namespace CSVWorld @@ -52,8 +53,6 @@ namespace CSVWorld virtual void setEditLock (bool locked); - virtual void updateEditorSetting (const QString& key, const QString& value); - virtual void setStatusBar (bool show); virtual void useHint (const std::string& hint); @@ -83,14 +82,6 @@ namespace CSVWorld void cellSelectionChanged (const CSMWorld::UniversalId& id); void handleDrop(const std::vector& data); - - public slots: - - void updateUserSetting (const QString &, const QStringList &); - - signals: - - void updateSceneUserSetting (const QString &, const QStringList &); }; } diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp index ad2cddbf84..9f1abcf970 100644 --- a/apps/opencs/view/world/scriptedit.cpp +++ b/apps/opencs/view/world/scriptedit.cpp @@ -12,8 +12,7 @@ #include "../../model/world/universalid.hpp" #include "../../model/world/tablemimedata.hpp" -#include "../../model/settings/usersettings.hpp" - +#include "../../model/prefs/state.hpp" CSVWorld::ScriptEdit::ChangeLock::ChangeLock (ScriptEdit& edit) : mEdit (edit) { @@ -92,31 +91,24 @@ CSVWorld::ScriptEdit::ScriptEdit (const CSMDoc::Document& document, ScriptHighli connect (&mUpdateTimer, SIGNAL (timeout()), this, SLOT (updateHighlighting())); - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); - connect (&userSettings, SIGNAL (userSettingUpdated(const QString &, const QStringList &)), - this, SLOT (updateUserSetting (const QString &, const QStringList &))); + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + { + ChangeLock lock (*this); + CSMPrefs::get()["Scripts"].update(); + } mUpdateTimer.setSingleShot (true); // TODO: provide a font selector dialogue mMonoFont.setStyleHint(QFont::TypeWriter); - if (userSettings.setting("script-editor/mono-font", "true") == "true") - setFont(mMonoFont); - mLineNumberArea = new LineNumberArea(this); updateLineNumberAreaWidth(0); connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int))); connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int))); - - showLineNum(userSettings.settingValue("script-editor/show-linenum") == "true"); -} - -void CSVWorld::ScriptEdit::updateUserSetting (const QString &name, const QStringList &list) -{ - if (mHighlighter->updateUserSetting (name, list)) - updateHighlighting(); + updateHighlighting(); } void CSVWorld::ScriptEdit::showLineNum(bool show) @@ -202,6 +194,16 @@ bool CSVWorld::ScriptEdit::stringNeedsQuote (const std::string& id) const return !(string.contains(mWhiteListQoutes)); } +void CSVWorld::ScriptEdit::settingChanged (const CSMPrefs::Setting *setting) +{ + if (mHighlighter->settingChanged (setting)) + updateHighlighting(); + else if (*setting=="Scripts/mono-font") + setFont (setting->isTrue() ? mMonoFont : mDefaultFont); + else if (*setting=="Scripts/show-linenum") + showLineNum (setting->isTrue()); +} + void CSVWorld::ScriptEdit::idListChanged() { mHighlighter->invalidateIds(); @@ -276,11 +278,11 @@ void CSVWorld::ScriptEdit::lineNumberAreaPaintEvent(QPaintEvent *event) if(textCursor().hasSelection()) { QString str = textCursor().selection().toPlainText(); - int selectedLines = str.count("\n")+1; + int offset = str.count("\n"); if(textCursor().position() < textCursor().anchor()) - endBlock += selectedLines; + endBlock += offset; else - startBlock -= selectedLines; + startBlock -= offset; } painter.setBackgroundMode(Qt::OpaqueMode); QFont font = painter.font(); diff --git a/apps/opencs/view/world/scriptedit.hpp b/apps/opencs/view/world/scriptedit.hpp index d17abf24ed..941a6295d8 100644 --- a/apps/opencs/view/world/scriptedit.hpp +++ b/apps/opencs/view/world/scriptedit.hpp @@ -91,6 +91,8 @@ namespace CSVWorld private slots: + void settingChanged (const CSMPrefs::Setting *setting); + void idListChanged(); void updateHighlighting(); @@ -98,10 +100,6 @@ namespace CSVWorld void updateLineNumberAreaWidth(int newBlockCount); void updateLineNumberArea(const QRect &, int); - - public slots: - - void updateUserSetting (const QString &name, const QStringList &list); }; class LineNumberArea : public QWidget diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp new file mode 100644 index 0000000000..c22bcf1991 --- /dev/null +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -0,0 +1,150 @@ +#include "scripterrortable.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "../../model/doc/document.hpp" + +#include "../../model/prefs/state.hpp" + +void CSVWorld::ScriptErrorTable::report (const std::string& message, const Compiler::TokenLoc& loc, Type type) +{ + std::ostringstream stream; + stream << message << " (" << loc.mLiteral << ")"; + + addMessage (stream.str(), type==Compiler::ErrorHandler::WarningMessage ? + CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error, + loc.mLine, loc.mColumn-loc.mLiteral.length()); +} + +void CSVWorld::ScriptErrorTable::report (const std::string& message, Type type) +{ + addMessage (message, type==Compiler::ErrorHandler::WarningMessage ? + CSMDoc::Message::Severity_Warning : CSMDoc::Message::Severity_Error); +} + +void CSVWorld::ScriptErrorTable::addMessage (const std::string& message, + CSMDoc::Message::Severity severity, int line, int column) +{ + int row = rowCount(); + + setRowCount (row+1); + + QTableWidgetItem *severityItem = new QTableWidgetItem ( + QString::fromUtf8 (CSMDoc::Message::toString (severity).c_str())); + severityItem->setFlags (severityItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 0, severityItem); + + if (line!=-1) + { + QTableWidgetItem *lineItem = new QTableWidgetItem; + lineItem->setData (Qt::DisplayRole, line+1); + lineItem->setFlags (lineItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 1, lineItem); + + QTableWidgetItem *columnItem = new QTableWidgetItem; + columnItem->setData (Qt::DisplayRole, column); + columnItem->setFlags (columnItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 3, columnItem); + } + + QTableWidgetItem *messageItem = new QTableWidgetItem (QString::fromUtf8 (message.c_str())); + messageItem->setFlags (messageItem->flags() ^ Qt::ItemIsEditable); + setItem (row, 2, messageItem); +} + +void CSVWorld::ScriptErrorTable::setWarningsMode (const std::string& value) +{ + if (value=="Ignore") + Compiler::ErrorHandler::setWarningsMode (0); + else if (value=="Normal") + Compiler::ErrorHandler::setWarningsMode (1); + else if (value=="Strict") + Compiler::ErrorHandler::setWarningsMode (2); +} + +CSVWorld::ScriptErrorTable::ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent) +: QTableWidget (parent), mContext (document.getData()) +{ + setColumnCount (4); + + QStringList headers; + headers << "Severity" << "Line" << "Description"; + setHorizontalHeaderLabels (headers); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + horizontalHeader()->setSectionResizeMode (0, QHeaderView::ResizeToContents); + horizontalHeader()->setSectionResizeMode (1, QHeaderView::ResizeToContents); +#else + horizontalHeader()->setResizeMode (0, QHeaderView::ResizeToContents); + horizontalHeader()->setResizeMode (1, QHeaderView::ResizeToContents); +#endif + horizontalHeader()->setStretchLastSection (true); + verticalHeader()->hide(); + setColumnHidden (3, true); + + setSelectionMode (QAbstractItemView::NoSelection); + + Compiler::registerExtensions (mExtensions); + mContext.setExtensions (&mExtensions); + + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + CSMPrefs::get()["Scripts"].update(); + + connect (this, SIGNAL (cellClicked (int, int)), this, SLOT (cellClicked (int, int))); +} + +void CSVWorld::ScriptErrorTable::update (const std::string& source) +{ + clear(); + + try + { + std::istringstream input (source); + + Compiler::Scanner scanner (*this, input, mContext.getExtensions()); + + Compiler::FileParser parser (*this, mContext); + + scanner.scan (parser); + } + catch (const Compiler::SourceException&) + { + // error has already been reported via error handler + } + catch (const std::exception& error) + { + addMessage (error.what(), CSMDoc::Message::Severity_SeriousError); + } +} + +void CSVWorld::ScriptErrorTable::clear() +{ + setRowCount (0); +} + +bool CSVWorld::ScriptErrorTable::clearLocals (const std::string& script) +{ + return mContext.clearLocals (script); +} + +void CSVWorld::ScriptErrorTable::settingChanged (const CSMPrefs::Setting *setting) +{ + if (*setting=="Scripts/warnings") + setWarningsMode (setting->toString()); +} + +void CSVWorld::ScriptErrorTable::cellClicked (int row, int column) +{ + if (item (row, 1)) + { + int scriptLine = item (row, 1)->data (Qt::DisplayRole).toInt(); + int scriptColumn = item (row, 3)->data (Qt::DisplayRole).toInt(); + emit highlightError (scriptLine-1, scriptColumn); + } +} diff --git a/apps/opencs/view/world/scripterrortable.hpp b/apps/opencs/view/world/scripterrortable.hpp new file mode 100644 index 0000000000..4841aac5b3 --- /dev/null +++ b/apps/opencs/view/world/scripterrortable.hpp @@ -0,0 +1,67 @@ +#ifndef CSV_WORLD_SCRIPTERRORTABLE_H +#define CSV_WORLD_SCRIPTERRORTABLE_H + +#include + +#include +#include + +#include "../../model/world/scriptcontext.hpp" +#include "../../model/doc/messages.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSMPrefs +{ + class Setting; +} + +namespace CSVWorld +{ + class ScriptErrorTable : public QTableWidget, private Compiler::ErrorHandler + { + Q_OBJECT + + Compiler::Extensions mExtensions; + CSMWorld::ScriptContext mContext; + + virtual void report (const std::string& message, const Compiler::TokenLoc& loc, Type type); + ///< Report error to the user. + + virtual void report (const std::string& message, Type type); + ///< Report a file related error + + void addMessage (const std::string& message, CSMDoc::Message::Severity severity, + int line = -1, int column = -1); + + void setWarningsMode (const std::string& value); + + public: + + ScriptErrorTable (const CSMDoc::Document& document, QWidget *parent = 0); + + void update (const std::string& source); + + void clear(); + + /// Clear local variable cache for \a script. + /// + /// \return Were there any locals that needed clearing? + bool clearLocals (const std::string& script); + + private slots: + + void settingChanged (const CSMPrefs::Setting *setting); + + void cellClicked (int row, int column); + + signals: + + void highlightError (int line, int column); + }; +} + +#endif diff --git a/apps/opencs/view/world/scripthighlighter.cpp b/apps/opencs/view/world/scripthighlighter.cpp index 4923a44d89..846a61b471 100644 --- a/apps/opencs/view/world/scripthighlighter.cpp +++ b/apps/opencs/view/world/scripthighlighter.cpp @@ -1,4 +1,3 @@ - #include "scripthighlighter.hpp" #include @@ -6,7 +5,8 @@ #include #include -#include "../../model/settings/usersettings.hpp" +#include "../../model/prefs/setting.hpp" +#include "../../model/prefs/category.hpp" bool CSVWorld::ScriptHighlighter::parseInt (int value, const Compiler::TokenLoc& loc, Compiler::Scanner& scanner) @@ -80,79 +80,12 @@ CSVWorld::ScriptHighlighter::ScriptHighlighter (const CSMWorld::Data& data, Mode : QSyntaxHighlighter (parent), Compiler::Parser (mErrorHandler, mContext), mContext (data), mMode (mode) { - CSMSettings::UserSettings &userSettings = CSMSettings::UserSettings::instance(); + QColor color ("black"); + QTextCharFormat format; + format.setForeground (color); - QColor color = QColor(); - - { - color.setNamedColor(userSettings.setting("script-editor/colour-int", "Dark magenta")); - if (!color.isValid()) - color = QColor(Qt::darkMagenta); - - QTextCharFormat format; - format.setForeground (color); - mScheme.insert (std::make_pair (Type_Int, format)); - } - - { - color.setNamedColor(userSettings.setting ("script-editor/colour-float", "Magenta")); - if (!color.isValid()) - color = QColor(Qt::magenta); - - QTextCharFormat format; - format.setForeground (color); - mScheme.insert (std::make_pair (Type_Float, format)); - } - - { - color.setNamedColor(userSettings.setting ("script-editor/colour-name", "Gray")); - if (!color.isValid()) - color = QColor(Qt::gray); - - QTextCharFormat format; - format.setForeground (color); - mScheme.insert (std::make_pair (Type_Name, format)); - } - - { - color.setNamedColor(userSettings.setting ("script-editor/colour-keyword", "Red")); - if (!color.isValid()) - color = QColor(Qt::red); - - QTextCharFormat format; - format.setForeground (color); - mScheme.insert (std::make_pair (Type_Keyword, format)); - } - - { - color.setNamedColor(userSettings.setting ("script-editor/colour-special", "Dark yellow")); - if (!color.isValid()) - color = QColor(Qt::darkYellow); - - QTextCharFormat format; - format.setForeground (color); - mScheme.insert (std::make_pair (Type_Special, format)); - } - - { - color.setNamedColor(userSettings.setting ("script-editor/colour-comment", "Green")); - if (!color.isValid()) - color = QColor(Qt::green); - - QTextCharFormat format; - format.setForeground (color); - mScheme.insert (std::make_pair (Type_Comment, format)); - } - - { - color.setNamedColor(userSettings.setting ("script-editor/colour-id", "Blue")); - if (!color.isValid()) - color = QColor(Qt::blue); - - QTextCharFormat format; - format.setForeground (color); - mScheme.insert (std::make_pair (Type_Id, format)); - } + for (int i=0; i<=Type_Id; ++i) + mScheme.insert (std::make_pair (static_cast (i), format)); // configure compiler Compiler::registerExtensions (mExtensions); @@ -177,85 +110,26 @@ void CSVWorld::ScriptHighlighter::invalidateIds() mContext.invalidateIds(); } -bool CSVWorld::ScriptHighlighter::updateUserSetting (const QString &name, const QStringList &list) +bool CSVWorld::ScriptHighlighter::settingChanged (const CSMPrefs::Setting *setting) { - if (list.empty()) - return false; - - QColor color = QColor(); - - if (name == "script-editor/colour-int") + if (setting->getParent()->getKey()=="Scripts") { - color.setNamedColor(list.at(0)); - if (!color.isValid()) - return false; + static const char *const colours[Type_Id+2] = + { + "colour-int", "colour-float", "colour-name", "colour-keyword", + "colour-special", "colour-comment", "colour-id", + 0 + }; - QTextCharFormat format; - format.setForeground (color); - mScheme[Type_Int] = format; + for (int i=0; colours[i]; ++i) + if (setting->getKey()==colours[i]) + { + QTextCharFormat format; + format.setForeground (setting->toColor()); + mScheme[static_cast (i)] = format; + return true; + } } - else if (name == "script-editor/colour-float") - { - color.setNamedColor(list.at(0)); - if (!color.isValid()) - return false; - QTextCharFormat format; - format.setForeground (color); - mScheme[Type_Float] = format; - } - else if (name == "script-editor/colour-name") - { - color.setNamedColor(list.at(0)); - if (!color.isValid()) - return false; - - QTextCharFormat format; - format.setForeground (color); - mScheme[Type_Name] = format; - } - else if (name == "script-editor/colour-keyword") - { - color.setNamedColor(list.at(0)); - if (!color.isValid()) - return false; - - QTextCharFormat format; - format.setForeground (color); - mScheme[Type_Keyword] = format; - } - else if (name == "script-editor/colour-special") - { - color.setNamedColor(list.at(0)); - if (!color.isValid()) - return false; - - QTextCharFormat format; - format.setForeground (color); - mScheme[Type_Special] = format; - } - else if (name == "script-editor/colour-comment") - { - color.setNamedColor(list.at(0)); - if (!color.isValid()) - return false; - - QTextCharFormat format; - format.setForeground (color); - mScheme[Type_Comment] = format; - } - else if (name == "script-editor/colour-id") - { - color.setNamedColor(list.at(0)); - if (!color.isValid()) - return false; - - QTextCharFormat format; - format.setForeground (color); - mScheme[Type_Id] = format; - } - else - return false; - - return true; + return false; } diff --git a/apps/opencs/view/world/scripthighlighter.hpp b/apps/opencs/view/world/scripthighlighter.hpp index 6f1f58e823..33824da0dc 100644 --- a/apps/opencs/view/world/scripthighlighter.hpp +++ b/apps/opencs/view/world/scripthighlighter.hpp @@ -11,6 +11,11 @@ #include "../../model/world/scriptcontext.hpp" +namespace CSMPrefs +{ + class Setting; +} + namespace CSVWorld { class ScriptHighlighter : public QSyntaxHighlighter, private Compiler::Parser @@ -19,13 +24,13 @@ namespace CSVWorld enum Type { - Type_Int, - Type_Float, - Type_Name, - Type_Keyword, - Type_Special, - Type_Comment, - Type_Id + Type_Int = 0, + Type_Float = 1, + Type_Name = 2, + Type_Keyword = 3, + Type_Special = 4, + Type_Comment = 5, + Type_Id = 6 }; enum Mode @@ -88,7 +93,7 @@ namespace CSVWorld void invalidateIds(); - bool updateUserSetting (const QString &name, const QStringList &list); + bool settingChanged (const CSMPrefs::Setting *setting); }; } diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 411eb3660d..ee0acb560a 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -4,7 +4,8 @@ #include #include -#include +#include +#include #include "../../model/doc/document.hpp" #include "../../model/world/universalid.hpp" @@ -12,48 +13,124 @@ #include "../../model/world/columnbase.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" -#include "../../model/settings/usersettings.hpp" +#include "../../model/prefs/state.hpp" #include "scriptedit.hpp" +#include "recordbuttonbar.hpp" +#include "tablebottombox.hpp" +#include "genericcreator.hpp" +#include "scripterrortable.hpp" + +void CSVWorld::ScriptSubView::addButtonBar() +{ + if (mButtons) + return; + + mButtons = new RecordButtonBar (getUniversalId(), *mModel, mBottom, &mCommandDispatcher, this); + + mLayout.insertWidget (1, mButtons); + + connect (mButtons, SIGNAL (switchToRow (int)), this, SLOT (switchToRow (int))); + + connect (this, SIGNAL (universalIdChanged (const CSMWorld::UniversalId&)), + mButtons, SLOT (universalIdChanged (const CSMWorld::UniversalId&))); +} + +void CSVWorld::ScriptSubView::recompile() +{ + if (!mCompileDelay->isActive() && !isDeleted()) + mCompileDelay->start (CSMPrefs::get()["Scripts"]["compile-delay"].toInt()); +} + +bool CSVWorld::ScriptSubView::isDeleted() const +{ + return mModel->data (mModel->getModelIndex (getUniversalId().getId(), mStateColumn)).toInt() + ==CSMWorld::RecordBase::State_Deleted; +} + +void CSVWorld::ScriptSubView::updateDeletedState() +{ + if (isDeleted()) + { + mErrors->clear(); + adjustSplitter(); + mEditor->setEnabled (false); + } + else + { + mEditor->setEnabled (true); + recompile(); + } +} + +void CSVWorld::ScriptSubView::adjustSplitter() +{ + QList sizes; + + if (mErrors->rowCount()) + { + if (mErrors->height()) + return; // keep old height if the error panel was already open + + sizes << (mMain->height()-mErrorHeight-mMain->handleWidth()) << mErrorHeight; + } + else + { + if (mErrors->height()) + mErrorHeight = mErrors->height(); + + sizes << 1 << 0; + } + + mMain->setSizes (sizes); +} CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: SubView (id), mDocument (document), mColumn (-1), mBottom(0), mStatus(0) +: SubView (id), mDocument (document), mColumn (-1), mBottom(0), mButtons (0), + mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())), + mErrorHeight (CSMPrefs::get()["Scripts"]["error-height"].toInt()) { - QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); + std::vector selection (1, id.getId()); + mCommandDispatcher.setSelection (selection); - mBottom = new QWidget(this); - QStackedLayout *bottmLayout = new QStackedLayout(mBottom); - bottmLayout->setContentsMargins (0, 0, 0, 0); - QStatusBar *statusBar = new QStatusBar(mBottom); - mStatus = new QLabel(mBottom); - statusBar->addWidget (mStatus); - bottmLayout->addWidget (statusBar); - mBottom->setLayout (bottmLayout); + mMain = new QSplitter (this); + mMain->setOrientation (Qt::Vertical); + mLayout.addWidget (mMain, 2); - layout->addWidget (mBottom, 0); - layout->insertWidget (0, mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this), 2); + mEditor = new ScriptEdit (mDocument, ScriptHighlighter::Mode_General, this); + mMain->addWidget (mEditor); + mMain->setCollapsible (0, false); - QWidget *widget = new QWidget; - widget->setLayout (layout); + mErrors = new ScriptErrorTable (document, this); + mMain->addWidget (mErrors); + + QList sizes; + sizes << 1 << 0; + mMain->setSizes (sizes); + + QWidget *widget = new QWidget (this);; + widget->setLayout (&mLayout); setWidget (widget); mModel = &dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts)); - for (int i=0; icolumnCount(); ++i) - if (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display)== - CSMWorld::ColumnBase::Display_ScriptFile) - { - mColumn = i; - break; - } + mColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_ScriptText); + mIdColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + mStateColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); - if (mColumn==-1) - throw std::logic_error ("Can't find script column"); + QString source = mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString(); - mEditor->setPlainText (mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString()); + mEditor->setPlainText (source); + // bottom box and buttons + mBottom = new TableBottomBox (CreatorFactory(), document, id, this); + connect (mBottom, SIGNAL (requestFocus (const std::string&)), + this, SLOT (switchToId (const std::string&))); + + mLayout.addWidget (mBottom); + + // signals connect (mEditor, SIGNAL (textChanged()), this, SLOT (textChanged())); connect (mModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), @@ -64,35 +141,65 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: updateStatusBar(); connect(mEditor, SIGNAL(cursorPositionChanged()), this, SLOT(updateStatusBar())); + + mErrors->update (source.toUtf8().constData()); + + connect (mErrors, SIGNAL (highlightError (int, int)), + this, SLOT (highlightError (int, int))); + + mCompileDelay = new QTimer (this); + mCompileDelay->setSingleShot (true); + connect (mCompileDelay, SIGNAL (timeout()), this, SLOT (updateRequest())); + + updateDeletedState(); + + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + CSMPrefs::get()["Scripts"].update(); } -void CSVWorld::ScriptSubView::updateUserSetting (const QString& name, const QStringList& value) +void CSVWorld::ScriptSubView::setStatusBar (bool show) { - if (name == "script-editor/show-linenum") + mBottom->setStatusBar (show); +} + +void CSVWorld::ScriptSubView::settingChanged (const CSMPrefs::Setting *setting) +{ + if (*setting=="Scripts/toolbar") { - std::string showLinenum = value.at(0).toStdString(); - mEditor->showLineNum(showLinenum == "true"); - mBottom->setVisible(showLinenum == "true"); + if (setting->isTrue()) + { + addButtonBar(); + } + else if (mButtons) + { + mLayout.removeWidget (mButtons); + delete mButtons; + mButtons = 0; + } } - else if (name == "script-editor/mono-font") + else if (*setting=="Scripts/compile-delay") { - mEditor->setMonoFont(value.at(0).toStdString() == "true"); + mCompileDelay->setInterval (setting->toInt()); } + else if (*setting=="Scripts/warnings") + recompile(); } void CSVWorld::ScriptSubView::updateStatusBar () { - std::ostringstream stream; - - stream << "(" << mEditor->textCursor().blockNumber() + 1 << ", " - << mEditor->textCursor().columnNumber() + 1 << ")"; - - mStatus->setText (QString::fromUtf8 (stream.str().c_str())); + mBottom->positionChanged (mEditor->textCursor().blockNumber() + 1, + mEditor->textCursor().columnNumber() + 1); } void CSVWorld::ScriptSubView::setEditLock (bool locked) { mEditor->setReadOnly (locked); + + if (mButtons) + mButtons->setEditLock (locked); + + mCommandDispatcher.setEditLock (locked); } void CSVWorld::ScriptSubView::useHint (const std::string& hint) @@ -100,26 +207,41 @@ void CSVWorld::ScriptSubView::useHint (const std::string& hint) if (hint.empty()) return; - if (hint[0]=='l') - { - std::istringstream stream (hint.c_str()+1); - - char ignore; - int line; - int column; - - if (stream >> ignore >> line >> column) + unsigned line = 0, column = 0; + char c; + std::istringstream stream (hint.c_str()+1); + switch(hint[0]){ + case 'R': + case 'r': { - QTextCursor cursor = mEditor->textCursor(); + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); + QString source = mModel->data (index).toString(); + unsigned pos, dummy; + if (!(stream >> c >> dummy >> pos) ) + return; - cursor.movePosition (QTextCursor::Start); - if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) - cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); - - mEditor->setFocus(); - mEditor->setTextCursor (cursor); + for (unsigned i = 0; i <= pos; ++i){ + if (source[i] == '\n'){ + ++line; + column = i+1; + } + } + column = pos - column; + break; } + case 'l': + if (!(stream >> c >> line >> column)) + return; } + + QTextCursor cursor = mEditor->textCursor(); + + cursor.movePosition (QTextCursor::Start); + if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) + cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); + + mEditor->setFocus(); + mEditor->setTextCursor (cursor); } void CSVWorld::ScriptSubView::textChanged() @@ -129,8 +251,12 @@ void CSVWorld::ScriptSubView::textChanged() ScriptEdit::ChangeLock lock (*mEditor); + QString source = mEditor->toPlainText(); + mDocument.getUndoStack().push (new CSMWorld::ModifyCommand (*mModel, - mModel->getModelIndex (getUniversalId().getId(), mColumn), mEditor->toPlainText())); + mModel->getModelIndex (getUniversalId().getId(), mColumn), source)); + + recompile(); } void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) @@ -140,22 +266,96 @@ void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QMo ScriptEdit::ChangeLock lock (*mEditor); + bool updateRequired = false; + + for (int i=topLeft.row(); i<=bottomRight.row(); ++i) + { + std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData(); + if (mErrors->clearLocals (id)) + updateRequired = true; + } + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); - if (index.row()>=topLeft.row() && index.row()<=bottomRight.row() && - index.column()>=topLeft.column() && index.column()<=bottomRight.column()) + if (index.row()>=topLeft.row() && index.row()<=bottomRight.row()) { - QTextCursor cursor = mEditor->textCursor(); - mEditor->setPlainText (mModel->data (index).toString()); - mEditor->setTextCursor (cursor); + if (mStateColumn>=topLeft.column() && mStateColumn<=bottomRight.column()) + updateDeletedState(); + + if (mColumn>=topLeft.column() && mColumn<=bottomRight.column()) + { + QString source = mModel->data (index).toString(); + + QTextCursor cursor = mEditor->textCursor(); + mEditor->setPlainText (source); + mEditor->setTextCursor (cursor); + + updateRequired = true; + } } + + if (updateRequired) + recompile(); } void CSVWorld::ScriptSubView::rowsAboutToBeRemoved (const QModelIndex& parent, int start, int end) { + bool updateRequired = false; + + for (int i=start; i<=end; ++i) + { + std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData(); + if (mErrors->clearLocals (id)) + updateRequired = true; + } + + if (updateRequired) + recompile(); + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); if (!parent.isValid() && index.row()>=start && index.row()<=end) emit closeRequest(); } +void CSVWorld::ScriptSubView::switchToRow (int row) +{ + int idColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + std::string id = mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData(); + setUniversalId (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, id)); + + mEditor->setPlainText (mModel->data (mModel->index (row, mColumn)).toString()); + + std::vector selection (1, id); + mCommandDispatcher.setSelection (selection); + + updateDeletedState(); +} + +void CSVWorld::ScriptSubView::switchToId (const std::string& id) +{ + switchToRow (mModel->getModelIndex (id, 0).row()); +} + +void CSVWorld::ScriptSubView::highlightError (int line, int column) +{ + QTextCursor cursor = mEditor->textCursor(); + + cursor.movePosition (QTextCursor::Start); + if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) + cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); + + mEditor->setFocus(); + mEditor->setTextCursor (cursor); +} + +void CSVWorld::ScriptSubView::updateRequest() +{ + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); + + QString source = mModel->data (index).toString(); + + mErrors->update (source.toUtf8().constData()); + + adjustSplitter(); +} diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 1c6474e542..c1016babf9 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -1,10 +1,17 @@ #ifndef CSV_WORLD_SCRIPTSUBVIEW_H #define CSV_WORLD_SCRIPTSUBVIEW_H +#include + +#include "../../model/world/commanddispatcher.hpp" + #include "../doc/subview.hpp" class QModelIndex; class QLabel; +class QVBoxLayout; +class QSplitter; +class QTime; namespace CSMDoc { @@ -16,9 +23,17 @@ namespace CSMWorld class IdTable; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSVWorld { class ScriptEdit; + class RecordButtonBar; + class TableBottomBox; + class ScriptErrorTable; class ScriptSubView : public CSVDoc::SubView { @@ -28,8 +43,28 @@ namespace CSVWorld CSMDoc::Document& mDocument; CSMWorld::IdTable *mModel; int mColumn; - QWidget *mBottom; - QLabel *mStatus; + int mIdColumn; + int mStateColumn; + TableBottomBox *mBottom; + RecordButtonBar *mButtons; + CSMWorld::CommandDispatcher mCommandDispatcher; + QVBoxLayout mLayout; + QSplitter *mMain; + ScriptErrorTable *mErrors; + QTimer *mCompileDelay; + int mErrorHeight; + + private: + + void addButtonBar(); + + void recompile(); + + bool isDeleted() const; + + void updateDeletedState(); + + void adjustSplitter(); public: @@ -39,7 +74,7 @@ namespace CSVWorld virtual void useHint (const std::string& hint); - virtual void updateUserSetting (const QString& name, const QStringList& value); + virtual void setStatusBar (bool show); public slots: @@ -51,7 +86,17 @@ namespace CSVWorld private slots: + void settingChanged (const CSMPrefs::Setting *setting); + void updateStatusBar(); + + void switchToRow (int row); + + void switchToId (const std::string& id); + + void highlightError (int line, int column); + + void updateRequest(); }; } diff --git a/apps/opencs/view/world/startscriptcreator.cpp b/apps/opencs/view/world/startscriptcreator.cpp new file mode 100644 index 0000000000..69b1b3ff12 --- /dev/null +++ b/apps/opencs/view/world/startscriptcreator.cpp @@ -0,0 +1,20 @@ +#include "startscriptcreator.hpp" + +CSVWorld::StartScriptCreator::StartScriptCreator(CSMWorld::Data &data, QUndoStack &undoStack, const CSMWorld::UniversalId &id, bool relaxedIdRules): + GenericCreator (data, undoStack, id, true) +{} + +std::string CSVWorld::StartScriptCreator::getErrors() const +{ + std::string errors; + + errors = getIdValidatorResult(); + if (errors.length() > 0) + return errors; + else if (getData().getScripts().searchId(getId()) == -1) + errors = "Script ID not found"; + else if (getData().getStartScripts().searchId(getId()) > -1 ) + errors = "Script with this ID already registered as Start Script"; + + return errors; +} diff --git a/apps/opencs/view/world/startscriptcreator.hpp b/apps/opencs/view/world/startscriptcreator.hpp new file mode 100644 index 0000000000..07fe8ff3d3 --- /dev/null +++ b/apps/opencs/view/world/startscriptcreator.hpp @@ -0,0 +1,25 @@ +#ifndef STARTSCRIPTCREATOR_HPP +#define STARTSCRIPTCREATOR_HPP + +#include "genericcreator.hpp" + +namespace CSVWorld { + + class StartScriptCreator : public GenericCreator + { + Q_OBJECT + + public: + StartScriptCreator(CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id, bool relaxedIdRules = false); + + virtual std::string getErrors() const; + ///< Return formatted error descriptions for the current state of the creator. if an empty + /// string is returned, there is no error. + }; + +} + + + +#endif // STARTSCRIPTCREATOR_HPP diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index ba3ab1358c..88375caafc 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -1,4 +1,3 @@ - #include "subviews.hpp" #include "../doc/subviewfactoryimp.hpp" @@ -11,6 +10,7 @@ #include "cellcreator.hpp" #include "referenceablecreator.hpp" #include "referencecreator.hpp" +#include "startscriptcreator.hpp" #include "scenesubview.hpp" #include "dialoguecreator.hpp" #include "infocreator.hpp" @@ -43,7 +43,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_BodyParts, CSMWorld::UniversalId::Type_SoundGens, CSMWorld::UniversalId::Type_Pathgrids, - CSMWorld::UniversalId::Type_StartScripts, CSMWorld::UniversalId::Type_None // end marker }; @@ -52,6 +51,9 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (sTableTypes[i], new CSVDoc::SubViewFactoryWithCreator >); + manager.add (CSMWorld::UniversalId::Type_StartScripts, + new CSVDoc::SubViewFactoryWithCreator >); + manager.add (CSMWorld::UniversalId::Type_Cells, new CSVDoc::SubViewFactoryWithCreator >); @@ -124,7 +126,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_BodyPart, CSMWorld::UniversalId::Type_SoundGen, CSMWorld::UniversalId::Type_Pathgrid, - CSMWorld::UniversalId::Type_StartScript, CSMWorld::UniversalId::Type_None // end marker }; @@ -134,6 +135,10 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_StartScript, + new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_Skill, new CSVDoc::SubViewFactoryWithCreator (false)); @@ -170,6 +175,9 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Filter, new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_MetaData, + new CSVDoc::SubViewFactory); + //preview manager.add (CSMWorld::UniversalId::Type_Preview, new CSVDoc::SubViewFactory); } diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 072b645c42..95dfa10340 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -1,4 +1,3 @@ - #include "table.hpp" #include @@ -9,6 +8,8 @@ #include #include +#include + #include "../../model/doc/document.hpp" #include "../../model/world/data.hpp" @@ -22,67 +23,35 @@ #include "../../model/world/tablemimedata.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/commanddispatcher.hpp" -#include "../../model/settings/usersettings.hpp" + +#include "../../model/prefs/state.hpp" #include "recordstatusdelegate.hpp" +#include "tableeditidaction.hpp" #include "util.hpp" void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) { // configure dispatcher - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - std::vector records; - - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) - { - int row = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)).row(); - - records.push_back (mModel->data ( - mModel->index (row, columnIndex)).toString().toUtf8().constData()); - } - - mDispatcher->setSelection (records); + mDispatcher->setSelection (getSelectedIds()); std::vector extendedTypes = mDispatcher->getExtendedTypes(); mDispatcher->setExtendedTypes (extendedTypes); // create context menu + QModelIndexList selectedRows = selectionModel()->selectedRows(); QMenu menu (this); /// \todo add menu items for select all and clear selection + int currentRow = rowAt(event->y()); + int currentColumn = columnAt(event->x()); + if (mEditIdAction->isValidIdCell(currentRow, currentColumn)) { - // Request UniversalId editing from table columns. - - int currRow = rowAt( event->y() ), - currCol = columnAt( event->x() ); - - currRow = mProxyModel->mapToSource(mProxyModel->index( currRow, 0 )).row(); - - CSMWorld::ColumnBase::Display colDisplay = - static_cast( - mModel->headerData( - currCol, - Qt::Horizontal, - CSMWorld::ColumnBase::Role_Display ).toInt()); - - QString cellData = mModel->data(mModel->index( currRow, currCol )).toString(); - CSMWorld::UniversalId::Type colType = CSMWorld::TableMimeData::convertEnums( colDisplay ); - - if ( !cellData.isEmpty() - && colType != CSMWorld::UniversalId::Type_None ) - { - mEditCellAction->setText(tr("Edit '").append(cellData).append("'")); - - menu.addAction( mEditCellAction ); - - mEditCellId = CSMWorld::UniversalId( colType, cellData.toUtf8().constData() ); - } + mEditIdAction->setCell(currentRow, currentColumn); + menu.addAction(mEditIdAction); + menu.addSeparator(); } if (!mEditLock && !(mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) @@ -128,17 +97,24 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) { int row = mProxyModel->mapToSource ( mProxyModel->index (selectedRows.begin()->row(), 0)).row(); + QString curData = mModel->data(mModel->index(row, column)).toString(); - if (row>0 && mModel->data (mModel->index (row, column))== - mModel->data (mModel->index (row-1, column))) + if (row > 0) { - menu.addAction (mMoveUpAction); + QString prevData = mModel->data(mModel->index(row - 1, column)).toString(); + if (Misc::StringUtils::ciEqual(curData.toStdString(), prevData.toStdString())) + { + menu.addAction(mMoveUpAction); + } } - if (rowrowCount()-1 && mModel->data (mModel->index (row, column))== - mModel->data (mModel->index (row+1, column))) + if (row < mModel->rowCount() - 1) { - menu.addAction (mMoveDownAction); + QString nextData = mModel->data(mModel->index(row + 1, column)).toString(); + if (Misc::StringUtils::ciEqual(curData.toStdString(), nextData.toStdString())) + { + menu.addAction(mMoveDownAction); + } } } } @@ -257,24 +233,6 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, : DragRecordTable(document), mCreateAction (0), mCloneAction(0),mRecordStatusDisplay (0) { - CSMSettings::UserSettings &settings = CSMSettings::UserSettings::instance(); - QString jumpSetting = settings.settingValue ("table-input/jump-to-added"); - if (jumpSetting.isEmpty() || jumpSetting == "Jump and Select") // default - { - mJumpToAddedRecord = true; - mUnselectAfterJump = false; - } - else if(jumpSetting == "Jump Only") - { - mJumpToAddedRecord = true; - mUnselectAfterJump = true; - } - else - { - mJumpToAddedRecord = false; - mUnselectAfterJump = false; - } - mModel = &dynamic_cast (*mDocument.getData().getTableModel (id)); bool isInfoTable = id.getType() == CSMWorld::UniversalId::Type_TopicInfos || @@ -298,12 +256,16 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, horizontalHeader()->setResizeMode (QHeaderView::Interactive); #endif verticalHeader()->hide(); - setSortingEnabled (sorting); setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); - int columns = mModel->columnCount(); + setSortingEnabled (sorting); + if (sorting) + { + sortByColumn (mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id), Qt::AscendingOrder); + } + int columns = mModel->columnCount(); for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); @@ -354,10 +316,6 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); addAction (mMoveDownAction); - mEditCellAction = new QAction( tr("Edit Cell"), this ); - connect( mEditCellAction, SIGNAL(triggered()), this, SLOT(editCell()) ); - addAction( mEditCellAction ); - mViewAction = new QAction (tr ("View"), this); connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord())); addAction (mViewAction); @@ -366,23 +324,25 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord())); addAction (mPreviewAction); - /// \todo add a user option, that redirects the extended action to an input panel (in - /// the bottom bar) that lets the user select which record collections should be - /// modified. - mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this); - connect (mExtendedDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedDelete())); + connect (mExtendedDeleteAction, SIGNAL (triggered()), this, SLOT (executeExtendedDelete())); addAction (mExtendedDeleteAction); mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this); - connect (mExtendedRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedRevert())); + connect (mExtendedRevertAction, SIGNAL (triggered()), this, SLOT (executeExtendedRevert())); addAction (mExtendedRevertAction); + mEditIdAction = new TableEditIdAction (*this, this); + connect (mEditIdAction, SIGNAL (triggered()), this, SLOT (editCell())); + addAction (mEditIdAction); + connect (mProxyModel, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); - connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (rowsInsertedEvent(const QModelIndex&, int, int))); + //connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + // this, SLOT (rowsInsertedEvent(const QModelIndex&, int, int))); + connect (mProxyModel, SIGNAL (rowAdded (const std::string &)), + this, SLOT (rowAdded (const std::string &))); /// \note This signal could instead be connected to a slot that filters out changes not affecting /// the records status column (for permanence reasons) @@ -398,6 +358,10 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier, Action_EditRecord)); mDoubleClickActions.insert (std::make_pair (Qt::ControlModifier, Action_View)); mDoubleClickActions.insert (std::make_pair (Qt::ShiftModifier | Qt::ControlModifier, Action_EditRecordAndClose)); + + connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), + this, SLOT (settingChanged (const CSMPrefs::Setting *))); + CSMPrefs::get()["ID Tables"].update(); } void CSVWorld::Table::setEditLock (bool locked) @@ -421,6 +385,22 @@ CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData()); } +std::vector CSVWorld::Table::getSelectedIds() const +{ + std::vector ids; + QModelIndexList selectedRows = selectionModel()->selectedRows(); + int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); + iter != selectedRows.end(); + ++iter) + { + int row = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)).row(); + ids.push_back (mModel->data (mModel->index (row, columnIndex)).toString().toUtf8().constData()); + } + return ids; +} + void CSVWorld::Table::editRecord() { if (!mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant)) @@ -438,7 +418,7 @@ void CSVWorld::Table::cloneRecord() { QModelIndexList selectedRows = selectionModel()->selectedRows(); const CSMWorld::UniversalId& toClone = getUniversalId(selectedRows.begin()->row()); - if (selectedRows.size()==1 && !mModel->isDeleted (toClone.getId())) + if (selectedRows.size() == 1) { emit cloneRequest (toClone); } @@ -513,7 +493,7 @@ void CSVWorld::Table::moveDownRecord() void CSVWorld::Table::editCell() { - emit editRequest( mEditCellId, std::string() ); + emit editRequest(mEditIdAction->getCurrentId(), ""); } void CSVWorld::Table::viewRecord() @@ -553,16 +533,40 @@ void CSVWorld::Table::previewRecord() } } -void CSVWorld::Table::updateUserSetting (const QString &name, const QStringList &list) +void CSVWorld::Table::executeExtendedDelete() { - if (name=="table-input/jump-to-added") + if (CSMPrefs::get()["ID Tables"]["extended-config"].isTrue()) { - if(list.isEmpty() || list.at(0) == "Jump and Select") // default + emit extendedDeleteConfigRequest(getSelectedIds()); + } + else + { + QMetaObject::invokeMethod(mDispatcher, "executeExtendedDelete", Qt::QueuedConnection); + } +} + +void CSVWorld::Table::executeExtendedRevert() +{ + if (CSMPrefs::get()["ID Tables"]["extended-config"].isTrue()) + { + emit extendedRevertConfigRequest(getSelectedIds()); + } + else + { + QMetaObject::invokeMethod(mDispatcher, "executeExtendedRevert", Qt::QueuedConnection); + } +} + +void CSVWorld::Table::settingChanged (const CSMPrefs::Setting *setting) +{ + if (*setting=="ID Tables/jump-to-added") + { + if (setting->toString()=="Jump and Select") { mJumpToAddedRecord = true; mUnselectAfterJump = false; } - else if(list.at(0) == "Jump Only") + else if (setting->toString()=="Jump Only") { mJumpToAddedRecord = true; mUnselectAfterJump = true; @@ -573,28 +577,23 @@ void CSVWorld::Table::updateUserSetting (const QString &name, const QStringList mUnselectAfterJump = false; } } - - if (name=="records/type-format" || name=="records/status-format") + else if (*setting=="Records/type-format" || *setting=="Records/status-format") { int columns = mModel->columnCount(); for (int i=0; i - (*delegate).updateUserSetting (name, list); - { - emit dataChanged (mModel->index (0, i), - mModel->index (mModel->rowCount()-1, i)); - } + dynamic_cast (*delegate).settingChanged (setting); + emit dataChanged (mModel->index (0, i), + mModel->index (mModel->rowCount()-1, i)); } - return; } - - QString base ("table-input/double"); - if (name.startsWith (base)) + else if (setting->getParent()->getKey()=="ID Tables" && + setting->getKey().substr (0, 6)=="double") { - QString modifierString = name.mid (base.size()); + std::string modifierString = setting->getKey().substr (6); + Qt::KeyboardModifiers modifiers = 0; if (modifierString=="-s") @@ -606,7 +605,7 @@ void CSVWorld::Table::updateUserSetting (const QString &name, const QStringList DoubleClickAction action = Action_None; - QString value = list.at (0); + std::string value = setting->toString(); if (value=="Edit in Place") action = Action_InPlaceEdit; @@ -624,8 +623,6 @@ void CSVWorld::Table::updateUserSetting (const QString &name, const QStringList action = Action_ViewAndClose; mDoubleClickActions[modifiers] = action; - - return; } } @@ -663,10 +660,6 @@ void CSVWorld::Table::tableSizeUpdate() } emit tableSizeChanged (size, deleted, modified); - - // not really related to tableSizeUpdate() but placed here for convenience rather than - // creating a bunch of extra connections & slot - mProxyModel->refreshFilter(); } void CSVWorld::Table::selectionSizeUpdate() @@ -697,36 +690,6 @@ void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event) } } -void CSVWorld::Table::dropEvent(QDropEvent *event) -{ - QModelIndex index = indexAt (event->pos()); - - if (!index.isValid()) - { - return; - } - - const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); - if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped - return; - - if (mime->fromDocument (mDocument)) - { - CSMWorld::ColumnBase::Display display = static_cast - (mModel->headerData (index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - - if (mime->holdsType (display)) - { - CSMWorld::UniversalId record (mime->returnMatching (display)); - - std::auto_ptr command (new CSMWorld::ModifyCommand - (*mProxyModel, index, QVariant (QString::fromUtf8 (record.getId().c_str())))); - - mDocument.getUndoStack().push (command.release()); - } - } //TODO handle drops from different document -} - std::vector CSVWorld::Table::getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const { const int count = mModel->columnCount(); @@ -758,12 +721,13 @@ std::vector< CSMWorld::UniversalId > CSVWorld::Table::getDraggedRecords() const return idToDrag; } -void CSVWorld::Table::rowsInsertedEvent(const QModelIndex& parent, int start, int end) +void CSVWorld::Table::rowAdded(const std::string &id) { tableSizeUpdate(); if(mJumpToAddedRecord) { - selectRow(end); + int idColumn = mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + selectRow(mProxyModel->getModelIndex(id, idColumn).row()); if(mUnselectAfterJump) clearSelection(); diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 67bf3fe857..53249f66fe 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -27,9 +27,15 @@ namespace CSMWorld class CommandDispatcher; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSVWorld { class CommandDelegate; + class TableEditIdAction; ///< Table widget class Table : public DragRecordTable @@ -57,15 +63,14 @@ namespace CSVWorld QAction *mMoveUpAction; QAction *mMoveDownAction; QAction *mViewAction; - QAction *mEditCellAction; QAction *mPreviewAction; QAction *mExtendedDeleteAction; QAction *mExtendedRevertAction; + TableEditIdAction *mEditIdAction; CSMWorld::IdTableProxyModel *mProxyModel; CSMWorld::IdTableBase *mModel; int mRecordStatusDisplay; CSMWorld::CommandDispatcher *mDispatcher; - CSMWorld::UniversalId mEditCellId; std::map mDoubleClickActions; bool mJumpToAddedRecord; bool mUnselectAfterJump; @@ -76,8 +81,6 @@ namespace CSVWorld void mouseMoveEvent(QMouseEvent *event); - void dropEvent(QDropEvent *event); - protected: virtual void mouseDoubleClickEvent (QMouseEvent *event); @@ -95,6 +98,8 @@ namespace CSVWorld std::vector getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const; + std::vector getSelectedIds() const; + virtual std::vector getDraggedRecords() const; signals: @@ -114,6 +119,10 @@ namespace CSVWorld void closeRequest(); + void extendedDeleteConfigRequest(const std::vector &selectedIds); + + void extendedRevertConfigRequest(const std::vector &selectedIds); + private slots: void editCell(); @@ -130,8 +139,14 @@ namespace CSVWorld void previewRecord(); + void executeExtendedDelete(); + + void executeExtendedRevert(); + public slots: + void settingChanged (const CSMPrefs::Setting *setting); + void tableSizeUpdate(); void selectionSizeUpdate(); @@ -140,9 +155,7 @@ namespace CSVWorld void recordFilterChanged (boost::shared_ptr filter); - void updateUserSetting (const QString &name, const QStringList &list); - - void rowsInsertedEvent(const QModelIndex& parent, int start, int end); + void rowAdded(const std::string &id); }; } diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index dc3a6cc764..eed522227e 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -1,4 +1,3 @@ - #include "tablebottombox.hpp" #include @@ -6,9 +5,25 @@ #include #include #include +#include +#include #include "creator.hpp" +void CSVWorld::TableBottomBox::updateSize() +{ + // Make sure that the size of the bottom box is determined by the currently visible widget + for (int i = 0; i < mLayout->count(); ++i) + { + QSizePolicy::Policy verPolicy = QSizePolicy::Ignored; + if (mLayout->widget(i) == mLayout->currentWidget()) + { + verPolicy = QSizePolicy::Expanding; + } + mLayout->widget(i)->setSizePolicy(QSizePolicy::Expanding, verPolicy); + } +} + void CSVWorld::TableBottomBox::updateStatus() { if (mShowStatusBar) @@ -35,15 +50,33 @@ void CSVWorld::TableBottomBox::updateStatus() } } + if (mHasPosition) + { + if (!first) + stream << " -- "; + + stream << "(" << mRow << ", " << mColumn << ")"; + } + mStatus->setText (QString::fromUtf8 (stream.str().c_str())); } } +void CSVWorld::TableBottomBox::extendedConfigRequest(CSVWorld::ExtendedCommandConfigurator::Mode mode, + const std::vector &selectedIds) +{ + mExtendedConfigurator->configure (mode, selectedIds); + mLayout->setCurrentWidget (mExtendedConfigurator); + mEditMode = EditMode_ExtendedConfig; + setVisible (true); + mExtendedConfigurator->setFocus(); +} + CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFactory, CSMDoc::Document& document, const CSMWorld::UniversalId& id, QWidget *parent) -: QWidget (parent), mShowStatusBar (false), mCreating (false) +: QWidget (parent), mShowStatusBar (false), mEditMode(EditMode_None), mHasPosition(false) { for (int i=0; i<4; ++i) mStatusCount[i] = 0; @@ -52,6 +85,7 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto mLayout = new QStackedLayout; mLayout->setContentsMargins (0, 0, 0, 0); + connect (mLayout, SIGNAL (currentChanged (int)), this, SLOT (currentWidgetChanged (int))); mStatus = new QLabel; @@ -67,19 +101,28 @@ CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFacto if (mCreator) { + mCreator->installEventFilter(this); mLayout->addWidget (mCreator); - connect (mCreator, SIGNAL (done()), this, SLOT (createRequestDone())); + connect (mCreator, SIGNAL (done()), this, SLOT (requestDone())); connect (mCreator, SIGNAL (requestFocus (const std::string&)), this, SIGNAL (requestFocus (const std::string&))); } + + mExtendedConfigurator = new ExtendedCommandConfigurator (document, id, this); + mExtendedConfigurator->installEventFilter(this); + mLayout->addWidget (mExtendedConfigurator); + connect (mExtendedConfigurator, SIGNAL (done()), this, SLOT (requestDone())); + + updateSize(); } void CSVWorld::TableBottomBox::setEditLock (bool locked) { if (mCreator) mCreator->setEditLock (locked); + mExtendedConfigurator->setEditLock (locked); } CSVWorld::TableBottomBox::~TableBottomBox() @@ -87,11 +130,25 @@ CSVWorld::TableBottomBox::~TableBottomBox() delete mCreator; } +bool CSVWorld::TableBottomBox::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) + { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) + { + requestDone(); + return true; + } + } + return QWidget::eventFilter(object, event); +} + void CSVWorld::TableBottomBox::setStatusBar (bool show) { if (show!=mShowStatusBar) { - setVisible (show || mCreating); + setVisible (show || (mEditMode != EditMode_None)); mShowStatusBar = show; @@ -105,7 +162,7 @@ bool CSVWorld::TableBottomBox::canCreateAndDelete() const return mCreator; } -void CSVWorld::TableBottomBox::createRequestDone() +void CSVWorld::TableBottomBox::requestDone() { if (!mShowStatusBar) setVisible (false); @@ -113,8 +170,12 @@ void CSVWorld::TableBottomBox::createRequestDone() updateStatus(); mLayout->setCurrentWidget (mStatusBar); + mEditMode = EditMode_None; +} - mCreating = false; +void CSVWorld::TableBottomBox::currentWidgetChanged(int /*index*/) +{ + updateSize(); } void CSVWorld::TableBottomBox::selectionSizeChanged (int size) @@ -152,24 +213,48 @@ void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modi updateStatus(); } +void CSVWorld::TableBottomBox::positionChanged (int row, int column) +{ + mRow = row; + mColumn = column; + mHasPosition = true; + updateStatus(); +} + +void CSVWorld::TableBottomBox::noMorePosition() +{ + mHasPosition = false; + updateStatus(); +} + void CSVWorld::TableBottomBox::createRequest() { mCreator->reset(); mCreator->toggleWidgets(true); mLayout->setCurrentWidget (mCreator); setVisible (true); - mCreating = true; + mEditMode = EditMode_Creation; mCreator->focus(); } -void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, - const CSMWorld::UniversalId::Type type) +void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, + const CSMWorld::UniversalId::Type type) { mCreator->reset(); mCreator->cloneMode(id, type); mLayout->setCurrentWidget(mCreator); mCreator->toggleWidgets(false); setVisible (true); - mCreating = true; + mEditMode = EditMode_Creation; mCreator->focus(); } + +void CSVWorld::TableBottomBox::extendedDeleteConfigRequest(const std::vector &selectedIds) +{ + extendedConfigRequest(ExtendedCommandConfigurator::Mode_Delete, selectedIds); +} + +void CSVWorld::TableBottomBox::extendedRevertConfigRequest(const std::vector &selectedIds) +{ + extendedConfigRequest(ExtendedCommandConfigurator::Mode_Revert, selectedIds); +} diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index a7d009c42d..781cccc9eb 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -4,10 +4,11 @@ #include #include +#include "extendedcommandconfigurator.hpp" + class QLabel; class QStackedLayout; class QStatusBar; -class QUndoStack; namespace CSMDoc { @@ -23,13 +24,21 @@ namespace CSVWorld { Q_OBJECT + enum EditMode { EditMode_None, EditMode_Creation, EditMode_ExtendedConfig }; + bool mShowStatusBar; QLabel *mStatus; QStatusBar *mStatusBar; int mStatusCount[4]; + + EditMode mEditMode; Creator *mCreator; - bool mCreating; + ExtendedCommandConfigurator *mExtendedConfigurator; + QStackedLayout *mLayout; + bool mHasPosition; + int mRow; + int mColumn; private: @@ -37,17 +46,24 @@ namespace CSVWorld TableBottomBox (const TableBottomBox&); TableBottomBox& operator= (const TableBottomBox&); + void updateSize(); + void updateStatus(); + void extendedConfigRequest(ExtendedCommandConfigurator::Mode mode, + const std::vector &selectedIds); + public: - TableBottomBox (const CreatorFactoryBase& creatorFactory, - CSMDoc::Document& document, - const CSMWorld::UniversalId& id, + TableBottomBox (const CreatorFactoryBase& creatorFactory, + CSMDoc::Document& document, + const CSMWorld::UniversalId& id, QWidget *parent = 0); virtual ~TableBottomBox(); + virtual bool eventFilter(QObject *object, QEvent *event); + void setEditLock (bool locked); void setStatusBar (bool show); @@ -65,9 +81,11 @@ namespace CSVWorld private slots: - void createRequestDone(); + void requestDone(); ///< \note This slot being called does not imply success. + void currentWidgetChanged(int index); + public slots: void selectionSizeChanged (int size); @@ -77,9 +95,16 @@ namespace CSVWorld /// \param deleted Number of deleted records /// \param modified Number of added and modified records + void positionChanged (int row, int column); + + void noMorePosition(); + void createRequest(); void cloneRequest(const std::string& id, const CSMWorld::UniversalId::Type type); + + void extendedDeleteConfigRequest(const std::vector &selectedIds); + void extendedRevertConfigRequest(const std::vector &selectedIds); }; } diff --git a/apps/opencs/view/world/tableeditidaction.cpp b/apps/opencs/view/world/tableeditidaction.cpp new file mode 100644 index 0000000000..4dfc537cc8 --- /dev/null +++ b/apps/opencs/view/world/tableeditidaction.cpp @@ -0,0 +1,49 @@ +#include "tableeditidaction.hpp" + +#include + +#include "../../model/world/tablemimedata.hpp" + +CSVWorld::TableEditIdAction::CellData CSVWorld::TableEditIdAction::getCellData(int row, int column) const +{ + QModelIndex index = mTable.model()->index(row, column); + if (index.isValid()) + { + QVariant display = mTable.model()->data(index, CSMWorld::ColumnBase::Role_Display); + QString value = mTable.model()->data(index).toString(); + return std::make_pair(static_cast(display.toInt()), value); + } + return std::make_pair(CSMWorld::ColumnBase::Display_None, ""); +} + +CSVWorld::TableEditIdAction::TableEditIdAction(const QTableView &table, QWidget *parent) + : QAction(parent), + mTable(table), + mCurrentId(CSMWorld::UniversalId::Type_None) +{} + +void CSVWorld::TableEditIdAction::setCell(int row, int column) +{ + CellData data = getCellData(row, column); + CSMWorld::UniversalId::Type idType = CSMWorld::TableMimeData::convertEnums(data.first); + + if (idType != CSMWorld::UniversalId::Type_None) + { + mCurrentId = CSMWorld::UniversalId(idType, data.second.toUtf8().constData()); + setText("Edit '" + data.second + "'"); + } +} + +CSMWorld::UniversalId CSVWorld::TableEditIdAction::getCurrentId() const +{ + return mCurrentId; +} + +bool CSVWorld::TableEditIdAction::isValidIdCell(int row, int column) const +{ + CellData data = getCellData(row, column); + CSMWorld::UniversalId::Type idType = CSMWorld::TableMimeData::convertEnums(data.first); + return CSMWorld::ColumnBase::isId(data.first) && + idType != CSMWorld::UniversalId::Type_None && + !data.second.isEmpty(); +} diff --git a/apps/opencs/view/world/tableeditidaction.hpp b/apps/opencs/view/world/tableeditidaction.hpp new file mode 100644 index 0000000000..f2cf0b7bd0 --- /dev/null +++ b/apps/opencs/view/world/tableeditidaction.hpp @@ -0,0 +1,31 @@ +#ifndef CSVWORLD_TABLEEDITIDACTION_HPP +#define CSVWORLD_TABLEEDITIDACTION_HPP + +#include + +#include "../../model/world/columnbase.hpp" +#include "../../model/world/universalid.hpp" + +class QTableView; + +namespace CSVWorld +{ + class TableEditIdAction : public QAction + { + const QTableView &mTable; + CSMWorld::UniversalId mCurrentId; + + typedef std::pair CellData; + CellData getCellData(int row, int column) const; + + public: + TableEditIdAction(const QTableView &table, QWidget *parent = 0); + + void setCell(int row, int column); + + CSMWorld::UniversalId getCurrentId() const; + bool isValidIdCell(int row, int column) const; + }; +} + +#endif diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 75671a50c4..1b25e1c086 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -1,4 +1,3 @@ - #include "tablesubview.hpp" #include @@ -23,8 +22,6 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D { QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - layout->addWidget (mBottom = new TableBottomBox (creatorFactory, document, id, this), 0); @@ -71,6 +68,11 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D connect (this, SIGNAL(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)), mBottom, SLOT(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type))); + + connect (mTable, SIGNAL(extendedDeleteConfigRequest(const std::vector &)), + mBottom, SLOT(extendedDeleteConfigRequest(const std::vector &))); + connect (mTable, SIGNAL(extendedRevertConfigRequest(const std::vector &)), + mBottom, SLOT(extendedRevertConfigRequest(const std::vector &))); } connect (mBottom, SIGNAL (requestFocus (const std::string&)), mTable, SLOT (requestFocus (const std::string&))); @@ -96,12 +98,6 @@ void CSVWorld::TableSubView::editRequest (const CSMWorld::UniversalId& id, const focusId (id, hint); } -void CSVWorld::TableSubView::updateUserSetting - (const QString &name, const QStringList &list) -{ - mTable->updateUserSetting(name, list); -} - void CSVWorld::TableSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); @@ -166,4 +162,3 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) } return false; } - diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index 9d86c32e40..7d143d927c 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -43,9 +43,6 @@ namespace CSVWorld virtual void setEditLock (bool locked); - virtual void updateUserSetting - (const QString& name, const QStringList &list); - virtual void setStatusBar (bool show); virtual void useHint (const std::string& hint); diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index 5974987c0c..b0431f71a0 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -1,4 +1,3 @@ - #include "util.hpp" #include @@ -13,11 +12,15 @@ #include #include #include +#include #include "../../model/world/commands.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/commanddispatcher.hpp" + #include "../widget/coloreditor.hpp" +#include "../widget/droplineedit.hpp" + #include "dialoguespinbox.hpp" #include "scriptedit.hpp" @@ -163,14 +166,14 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO const QModelIndex& index) const { CSMWorld::ColumnBase::Display display = getDisplayTypeFromIndex(index); - + // This createEditor() method is called implicitly from tables. // For boolean values in tables use the default editor (combobox). // Checkboxes is looking ugly in the table view. // TODO: Find a better solution? if (display == CSMWorld::ColumnBase::Display_Boolean) { - return QStyledItemDelegate::createEditor(parent, option, index); + return QItemEditorFactory::defaultFactory()->createEditor(QVariant::Bool, parent); } // For tables the pop-up of the color editor should appear immediately after the editor creation // (the third parameter of ColorEditor's constructor) @@ -229,33 +232,35 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO return edit; } + case CSMWorld::ColumnBase::Display_LongString256: + { + /// \todo implement size limit. QPlainTextEdit does not support a size limit. + QPlainTextEdit *edit = new QPlainTextEdit(parent); + edit->setUndoRedoEnabled (false); + return edit; + } + case CSMWorld::ColumnBase::Display_Boolean: return new QCheckBox(parent); - case CSMWorld::ColumnBase::Display_String: - case CSMWorld::ColumnBase::Display_Skill: - case CSMWorld::ColumnBase::Display_Script: - case CSMWorld::ColumnBase::Display_Race: - case CSMWorld::ColumnBase::Display_Region: - case CSMWorld::ColumnBase::Display_Class: - case CSMWorld::ColumnBase::Display_Faction: - case CSMWorld::ColumnBase::Display_Miscellaneous: - case CSMWorld::ColumnBase::Display_Sound: - case CSMWorld::ColumnBase::Display_Mesh: - case CSMWorld::ColumnBase::Display_Icon: - case CSMWorld::ColumnBase::Display_Music: - case CSMWorld::ColumnBase::Display_SoundRes: - case CSMWorld::ColumnBase::Display_Texture: - case CSMWorld::ColumnBase::Display_Video: - case CSMWorld::ColumnBase::Display_GlobalVariable: - - return new DropLineEdit(parent); - case CSMWorld::ColumnBase::Display_ScriptLines: return new ScriptEdit (mDocument, ScriptHighlighter::Mode_Console, parent); + case CSMWorld::ColumnBase::Display_String: + // For other Display types (that represent record IDs) with drop support IdCompletionDelegate is used + + return new CSVWidget::DropLineEdit(display, parent); + + case CSMWorld::ColumnBase::Display_String32: + { + // For other Display types (that represent record IDs) with drop support IdCompletionDelegate is used + CSVWidget::DropLineEdit *widget = new CSVWidget::DropLineEdit(display, parent); + widget->setMaxLength (32); + return widget; + } + default: return QStyledItemDelegate::createEditor (parent, option, index); @@ -325,28 +330,4 @@ void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelInde } -CSVWorld::DropLineEdit::DropLineEdit(QWidget* parent) : -QLineEdit(parent) -{ - setAcceptDrops(true); -} - -void CSVWorld::DropLineEdit::dragEnterEvent(QDragEnterEvent *event) -{ - event->acceptProposedAction(); -} - -void CSVWorld::DropLineEdit::dragMoveEvent(QDragMoveEvent *event) -{ - event->accept(); -} - -void CSVWorld::DropLineEdit::dropEvent(QDropEvent *event) -{ - const CSMWorld::TableMimeData* data(dynamic_cast(event->mimeData())); - if (!data) // May happen when non-records (e.g. plain text) are dragged and dropped - return; - - emit tableMimeDataDropped(data->getData(), data->getDocumentPtr()); - //WIP -} +void CSVWorld::CommandDelegate::settingChanged (const CSMPrefs::Setting *setting) {} diff --git a/apps/opencs/view/world/util.hpp b/apps/opencs/view/world/util.hpp index 8355e971cb..766d729588 100644 --- a/apps/opencs/view/world/util.hpp +++ b/apps/opencs/view/world/util.hpp @@ -5,7 +5,6 @@ #include #include -#include #include "../../model/world/columnbase.hpp" #include "../../model/doc/document.hpp" @@ -19,6 +18,11 @@ namespace CSMWorld class CommandDispatcher; } +namespace CSMPrefs +{ + class Setting; +} + namespace CSVWorld { ///< \brief Getting the data out of an editor widget @@ -91,24 +95,6 @@ namespace CSVWorld }; - class DropLineEdit : public QLineEdit - { - Q_OBJECT - - public: - DropLineEdit(QWidget *parent); - - private: - void dragEnterEvent(QDragEnterEvent *event); - - void dragMoveEvent(QDragMoveEvent *event); - - void dropEvent(QDropEvent *event); - - signals: - void tableMimeDataDropped(const std::vector& data, const CSMDoc::Document* document); - }; - ///< \brief Use commands instead of manipulating the model directly class CommandDelegate : public QStyledItemDelegate { @@ -157,10 +143,9 @@ namespace CSVWorld virtual void setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay) const; - public slots: - - virtual void updateUserSetting - (const QString &name, const QStringList &list) {} + /// \attention This is not a slot. For ordering reasons this function needs to be + /// called manually from the parent object's settingChanged function. + virtual void settingChanged (const CSMPrefs::Setting *setting); }; } diff --git a/apps/opencs/view/world/vartypedelegate.cpp b/apps/opencs/view/world/vartypedelegate.cpp index 90a686a67e..a63e6028c2 100644 --- a/apps/opencs/view/world/vartypedelegate.cpp +++ b/apps/opencs/view/world/vartypedelegate.cpp @@ -1,4 +1,3 @@ - #include "vartypedelegate.hpp" #include diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index c7c701d20e..0113fed022 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -20,10 +20,10 @@ set(GAME_HEADER source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender - renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation - actors objects renderinginterface localmap occlusionquery water shadows - characterpreview globalmap ripplesimulation refraction - terrainstorage renderconst effectmanager weaponanimation + actors objects renderingmanager animation rotatecontroller sky npcanimation vismask + creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation + bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation + renderbin ) add_openmw_dir (mwinput @@ -31,7 +31,7 @@ add_openmw_dir (mwinput ) add_openmw_dir (mwgui - textinput widgets race class birth review windowmanagerimp console dialogue + layout textinput widgets race class birth review windowmanagerimp console dialogue windowbase statswindow messagebox journalwindow charactercreation mapwindow windowpinnablebase tooltips scrollwindow bookwindow formatting inventorywindow container hud countdialog tradewindow settingswindow @@ -57,28 +57,33 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwsound - soundmanagerimp openal_output ffmpeg_decoder sound sound_decoder sound_output loudness libavwrapper movieaudiofactory + soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output loudness movieaudiofactory ) add_openmw_dir (mwworld - refdata worldimp physicssystem scene globals class action nullaction actionteleport - containerstore actiontalk actiontake manualref player cellfunctors failedaction - cells localscripts customdata weather inventorystore ptr actionopen actionread + refdata worldimp scene globals class action nullaction actionteleport + containerstore actiontalk actiontake manualref player cellvisitors failedaction + cells localscripts customdata inventorystore ptr actionopen actionread actionequip timestamp actionalchemy cellstore actionapply actioneat - esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor - contentloader esmloader actiontrap cellreflist projectilemanager cellref + store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor + contentloader esmloader actiontrap cellreflist cellref physicssystem weather projectilemanager + ) + +add_openmw_dir (mwphysics + physicssystem trace collisiontype actor convert ) add_openmw_dir (mwclass classes activator creature npc weapon armor potion apparatus book clothing container door - ingredient creaturelevlist itemlevlist light lockpick misc probe repair static + ingredient creaturelevlist itemlevlist light lockpick misc probe repair static actor ) add_openmw_dir (mwmechanics - mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects + mechanicsmanagerimp stat creaturestats magiceffects movement actorutil drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning + character actors objects aistate coordinateconverter ) add_openmw_dir (mwstate @@ -108,44 +113,54 @@ endif () # Sound stuff - here so CMake doesn't stupidly recompile EVERYTHING # when we change the backend. -include_directories(${SOUND_INPUT_INCLUDES}) +include_directories( + ${FFMPEG_INCLUDE_DIRS} +) target_link_libraries(openmw - ${OENGINE_LIBRARY} - ${OGRE_LIBRARIES} - ${OGRE_STATIC_PLUGINS} - ${SHINY_LIBRARIES} - ${OPENAL_LIBRARY} - ${SOUND_INPUT_LIBRARY} - ${BULLET_LIBRARIES} + ${OSG_LIBRARIES} + ${OPENTHREADS_LIBRARIES} + ${OSGPARTICLE_LIBRARIES} + ${OSGUTIL_LIBRARIES} + ${OSGDB_LIBRARIES} + ${OSGVIEWER_LIBRARIES} + ${OSGGA_LIBRARIES} ${Boost_SYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${Boost_WAVE_LIBRARY} + ${OPENAL_LIBRARY} + ${FFMPEG_LIBRARIES} + ${BULLET_LIBRARIES} ${MYGUI_LIBRARIES} ${SDL2_LIBRARY} - ${MYGUI_PLATFORM_LIBRARIES} - "ogre-ffmpeg-videoplayer" + "osg-ffmpeg-videoplayer" "oics" - "sdl4ogre" components ) if (ANDROID) + set (OSG_PLUGINS + -Wl,--whole-archive + ${OSG_PLUGINS_DIR}/libosgdb_dds.a + ${OSG_PLUGINS_DIR}/libosgdb_bmp.a + ${OSG_PLUGINS_DIR}/libosgdb_tga.a + ${OSG_PLUGINS_DIR}/libosgdb_gif.a + ${OSG_PLUGINS_DIR}/libosgdb_jpeg.a + ${OSG_PLUGINS_DIR}/libosgdb_png.a + -Wl,--no-whole-archive + ) target_link_libraries(openmw - ${OGRE_STATIC_PLUGINS} EGL android log dl - MyGUI.OgrePlatform - MyGUIEngineStatic - Plugin_StrangeButtonStatic - cpufeatures - BulletCollision - BulletDynamics - LinearMath + z + ${OPENSCENEGRAPH_LIBRARIES} + ${OSG_PLUGINS} + jpeg + gif + png ) endif (ANDROID) @@ -184,4 +199,5 @@ if (MSVC) if (CMAKE_CL_64) set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") endif (CMAKE_CL_64) + add_definitions("-D_USE_MATH_DEFINES") endif (MSVC) diff --git a/apps/openmw/android_commandLine.cpp b/apps/openmw/android_commandLine.cpp index ebfff28ca2..7e7f368e05 100644 --- a/apps/openmw/android_commandLine.cpp +++ b/apps/openmw/android_commandLine.cpp @@ -7,21 +7,21 @@ int argcData; extern "C" void releaseArgv(); void releaseArgv() { - delete[] argvData; + delete[] argvData; } JNIEXPORT void JNICALL Java_ui_activity_GameActivity_commandLine(JNIEnv *env, - jobject obj, jint argc, jobjectArray stringArray) { - jboolean iscopy; - argcData = (int) argc; - argvData = new const char *[argcData + 1]; - argvData[0] = "openmw"; - for (int i = 1; i < argcData + 1; i++) { - jstring string = (jstring) (env)->GetObjectArrayElement(stringArray, - i - 1); - argvData[i] = (env)->GetStringUTFChars(string, &iscopy); - (env)->DeleteLocalRef(string); - } - (env)->DeleteLocalRef(stringArray); + jobject obj, jint argc, jobjectArray stringArray) { + jboolean iscopy; + argcData = (int) argc; + argvData = new const char *[argcData + 1]; + argvData[0] = "openmw"; + for (int i = 1; i < argcData + 1; i++) { + jstring string = (jstring) (env)->GetObjectArrayElement(stringArray, + i - 1); + argvData[i] = (env)->GetStringUTFChars(string, &iscopy); + (env)->DeleteLocalRef(string); + } + (env)->DeleteLocalRef(stringArray); } diff --git a/apps/openmw/android_commandLine.h b/apps/openmw/android_commandLine.h index 21d1064c6c..5ca79c2d0f 100644 --- a/apps/openmw/android_commandLine.h +++ b/apps/openmw/android_commandLine.h @@ -1,5 +1,4 @@ - /* DO NOT EDIT THIS FILE - it is machine generated */ #include #ifndef _Included_ui_activity_GameActivity_commandLine diff --git a/apps/openmw/android_main.c b/apps/openmw/android_main.c index 1b28395199..8cd69e8f01 100644 --- a/apps/openmw/android_main.c +++ b/apps/openmw/android_main.c @@ -1,4 +1,3 @@ -#include "../../SDL_internal.h" #ifdef __ANDROID__ #include "SDL_main.h" @@ -16,22 +15,22 @@ extern const char **argvData; void releaseArgv(); int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, - jobject obj) { + jobject obj) { - SDL_Android_Init(env, cls); + SDL_Android_Init(env, cls); - SDL_SetMainReady(); + SDL_SetMainReady(); - /* Run the application code! */ + /* Run the application code! */ - int status; + int status; - status = main(argcData+1, argvData); - releaseArgv(); - /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */ - /* exit(status); */ + status = main(argcData+1, argvData); + releaseArgv(); + /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */ + /* exit(status); */ - return status; + return status; } #endif /* __ANDROID__ */ diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index 8f25d041cb..0b4ff6304f 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -37,8 +37,8 @@ static const char pipe_err[] = "!!! Failed to create pipe\n"; static const char fork_err[] = "!!! Failed to fork debug process\n"; static const char exec_err[] = "!!! Failed to exec debug process\n"; -#ifndef PATH_MAX /* Not all platforms (GNU Hurd) have this. */ -# define PATH_MAX 256 +#ifndef PATH_MAX /* Not all platforms (GNU Hurd) have this. */ +# define PATH_MAX 256 #endif static char argv0[PATH_MAX]; @@ -387,11 +387,17 @@ static void crash_handler(const char *logfile) kill(crash_info.pid, SIGKILL); } + // delay between killing of the crashed process and showing the message box to + // work around occasional X server lock-up. this can only be a bug in X11 since + // even faulty applications shouldn't be able to freeze the X server. + usleep(100000); + if(logfile) { std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !"; SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), NULL); } + exit(0); } diff --git a/apps/openmw/doc.hpp b/apps/openmw/doc.hpp index 978f0f5fb2..ffeef94e1e 100644 --- a/apps/openmw/doc.hpp +++ b/apps/openmw/doc.hpp @@ -25,7 +25,7 @@ /// \namespace MWRender /// \ingroup openmw -/// \brief Rendering via Ogre +/// \brief Rendering /// \namespace MWWorld /// \ingroup openmw diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 4496490d42..b43fd2f530 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -3,26 +3,31 @@ #include #include -#include -#include +#include -#include +#include +#include +#include #include -#include +#include + +#include +#include + +#include +#include + +#include +#include #include -#include #include #include -#include -#include -#include - -#include +#include #include "mwinput/inputmanagerimp.hpp" @@ -38,6 +43,8 @@ #include "mwworld/player.hpp" #include "mwworld/worldimp.hpp" +#include "mwrender/vismask.hpp" + #include "mwclass/classes.hpp" #include "mwdialogue/dialoguemanagerimp.hpp" @@ -48,9 +55,18 @@ #include "mwstate/statemanagerimp.hpp" +namespace +{ + void checkSDLError(int ret) + { + if (ret != 0) + std::cerr << "SDL error: " << SDL_GetError() << std::endl; + } +} + void OMW::Engine::executeLocalScripts() { - MWWorld::LocalScripts& localScripts = MWBase::Environment::get().getWorld()->getLocalScripts(); + MWWorld::LocalScripts& localScripts = mEnvironment.getWorld()->getLocalScripts(); localScripts.startIteration(); @@ -60,123 +76,126 @@ void OMW::Engine::executeLocalScripts() MWScript::InterpreterContext interpreterContext ( &script.second.getRefData().getLocals(), script.second); - MWBase::Environment::get().getScriptManager()->run (script.first, interpreterContext); + mEnvironment.getScriptManager()->run (script.first, interpreterContext); } localScripts.setIgnore (MWWorld::Ptr()); } -bool OMW::Engine::frameStarted (const Ogre::FrameEvent& evt) -{ - if (MWBase::Environment::get().getStateManager()->getState()!= - MWBase::StateManager::State_NoGame) - { - bool paused = MWBase::Environment::get().getWindowManager()->isGuiMode(); - MWBase::Environment::get().getWorld()->frameStarted(evt.timeSinceLastFrame, paused); - MWBase::Environment::get().getWindowManager ()->frameStarted(evt.timeSinceLastFrame); - } - return true; -} - -bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) +void OMW::Engine::frame(float frametime) { try { - float frametime = evt.timeSinceLastFrame; + mStartTick = mViewer->getStartTick(); mEnvironment.setFrameDuration (frametime); // update input - MWBase::Environment::get().getInputManager()->update(frametime, false); + mEnvironment.getInputManager()->update(frametime, false); - // When the window is minimized, pause everything. Currently this *has* to be here to work around a MyGUI bug. - // If we are not currently rendering, then RenderItems will not be reused resulting in a memory leak upon changing widget textures. - if (!mOgre->getWindow()->isActive() || !mOgre->getWindow()->isVisible()) - return true; + // When the window is minimized, pause the game. Currently this *has* to be here to work around a MyGUI bug. + // If we are not currently rendering, then RenderItems will not be reused resulting in a memory leak upon changing widget textures (fixed in MyGUI 3.3.2), + // and destroyed widgets will not be deleted (not fixed yet, https://github.com/MyGUI/mygui/issues/21) + if (!mEnvironment.getInputManager()->isWindowVisible()) + return; // sound if (mUseSound) - MWBase::Environment::get().getSoundManager()->update(frametime); - - // GUI active? Most game processing will be paused, but scripts still run. - bool guiActive = MWBase::Environment::get().getWindowManager()->isGuiMode(); + mEnvironment.getSoundManager()->update(frametime); // Main menu opened? Then scripts are also paused. - bool paused = MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu); + bool paused = mEnvironment.getWindowManager()->containsMode(MWGui::GM_MainMenu); // update game state - MWBase::Environment::get().getStateManager()->update (frametime); + mEnvironment.getStateManager()->update (frametime); - if (MWBase::Environment::get().getStateManager()->getState()== + bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); + + osg::Timer_t beforeScriptTick = osg::Timer::instance()->tick(); + if (mEnvironment.getStateManager()->getState()== MWBase::StateManager::State_Running) { if (!paused) { - if (MWBase::Environment::get().getWorld()->getScriptsEnabled()) + if (mEnvironment.getWorld()->getScriptsEnabled()) { // local scripts executeLocalScripts(); // global scripts - MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); + mEnvironment.getScriptManager()->getGlobalScripts().run(); } - MWBase::Environment::get().getWorld()->markCellAsUnchanged(); + mEnvironment.getWorld()->markCellAsUnchanged(); } if (!guiActive) - MWBase::Environment::get().getWorld()->advanceTime( - frametime*MWBase::Environment::get().getWorld()->getTimeScaleFactor()/3600); + { + double hours = (frametime * mEnvironment.getWorld()->getTimeScaleFactor()) / 3600.0; + mEnvironment.getWorld()->advanceTime(hours, true); + } } - + osg::Timer_t afterScriptTick = osg::Timer::instance()->tick(); // update actors - if (MWBase::Environment::get().getStateManager()->getState()!= + osg::Timer_t beforeMechanicsTick = osg::Timer::instance()->tick(); + if (mEnvironment.getStateManager()->getState()!= MWBase::StateManager::State_NoGame) { - MWBase::Environment::get().getMechanicsManager()->update(frametime, + mEnvironment.getMechanicsManager()->update(frametime, guiActive); } + osg::Timer_t afterMechanicsTick = osg::Timer::instance()->tick(); - if (MWBase::Environment::get().getStateManager()->getState()== + if (mEnvironment.getStateManager()->getState()== MWBase::StateManager::State_Running) { MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr(); if(!guiActive && player.getClass().getCreatureStats(player).isDead()) - MWBase::Environment::get().getStateManager()->endGame(); + mEnvironment.getStateManager()->endGame(); } // update world - if (MWBase::Environment::get().getStateManager()->getState()!= + osg::Timer_t beforePhysicsTick = osg::Timer::instance()->tick();; + if (mEnvironment.getStateManager()->getState()!= MWBase::StateManager::State_NoGame) { - MWBase::Environment::get().getWorld()->update(frametime, guiActive); + mEnvironment.getWorld()->update(frametime, guiActive); } + osg::Timer_t afterPhysicsTick = osg::Timer::instance()->tick(); // update GUI - MWBase::Environment::get().getWindowManager()->onFrame(frametime); - if (MWBase::Environment::get().getStateManager()->getState()!= + mEnvironment.getWindowManager()->onFrame(frametime); + if (mEnvironment.getStateManager()->getState()!= MWBase::StateManager::State_NoGame) { - Ogre::RenderWindow* window = mOgre->getWindow(); - unsigned int tri, batch; - MWBase::Environment::get().getWorld()->getTriangleBatchCount(tri, batch); - MWBase::Environment::get().getWindowManager()->wmUpdateFps(window->getLastFPS(), tri, batch); - - MWBase::Environment::get().getWindowManager()->update(); + mEnvironment.getWindowManager()->update(); } + + int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); + osg::Stats* stats = mViewer->getViewerStats(); + stats->setAttribute(frameNumber, "script_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforeScriptTick)); + stats->setAttribute(frameNumber, "script_time_taken", osg::Timer::instance()->delta_s(beforeScriptTick, afterScriptTick)); + stats->setAttribute(frameNumber, "script_time_end", osg::Timer::instance()->delta_s(mStartTick, afterScriptTick)); + + stats->setAttribute(frameNumber, "mechanics_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforeMechanicsTick)); + stats->setAttribute(frameNumber, "mechanics_time_taken", osg::Timer::instance()->delta_s(beforeMechanicsTick, afterMechanicsTick)); + stats->setAttribute(frameNumber, "mechanics_time_end", osg::Timer::instance()->delta_s(mStartTick, afterMechanicsTick)); + + stats->setAttribute(frameNumber, "physics_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforePhysicsTick)); + stats->setAttribute(frameNumber, "physics_time_taken", osg::Timer::instance()->delta_s(beforePhysicsTick, afterPhysicsTick)); + stats->setAttribute(frameNumber, "physics_time_end", osg::Timer::instance()->delta_s(mStartTick, afterPhysicsTick)); + } catch (const std::exception& e) { std::cerr << "Error in framelistener: " << e.what() << std::endl; } - - return true; } OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) - : mEncoding(ToUTF8::WINDOWS_1252) + : mWindow(NULL) + , mEncoding(ToUTF8::WINDOWS_1252) , mEncoder(NULL) - , mOgre (0) , mVerboseScripts (false) , mSkipMenu (false) , mUseSound (true) @@ -193,8 +212,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mNewGame (false) , mCfgMgr(configurationManager) { - OEngine::Misc::Rng::init(); - std::srand ( static_cast(std::time(NULL)) ); + Misc::Rng::init(); MWClass::registerClasses(); Uint32 flags = SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE|SDL_INIT_GAMECONTROLLER|SDL_INIT_JOYSTICK; @@ -206,33 +224,30 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) throw std::runtime_error("Could not initialize SDL! " + std::string(SDL_GetError())); } } + + mStartTick = osg::Timer::instance()->tick(); } OMW::Engine::~Engine() { - if (mOgre) - mOgre->restoreWindowGammaRamp(); mEnvironment.cleanup(); + delete mScriptContext; - delete mOgre; + mScriptContext = NULL; + + mResourceSystem.reset(); + + mViewer = NULL; + + if (mWindow) + { + SDL_DestroyWindow(mWindow); + mWindow = NULL; + } + SDL_Quit(); } -// add resources directory -// \note This function works recursively. - -void OMW::Engine::addResourcesDirectory (const boost::filesystem::path& path) -{ - mOgre->getRoot()->addResourceLocation (path.string(), "FileSystem", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true); -} - -void OMW::Engine::addZipResource (const boost::filesystem::path& path) -{ - mOgre->getRoot()->addResourceLocation (path.string(), "Zip", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, false); -} - void OMW::Engine::enableFSStrict(bool fsStrict) { mFSStrict = fsStrict; @@ -283,8 +298,8 @@ void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) std::string OMW::Engine::loadSettings (Settings::Manager & settings) { // Create the settings manager and load default settings file - const std::string localdefault = mCfgMgr.getLocalPath().string() + "/settings-default.cfg"; - const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/settings-default.cfg"; + const std::string localdefault = (mCfgMgr.getLocalPath() / "settings-default.cfg").string(); + const std::string globaldefault = (mCfgMgr.getGlobalPath() / "settings-default.cfg").string(); // prefer local if (boost::filesystem::exists(localdefault)) @@ -295,75 +310,154 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) throw std::runtime_error ("No default settings file found! Make sure the file \"settings-default.cfg\" was properly installed."); // load user settings if they exist - const std::string settingspath = mCfgMgr.getUserConfigPath().string() + "/settings.cfg"; + const std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); if (boost::filesystem::exists(settingspath)) settings.loadUser(settingspath); - // load nif overrides - NifOverrides::Overrides nifOverrides; - std::string transparencyOverrides = "/transparency-overrides.cfg"; - std::string materialOverrides = "/material-overrides.cfg"; - if (boost::filesystem::exists(mCfgMgr.getLocalPath().string() + transparencyOverrides)) - nifOverrides.loadTransparencyOverrides(mCfgMgr.getLocalPath().string() + transparencyOverrides); - else if (boost::filesystem::exists(mCfgMgr.getGlobalPath().string() + transparencyOverrides)) - nifOverrides.loadTransparencyOverrides(mCfgMgr.getGlobalPath().string() + transparencyOverrides); - if (boost::filesystem::exists(mCfgMgr.getLocalPath().string() + materialOverrides)) - nifOverrides.loadMaterialOverrides(mCfgMgr.getLocalPath().string() + materialOverrides); - else if (boost::filesystem::exists(mCfgMgr.getGlobalPath().string() + materialOverrides)) - nifOverrides.loadMaterialOverrides(mCfgMgr.getGlobalPath().string() + materialOverrides); - return settingspath; } +void OMW::Engine::createWindow(Settings::Manager& settings) +{ + int screen = settings.getInt("screen", "Video"); + int width = settings.getInt("resolution x", "Video"); + int height = settings.getInt("resolution y", "Video"); + bool fullscreen = settings.getBool("fullscreen", "Video"); + bool windowBorder = settings.getBool("window border", "Video"); + bool vsync = settings.getBool("vsync", "Video"); + int antialiasing = settings.getInt("antialiasing", "Video"); + + int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen), + pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen); + + if(fullscreen) + { + pos_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen); + pos_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen); + } + + Uint32 flags = SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE; + if(fullscreen) + flags |= SDL_WINDOW_FULLSCREEN; + + if (!windowBorder) + flags |= SDL_WINDOW_BORDERLESS; + + SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, + settings.getBool("minimize on focus loss", "Video") ? "1" : "0"); + + checkSDLError(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8)); + checkSDLError(SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8)); + checkSDLError(SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8)); + checkSDLError(SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0)); + checkSDLError(SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24)); + + if (antialiasing > 0) + { + checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1)); + checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); + } + + while (!mWindow) + { + mWindow = SDL_CreateWindow("OpenMW", pos_x, pos_y, width, height, flags); + if (!mWindow) + { + // Try with a lower AA + if (antialiasing > 0) + { + std::cout << "Note: " << antialiasing << "x antialiasing not supported, trying " << antialiasing/2 << std::endl; + antialiasing /= 2; + Settings::Manager::setInt("antialiasing", "Video", antialiasing); + checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); + continue; + } + else + { + std::stringstream error; + error << "Failed to create SDL window: " << SDL_GetError() << std::endl; + throw std::runtime_error(error.str()); + } + } + } + + setWindowIcon(); + + osg::ref_ptr traits = new osg::GraphicsContext::Traits; + SDL_GetWindowPosition(mWindow, &traits->x, &traits->y); + SDL_GetWindowSize(mWindow, &traits->width, &traits->height); + traits->windowName = SDL_GetWindowTitle(mWindow); + traits->windowDecoration = !(SDL_GetWindowFlags(mWindow)&SDL_WINDOW_BORDERLESS); + traits->screenNum = SDL_GetWindowDisplayIndex(mWindow); + // FIXME: Some way to get these settings back from the SDL window? + traits->red = 8; + traits->green = 8; + traits->blue = 8; + traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel + traits->depth = 24; + traits->stencil = 8; + traits->vsync = vsync; + traits->doubleBuffer = true; + traits->inheritedWindowData = new SDLUtil::GraphicsWindowSDL2::WindowData(mWindow); + + osg::ref_ptr graphicsWindow = new SDLUtil::GraphicsWindowSDL2(traits); + if(!graphicsWindow->valid()) throw std::runtime_error("Failed to create GraphicsContext"); + + osg::ref_ptr camera = mViewer->getCamera(); + camera->setGraphicsContext(graphicsWindow); + camera->setViewport(0, 0, width, height); + + mViewer->realize(); +} + +void OMW::Engine::setWindowIcon() +{ + boost::filesystem::ifstream windowIconStream; + std::string windowIcon = (mResDir / "mygui" / "openmw.png").string(); + windowIconStream.open(windowIcon, std::ios_base::in | std::ios_base::binary); + if (windowIconStream.fail()) + std::cerr << "Failed to open " << windowIcon << std::endl; + osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png"); + if (!reader) + { + std::cerr << "Failed to read window icon, no png readerwriter found" << std::endl; + return; + } + osgDB::ReaderWriter::ReadResult result = reader->readImage(windowIconStream); + if (!result.success()) + std::cerr << "Failed to read " << windowIcon << ": " << result.message() << " code " << result.status() << std::endl; + else + { + osg::ref_ptr image = result.getImage(); + SDL_Surface* surface = SDLUtil::imageToSurface(image, true); + SDL_SetWindowIcon(mWindow, surface); + SDL_FreeSurface(surface); + } +} + void OMW::Engine::prepareEngine (Settings::Manager & settings) { mEnvironment.setStateManager ( new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0))); - std::string renderSystem = settings.getString("render system", "Video"); - if (renderSystem == "") - { -#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 - renderSystem = "Direct3D9 Rendering Subsystem"; -#else - renderSystem = "OpenGL Rendering Subsystem"; -#endif - } + createWindow(settings); - mOgre = new OEngine::Render::OgreRenderer; + osg::ref_ptr rootNode (new osg::Group); + mViewer->setSceneData(rootNode); - mOgre->configure( - mCfgMgr.getLogPath().string(), - renderSystem, - Settings::Manager::getString("opengl rtt mode", "Video")); + mVFS.reset(new VFS::Manager(mFSStrict)); - // This has to be added BEFORE MyGUI is initialized, as it needs - // to find core.xml here. + VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); - addResourcesDirectory(mCfgMgr.getCachePath ().string()); - - addResourcesDirectory(mResDir / "mygui"); - addResourcesDirectory(mResDir / "water"); - addResourcesDirectory(mResDir / "shadows"); - addResourcesDirectory(mResDir / "materials"); - - OEngine::Render::WindowSettings windowSettings; - windowSettings.fullscreen = settings.getBool("fullscreen", "Video"); - windowSettings.window_border = settings.getBool("window border", "Video"); - windowSettings.window_x = settings.getInt("resolution x", "Video"); - windowSettings.window_y = settings.getInt("resolution y", "Video"); - windowSettings.screen = settings.getInt("screen", "Video"); - windowSettings.vsync = settings.getBool("vsync", "Video"); - windowSettings.icon = "openmw.png"; - std::string aa = settings.getString("antialiasing", "Video"); - windowSettings.fsaa = (aa.substr(0, 4) == "MSAA") ? aa.substr(5, aa.size()-5) : "0"; - - SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, - settings.getBool("minimize on focus loss", "Video") ? "1" : "0"); - - mOgre->createWindow("OpenMW", windowSettings); - - Bsa::registerResources (mFileCollections, mArchives, true, mFSStrict); + mResourceSystem.reset(new Resource::ResourceSystem(mVFS.get())); + mResourceSystem->getTextureManager()->setUnRefImageDataAfterApply(true); + mResourceSystem->getTextureManager()->setFilterSettings( + Settings::Manager::getString("texture mag filter", "General"), + Settings::Manager::getString("texture min filter", "General"), + Settings::Manager::getString("texture mipmap", "General"), + Settings::Manager::getInt("anisotropy", "General"), + NULL + ); // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so @@ -390,33 +484,37 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) else gameControllerdb = ""; //if it doesn't exist, pass in an empty string - MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists, gameControllerdb, mGrab); + MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, keybinderUser, keybinderUserExists, gameControllerdb, mGrab); mEnvironment.setInputManager (input); - MWGui::WindowManager* window = new MWGui::WindowManager( - mExtensions, mOgre, mCfgMgr.getLogPath().string() + std::string("/"), - mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, mFallbackMap); + std::string myguiResources = (mResDir / "mygui").string(); + osg::ref_ptr guiRoot = new osg::Group; + guiRoot->setNodeMask(MWRender::Mask_GUI); + rootNode->addChild(guiRoot); + MWGui::WindowManager* window = new MWGui::WindowManager(mViewer, guiRoot, mResourceSystem.get(), + mCfgMgr.getLogPath().string() + std::string("/"), myguiResources, + mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, mFallbackMap, + Version::getOpenmwVersionDescription(mResDir.string())); mEnvironment.setWindowManager (window); // Create sound system - mEnvironment.setSoundManager (new MWSound::SoundManager(mUseSound)); - - mOgre->setWindowGammaContrast(Settings::Manager::getFloat("gamma", "General"), Settings::Manager::getFloat("contrast", "General")); + mEnvironment.setSoundManager (new MWSound::SoundManager(mVFS.get(), mUseSound)); if (!mSkipMenu) { std::string logo = mFallbackMap["Movies_Company_Logo"]; if (!logo.empty()) - window->playVideo(logo, 1); + window->playVideo(logo, true); } // Create the world - mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mContentFiles, - mResDir, mCfgMgr.getCachePath(), mEncoder, mFallbackMap, - mActivationDistanceOverride, mCellName, mStartupScript)); - MWBase::Environment::get().getWorld()->setupPlayer(); + mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), + mFileCollections, mContentFiles, mEncoder, mFallbackMap, + mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string())); + mEnvironment.getWorld()->setupPlayer(); input->setPlayer(&mEnvironment.getWorld()->getPlayer()); + window->setStore(mEnvironment.getWorld()->getStore()); window->initUI(); window->renderWorldMap(); @@ -431,7 +529,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full); mScriptContext->setExtensions (&mExtensions); - mEnvironment.setScriptManager (new MWScript::ScriptManager (MWBase::Environment::get().getWorld()->getStore(), + mEnvironment.setScriptManager (new MWScript::ScriptManager (mEnvironment.getWorld()->getStore(), mVerboseScripts, *mScriptContext, mWarningsMode, mScriptBlacklistUse ? mScriptBlacklist : std::vector())); @@ -443,12 +541,10 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mEnvironment.setJournal (new MWDialogue::Journal); mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mVerboseScripts, mTranslationDataStorage)); - mOgre->getRoot()->addFrameListener (this); - // scripts if (mCompileAll) { - std::pair result = MWBase::Environment::get().getScriptManager()->compileAll(); + std::pair result = mEnvironment.getScriptManager()->compileAll(); if (result.first) std::cout << "compiled " << result.second << " of " << result.first << " scripts (" @@ -468,118 +564,160 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } } +class WriteScreenshotToFileOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation +{ +public: + WriteScreenshotToFileOperation(const std::string& screenshotPath, const std::string& screenshotFormat) + : mScreenshotPath(screenshotPath) + , mScreenshotFormat(screenshotFormat) + { + } + + virtual void operator()(const osg::Image& image, const unsigned int context_id) + { + // Count screenshots. + int shotCount = 0; + + // Find the first unused filename with a do-while + std::ostringstream stream; + do + { + // Reset the stream + stream.str(""); + stream.clear(); + + stream << mScreenshotPath << "/screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << "." << mScreenshotFormat; + + } while (boost::filesystem::exists(stream.str())); + + boost::filesystem::ofstream outStream; + outStream.open(boost::filesystem::path(stream.str()), std::ios::binary); + + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension(mScreenshotFormat); + if (!readerwriter) + { + std::cerr << "Can't write screenshot, no '" << mScreenshotFormat << "' readerwriter found" << std::endl; + return; + } + + osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(image, outStream); + if (!result.success()) + { + std::cerr << "Can't write screenshot: " << result.message() << " code " << result.status() << std::endl; + } + } + +private: + std::string mScreenshotPath; + std::string mScreenshotFormat; +}; + // Initialise and enter main loop. void OMW::Engine::go() { assert (!mContentFiles.empty()); - assert (!mOgre); + + mViewer = new osgViewer::Viewer; + + osg::ref_ptr statshandler = new osgViewer::StatsHandler; + statshandler->setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3); + + statshandler->addUserStatsLine("Script", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f), + "script_time_taken", 1000.0, true, false, "script_time_begin", "script_time_end", 10000); + statshandler->addUserStatsLine("Mechanics", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f), + "mechanics_time_taken", 1000.0, true, false, "mechanics_time_begin", "mechanics_time_end", 10000); + statshandler->addUserStatsLine("Physics", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f), + "physics_time_taken", 1000.0, true, false, "physics_time_begin", "physics_time_end", 10000); + + mViewer->addEventHandler(statshandler); Settings::Manager settings; std::string settingspath; settingspath = loadSettings (settings); + mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(new WriteScreenshotToFileOperation(mCfgMgr.getUserDataPath().string(), + Settings::Manager::getString("screenshot format", "General"))); + mViewer->addEventHandler(mScreenCaptureHandler); + // Create encoder ToUTF8::Utf8Encoder encoder (mEncoding); mEncoder = &encoder; prepareEngine (settings); - // Play some good 'ol tunes - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); - if (!mSaveGameFile.empty()) { - MWBase::Environment::get().getStateManager()->loadGame(mSaveGameFile); + mEnvironment.getStateManager()->loadGame(mSaveGameFile); } else if (!mSkipMenu) { // start in main menu - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + mEnvironment.getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); try { // Is there an ini setting for this filename or something? - MWBase::Environment::get().getSoundManager()->streamMusic("Special/morrowind title.mp3"); + mEnvironment.getSoundManager()->streamMusic("Special/morrowind title.mp3"); std::string logo = mFallbackMap["Movies_Morrowind_Logo"]; if (!logo.empty()) - MWBase::Environment::get().getWindowManager()->playVideo(logo, true); + mEnvironment.getWindowManager()->playVideo(logo, true); } catch (...) {} } else { - MWBase::Environment::get().getStateManager()->newGame (!mNewGame); + mEnvironment.getStateManager()->newGame (!mNewGame); } // Start the main rendering loop - Ogre::Timer timer; - while (!MWBase::Environment::get().getStateManager()->hasQuitRequest()) + osg::Timer frameTimer; + double simulationTime = 0.0; + float framerateLimit = Settings::Manager::getFloat("framerate limit", "Video"); + while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest()) { - float dt = timer.getMilliseconds()/1000.f; - dt = std::min(dt, 0.2f); + double dt = frameTimer.time_s(); + frameTimer.setStartTick(); + dt = std::min(dt, 0.2); - timer.reset(); - Ogre::Root::getSingleton().renderOneFrame(dt); + bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); + if (!guiActive) + simulationTime += dt; + + mViewer->advance(simulationTime); + + frame(dt); + + if (!mEnvironment.getInputManager()->isWindowVisible()) + { + OpenThreads::Thread::microSleep(5000); + continue; + } + else + { + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + } + + if (framerateLimit > 0.f) + { + double thisFrameTime = frameTimer.time_s(); + double minFrameTime = 1.0 / framerateLimit; + if (thisFrameTime < minFrameTime) + { + OpenThreads::Thread::microSleep(1000*1000*(minFrameTime-thisFrameTime)); + } + } } + // Save user settings settings.saveUser(settingspath); std::cout << "Quitting peacefully." << std::endl; } -void OMW::Engine::activate() -{ - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) - return; - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0 - || player.getClass().getCreatureStats(player).getKnockedDown()) - return; - - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getFacedObject(); - - if (ptr.isEmpty()) - return; - - if (ptr.getClass().getName(ptr) == "") // objects without name presented to user can never be activated - return; - - if (ptr.getClass().isActor()) - { - MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); - - if (stats.getAiSequence().isInCombat() && !stats.isDead()) - return; - } - - MWBase::Environment::get().getWorld()->activate(ptr, MWBase::Environment::get().getWorld()->getPlayerPtr()); -} - -void OMW::Engine::screenshot() -{ - // Count screenshots. - int shotCount = 0; - - const std::string& screenshotPath = mCfgMgr.getUserDataPath().string(); - std::string format = Settings::Manager::getString("screenshot format", "General"); - // Find the first unused filename with a do-while - std::ostringstream stream; - do - { - // Reset the stream - stream.str(""); - stream.clear(); - - stream << screenshotPath << "screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << "." << format; - - } while (boost::filesystem::exists(stream.str())); - - mOgre->screenshot(stream.str(), format); -} - void OMW::Engine::setCompileAll (bool all) { mCompileAll = all; diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 3b088595c0..a03752b86f 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -1,19 +1,28 @@ #ifndef ENGINE_H #define ENGINE_H -#include - #include #include #include #include -#include + +#include #include "mwbase/environment.hpp" #include "mwworld/ptr.hpp" +namespace Resource +{ + class ResourceSystem; +} + +namespace VFS +{ + class Manager; +} + namespace Compiler { class Context; @@ -39,36 +48,34 @@ namespace MWGui class WindowManager; } -namespace OEngine -{ - namespace GUI - { - class MyGUIManager; - } - - namespace Render - { - class OgreRenderer; - } -} - namespace Files { struct ConfigurationManager; } +namespace osgViewer +{ + class ScreenCaptureHandler; +} + +struct SDL_Window; + namespace OMW { /// \brief Main engine class, that brings together all the components of OpenMW - class Engine : private Ogre::FrameListener + class Engine { + SDL_Window* mWindow; + std::auto_ptr mVFS; + std::auto_ptr mResourceSystem; MWBase::Environment mEnvironment; ToUTF8::FromType mEncoding; ToUTF8::Utf8Encoder* mEncoder; Files::PathContainer mDataDirs; std::vector mArchives; boost::filesystem::path mResDir; - OEngine::Render::OgreRenderer *mOgre; + osg::ref_ptr mViewer; + osg::ref_ptr mScreenCaptureHandler; std::string mCellName; std::vector mContentFiles; bool mVerboseScripts; @@ -98,23 +105,15 @@ namespace OMW bool mScriptBlacklistUse; bool mNewGame; - Nif::Cache mNifCache; + osg::Timer_t mStartTick; // not implemented Engine (const Engine&); Engine& operator= (const Engine&); - /// add resources directory - /// \note This function works recursively. - void addResourcesDirectory (const boost::filesystem::path& path); - - /// add a .zip resource - void addZipResource (const boost::filesystem::path& path); - void executeLocalScripts(); - virtual bool frameRenderingQueued (const Ogre::FrameEvent& evt); - virtual bool frameStarted (const Ogre::FrameEvent& evt); + void frame (float dt); /// Load settings from various files, returns the path to the user settings file std::string loadSettings (Settings::Manager & settings); @@ -122,6 +121,9 @@ namespace OMW /// Prepare engine for game play void prepareEngine (Settings::Manager & settings); + void createWindow(Settings::Manager& settings); + void setWindowIcon(); + public: Engine(Files::ConfigurationManager& configurationManager); virtual ~Engine(); @@ -167,12 +169,6 @@ namespace OMW /// Initialise and enter main loop. void go(); - /// Activate the focussed object. - void activate(); - - /// Write screenshot to file. - void screenshot(); - /// Compile all scripts (excludign dialogue scripts) at startup? void setCompileAll (bool all); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 2d2c9af0c9..c3f0f8688e 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -14,24 +14,27 @@ #if defined(_WIN32) // For OutputDebugString +#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN +#endif #include // makes __argc and __argv available on windows #include #endif -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE +#if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) || defined(__posix)) + #define USE_CRASH_CATCHER 1 +#else + #define USE_CRASH_CATCHER 0 +#endif + +#if USE_CRASH_CATCHER #include extern int cc_install_handlers(int argc, char **argv, int num_signals, int *sigs, const char *logfile, int (*user_info)(char*, char*)); extern int is_debugger_attached(void); #endif -// for Ogre::macBundlePath -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE -#include -#endif - #include /** * Workaround for problems with whitespaces in paths in older versions of Boost library @@ -196,21 +199,20 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return false; } - std::cout << "OpenMW version " << OPENMW_VERSION; - std::string rev = OPENMW_VERSION_COMMITHASH; - std::string tag = OPENMW_VERSION_TAGHASH; - if (!rev.empty() && !tag.empty()) - { - rev = rev.substr(0, 10); - std::cout << " (revision " << rev << ")"; - } - std::cout << std::endl; - if (variables.count ("version")) + { + cfgMgr.readConfiguration(variables, desc, true); + + Version::Version v = Version::getOpenmwVersion(variables["resources"].as()); + std::cout << v.describe() << std::endl; return false; + } cfgMgr.readConfiguration(variables, desc); + Version::Version v = Version::getOpenmwVersion(variables["resources"].as()); + std::cout << v.describe() << std::endl; + engine.setGrabMouse(!variables.count("no-grab")); // Font encoding settings @@ -322,6 +324,10 @@ private: int main(int argc, char**argv) { +#if defined(__APPLE__) + setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); +#endif + // Some objects used to redirect cout and cerr // Scope must be here, so this still works inside the catch block for logging exceptions std::streambuf* cout_rdbuf = std::cout.rdbuf (); @@ -362,7 +368,7 @@ int main(int argc, char**argv) #endif -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE +#if USE_CRASH_CATCHER // Unix crash catcher if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached()) { @@ -374,10 +380,10 @@ int main(int argc, char**argv) std::cout << "Running in a debugger, not installing crash catcher" << std::endl; #endif -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - // set current dir to bundle path - boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path(); - boost::filesystem::current_path(bundlePath); +#ifdef __APPLE__ + // FIXME: set current dir to bundle path + //boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path(); + //boost::filesystem::current_path(bundlePath); #endif engine.reset(new OMW::Engine(cfgMgr)); @@ -389,7 +395,7 @@ int main(int argc, char**argv) } catch (std::exception &e) { -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE +#if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) if (!isatty(fileno(stdin))) #endif SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL); diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index a90eec5bf6..4efa7c2737 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -1,4 +1,3 @@ - #include "environment.hpp" #include diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index 79477d883f..75c55e0289 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -25,6 +25,8 @@ namespace MWBase virtual ~InputManager() {} + virtual bool isWindowVisible() = 0; + virtual void update(float dt, bool disableControls, bool disableEvents=false) = 0; virtual void changeInputMode(bool guiMode) = 0; diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index f7fc515f57..42da1a052a 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -6,9 +6,9 @@ #include #include -namespace Ogre +namespace osg { - class Vector3; + class Vec3f; } namespace ESM @@ -23,6 +23,7 @@ namespace MWWorld { class Ptr; class CellStore; + class CellRef; } namespace Loading @@ -128,6 +129,11 @@ namespace MWBase OffenseType type, int arg=0, bool victimAware=false) = 0; /// @return false if the attack was considered a "friendly hit" and forgiven virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) = 0; + + /// Notify that actor was killed, add a murder bounty if applicable + /// @note No-op for non-player attackers + virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) = 0; + /// Utility to check if taking this item is illegal and calling commitCrime if so /// @param container The container the item is in; may be empty for an item in the world virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, @@ -154,12 +160,13 @@ namespace MWBase virtual void forceStateUpdate(const MWWorld::Ptr &ptr) = 0; ///< Forces an object to refresh its animation state. - virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number=1) = 0; + virtual bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number=1) = 0; ///< Run animation for a MW-reference. Calls to this function for references that are currently not /// in the scene should be ignored. /// /// \param mode 0 normal, 1 immediate start, 2 immediate loop /// \param count How many times the animation should be run + /// \return Success or error virtual void skipAnimation(const MWWorld::Ptr& ptr) = 0; ///< Skip the animation for the given MW-reference for one frame. Calls to this function for @@ -174,11 +181,12 @@ namespace MWBase virtual bool toggleAI() = 0; virtual bool isAIActive() = 0; - virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& objects) = 0; - virtual void getActorsInRange(const Ogre::Vector3 &position, float radius, std::vector &objects) = 0; + virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector& objects) = 0; + virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector &objects) = 0; - ///return the list of actors which are following the given actor - /**ie AiFollow is active and the target is the actor**/ + ///Returns the list of actors which are siding with the given actor in fights + /**ie AiFollow or AiEscort is active and the target is the actor **/ + virtual std::list getActorsSidingWith(const MWWorld::Ptr& actor) = 0; virtual std::list getActorsFollowing(const MWWorld::Ptr& actor) = 0; virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0; @@ -211,6 +219,15 @@ namespace MWBase /// Has the player stolen this item from the given owner? virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid) = 0; + + virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim) = 0; + + /// Turn actor into werewolf or normal form. + virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) = 0; + + /// Sets the NPC's Acrobatics skill to match the fWerewolfAcrobatics GMST. + /// It only applies to the current form the NPC is in. + virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) = 0; }; } diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index e71558de0b..22582c2b1f 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -7,11 +7,6 @@ #include "../mwworld/ptr.hpp" -namespace Ogre -{ - class Vector3; -} - namespace MWWorld { class CellStore; @@ -20,6 +15,7 @@ namespace MWWorld namespace MWSound { class Sound; + class Stream; struct Sound_Decoder; typedef boost::shared_ptr DecoderPtr; } @@ -27,6 +23,7 @@ namespace MWSound namespace MWBase { typedef boost::shared_ptr SoundPtr; + typedef boost::shared_ptr SoundStreamPtr; /// \brief Interface for sound manager (implemented in MWSound) class SoundManager @@ -34,18 +31,17 @@ namespace MWBase public: /* These must all fit together */ enum PlayMode { - Play_Normal = 0, /* tracked, non-looping, multi-instance, environment */ + Play_Normal = 0, /* non-looping, affected by environment */ Play_Loop = 1<<0, /* Sound will continually loop until explicitly stopped */ Play_NoEnv = 1<<1, /* Do not apply environment effects (eg, underwater filters) */ - Play_NoTrack = 1<<2, /* (3D only) Play the sound at the given object's position - * but do not keep it updated (the sound will not move with - * the object and will not stop when the object is deleted. */ - Play_RemoveAtDistance = 1<<3, /* (3D only) If the listener gets further than 2000 units away + Play_RemoveAtDistance = 1<<2, /* (3D only) If the listener gets further than 2000 units away from the sound source, the sound is removed. This is weird stuff but apparently how vanilla works for sounds played by the PlayLoopSound family of script functions. Perhaps we can make this cut off a more subtle fade later, but have to be careful to not change the overall volume of areas by too much. */ + Play_NoPlayerLocal = 1<<3, /* (3D only) Don't play the sound local to the listener even if the + player is making it. */ Play_LoopNoEnv = Play_Loop | Play_NoEnv, Play_LoopRemoveAtDistance = Play_Loop | Play_RemoveAtDistance }; @@ -91,7 +87,7 @@ namespace MWBase ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist - virtual void say(const MWWorld::Ptr &reference, const std::string& filename) = 0; + virtual void say(const MWWorld::ConstPtr &reference, const std::string& filename) = 0; ///< Make an actor say some text. /// \param filename name of a sound file in "Sound/" in the data directory. @@ -99,58 +95,66 @@ namespace MWBase ///< Say some text, without an actor ref /// \param filename name of a sound file in "Sound/" in the data directory. - virtual bool sayDone(const MWWorld::Ptr &reference=MWWorld::Ptr()) const = 0; + virtual bool sayDone(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) const = 0; ///< Is actor not speaking? - virtual void stopSay(const MWWorld::Ptr &reference=MWWorld::Ptr()) = 0; + virtual void stopSay(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) = 0; ///< Stop an actor speaking - virtual float getSaySoundLoudness(const MWWorld::Ptr& reference) const = 0; + virtual float getSaySoundLoudness(const MWWorld::ConstPtr& reference) const = 0; ///< Check the currently playing say sound for this actor /// and get an average loudness value (scale [0,1]) at the current time position. /// If the actor is not saying anything, returns 0. - virtual SoundPtr playTrack(const MWSound::DecoderPtr& decoder, PlayType type) = 0; + virtual SoundStreamPtr playTrack(const MWSound::DecoderPtr& decoder, PlayType type) = 0; ///< Play a 2D audio track, using a custom decoder + virtual void stopTrack(SoundStreamPtr stream) = 0; + ///< Stop the given audio track from playing + + virtual double getTrackTimeDelay(SoundStreamPtr stream) = 0; + ///< Retives the time delay, in seconds, of the audio track (must be a sound + /// returned by \ref playTrack). Only intended to be called by the track + /// decoder's read method. + virtual SoundPtr playSound(const std::string& soundId, float volume, float pitch, PlayType type=Play_TypeSfx, PlayMode mode=Play_Normal, float offset=0) = 0; ///< Play a sound, independently of 3D-position - ///< @param offset Value from [0,1] meaning from which fraction the sound the playback starts. + ///< @param offset Number of seconds into the sound to start playback. - virtual MWBase::SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId, + virtual MWBase::SoundPtr playSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, float volume, float pitch, PlayType type=Play_TypeSfx, PlayMode mode=Play_Normal, float offset=0) = 0; ///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless Play_NoTrack is specified. - ///< @param offset Value from [0,1] meaning from which fraction the sound the playback starts. + ///< @param offset Number of seconds into the sound to start playback. - virtual MWBase::SoundPtr playManualSound3D(const Ogre::Vector3& initialPos, const std::string& soundId, - float volume, float pitch, PlayType type, PlayMode mode, float offset=0) = 0; - ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated manually using Sound::setPosition. + virtual MWBase::SoundPtr playSound3D(const osg::Vec3f& initialPos, const std::string& soundId, + float volume, float pitch, PlayType type=Play_TypeSfx, PlayMode mode=Play_Normal, float offset=0) = 0; + ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated using Sound::setPosition. - virtual void stopSound3D(const MWWorld::Ptr &reference, const std::string& soundId) = 0; + virtual void stopSound(SoundPtr sound) = 0; + ///< Stop the given sound from playing + + virtual void stopSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId) = 0; ///< Stop the given object from playing the given sound, - virtual void stopSound3D(const MWWorld::Ptr &reference) = 0; + virtual void stopSound3D(const MWWorld::ConstPtr &reference) = 0; ///< Stop the given object from playing all sounds. - virtual void stopSound(MWBase::SoundPtr sound) = 0; - ///< Stop the given sound handle - virtual void stopSound(const MWWorld::CellStore *cell) = 0; ///< Stop all sounds for the given cell. virtual void stopSound(const std::string& soundId) = 0; ///< Stop a non-3d looping sound - virtual void fadeOutSound3D(const MWWorld::Ptr &reference, const std::string& soundId, float duration) = 0; + virtual void fadeOutSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, float duration) = 0; ///< Fade out given sound (that is already playing) of given object ///< @param reference Reference to object, whose sound is faded out ///< @param soundId ID of the sound to fade out. ///< @param duration Time until volume reaches 0. - virtual bool getSoundPlaying(const MWWorld::Ptr &reference, const std::string& soundId) const = 0; + virtual bool getSoundPlaying(const MWWorld::ConstPtr &reference, const std::string& soundId) const = 0; ///< Is the given sound currently playing on the given object? /// If you want to check if sound played with playSound is playing, use empty Ptr @@ -162,9 +166,9 @@ namespace MWBase virtual void update(float duration) = 0; - virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up) = 0; + virtual void setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up, bool underwater) = 0; - virtual void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated) = 0; + virtual void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) = 0; virtual void clear() = 0; }; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index fdd51ef446..f364ada7ac 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -26,14 +26,6 @@ namespace MyGUI class UString; } -namespace OEngine -{ - namespace GUI - { - class Layout; - } -} - namespace ESM { struct Class; @@ -58,6 +50,8 @@ namespace MWWorld namespace MWGui { + class Layout; + class Console; class SpellWindow; class TradeWindow; @@ -154,12 +148,13 @@ namespace MWBase virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0; + /// Make the player use an item, while updating GUI state accordingly + virtual void useItem(const MWWorld::Ptr& item) = 0; + virtual void updateSpellWindow() = 0; virtual void setConsoleSelectedObject(const MWWorld::Ptr& object) = 0; - virtual void wmUpdateFps(float fps, unsigned int triangleCount, unsigned int batchCount) = 0; - /// Set value for the given ID. virtual void setValue (const std::string& id, const MWMechanics::AttributeValue& value) = 0; virtual void setValue (int parSkill, const MWMechanics::SkillValue& value) = 0; @@ -184,12 +179,6 @@ namespace MWBase virtual void changeCell(MWWorld::CellStore* cell) = 0; ///< change the active cell - virtual void setPlayerPos(int cellX, int cellY, const float x, const float y) = 0; - ///< set player position in map space - - virtual void setPlayerDir(const float x, const float y) = 0; - ///< set player view direction in map space - virtual void setFocusObject(const MWWorld::Ptr& focus) = 0; virtual void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) = 0; @@ -241,7 +230,7 @@ namespace MWBase virtual void addVisitedLocation(const std::string& name, int x, int y) = 0; /// Hides dialog and schedules dialog to be deleted. - virtual void removeDialog(OEngine::GUI::Layout* dialog) = 0; + virtual void removeDialog(MWGui::Layout* dialog) = 0; ///Gracefully attempts to exit the topmost GUI mode /** No guarentee of actually closing the window **/ @@ -303,8 +292,6 @@ namespace MWBase virtual void showSoulgemDialog (MWWorld::Ptr item) = 0; - virtual void frameStarted(float dt) = 0; - virtual void changePointer (const std::string& name) = 0; virtual void setEnemy (const MWWorld::Ptr& enemy) = 0; @@ -361,6 +348,15 @@ namespace MWBase virtual void cycleSpell(bool next) = 0; /// Cycle to next or previous weapon virtual void cycleWeapon(bool next) = 0; + + // In WindowManager for now since there isn't a VFS singleton + virtual std::string correctIconPath(const std::string& path) = 0; + virtual std::string correctBookartPath(const std::string& path, int width, int height) = 0; + virtual std::string correctTexturePath(const std::string& path) = 0; + + virtual void requestMap(std::set cells) = 0; + virtual void removeCell(MWWorld::CellStore* cell) = 0; + virtual void writeFog(MWWorld::CellStore* cell) = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index c110e94d61..ebce2e1bf8 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -9,11 +9,13 @@ #include "../mwworld/ptr.hpp" -namespace Ogre +#include "../mwrender/rendermode.hpp" + +namespace osg { - class Vector2; - class Vector3; - class Quaternion; + class Vec3f; + class Matrixf; + class Quat; class Image; } @@ -78,14 +80,6 @@ namespace MWBase public: - enum RenderMode - { - Render_CollisionDebug, - Render_Wireframe, - Render_Pathgrid, - Render_BoundingBoxes - }; - struct DoorMarker { std::string name; @@ -125,8 +119,6 @@ namespace MWBase virtual void adjustSky() = 0; - virtual void getTriangleBatchCount(unsigned int &triangles, unsigned int &batches) = 0; - virtual const MWWorld::Fallback *getFallback () const = 0; virtual MWWorld::Player& getPlayer() = 0; @@ -145,21 +137,12 @@ namespace MWBase virtual bool isCellQuasiExterior() const = 0; - virtual Ogre::Vector2 getNorthVector (MWWorld::CellStore* cell) = 0; + virtual osg::Vec2f getNorthVector (MWWorld::CellStore* cell) = 0; ///< get north vector for given interior cell virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out) = 0; ///< get a list of teleport door markers for a given cell, to be displayed on the local map - virtual void worldToInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y) = 0; - ///< see MWRender::LocalMap::worldToInteriorMapPosition - - virtual Ogre::Vector2 interiorMapToWorldPosition (float nX, float nY, int x, int y) = 0; - ///< see MWRender::LocalMap::interiorMapToWorldPosition - - virtual bool isPositionExplored (float nX, float nY, int x, int y, bool interior) = 0; - ///< see MWRender::LocalMap::isPositionExplored - virtual void setGlobalInt (const std::string& name, int value) = 0; ///< Set value independently from real type. @@ -192,16 +175,10 @@ namespace MWBase ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. - virtual MWWorld::Ptr getPtrViaHandle (const std::string& handle) = 0; - ///< Return a pointer to a liveCellRef with the given Ogre handle. - - virtual MWWorld::Ptr searchPtrViaHandle (const std::string& handle) = 0; - ///< Return a pointer to a liveCellRef with the given Ogre handle or Ptr() if not found - virtual MWWorld::Ptr searchPtrViaActorId (int actorId) = 0; ///< Search is limited to the active cells. - virtual MWWorld::Ptr findContainer (const MWWorld::Ptr& ptr) = 0; + virtual MWWorld::Ptr findContainer (const MWWorld::ConstPtr& ptr) = 0; ///< Return a pointer to a liveCellRef which contains \a ptr. /// \note Search is limited to the active cells. @@ -209,7 +186,7 @@ namespace MWBase virtual void disable (const MWWorld::Ptr& ptr) = 0; - virtual void advanceTime (double hours) = 0; + virtual void advanceTime (double hours, bool incremental = false) = 0; ///< Advance in-game time. virtual void setHour (double hour) = 0; @@ -271,7 +248,7 @@ namespace MWBase /// Returns a pointer to the object the provided object would hit (if within the /// specified distance), and the point where the hit occurs. This will attempt to /// use the "Head" node, or alternatively the "Bip01 Head" node as a basis. - virtual std::pair getHitContact(const MWWorld::Ptr &ptr, float distance) = 0; + virtual std::pair getHitContact(const MWWorld::ConstPtr &ptr, float distance) = 0; virtual void adjustPosition (const MWWorld::Ptr& ptr, bool force) = 0; ///< Adjust position after load to be on ground. Must be called after model load. @@ -280,6 +257,7 @@ namespace MWBase virtual void fixPosition (const MWWorld::Ptr& actor) = 0; ///< Attempt to fix position so that the Ptr is no longer inside collision geometry. + /// @note No-op for items in containers. Use ContainerStore::removeItem instead. virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0; @@ -293,9 +271,7 @@ namespace MWBase virtual void rotateObject(const MWWorld::Ptr& ptr,float x,float y,float z, bool adjust = false) = 0; - virtual void localRotateObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; - - virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; + virtual MWWorld::Ptr safePlaceObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; ///< place an object in a "safe" location (ie not in the void, etc). virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) @@ -305,7 +281,7 @@ namespace MWBase virtual void positionToIndex (float x, float y, int &cellX, int &cellY) const = 0; ///< Convert position to cell numbers - virtual void queueMovement(const MWWorld::Ptr &ptr, const Ogre::Vector3 &velocity) = 0; + virtual void queueMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity) = 0; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. @@ -317,7 +293,7 @@ namespace MWBase /// collisions and gravity. /// \return Resulting mode - virtual bool toggleRenderMode (RenderMode mode) = 0; + virtual bool toggleRenderMode (MWRender::RenderMode mode) = 0; ///< Toggle a render mode. ///< \return Resulting mode @@ -371,14 +347,14 @@ namespace MWBase virtual void update (float duration, bool paused) = 0; - virtual MWWorld::Ptr placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount) = 0; + virtual MWWorld::Ptr placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) = 0; ///< copy and place an object into the gameworld at the specified cursor position /// @param object /// @param cursor X (relative 0-1) /// @param cursor Y (relative 0-1) /// @param number of objects to place - virtual MWWorld::Ptr dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount) = 0; + virtual MWWorld::Ptr dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object, int amount) = 0; ///< copy and place an object into the gameworld at the given actor's position /// @param actor giving the dropped object position /// @param object @@ -391,13 +367,15 @@ namespace MWBase virtual bool isFlying(const MWWorld::Ptr &ptr) const = 0; virtual bool isSlowFalling(const MWWorld::Ptr &ptr) const = 0; - virtual bool isSwimming(const MWWorld::Ptr &object) const = 0; - virtual bool isWading(const MWWorld::Ptr &object) const = 0; + virtual bool isSwimming(const MWWorld::ConstPtr &object) const = 0; + virtual bool isWading(const MWWorld::ConstPtr &object) const = 0; ///Is the head of the creature underwater? - virtual bool isSubmerged(const MWWorld::Ptr &object) const = 0; - virtual bool isUnderwater(const MWWorld::CellStore* cell, const Ogre::Vector3 &pos) const = 0; + virtual bool isSubmerged(const MWWorld::ConstPtr &object) const = 0; + virtual bool isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const = 0; virtual bool isOnGround(const MWWorld::Ptr &ptr) const = 0; + virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const = 0; + virtual void togglePOV() = 0; virtual bool isFirstPerson() const = 0; virtual void togglePreviewMode(bool enable) = 0; @@ -418,28 +396,28 @@ namespace MWBase /// @note throws an exception when invoked on a teleport door virtual void activateDoor(const MWWorld::Ptr& door, int state) = 0; - virtual bool getPlayerStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if the player is standing on \a object - virtual bool getActorStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if any actor is standing on \a object - virtual bool getPlayerCollidingWith(const MWWorld::Ptr& object) = 0; ///< @return true if the player is colliding with \a object - virtual bool getActorCollidingWith (const MWWorld::Ptr& object) = 0; ///< @return true if any actor is colliding with \a object - virtual void hurtStandingActors (const MWWorld::Ptr& object, float dmgPerSecond) = 0; + virtual bool getPlayerStandingOn (const MWWorld::ConstPtr& object) = 0; ///< @return true if the player is standing on \a object + virtual bool getActorStandingOn (const MWWorld::ConstPtr& object) = 0; ///< @return true if any actor is standing on \a object + virtual bool getPlayerCollidingWith(const MWWorld::ConstPtr& object) = 0; ///< @return true if the player is colliding with \a object + virtual bool getActorCollidingWith (const MWWorld::ConstPtr& object) = 0; ///< @return true if any actor is colliding with \a object + virtual void hurtStandingActors (const MWWorld::ConstPtr& object, float dmgPerSecond) = 0; ///< Apply a health difference to any actors standing on \a object. /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. - virtual void hurtCollidingActors (const MWWorld::Ptr& object, float dmgPerSecond) = 0; + virtual void hurtCollidingActors (const MWWorld::ConstPtr& object, float dmgPerSecond) = 0; ///< Apply a health difference to any actors colliding with \a object. /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. virtual float getWindSpeed() = 0; - virtual void getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out) = 0; + virtual void getContainersOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) = 0; ///< get all containers in active cells owned by this Npc - virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out) = 0; + virtual void getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) = 0; ///< get all items in active cells owned by this Npc - virtual bool getLOS(const MWWorld::Ptr& actor,const MWWorld::Ptr& targetActor) = 0; + virtual bool getLOS(const MWWorld::ConstPtr& actor,const MWWorld::ConstPtr& targetActor) = 0; ///< get Line of Sight (morrowind stupid implementation) - virtual float getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist) = 0; + virtual float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist) = 0; virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0; @@ -452,11 +430,11 @@ namespace MWBase /// \todo Probably shouldn't be here virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) = 0; + virtual const MWRender::Animation* getAnimation(const MWWorld::ConstPtr &ptr) const = 0; virtual void reattachPlayerCamera() = 0; /// \todo this does not belong here - virtual void frameStarted (float dt, bool paused) = 0; - virtual void screenshot (Ogre::Image& image, int w, int h) = 0; + virtual void screenshot (osg::Image* image, int w, int h) = 0; /// Find default position inside exterior cell specified by name /// \return false if exterior with given name not exists, true otherwise @@ -478,13 +456,6 @@ namespace MWBase /// Returns true if levitation spell effect is allowed. virtual bool isLevitationEnabled() const = 0; - /// Turn actor into werewolf or normal form. - virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) = 0; - - /// Sets the NPC's Acrobatics skill to match the fWerewolfAcrobatics GMST. - /// It only applies to the current form the NPC is in. - virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) = 0; - virtual bool getGodModeState() = 0; virtual bool toggleGodMode() = 0; @@ -503,9 +474,9 @@ namespace MWBase virtual void launchMagicBolt (const std::string& model, const std::string& sound, const std::string& spellId, float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, const std::string& sourceName, const Ogre::Vector3& fallbackDirection) = 0; - virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, - const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed) = 0; + const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) = 0; + virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, + const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) = 0; virtual const std::vector& getContentFiles() const = 0; @@ -514,7 +485,7 @@ namespace MWBase // Are we in an exterior or pseudo-exterior cell and it's night? virtual bool isDark() const = 0; - virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result) = 0; + virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, osg::Vec3f& result) = 0; /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) /// @note id must be lower case @@ -546,11 +517,11 @@ namespace MWBase virtual void spawnRandomCreature(const std::string& creatureList) = 0; /// Spawn a blood effect for \a ptr at \a worldPosition - virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition) = 0; + virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0; - virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const Ogre::Vector3& worldPos) = 0; + virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) = 0; - virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, + virtual void explodeSpell (const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName) = 0; virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; @@ -559,12 +530,21 @@ namespace MWBase virtual bool isInStorm() const = 0; /// @see MWWorld::WeatherManager::getStormDirection - virtual Ogre::Vector3 getStormDirection() const = 0; + virtual osg::Vec3f getStormDirection() const = 0; /// Resets all actors in the current active cells to their original location within that cell. virtual void resetActors() = 0; - virtual bool isWalkingOnWater (const MWWorld::Ptr& actor) = 0; + virtual bool isWalkingOnWater (const MWWorld::ConstPtr& actor) = 0; + + /// Return a vector aiming the actor's weapon towards a target. + /// @note The length of the vector is the distance between actor and target. + virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; + + /// Return the distance between actor's weapon and target's collision box. + virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; + + virtual void removeContainerScripts(const MWWorld::Ptr& reference) = 0; }; } diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 457b0cec10..9785eef1e6 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -1,4 +1,3 @@ - #include "activator.hpp" #include @@ -11,12 +10,13 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/ptr.hpp" -#include "../mwworld/physicssystem.hpp" #include "../mwworld/action.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/nullaction.hpp" -#include "../mwrender/actors.hpp" +#include "../mwphysics/physicssystem.hpp" + +#include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" @@ -26,31 +26,24 @@ namespace MWClass { - std::string Activator::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Activator::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { - MWRender::Actors& actors = renderingInterface.getActors(); - actors.insertActivator(ptr, model); + renderingInterface.getObjects().insertModel(ptr, model, true); } } - void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) physics.addObject(ptr, model); MWBase::Environment::get().getMechanicsManager()->add(ptr); } - std::string Activator::getModel(const MWWorld::Ptr &ptr) const + std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -59,17 +52,16 @@ namespace MWClass return ""; } - std::string Activator::getName (const MWWorld::Ptr& ptr) const + std::string Activator::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } - std::string Activator::getScript (const MWWorld::Ptr& ptr) const + std::string Activator::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; @@ -82,21 +74,19 @@ namespace MWClass registerClass (typeid (ESM::Activator).name(), instance); } - bool Activator::hasToolTip (const MWWorld::Ptr& ptr) const + bool Activator::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Activator::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Activator::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) @@ -125,12 +115,10 @@ namespace MWClass } - MWWorld::Ptr - Activator::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Activator::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } } diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index e79318a55e..15dbf57677 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -8,30 +8,26 @@ namespace MWClass class Activator : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; @@ -39,7 +35,7 @@ namespace MWClass static void registerSelf(); - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; }; } diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp new file mode 100644 index 0000000000..4e89c7282e --- /dev/null +++ b/apps/openmw/mwclass/actor.cpp @@ -0,0 +1,82 @@ +#include "actor.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" + +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/movement.hpp" +#include "../mwmechanics/magiceffects.hpp" + +#include "../mwphysics/physicssystem.hpp" + +#include "../mwworld/inventorystore.hpp" + +namespace MWClass +{ + Actor::Actor() {} + + Actor::~Actor() {} + + void Actor::adjustPosition(const MWWorld::Ptr& ptr, bool force) const + { + MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); + } + + void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + if (!model.empty()) + { + physics.addActor(ptr, model); + if (getCreatureStats(ptr).isDead()) + MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); + } + MWBase::Environment::get().getMechanicsManager()->add(ptr); + } + + void Actor::block(const MWWorld::Ptr &ptr) const + { + MWWorld::InventoryStore& inv = getInventoryStore(ptr); + MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (shield == inv.end()) + return; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + switch (shield->getClass().getEquipmentSkill(*shield)) + { + case ESM::Skill::LightArmor: + sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f); + break; + case ESM::Skill::MediumArmor: + sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f); + break; + case ESM::Skill::HeavyArmor: + sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f); + break; + default: + return; + } + } + + osg::Vec3f Actor::getRotationVector(const MWWorld::Ptr& ptr) const + { + MWMechanics::Movement &movement = getMovementSettings(ptr); + osg::Vec3f vec(movement.mRotation[0], movement.mRotation[1], movement.mRotation[2]); + movement.mRotation[0] = 0.0f; + movement.mRotation[1] = 0.0f; + movement.mRotation[2] = 0.0f; + return vec; + } + + float Actor::getEncumbrance(const MWWorld::Ptr& ptr) const + { + float weight = getContainerStore(ptr).getWeight(); + const MWMechanics::MagicEffects& effects = getCreatureStats(ptr).getMagicEffects(); + weight -= effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Feather)).getMagnitude(); + weight += effects.get(MWMechanics::EffectKey(ESM::MagicEffect::Burden)).getMagnitude(); + return (weight < 0) ? 0.0f : weight; + } +} diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp new file mode 100644 index 0000000000..88a3f1a328 --- /dev/null +++ b/apps/openmw/mwclass/actor.hpp @@ -0,0 +1,44 @@ +#ifndef GAME_MWCLASS_MOBILE_H +#define GAME_MWCLASS_MOBILE_H + +#include "../mwworld/class.hpp" + +namespace ESM +{ + struct GameSetting; +} + +namespace MWClass +{ + /// \brief Class holding functionality common to Creature and NPC + class Actor : public MWWorld::Class + { + protected: + + Actor(); + + public: + virtual ~Actor(); + + virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) const; + ///< Adjust position to stand on ground. Must be called post model load + /// @param force do this even if the ptr is flying + + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; + + virtual void block(const MWWorld::Ptr &ptr) const; + + virtual osg::Vec3f getRotationVector(const MWWorld::Ptr& ptr) const; + ///< Return desired rotations, as euler angles. + + virtual float getEncumbrance(const MWWorld::Ptr& ptr) const; + ///< Returns total weight of objects inside this object (including modifications from magic + /// effects). Throws an exception, if the object can't hold other objects. + + // not implemented + Actor(const Actor&); + Actor& operator= (const Actor&); + }; +} + +#endif diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 2abd071bd0..8907a5f8da 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -1,4 +1,3 @@ - #include "apparatus.hpp" #include @@ -11,7 +10,7 @@ #include "../mwworld/actiontake.hpp" #include "../mwworld/actionalchemy.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwrender/objects.hpp" @@ -21,10 +20,6 @@ namespace MWClass { - std::string Apparatus::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Apparatus::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -33,17 +28,14 @@ namespace MWClass } } - void Apparatus::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Apparatus::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, true); + // TODO: add option somewhere to enable collision for placeable objects } - std::string Apparatus::getModel(const MWWorld::Ptr &ptr) const + std::string Apparatus::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -52,10 +44,9 @@ namespace MWClass return ""; } - std::string Apparatus::getName (const MWWorld::Ptr& ptr) const + std::string Apparatus::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -66,18 +57,16 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Apparatus::getScript (const MWWorld::Ptr& ptr) const + std::string Apparatus::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - int Apparatus::getValue (const MWWorld::Ptr& ptr) const + int Apparatus::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -89,39 +78,36 @@ namespace MWClass registerClass (typeid (ESM::Apparatus).name(), instance); } - std::string Apparatus::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Apparatus::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Apparatus Up"); } - std::string Apparatus::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Apparatus::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Apparatus Down"); } - std::string Apparatus::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Apparatus::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Apparatus::hasToolTip (const MWWorld::Ptr& ptr) const + bool Apparatus::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Apparatus::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Apparatus::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -144,24 +130,21 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionAlchemy()); } - MWWorld::Ptr - Apparatus::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Apparatus::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Apparatus::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Apparatus::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Apparatus) != 0; } - float Apparatus::getWeight(const MWWorld::Ptr &ptr) const + float Apparatus::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 2ab0a47e3b..d669b9c2da 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -8,22 +8,18 @@ namespace MWClass class Apparatus : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -31,36 +27,36 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 686f5af619..2e4667016f 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -1,4 +1,3 @@ - #include "armor.hpp" #include @@ -15,21 +14,18 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/containerstore.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwgui/tooltips.hpp" namespace MWClass { - std::string Armor::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Armor::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -38,17 +34,14 @@ namespace MWClass } } - void Armor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Armor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, true); + // TODO: add option somewhere to enable collision for placeable objects } - std::string Armor::getModel(const MWWorld::Ptr &ptr) const + std::string Armor::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -57,10 +50,9 @@ namespace MWClass return ""; } - std::string Armor::getName (const MWWorld::Ptr& ptr) const + std::string Armor::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -71,31 +63,28 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - bool Armor::hasItemHealth (const MWWorld::Ptr& ptr) const + bool Armor::hasItemHealth (const MWWorld::ConstPtr& ptr) const { return true; } - int Armor::getItemMaxHealth (const MWWorld::Ptr& ptr) const + int Armor::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mHealth; } - std::string Armor::getScript (const MWWorld::Ptr& ptr) const + std::string Armor::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Armor::getEquipmentSlots (const MWWorld::Ptr& ptr) const + std::pair, bool> Armor::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); std::vector slots_; @@ -126,10 +115,9 @@ namespace MWClass return std::make_pair (slots_, false); } - int Armor::getEquipmentSkill (const MWWorld::Ptr& ptr) const + int Armor::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); std::string typeGmst; @@ -171,10 +159,9 @@ namespace MWClass return ESM::Skill::HeavyArmor; } - int Armor::getValue (const MWWorld::Ptr& ptr) const + int Armor::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -186,7 +173,7 @@ namespace MWClass registerClass (typeid (ESM::Armor).name(), instance); } - std::string Armor::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Armor::getUpSoundId (const MWWorld::ConstPtr& ptr) const { int es = getEquipmentSkill(ptr); if (es == ESM::Skill::LightArmor) @@ -197,7 +184,7 @@ namespace MWClass return std::string("Item Armor Heavy Up"); } - std::string Armor::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Armor::getDownSoundId (const MWWorld::ConstPtr& ptr) const { int es = getEquipmentSkill(ptr); if (es == ESM::Skill::LightArmor) @@ -208,29 +195,26 @@ namespace MWClass return std::string("Item Armor Heavy Down"); } - std::string Armor::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Armor::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Armor::hasToolTip (const MWWorld::Ptr& ptr) const + bool Armor::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Armor::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Armor::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -246,7 +230,7 @@ namespace MWClass typeText = "#{sHeavy}"; text += "\n#{sArmorRating}: " + MWGui::ToolTips::toString(getEffectiveArmorRating(ptr, - MWBase::Environment::get().getWorld()->getPlayerPtr())); + MWMechanics::getPlayer())); int remainingHealth = getItemHealth(ptr); text += "\n#{sCondition}: " + MWGui::ToolTips::toString(remainingHealth) + "/" @@ -269,18 +253,16 @@ namespace MWClass return info; } - std::string Armor::getEnchantment (const MWWorld::Ptr& ptr) const + std::string Armor::getEnchantment (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mEnchant; } - std::string Armor::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + std::string Armor::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Armor newItem = *ref->mBase; newItem.mId=""; @@ -291,9 +273,9 @@ namespace MWClass return record->mId; } - int Armor::getEffectiveArmorRating(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor) const + int Armor::getEffectiveArmorRating(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &actor) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); int armorSkillType = getEquipmentSkill(ptr); int armorSkill = actor.getClass().getSkill(actor, armorSkillType); @@ -307,7 +289,7 @@ namespace MWClass return ref->mBase->mData.mArmor * armorSkill / iBaseArmorSkill; } - std::pair Armor::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const + std::pair Armor::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc); @@ -377,33 +359,29 @@ namespace MWClass return action; } - MWWorld::Ptr - Armor::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Armor::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - int Armor::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Armor::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mEnchant; } - bool Armor::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Armor::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Armor) || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } - float Armor::getWeight(const MWWorld::Ptr &ptr) const + float Armor::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index 21d711a0d3..9746d6d209 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -7,22 +7,18 @@ namespace MWClass { class Armor : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -30,50 +26,50 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasItemHealth (const MWWorld::Ptr& ptr) const; + virtual bool hasItemHealth (const MWWorld::ConstPtr& ptr) const; ///< \return Item health data available? - virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const; + virtual int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + virtual std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? - virtual int getEquipmentSkill (const MWWorld::Ptr& ptr) const; - /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + virtual int getEquipmentSkill (const MWWorld::ConstPtr& ptr) const; + /// Return the index of the skill this item corresponds to when equipped or -1, if there is /// no such skill. - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; + virtual std::string getEnchantment (const MWWorld::ConstPtr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - virtual std::string applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + virtual std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. - virtual std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; + virtual std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. \n /// Second item in the pair specifies the error message @@ -81,14 +77,14 @@ namespace MWClass const; ///< Generate action for using via inventory menu - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; /// Get the effective armor rating, factoring in the actor's skills, for the given armor. - virtual int getEffectiveArmorRating(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; + virtual int getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const; }; } diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index a9c96e7c7d..a7d8ed8f4d 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -12,7 +12,7 @@ #include "../mwworld/failedaction.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -23,10 +23,6 @@ namespace MWClass { - std::string Book::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Book::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -35,17 +31,14 @@ namespace MWClass } } - void Book::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Book::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, true); + // TODO: add option somewhere to enable collision for placeable objects } - std::string Book::getModel(const MWWorld::Ptr &ptr) const + std::string Book::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -54,10 +47,9 @@ namespace MWClass return ""; } - std::string Book::getName (const MWWorld::Ptr& ptr) const + std::string Book::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -79,18 +71,16 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionRead(ptr)); } - std::string Book::getScript (const MWWorld::Ptr& ptr) const + std::string Book::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - int Book::getValue (const MWWorld::Ptr& ptr) const + int Book::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -102,39 +92,36 @@ namespace MWClass registerClass (typeid (ESM::Book).name(), instance); } - std::string Book::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Book::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Book Up"); } - std::string Book::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Book::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Book Down"); } - std::string Book::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Book::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Book::hasToolTip (const MWWorld::Ptr& ptr) const + bool Book::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Book::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Book::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -154,18 +141,16 @@ namespace MWClass return info; } - std::string Book::getEnchantment (const MWWorld::Ptr& ptr) const + std::string Book::getEnchantment (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mEnchant; } - std::string Book::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + std::string Book::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Book newItem = *ref->mBase; newItem.mId=""; @@ -182,33 +167,29 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionRead(ptr)); } - MWWorld::Ptr - Book::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Book::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - int Book::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Book::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mEnchant; } - bool Book::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Book::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Books) || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } - float Book::getWeight(const MWWorld::Ptr &ptr) const + float Book::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index 05ff88bb2e..73bfecacd9 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Book : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,45 +24,45 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; + virtual std::string getEnchantment (const MWWorld::ConstPtr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - virtual std::string applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + virtual std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/classes.cpp b/apps/openmw/mwclass/classes.cpp index e9538a6cb4..c303b23af0 100644 --- a/apps/openmw/mwclass/classes.cpp +++ b/apps/openmw/mwclass/classes.cpp @@ -1,4 +1,3 @@ - #include "classes.hpp" #include "activator.hpp" diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index b387a3e9f1..20ebab3145 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -1,4 +1,3 @@ - #include "clothing.hpp" #include @@ -13,7 +12,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" @@ -23,10 +22,6 @@ namespace MWClass { - std::string Clothing::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Clothing::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -35,17 +30,14 @@ namespace MWClass } } - void Clothing::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Clothing::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, true); + // TODO: add option somewhere to enable collision for placeable objects } - std::string Clothing::getModel(const MWWorld::Ptr &ptr) const + std::string Clothing::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -54,10 +46,9 @@ namespace MWClass return ""; } - std::string Clothing::getName (const MWWorld::Ptr& ptr) const + std::string Clothing::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -68,18 +59,16 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Clothing::getScript (const MWWorld::Ptr& ptr) const + std::string Clothing::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Clothing::getEquipmentSlots (const MWWorld::Ptr& ptr) const + std::pair, bool> Clothing::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); std::vector slots_; @@ -116,10 +105,9 @@ namespace MWClass return std::make_pair (slots_, false); } - int Clothing::getEquipmentSkill (const MWWorld::Ptr& ptr) const + int Clothing::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mData.mType==ESM::Clothing::Shoes) return ESM::Skill::Unarmored; @@ -127,10 +115,9 @@ namespace MWClass return -1; } - int Clothing::getValue (const MWWorld::Ptr& ptr) const + int Clothing::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -142,10 +129,9 @@ namespace MWClass registerClass (typeid (ESM::Clothing).name(), instance); } - std::string Clothing::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Clothing::getUpSoundId (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mData.mType == 8) { @@ -154,10 +140,9 @@ namespace MWClass return std::string("Item Clothes Up"); } - std::string Clothing::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Clothing::getDownSoundId (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mData.mType == 8) { @@ -166,29 +151,26 @@ namespace MWClass return std::string("Item Clothes Down"); } - std::string Clothing::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Clothing::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Clothing::hasToolTip (const MWWorld::Ptr& ptr) const + bool Clothing::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Clothing::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Clothing::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -210,18 +192,16 @@ namespace MWClass return info; } - std::string Clothing::getEnchantment (const MWWorld::Ptr& ptr) const + std::string Clothing::getEnchantment (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mEnchant; } - std::string Clothing::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + std::string Clothing::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Clothing newItem = *ref->mBase; newItem.mId=""; @@ -232,7 +212,7 @@ namespace MWClass return record->mId; } - std::pair Clothing::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const + std::pair Clothing::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { // slots that this item can be equipped in std::pair, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr); @@ -272,33 +252,29 @@ namespace MWClass return action; } - MWWorld::Ptr - Clothing::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Clothing::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - int Clothing::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Clothing::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mEnchant; } - bool Clothing::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Clothing::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Clothing) || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } - float Clothing::getWeight(const MWWorld::Ptr &ptr) const + float Clothing::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index 5700543481..e1c0f63f78 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Clothing : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,44 +24,44 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + virtual std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? - virtual int getEquipmentSkill (const MWWorld::Ptr& ptr) const; - /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + virtual int getEquipmentSkill (const MWWorld::ConstPtr& ptr) const; + /// Return the index of the skill this item corresponds to when equipped or -1, if there is /// no such skill. - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; + virtual std::string getEnchantment (const MWWorld::ConstPtr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - virtual std::string applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + virtual std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. - virtual std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; + virtual std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. /// Second item in the pair specifies the error message @@ -73,13 +69,13 @@ namespace MWClass const; ///< Generate action for using via inventory menu - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 5f49a74b6c..675287af96 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -1,4 +1,3 @@ - #include "container.hpp" #include @@ -18,37 +17,35 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/actionopen.hpp" #include "../mwworld/actiontrap.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwrender/actors.hpp" +#include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwmechanics/npcstats.hpp" -namespace +namespace MWClass { - struct ContainerCustomData : public MWWorld::CustomData + class ContainerCustomData : public MWWorld::CustomData { + public: MWWorld::ContainerStore mContainerStore; virtual MWWorld::CustomData *clone() const; + + virtual ContainerCustomData& asContainerCustomData() + { + return *this; + } }; MWWorld::CustomData *ContainerCustomData::clone() const { return new ContainerCustomData (*this); } -} - -namespace MWClass -{ - std::string Container::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Container::ensureCustomData (const MWWorld::Ptr& ptr) const { @@ -75,6 +72,7 @@ namespace MWClass ptr.get(); if (ref->mBase->mFlags & ESM::Container::Respawn) { + MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(NULL); } } @@ -93,23 +91,20 @@ namespace MWClass void Container::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { - MWRender::Actors& actors = renderingInterface.getActors(); - actors.insertActivator(ptr, model); + renderingInterface.getObjects().insertModel(ptr, model, true); } } - void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) physics.addObject(ptr, model); MWBase::Environment::get().getMechanicsManager()->add(ptr); } - std::string Container::getModel(const MWWorld::Ptr &ptr) const + std::string Container::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -147,11 +142,11 @@ namespace MWClass // make key id lowercase std::string keyId = ptr.getCellRef().getKey(); - Misc::StringUtils::toLower(keyId); + Misc::StringUtils::lowerCaseInPlace(keyId); for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::toLower(refId); + Misc::StringUtils::lowerCaseInPlace(refId); if (refId == keyId) { hasKey = true; @@ -191,10 +186,9 @@ namespace MWClass } } - std::string Container::getName (const MWWorld::Ptr& ptr) const + std::string Container::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -204,13 +198,12 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore; + return ptr.getRefData().getCustomData()->asContainerCustomData().mContainerStore; } - std::string Container::getScript (const MWWorld::Ptr& ptr) const + std::string Container::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } @@ -222,18 +215,16 @@ namespace MWClass registerClass (typeid (ESM::Container).name(), instance); } - bool Container::hasToolTip (const MWWorld::Ptr& ptr) const + bool Container::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Container::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Container::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = ref->mBase->mName; @@ -282,19 +273,22 @@ namespace MWClass ptr.getCellRef().setLockLevel(-abs(ptr.getCellRef().getLockLevel())); //Makes lockLevel negative } - - MWWorld::Ptr - Container::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + bool Container::canLock(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return true; } - void Container::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) - const + MWWorld::Ptr Container::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { + const MWWorld::LiveCellRef *ref = ptr.get(); + + return MWWorld::Ptr(cell.insert(ref), &cell); + } + + void Container::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const + { + if (!state.mHasCustomState) + return; const ESM::ContainerState& state2 = dynamic_cast (state); if (!ptr.getRefData().getCustomData()) @@ -308,14 +302,17 @@ namespace MWClass readState (state2.mInventory); } - void Container::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) - const + void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { ESM::ContainerState& state2 = dynamic_cast (state); - ensureCustomData (ptr); + if (!ptr.getRefData().getCustomData()) + { + state.mHasCustomState = false; + return; + } - dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. + dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. writeState (state2.mInventory); } } diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 52873374e4..3add65a7e4 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -10,20 +10,16 @@ namespace MWClass void ensureCustomData (const MWWorld::Ptr& ptr) const; - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -31,16 +27,16 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. virtual MWWorld::ContainerStore& getContainerStore (const MWWorld::Ptr& ptr) const; ///< Return container store - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr virtual float getCapacity (const MWWorld::Ptr& ptr) const; @@ -57,11 +53,13 @@ namespace MWClass virtual void unlock (const MWWorld::Ptr& ptr) const; ///< Unlock object + virtual bool canLock(const MWWorld::ConstPtr &ptr) const; + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const; ///< Read additional state from \a state into \a ptr. - virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + virtual void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. @@ -71,7 +69,7 @@ namespace MWClass virtual void restock (const MWWorld::Ptr &ptr) const; - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; }; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 192bdf2ce8..e9502d86b8 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -1,7 +1,6 @@ - #include "creature.hpp" -#include +#include #include #include @@ -25,11 +24,11 @@ #include "../mwworld/failedaction.hpp" #include "../mwworld/customdata.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/cellstore.hpp" #include "../mwrender/renderinginterface.hpp" -#include "../mwrender/actors.hpp" +#include "../mwrender/objects.hpp" #include "../mwgui/tooltips.hpp" @@ -37,17 +36,37 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/combat.hpp" +#include "../mwmechanics/actorutil.hpp" namespace { - struct CreatureCustomData : public MWWorld::CustomData + bool isFlagBitSet(const MWWorld::ConstPtr &ptr, ESM::Creature::Flags bitMask) { + return (ptr.get()->mBase->mFlags & bitMask) != 0; + } +} + +namespace MWClass +{ + + class CreatureCustomData : public MWWorld::CustomData + { + public: MWMechanics::CreatureStats mCreatureStats; MWWorld::ContainerStore* mContainerStore; // may be InventoryStore for some creatures MWMechanics::Movement mMovement; virtual MWWorld::CustomData *clone() const; + virtual CreatureCustomData& asCreatureCustomData() + { + return *this; + } + virtual const CreatureCustomData& asCreatureCustomData() const + { + return *this; + } + CreatureCustomData() : mContainerStore(0) {} virtual ~CreatureCustomData() { delete mContainerStore; } }; @@ -58,10 +77,7 @@ namespace cloned->mContainerStore = mContainerStore->clone(); return cloned; } -} -namespace MWClass -{ const Creature::GMST& Creature::getGmst() { static GMST gmst; @@ -129,7 +145,8 @@ namespace MWClass } // inventory - if (ref->mBase->mFlags & ESM::Creature::Weapon) + bool hasInventory = hasInventoryStore(ptr); + if (hasInventory) data->mContainerStore = new MWWorld::InventoryStore(); else data->mContainerStore = new MWWorld::ContainerStore(); @@ -141,50 +158,22 @@ namespace MWClass // store ptr.getRefData().setCustomData(data.release()); - getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr)); + getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); - if (ref->mBase->mFlags & ESM::Creature::Weapon) + if (hasInventory) getInventoryStore(ptr).autoEquip(ptr); } } - std::string Creature::getId (const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return ref->mBase->mId; - } - - void Creature::adjustPosition(const MWWorld::Ptr& ptr, bool force) const - { - MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); - } - void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = ptr.get(); - - MWRender::Actors& actors = renderingInterface.getActors(); - actors.insertCreature(ptr, model, (ref->mBase->mFlags & ESM::Creature::Weapon) != 0); + MWRender::Objects& objects = renderingInterface.getObjects(); + objects.insertCreature(ptr, model, hasInventoryStore(ptr)); } - void Creature::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + std::string Creature::getModel(const MWWorld::ConstPtr &ptr) const { - if(!model.empty()) - { - physics.addActor(ptr, model); - if (getCreatureStats(ptr).isDead()) - MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); - } - MWBase::Environment::get().getMechanicsManager()->add(ptr); - } - - std::string Creature::getModel(const MWWorld::Ptr &ptr) const - { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert (ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -193,10 +182,9 @@ namespace MWClass return ""; } - std::string Creature::getName (const MWWorld::Ptr& ptr) const + std::string Creature::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -205,11 +193,11 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mCreatureStats; + return ptr.getRefData().getCustomData()->asCreatureCustomData().mCreatureStats; } - void Creature::hit(const MWWorld::Ptr& ptr, int type) const + void Creature::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -229,7 +217,7 @@ namespace MWClass weapon = *weaponslot; } - MWMechanics::applyFatigueLoss(ptr, weapon); + MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength); // TODO: where is the distance defined? float dist = 200.f; @@ -238,7 +226,7 @@ namespace MWClass const float fCombatDistance = gmst.find("fCombatDistance")->getFloat(); dist = fCombatDistance * weapon.get()->mBase->mData.mReach; } - std::pair result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist); + std::pair result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist); if (result.first.isEmpty()) return; // Didn't hit anything @@ -247,11 +235,11 @@ namespace MWClass if (!victim.getClass().isActor()) return; // Can't hit non-actors - Ogre::Vector3 hitPosition = result.second; + osg::Vec3f hitPosition (result.second); float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat); - if(OEngine::Misc::Rng::roll0to99() >= hitchance) + if(Misc::Rng::roll0to99() >= hitchance) { victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); @@ -276,7 +264,7 @@ namespace MWClass break; } - float damage = min + (max - min) * stats.getAttackStrength(); + float damage = min + (max - min) * attackStrength; bool healthdmg = true; if (!weapon.isEmpty()) { @@ -289,7 +277,7 @@ namespace MWClass attack = weapon.get()->mBase->mData.mThrust; if(attack) { - damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength()); + damage = attack[0] + ((attack[1]-attack[0])*attackStrength); MWMechanics::adjustWeaponDamage(damage, weapon, ptr); MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); } @@ -310,12 +298,12 @@ namespace MWClass } else if (isBipedal(ptr)) { - MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg); + MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg, attackStrength); } MWMechanics::applyElementalShields(ptr, victim); - if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage)) + if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) damage = 0; if (damage > 0) @@ -343,7 +331,15 @@ namespace MWClass } if(!object.isEmpty()) - getCreatureStats(ptr).setLastHitAttemptObject(object.getClass().getId(object)); + getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId()); + + if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) + { + const std::string &script = ptr.get()->mBase->mScript; + /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ + if(!script.empty()) + ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); + } if(!successful) { @@ -353,15 +349,7 @@ namespace MWClass } if(!object.isEmpty()) - getCreatureStats(ptr).setLastHitObject(object.getClass().getId(object)); - - if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) - { - const std::string &script = ptr.get()->mBase->mScript; - /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ - if(!script.empty()) - ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); - } + getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId()); if (damage > 0.0f && !object.isEmpty()) MWMechanics::resistNormalWeapon(ptr, attacker, object, damage); @@ -377,7 +365,7 @@ namespace MWClass float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat(); float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().iKnockDownOddsMult->getInt() * 0.01f + getGmst().iKnockDownOddsBase->getInt(); - if (ishealth && agilityTerm <= damage && knockdownTerm <= OEngine::Misc::Rng::roll0to99()) + if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) { getCreatureStats(ptr).setKnockedDown(true); @@ -394,8 +382,10 @@ namespace MWClass damage = scaleDamage(damage, attacker, ptr); MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); - float health = getCreatureStats(ptr).getHealth().getCurrent() - damage; - setActorHealth(ptr, health, attacker); + + MWMechanics::DynamicStat health(getCreatureStats(ptr).getHealth()); + health.setCurrent(health.getCurrent() - damage); + getCreatureStats(ptr).setHealth(health); } else { @@ -406,50 +396,6 @@ namespace MWClass } } - void Creature::block(const MWWorld::Ptr &ptr) const - { - MWWorld::InventoryStore& inv = getInventoryStore(ptr); - MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if (shield == inv.end()) - return; - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - switch(shield->getClass().getEquipmentSkill(*shield)) - { - case ESM::Skill::LightArmor: - sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f); - break; - case ESM::Skill::MediumArmor: - sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f); - break; - case ESM::Skill::HeavyArmor: - sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f); - break; - default: - return; - } - } - - void Creature::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const - { - MWMechanics::CreatureStats &crstats = getCreatureStats(ptr); - bool wasDead = crstats.isDead(); - - MWMechanics::DynamicStat stat(crstats.getHealth()); - stat.setCurrent(health); - crstats.setHealth(stat); - - if(!wasDead && crstats.isDead()) - { - // actor was just killed - } - else if(wasDead && !crstats.isDead()) - { - // actor was just resurrected - } - } - - boost::shared_ptr Creature::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { @@ -475,14 +421,12 @@ namespace MWClass { ensureCustomData (ptr); - return *dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore; + return *ptr.getRefData().getCustomData()->asCreatureCustomData().mContainerStore; } MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr &ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); - - if (ref->mBase->mFlags & ESM::Creature::Weapon) + if (hasInventoryStore(ptr)) return dynamic_cast(getContainerStore(ptr)); else throw std::runtime_error("this creature has no inventory store"); @@ -490,24 +434,19 @@ namespace MWClass bool Creature::hasInventoryStore(const MWWorld::Ptr &ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); - - return (ref->mBase->mFlags & ESM::Creature::Weapon) != 0; + return isFlagBitSet(ptr, ESM::Creature::Weapon); } - std::string Creature::getScript (const MWWorld::Ptr& ptr) const + std::string Creature::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - bool Creature::isEssential (const MWWorld::Ptr& ptr) const + bool Creature::isEssential (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return (ref->mBase->mFlags & ESM::Creature::Essential) != 0; + return isFlagBitSet(ptr, ESM::Creature::Essential); } void Creature::registerSelf() @@ -517,11 +456,6 @@ namespace MWClass registerClass (typeid (ESM::Creature).name(), instance); } - bool Creature::hasToolTip (const MWWorld::Ptr& ptr) const - { - return !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat() || getCreatureStats(ptr).isDead(); - } - float Creature::getSpeed(const MWWorld::Ptr &ptr) const { MWMechanics::CreatureStats& stats = getCreatureStats(ptr); @@ -577,33 +511,21 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mMovement; + return ptr.getRefData().getCustomData()->asCreatureCustomData().mMovement; } - Ogre::Vector3 Creature::getMovementVector (const MWWorld::Ptr& ptr) const + bool Creature::hasToolTip(const MWWorld::ConstPtr& ptr) const { - MWMechanics::Movement &movement = getMovementSettings(ptr); - Ogre::Vector3 vec(movement.mPosition); - movement.mPosition[0] = 0.0f; - movement.mPosition[1] = 0.0f; - movement.mPosition[2] = 0.0f; - return vec; + if (!ptr.getRefData().getCustomData()) + return true; + + const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); + return !customData.mCreatureStats.getAiSequence().isInCombat() || customData.mCreatureStats.isDead(); } - Ogre::Vector3 Creature::getRotationVector (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWMechanics::Movement &movement = getMovementSettings(ptr); - Ogre::Vector3 vec(movement.mRotation); - movement.mRotation[0] = 0.0f; - movement.mRotation[1] = 0.0f; - movement.mRotation[2] = 0.0f; - return vec; - } - - MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = ref->mBase->mName; @@ -628,35 +550,18 @@ namespace MWClass return static_cast(stats.getAttribute(0).getModified() * 5); } - float Creature::getEncumbrance (const MWWorld::Ptr& ptr) const + int Creature::getServices(const MWWorld::ConstPtr &actor) const { - float weight = getContainerStore (ptr).getWeight(); - - const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); - - weight -= stats.getMagicEffects().get (MWMechanics::EffectKey (ESM::MagicEffect::Feather)).getMagnitude(); - - weight += stats.getMagicEffects().get (MWMechanics::EffectKey (ESM::MagicEffect::Burden)).getMagnitude(); - - if (weight<0) - weight = 0; - - return weight; - } - - - int Creature::getServices(const MWWorld::Ptr &actor) const - { - MWWorld::LiveCellRef* ref = actor.get(); + const MWWorld::LiveCellRef* ref = actor.get(); if (ref->mBase->mHasAI) return ref->mBase->mAiData.mServices; else return 0; } - bool Creature::isPersistent(const MWWorld::Ptr &actor) const + bool Creature::isPersistent(const MWWorld::ConstPtr &actor) const { - MWWorld::LiveCellRef* ref = actor.get(); + const MWWorld::LiveCellRef* ref = actor.get(); return ref->mBase->mPersistent; } @@ -671,7 +576,7 @@ namespace MWClass MWWorld::LiveCellRef* ref = ptr.get(); - const std::string& ourId = (ref->mBase->mOriginal.empty()) ? getId(ptr) : ref->mBase->mOriginal; + const std::string& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal; MWWorld::Store::iterator sound = store.begin(); while(sound != store.end()) @@ -681,7 +586,7 @@ namespace MWClass ++sound; } if(!sounds.empty()) - return sounds[OEngine::Misc::Rng::rollDice(sounds.size())]->mSound; + return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; } if (type == ESM::SoundGenerator::Land) @@ -690,45 +595,31 @@ namespace MWClass return ""; } - MWWorld::Ptr - Creature::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Creature::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Creature::isBipedal(const MWWorld::Ptr &ptr) const + bool Creature::isBipedal(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return ref->mBase->mFlags & ESM::Creature::Bipedal; + return isFlagBitSet(ptr, ESM::Creature::Bipedal); } - bool Creature::canFly(const MWWorld::Ptr &ptr) const + bool Creature::canFly(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return (ref->mBase->mFlags & ESM::Creature::Flies) != 0; + return isFlagBitSet(ptr, ESM::Creature::Flies); } - bool Creature::canSwim(const MWWorld::Ptr &ptr) const + bool Creature::canSwim(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return ref->mBase->mFlags & ESM::Creature::Swims || ref->mBase->mFlags & ESM::Creature::Bipedal; + return isFlagBitSet(ptr, static_cast(ESM::Creature::Swims | ESM::Creature::Bipedal)); } - bool Creature::canWalk(const MWWorld::Ptr &ptr) const + bool Creature::canWalk(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return ref->mBase->mFlags & ESM::Creature::Walks || ref->mBase->mFlags & ESM::Creature::Bipedal; + return isFlagBitSet(ptr, static_cast(ESM::Creature::Walks | ESM::Creature::Bipedal)); } int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name) @@ -736,7 +627,7 @@ namespace MWClass if(name == "left") { MWBase::World *world = MWBase::Environment::get().getWorld(); - Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); + osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) return 2; if(world->isOnGround(ptr)) @@ -746,7 +637,7 @@ namespace MWClass if(name == "right") { MWBase::World *world = MWBase::Environment::get().getWorld(); - Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); + osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) return 3; if(world->isOnGround(ptr)) @@ -789,13 +680,13 @@ namespace MWClass } } - int Creature::getBloodTexture(const MWWorld::Ptr &ptr) const + int Creature::getBloodTexture(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + int flags = ptr.get()->mBase->mFlags; - if (ref->mBase->mFlags & ESM::Creature::Skeleton) + if (flags & ESM::Creature::Skeleton) return 1; - if (ref->mBase->mFlags & ESM::Creature::Metal) + if (flags & ESM::Creature::Metal) return 2; return 0; } @@ -808,35 +699,31 @@ namespace MWClass const ESM::CreatureState& state2 = dynamic_cast (state); - ensureCustomData(ptr); - - // If we do the following instead we get a sizable speedup, but this causes compatibility issues - // with 0.30 savegames, where some state in CreatureStats was not saved yet, - // and therefore needs to be loaded from ESM records. TODO: re-enable this in a future release. - /* - if (!ptr.getRefData().getCustomData()) + if (state.mVersion > 0) { - // Create a CustomData, but don't fill it from ESM records (not needed) - std::auto_ptr data (new CreatureCustomData); + if (!ptr.getRefData().getCustomData()) + { + // Create a CustomData, but don't fill it from ESM records (not needed) + std::auto_ptr data (new CreatureCustomData); - MWWorld::LiveCellRef *ref = ptr.get(); + if (hasInventoryStore(ptr)) + data->mContainerStore = new MWWorld::InventoryStore(); + else + data->mContainerStore = new MWWorld::ContainerStore(); - if (ref->mBase->mFlags & ESM::Creature::Weapon) - data->mContainerStore = new MWWorld::InventoryStore(); - else - data->mContainerStore = new MWWorld::ContainerStore(); - - ptr.getRefData().setCustomData (data.release()); + ptr.getRefData().setCustomData (data.release()); + } } - */ + else + ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. - CreatureCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); customData.mContainerStore->readState (state2.mInventory); customData.mCreatureStats.readState (state2.mCreatureStats); } - void Creature::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + void Creature::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { ESM::CreatureState& state2 = dynamic_cast (state); @@ -847,22 +734,20 @@ namespace MWClass return; } - ensureCustomData (ptr); - - CreatureCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); customData.mContainerStore->writeState (state2.mInventory); customData.mCreatureStats.writeState (state2.mCreatureStats); } - int Creature::getBaseGold(const MWWorld::Ptr& ptr) const + int Creature::getBaseGold(const MWWorld::ConstPtr& ptr) const { return ptr.get()->mBase->mData.mGold; } void Creature::respawn(const MWWorld::Ptr &ptr) const { - if (ptr.get()->mBase->mFlags & ESM::Creature::Respawn) + if (isFlagBitSet(ptr, ESM::Creature::Respawn)) { // Note we do not respawn moved references in the cell they were moved to. Instead they are respawned in the original cell. // This also means we cannot respawn dynamically placed references with no content file connection. @@ -874,6 +759,7 @@ namespace MWClass // Reset to original position ptr.getRefData().setPosition(ptr.getCellRef().getPosition()); + MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(NULL); } } @@ -887,15 +773,15 @@ namespace MWClass store.restock(list, ptr, ptr.getCellRef().getRefId()); } - int Creature::getBaseFightRating(const MWWorld::Ptr &ptr) const + int Creature::getBaseFightRating(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mAiData.mFight; } - void Creature::adjustScale(const MWWorld::Ptr &ptr, float &scale) const + void Creature::adjustScale(const MWWorld::ConstPtr &ptr, osg::Vec3f &scale, bool /* rendering */) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); scale *= ref->mBase->mScale; } } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index e11529b2e8..cb89a53d62 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWCLASS_CREATURE_H #define GAME_MWCLASS_CREATURE_H -#include "../mwworld/class.hpp" +#include "actor.hpp" namespace ESM { @@ -10,12 +10,11 @@ namespace ESM namespace MWClass { - class Creature : public MWWorld::Class + class Creature : public Actor { void ensureCustomData (const MWWorld::Ptr& ptr) const; - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; static int getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name); @@ -41,39 +40,26 @@ namespace MWClass public: - virtual std::string getId (const MWWorld::Ptr& ptr) const; - ///< Return ID of \a ptr - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; - - virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) const; - ///< Adjust position to stand on ground. Must be called post model load - /// @param force do this even if the ptr is flying - - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip(const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. virtual MWMechanics::CreatureStats& getCreatureStats (const MWWorld::Ptr& ptr) const; ///< Return creature stats - virtual void hit(const MWWorld::Ptr& ptr, int type) const; - - virtual void block(const MWWorld::Ptr &ptr) const; + virtual void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const; virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const; - virtual void setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const; - virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; ///< Generate action for activation @@ -87,77 +73,65 @@ namespace MWClass virtual bool hasInventoryStore (const MWWorld::Ptr &ptr) const; - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr virtual float getCapacity (const MWWorld::Ptr& ptr) const; ///< Return total weight that fits into the object. Throws an exception, if the object can't /// hold other objects. - virtual float getEncumbrance (const MWWorld::Ptr& ptr) const; - ///< Returns total weight of objects inside this object (including modifications from magic - /// effects). Throws an exception, if the object can't hold other objects. - virtual float getArmorRating (const MWWorld::Ptr& ptr) const; ///< @return combined armor rating of this actor - virtual bool isEssential (const MWWorld::Ptr& ptr) const; + virtual bool isEssential (const MWWorld::ConstPtr& ptr) const; ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) - virtual int getServices (const MWWorld::Ptr& actor) const; + virtual int getServices (const MWWorld::ConstPtr& actor) const; - virtual bool isPersistent (const MWWorld::Ptr& ptr) const; + virtual bool isPersistent (const MWWorld::ConstPtr& ptr) const; virtual std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const; virtual MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const; ///< Return desired movement. - virtual Ogre::Vector3 getMovementVector (const MWWorld::Ptr& ptr) const; - ///< Return desired movement vector (determined based on movement settings, - /// stance and stats). - - virtual Ogre::Vector3 getRotationVector (const MWWorld::Ptr& ptr) const; - ///< Return desired rotations, as euler angles. - float getSpeed (const MWWorld::Ptr& ptr) const; static void registerSelf(); - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; virtual bool isActor() const { return true; } - virtual bool isBipedal (const MWWorld::Ptr &ptr) const; - virtual bool canFly (const MWWorld::Ptr &ptr) const; - virtual bool canSwim (const MWWorld::Ptr &ptr) const; - virtual bool canWalk (const MWWorld::Ptr &ptr) const; + virtual bool isBipedal (const MWWorld::ConstPtr &ptr) const; + virtual bool canFly (const MWWorld::ConstPtr &ptr) const; + virtual bool canSwim (const MWWorld::ConstPtr &ptr) const; + virtual bool canWalk (const MWWorld::ConstPtr &ptr) const; virtual int getSkill(const MWWorld::Ptr &ptr, int skill) const; /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) - virtual int getBloodTexture (const MWWorld::Ptr& ptr) const; + virtual int getBloodTexture (const MWWorld::ConstPtr& ptr) const; - virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) - const; + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const; ///< Read additional state from \a state into \a ptr. - virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) - const; + virtual void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. - virtual int getBaseGold(const MWWorld::Ptr& ptr) const; + virtual int getBaseGold(const MWWorld::ConstPtr& ptr) const; virtual void respawn (const MWWorld::Ptr& ptr) const; virtual void restock (const MWWorld::Ptr &ptr) const; - virtual int getBaseFightRating(const MWWorld::Ptr &ptr) const; + virtual int getBaseFightRating(const MWWorld::ConstPtr &ptr) const; - virtual void adjustScale(const MWWorld::Ptr& ptr,float& scale) const; + virtual void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const; + /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh }; } diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index dbc4b6af7b..db2ba45bc7 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -1,4 +1,3 @@ - #include "creaturelevlist.hpp" #include @@ -8,31 +7,33 @@ #include "../mwworld/customdata.hpp" -namespace +namespace MWClass { - struct CreatureLevListCustomData : public MWWorld::CustomData + class CreatureLevListCustomData : public MWWorld::CustomData { + public: // actorId of the creature we spawned int mSpawnActorId; bool mSpawn; // Should a new creature be spawned? virtual MWWorld::CustomData *clone() const; + + virtual CreatureLevListCustomData& asCreatureLevListCustomData() + { + return *this; + } + virtual const CreatureLevListCustomData& asCreatureLevListCustomData() const + { + return *this; + } }; MWWorld::CustomData *CreatureLevListCustomData::clone() const { return new CreatureLevListCustomData (*this); } -} -namespace MWClass -{ - std::string CreatureLevList::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } - - std::string CreatureLevList::getName (const MWWorld::Ptr& ptr) const + std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const { return ""; } @@ -41,7 +42,7 @@ namespace MWClass { ensureCustomData(ptr); - CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); customData.mSpawn = true; } @@ -56,7 +57,7 @@ namespace MWClass { ensureCustomData(ptr); - CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); if (!customData.mSpawn) return; @@ -102,21 +103,29 @@ namespace MWClass void CreatureLevList::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { + if (!state.mHasCustomState) + return; + const ESM::CreatureLevListState& state2 = dynamic_cast (state); ensureCustomData(ptr); - CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); customData.mSpawnActorId = state2.mSpawnActorId; customData.mSpawn = state2.mSpawn; } - void CreatureLevList::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + void CreatureLevList::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { ESM::CreatureLevListState& state2 = dynamic_cast (state); - ensureCustomData(ptr); - CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + if (!ptr.getRefData().getCustomData()) + { + state.mHasCustomState = false; + return; + } + + const CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); state2.mSpawnActorId = customData.mSpawnActorId; state2.mSpawn = customData.mSpawn; } diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index 177aa72359..67a7858d87 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -11,10 +11,7 @@ namespace MWClass public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -23,12 +20,10 @@ namespace MWClass virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) - const; + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const; ///< Read additional state from \a state into \a ptr. - virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) - const; + virtual void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. virtual void respawn (const MWWorld::Ptr& ptr) const; diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 48fc3b64c1..8d54dff5d0 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -1,4 +1,3 @@ - #include "door.hpp" #include @@ -17,55 +16,58 @@ #include "../mwworld/actiondoor.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/actiontrap.hpp" #include "../mwworld/customdata.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwrender/actors.hpp" +#include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" -namespace +#include "../mwmechanics/actorutil.hpp" + +namespace MWClass { - struct DoorCustomData : public MWWorld::CustomData + class DoorCustomData : public MWWorld::CustomData { + public: int mDoorState; // 0 = nothing, 1 = opening, 2 = closing virtual MWWorld::CustomData *clone() const; + + virtual DoorCustomData& asDoorCustomData() + { + return *this; + } + virtual const DoorCustomData& asDoorCustomData() const + { + return *this; + } }; MWWorld::CustomData *DoorCustomData::clone() const { return new DoorCustomData (*this); } -} - -namespace MWClass -{ - std::string Door::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { - MWRender::Actors& actors = renderingInterface.getActors(); - actors.insertActivator(ptr, model); + renderingInterface.getObjects().insertModel(ptr, model, true); } } - void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model); + physics.addObject(ptr, model, MWPhysics::CollisionType_Door); // Resume the door's opening/closing animation if it wasn't finished if (ptr.getRefData().getCustomData()) { - const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); if (customData.mDoorState > 0) { MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState); @@ -75,11 +77,9 @@ namespace MWClass MWBase::Environment::get().getMechanicsManager()->add(ptr); } - std::string Door::getModel(const MWWorld::Ptr &ptr) const + std::string Door::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -88,10 +88,9 @@ namespace MWClass return ""; } - std::string Door::getName (const MWWorld::Ptr& ptr) const + std::string Door::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -114,11 +113,11 @@ namespace MWClass // make key id lowercase std::string keyId = ptr.getCellRef().getKey(); - Misc::StringUtils::toLower(keyId); + Misc::StringUtils::lowerCaseInPlace(keyId); for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::toLower(refId); + Misc::StringUtils::lowerCaseInPlace(refId); if (refId == keyId) { hasKey = true; @@ -128,7 +127,7 @@ namespace MWClass if (needKey && hasKey) { - if(actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(actor == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox(keyName + " #{sKeyUsed}"); unlock(ptr); //Call the function here. because that makes sense. // using a key disarms the trap @@ -159,16 +158,19 @@ namespace MWClass boost::shared_ptr action(new MWWorld::ActionDoor(ptr)); int doorstate = getDoorState(ptr); bool opening = true; + float doorRot = ptr.getRefData().getPosition().rot[2] - ptr.getCellRef().getPosition().rot[2]; if (doorstate == 1) opening = false; - if (doorstate == 0 && ptr.getRefData().getLocalRotation().rot[2] != 0) + if (doorstate == 0 && doorRot != 0) opening = false; if (opening) { MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr, closeSound, 0.5f); - float offset = ptr.getRefData().getLocalRotation().rot[2]/ 3.14159265f * 2.0f; + // Doors rotate at 90 degrees per second, so start the sound at + // where it would be at the current rotation. + float offset = doorRot/(3.14159265f * 0.5f); action->setSoundOffset(offset); action->setSound(openSound); } @@ -176,10 +178,8 @@ namespace MWClass { MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr, openSound, 0.5f); - float offset = 1.0f - ptr.getRefData().getLocalRotation().rot[2]/ 3.14159265f * 2.0f; - //most if not all door have closing bang somewhere in the middle of the sound, - //so we divide offset by two - action->setSoundOffset(offset * 0.5f); + float offset = 1.0f - doorRot/(3.14159265f * 0.5f); + action->setSoundOffset(std::max(offset, 0.0f)); action->setSound(closeSound); } @@ -208,10 +208,14 @@ namespace MWClass ptr.getCellRef().setLockLevel(-abs(ptr.getCellRef().getLockLevel())); //Makes lockLevel negative } - std::string Door::getScript (const MWWorld::Ptr& ptr) const + bool Door::canLock(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + return true; + } + + std::string Door::getScript (const MWWorld::ConstPtr& ptr) const + { + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } @@ -223,18 +227,16 @@ namespace MWClass registerClass (typeid (ESM::Door).name(), instance); } - bool Door::hasToolTip (const MWWorld::Ptr& ptr) const + bool Door::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Door::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Door::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; info.caption = ref->mBase->mName; @@ -295,13 +297,11 @@ namespace MWClass return "#{sCell=" + dest + "}"; } - MWWorld::Ptr - Door::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Door::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } void Door::ensureCustomData(const MWWorld::Ptr &ptr) const @@ -315,10 +315,11 @@ namespace MWClass } } - int Door::getDoorState (const MWWorld::Ptr &ptr) const + int Door::getDoorState (const MWWorld::ConstPtr &ptr) const { - ensureCustomData(ptr); - const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + if (!ptr.getRefData().getCustomData()) + return 0; + const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); return customData.mDoorState; } @@ -328,23 +329,29 @@ namespace MWClass throw std::runtime_error("load doors can't be moved"); ensureCustomData(ptr); - DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); customData.mDoorState = state; } void Door::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { + if (!state.mHasCustomState) + return; ensureCustomData(ptr); - DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); const ESM::DoorState& state2 = dynamic_cast(state); customData.mDoorState = state2.mDoorState; } - void Door::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const + void Door::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { - ensureCustomData(ptr); - const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + if (!ptr.getRefData().getCustomData()) + { + state.mHasCustomState = false; + return; + } + const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData(); ESM::DoorState& state2 = dynamic_cast(state); state2.mDoorState = customData.mDoorState; diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index c5f258d3e0..42aa6d64d3 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -11,20 +11,16 @@ namespace MWClass { void ensureCustomData (const MWWorld::Ptr& ptr) const; - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -32,10 +28,10 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. static std::string getDestination (const MWWorld::LiveCellRef& door); @@ -47,15 +43,17 @@ namespace MWClass virtual void unlock (const MWWorld::Ptr& ptr) const; ///< Unlock object - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual bool canLock(const MWWorld::ConstPtr &ptr) const; + + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr static void registerSelf(); - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; /// 0 = nothing, 1 = opening, 2 = closing - virtual int getDoorState (const MWWorld::Ptr &ptr) const; + virtual int getDoorState (const MWWorld::ConstPtr &ptr) const; /// This does not actually cause the door to move. Use World::activateDoor instead. virtual void setDoorState (const MWWorld::Ptr &ptr, int state) const; @@ -64,7 +62,7 @@ namespace MWClass const; ///< Read additional state from \a state into \a ptr. - virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + virtual void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. }; diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index de43e818e9..99a50a67a0 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -1,4 +1,3 @@ - #include "ingredient.hpp" #include @@ -11,7 +10,7 @@ #include "../mwworld/actiontake.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/actioneat.hpp" #include "../mwworld/nullaction.hpp" @@ -24,13 +23,6 @@ namespace MWClass { - std::string Ingredient::getId (const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return ref->mBase->mId; - } void Ingredient::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -39,17 +31,14 @@ namespace MWClass } } - void Ingredient::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Ingredient::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, true); + // TODO: add option somewhere to enable collision for placeable objects } - std::string Ingredient::getModel(const MWWorld::Ptr &ptr) const + std::string Ingredient::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -58,10 +47,9 @@ namespace MWClass return ""; } - std::string Ingredient::getName (const MWWorld::Ptr& ptr) const + std::string Ingredient::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -72,18 +60,16 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Ingredient::getScript (const MWWorld::Ptr& ptr) const + std::string Ingredient::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - int Ingredient::getValue (const MWWorld::Ptr& ptr) const + int Ingredient::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -105,39 +91,36 @@ namespace MWClass registerClass (typeid (ESM::Ingredient).name(), instance); } - std::string Ingredient::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Ingredient::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Ingredient Up"); } - std::string Ingredient::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Ingredient::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Ingredient Down"); } - std::string Ingredient::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Ingredient::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Ingredient::hasToolTip (const MWWorld::Ptr& ptr) const + bool Ingredient::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Ingredient::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Ingredient::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -181,25 +164,22 @@ namespace MWClass return info; } - MWWorld::Ptr - Ingredient::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Ingredient::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Ingredient::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Ingredient::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Ingredients) != 0; } - float Ingredient::getWeight(const MWWorld::Ptr &ptr) const + float Ingredient::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index a4681f4621..9a8bdacfb8 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Ingredient : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - virtual std::string getId (const MWWorld::Ptr& ptr) const; - ///< Return ID of \a ptr - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,16 +24,16 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) @@ -46,20 +42,20 @@ namespace MWClass static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/itemlevlist.cpp b/apps/openmw/mwclass/itemlevlist.cpp index d31080bb2c..290ac4c26d 100644 --- a/apps/openmw/mwclass/itemlevlist.cpp +++ b/apps/openmw/mwclass/itemlevlist.cpp @@ -1,16 +1,11 @@ - #include "itemlevlist.hpp" #include namespace MWClass { - std::string ItemLevList::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } - std::string ItemLevList::getName (const MWWorld::Ptr& ptr) const + std::string ItemLevList::getName (const MWWorld::ConstPtr& ptr) const { return ""; } diff --git a/apps/openmw/mwclass/itemlevlist.hpp b/apps/openmw/mwclass/itemlevlist.hpp index 2b507135fd..a8b313185c 100644 --- a/apps/openmw/mwclass/itemlevlist.hpp +++ b/apps/openmw/mwclass/itemlevlist.hpp @@ -9,10 +9,7 @@ namespace MWClass { public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 90c708f970..c7ebc184fd 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -1,8 +1,8 @@ - #include "light.hpp" #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -17,21 +17,16 @@ #include "../mwworld/failedaction.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/customdata.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" -#include "../mwrender/actors.hpp" #include "../mwrender/renderinginterface.hpp" namespace MWClass { - std::string Light::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Light::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -39,18 +34,18 @@ namespace MWClass ptr.get(); // Insert even if model is empty, so that the light is added - MWRender::Actors& actors = renderingInterface.getActors(); - actors.insertActivator(ptr, model, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); + renderingInterface.getObjects().insertModel(ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } - void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { MWWorld::LiveCellRef *ref = ptr.get(); assert (ref->mBase != NULL); - if(!model.empty()) - physics.addObject(ptr, model, (ref->mBase->mData.mFlags & ESM::Light::Carry) != 0); + // TODO: add option somewhere to enable collision for placeable objects + if (!model.empty() && (ref->mBase->mData.mFlags & ESM::Light::Carry) == 0) + physics.addObject(ptr, model); if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, @@ -60,11 +55,9 @@ namespace MWClass MWBase::Environment::get().getMechanicsManager()->add(ptr); } - std::string Light::getModel(const MWWorld::Ptr &ptr) const + std::string Light::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert (ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -73,10 +66,9 @@ namespace MWClass return ""; } - std::string Light::getName (const MWWorld::Ptr& ptr) const + std::string Light::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mModel.empty()) return ""; @@ -97,18 +89,16 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Light::getScript (const MWWorld::Ptr& ptr) const + std::string Light::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Light::getEquipmentSlots (const MWWorld::Ptr& ptr) const + std::pair, bool> Light::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); std::vector slots_; @@ -118,10 +108,9 @@ namespace MWClass return std::make_pair (slots_, false); } - int Light::getValue (const MWWorld::Ptr& ptr) const + int Light::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -133,44 +122,43 @@ namespace MWClass registerClass (typeid (ESM::Light).name(), instance); } - std::string Light::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Light::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Misc Up"); } - std::string Light::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Light::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Misc Down"); } - std::string Light::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Light::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Light::hasToolTip (const MWWorld::Ptr& ptr) const + bool Light::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Light::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Light::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; + if (Settings::Manager::getBool("show effect duration","Game")) + text += "\n#{sDuration}: " + MWGui::ToolTips::toString(ptr.getClass().getRemainingUsageTime(ptr)); if (ref->mBase->mData.mWeight != 0) { text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); @@ -201,38 +189,39 @@ namespace MWClass ptr.getCellRef().setChargeFloat(duration); } - float Light::getRemainingUsageTime (const MWWorld::Ptr& ptr) const + float Light::getRemainingUsageTime (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); if (ptr.getCellRef().getCharge() == -1) return static_cast(ref->mBase->mData.mTime); else return ptr.getCellRef().getChargeFloat(); } - MWWorld::Ptr - Light::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Light::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Light::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Light::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Lights) != 0; } - float Light::getWeight(const MWWorld::Ptr &ptr) const + float Light::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } - std::pair Light::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const + std::pair Light::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { + const MWWorld::LiveCellRef *ref = ptr.get(); + if (!(ref->mBase->mData.mFlags & ESM::Light::Carry)) + return std::make_pair(0,""); + MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc); MWWorld::ContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); @@ -254,7 +243,7 @@ namespace MWClass return std::make_pair(1,""); } - std::string Light::getSound(const MWWorld::Ptr& ptr) const + std::string Light::getSound(const MWWorld::ConstPtr& ptr) const { return ptr.get()->mBase->mSound; } diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 8658375b77..5ec21f41fd 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -7,52 +7,48 @@ namespace MWClass { class Light : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + virtual std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) @@ -62,18 +58,18 @@ namespace MWClass virtual void setRemainingUsageTime (const MWWorld::Ptr& ptr, float duration) const; ///< Sets the remaining duration of the object. - virtual float getRemainingUsageTime (const MWWorld::Ptr& ptr) const; + virtual float getRemainingUsageTime (const MWWorld::ConstPtr& ptr) const; ///< Returns the remaining duration of the object. - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; - std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; + std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; - virtual std::string getSound(const MWWorld::Ptr& ptr) const; + virtual std::string getSound(const MWWorld::ConstPtr& ptr) const; }; } diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 478c50301d..d3889d2fcd 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -1,4 +1,3 @@ - #include "lockpick.hpp" #include @@ -12,7 +11,7 @@ #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" @@ -22,10 +21,6 @@ namespace MWClass { - std::string Lockpick::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Lockpick::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -34,17 +29,14 @@ namespace MWClass } } - void Lockpick::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Lockpick::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, true); + // TODO: add option somewhere to enable collision for placeable objects } - std::string Lockpick::getModel(const MWWorld::Ptr &ptr) const + std::string Lockpick::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -53,10 +45,9 @@ namespace MWClass return ""; } - std::string Lockpick::getName (const MWWorld::Ptr& ptr) const + std::string Lockpick::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -67,15 +58,14 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Lockpick::getScript (const MWWorld::Ptr& ptr) const + std::string Lockpick::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Lockpick::getEquipmentSlots (const MWWorld::Ptr& ptr) const + std::pair, bool> Lockpick::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { std::vector slots_; @@ -84,10 +74,9 @@ namespace MWClass return std::make_pair (slots_, false); } - int Lockpick::getValue (const MWWorld::Ptr& ptr) const + int Lockpick::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -99,39 +88,36 @@ namespace MWClass registerClass (typeid (ESM::Lockpick).name(), instance); } - std::string Lockpick::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Lockpick::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Lockpick Up"); } - std::string Lockpick::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Lockpick::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Lockpick Down"); } - std::string Lockpick::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Lockpick::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Lockpick::hasToolTip (const MWWorld::Ptr& ptr) const + bool Lockpick::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Lockpick::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Lockpick::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -162,32 +148,28 @@ namespace MWClass return action; } - MWWorld::Ptr - Lockpick::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Lockpick::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Lockpick::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Lockpick::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Picks) != 0; } - int Lockpick::getItemMaxHealth (const MWWorld::Ptr& ptr) const + int Lockpick::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mUses; } - float Lockpick::getWeight(const MWWorld::Ptr &ptr) const + float Lockpick::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index 293a40be11..f04674f5c0 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Lockpick : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,47 +24,47 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + virtual std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const; + virtual int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health - virtual bool hasItemHealth (const MWWorld::Ptr& ptr) const { return true; } + virtual bool hasItemHealth (const MWWorld::ConstPtr& ptr) const { return true; } ///< \return Item health data available? (default implementation: false) }; } diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index f5daafeec2..2f41fca303 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -1,4 +1,3 @@ - #include "misc.hpp" #include @@ -13,7 +12,7 @@ #include "../mwworld/actiontake.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/actionsoulgem.hpp" @@ -25,23 +24,15 @@ #include -namespace -{ -bool isGold (const MWWorld::Ptr& ptr) -{ - return Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_001") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_005") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_010") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_025") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_100"); -} -} - namespace MWClass { - std::string Miscellaneous::getId (const MWWorld::Ptr& ptr) const + bool Miscellaneous::isGold (const MWWorld::ConstPtr& ptr) const { - return ptr.get()->mBase->mId; + return Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_001") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_005") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_010") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_025") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_100"); } void Miscellaneous::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const @@ -51,17 +42,14 @@ namespace MWClass } } - void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, true); + // TODO: add option somewhere to enable collision for placeable objects } - std::string Miscellaneous::getModel(const MWWorld::Ptr &ptr) const + std::string Miscellaneous::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -70,10 +58,9 @@ namespace MWClass return ""; } - std::string Miscellaneous::getName (const MWWorld::Ptr& ptr) const + std::string Miscellaneous::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -84,18 +71,16 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Miscellaneous::getScript (const MWWorld::Ptr& ptr) const + std::string Miscellaneous::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - int Miscellaneous::getValue (const MWWorld::Ptr& ptr) const + int Miscellaneous::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); int value = ref->mBase->mData.mValue; if (ptr.getCellRef().getGoldValue() > 1 && ptr.getRefData().getCount() == 1) @@ -117,47 +102,42 @@ namespace MWClass registerClass (typeid (ESM::Miscellaneous).name(), instance); } - std::string Miscellaneous::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Miscellaneous::getUpSoundId (const MWWorld::ConstPtr& ptr) const { if (isGold(ptr)) return std::string("Item Gold Up"); return std::string("Item Misc Up"); } - std::string Miscellaneous::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Miscellaneous::getDownSoundId (const MWWorld::ConstPtr& ptr) const { if (isGold(ptr)) return std::string("Item Gold Down"); return std::string("Item Misc Down"); } - std::string Miscellaneous::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Miscellaneous::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Miscellaneous::hasToolTip (const MWWorld::Ptr& ptr) const + bool Miscellaneous::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Miscellaneous::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Miscellaneous::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - int count = ptr.getRefData().getCount(); - bool gold = isGold(ptr); if (gold) count *= getValue(ptr); @@ -195,8 +175,7 @@ namespace MWClass return info; } - MWWorld::Ptr - Miscellaneous::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Miscellaneous::copyToCell(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell, int count) const { MWWorld::Ptr newPtr; @@ -204,7 +183,7 @@ namespace MWClass MWBase::Environment::get().getWorld()->getStore(); if (isGold(ptr)) { - int goldAmount = getValue(ptr) * ptr.getRefData().getCount(); + int goldAmount = getValue(ptr) * count; std::string base = "Gold_001"; if (goldAmount >= 100) @@ -219,16 +198,20 @@ namespace MWClass // Really, I have no idea why moving ref out of conditional // scope causes list::push_back throwing std::bad_alloc MWWorld::ManualRef newRef(store, base); - MWWorld::LiveCellRef *ref = + const MWWorld::LiveCellRef *ref = newRef.getPtr().get(); - newPtr = MWWorld::Ptr(&cell.get().insert(*ref), &cell); + + newPtr = MWWorld::Ptr(cell.insert(ref), &cell); newPtr.getCellRef().setGoldValue(goldAmount); newPtr.getRefData().setCount(1); } else { - MWWorld::LiveCellRef *ref = + const MWWorld::LiveCellRef *ref = ptr.get(); - newPtr = MWWorld::Ptr(&cell.get().insert(*ref), &cell); + newPtr = MWWorld::Ptr(cell.insert(ref), &cell); + newPtr.getRefData().setCount(count); } + newPtr.getCellRef().unsetRefNum(); + return newPtr; } @@ -240,25 +223,22 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionSoulgem(ptr)); } - bool Miscellaneous::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Miscellaneous::canSell (const MWWorld::ConstPtr& item, int npcServices) const { - MWWorld::LiveCellRef *ref = - item.get(); + const MWWorld::LiveCellRef *ref = item.get(); return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc) && !isGold(item); } - float Miscellaneous::getWeight(const MWWorld::Ptr &ptr) const + float Miscellaneous::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } - bool Miscellaneous::isKey(const MWWorld::Ptr &ptr) const + bool Miscellaneous::isKey(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mIsKey != 0; } diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 23160d41c6..2a534f058b 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Miscellaneous : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; - public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; + virtual MWWorld::Ptr copyToCell(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell, int count) const; virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,40 +24,42 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; - virtual bool isKey (const MWWorld::Ptr &ptr) const; + virtual bool isKey (const MWWorld::ConstPtr &ptr) const; + + virtual bool isGold (const MWWorld::ConstPtr& ptr) const; }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 3ca57aca88..57a6d088ab 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1,11 +1,8 @@ - #include "npc.hpp" #include -#include - -#include +#include #include #include @@ -27,6 +24,7 @@ #include "../mwmechanics/autocalcspell.hpp" #include "../mwmechanics/difficultyscaling.hpp" #include "../mwmechanics/character.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actiontalk.hpp" @@ -34,29 +32,16 @@ #include "../mwworld/failedaction.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/customdata.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwrender/actors.hpp" +#include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" namespace { - struct NpcCustomData : public MWWorld::CustomData - { - MWMechanics::NpcStats mNpcStats; - MWMechanics::Movement mMovement; - MWWorld::InventoryStore mInventoryStore; - - virtual MWWorld::CustomData *clone() const; - }; - - MWWorld::CustomData *NpcCustomData::clone() const - { - return new NpcCustomData (*this); - } int is_even(double d) { double int_part; @@ -253,6 +238,31 @@ namespace namespace MWClass { + + class NpcCustomData : public MWWorld::CustomData + { + public: + MWMechanics::NpcStats mNpcStats; + MWMechanics::Movement mMovement; + MWWorld::InventoryStore mInventoryStore; + + virtual MWWorld::CustomData *clone() const; + + virtual NpcCustomData& asNpcCustomData() + { + return *this; + } + virtual const NpcCustomData& asNpcCustomData() const + { + return *this; + } + }; + + MWWorld::CustomData *NpcCustomData::clone() const + { + return new NpcCustomData (*this); + } + const Npc::GMST& Npc::getGmst() { static GMST gmst; @@ -385,7 +395,7 @@ namespace MWClass // inventory // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items - data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr)); + data->mInventoryStore.fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); data->mNpcStats.setGoldPool(gold); @@ -396,43 +406,20 @@ namespace MWClass } } - std::string Npc::getId (const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = - ptr.get(); - - return ref->mBase->mId; - } - - void Npc::adjustPosition(const MWWorld::Ptr& ptr, bool force) const - { - MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); - } - void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - renderingInterface.getActors().insertNPC(ptr); + renderingInterface.getObjects().insertNPC(ptr); } - void Npc::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + bool Npc::isPersistent(const MWWorld::ConstPtr &actor) const { - physics.addActor(ptr, model); - MWBase::Environment::get().getMechanicsManager()->add(ptr); - if (getCreatureStats(ptr).isDead()) - MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); - } - - bool Npc::isPersistent(const MWWorld::Ptr &actor) const - { - MWWorld::LiveCellRef* ref = actor.get(); + const MWWorld::LiveCellRef* ref = actor.get(); return ref->mBase->mPersistent; } - std::string Npc::getModel(const MWWorld::Ptr &ptr) const + std::string Npc::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); std::string model = "meshes\\base_anim.nif"; const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); @@ -440,12 +427,11 @@ namespace MWClass model = "meshes\\base_animkna.nif"; return model; - } - std::string Npc::getName (const MWWorld::Ptr& ptr) const + std::string Npc::getName (const MWWorld::ConstPtr& ptr) const { - if(getNpcStats(ptr).isWerewolf()) + if(ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) { const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &store = world->getStore().get(); @@ -453,7 +439,7 @@ namespace MWClass return store.find("sWerewolfPopup")->getString(); } - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -461,18 +447,18 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mNpcStats; + return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats; } MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mNpcStats; + return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats; } - void Npc::hit(const MWWorld::Ptr& ptr, int type) const + void Npc::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const { MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -485,7 +471,7 @@ namespace MWClass if(!weapon.isEmpty() && weapon.getTypeName() != typeid(ESM::Weapon).name()) weapon = MWWorld::Ptr(); - MWMechanics::applyFatigueLoss(ptr, weapon); + MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength); const float fCombatDistance = store.find("fCombatDistance")->getFloat(); float dist = fCombatDistance * (!weapon.isEmpty() ? @@ -493,9 +479,9 @@ namespace MWClass store.find("fHandToHandReach")->getFloat()); // TODO: Use second to work out the hit angle - std::pair result = world->getHitContact(ptr, dist); + std::pair result = world->getHitContact(ptr, dist); MWWorld::Ptr victim = result.first; - Ogre::Vector3 hitPosition = result.second; + osg::Vec3f hitPosition (result.second); if(victim.isEmpty()) // Didn't hit anything return; @@ -506,7 +492,7 @@ namespace MWClass if(otherstats.isDead()) // Can't hit dead actors return; - if(ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(ptr == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->setEnemy(victim); int weapskill = ESM::Skill::HandToHand; @@ -515,7 +501,7 @@ namespace MWClass float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.getClass().getSkill(ptr, weapskill)); - if (OEngine::Misc::Rng::roll0to99() >= hitchance) + if (Misc::Rng::roll0to99() >= hitchance) { othercls.onHit(victim, 0.0f, false, weapon, ptr, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); @@ -524,7 +510,6 @@ namespace MWClass bool healthdmg; float damage = 0.0f; - MWMechanics::NpcStats &stats = getNpcStats(ptr); if(!weapon.isEmpty()) { const unsigned char *attack = NULL; @@ -536,7 +521,7 @@ namespace MWClass attack = weapon.get()->mBase->mData.mThrust; if(attack) { - damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength()); + damage = attack[0] + ((attack[1]-attack[0])*attackStrength); } MWMechanics::adjustWeaponDamage(damage, weapon, ptr); MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); @@ -544,9 +529,9 @@ namespace MWClass } else { - MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg); + MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg, attackStrength); } - if(ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(ptr == MWMechanics::getPlayer()) { skillUsageSucceeded(ptr, weapskill, 0); @@ -581,7 +566,7 @@ namespace MWClass MWMechanics::applyElementalShields(ptr, victim); - if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage)) + if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) damage = 0; if (healthdmg && damage > 0) @@ -610,7 +595,15 @@ namespace MWClass } if(!object.isEmpty()) - getCreatureStats(ptr).setLastHitAttemptObject(object.getClass().getId(object)); + getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId()); + + if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) + { + const std::string &script = ptr.getClass().getScript(ptr); + /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ + if(!script.empty()) + ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); + } if(!successful) { @@ -620,15 +613,8 @@ namespace MWClass } if(!object.isEmpty()) - getCreatureStats(ptr).setLastHitObject(object.getClass().getId(object)); + getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId()); - if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) - { - const std::string &script = ptr.getClass().getScript(ptr); - /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ - if(!script.empty()) - ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); - } if (damage > 0.0f && !object.isEmpty()) MWMechanics::resistNormalWeapon(ptr, attacker, object, damage); @@ -645,7 +631,7 @@ namespace MWClass const GMST& gmst = getGmst(); int chance = store.get().find("iVoiceHitOdds")->getInt(); - if (OEngine::Misc::Rng::roll0to99() < chance) + if (Misc::Rng::roll0to99() < chance) { MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); } @@ -654,7 +640,7 @@ namespace MWClass float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat(); float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * gmst.iKnockDownOddsMult->getInt() * 0.01f + gmst.iKnockDownOddsBase->getInt(); - if (ishealth && agilityTerm <= damage && knockdownTerm <= OEngine::Misc::Rng::roll0to99()) + if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) { getCreatureStats(ptr).setKnockedDown(true); @@ -680,7 +666,7 @@ namespace MWClass MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet }; - int hitslot = hitslots[OEngine::Misc::Rng::rollDice(20)]; + int hitslot = hitslots[Misc::Rng::rollDice(20)]; float unmitigatedDamage = damage; float x = damage / (damage + getArmorRating(ptr)); @@ -703,7 +689,7 @@ namespace MWClass if (armorhealth == 0) armor = *inv.unequipItem(armor, ptr); - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == MWMechanics::getPlayer()) skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0); switch(armor.getClass().getEquipmentSkill(armor)) @@ -719,7 +705,7 @@ namespace MWClass break; } } - else if(ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + else if(ptr == MWMechanics::getPlayer()) skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0); } } @@ -732,11 +718,12 @@ namespace MWClass if(damage > 0.0f) { sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->activateHitOverlay(); } - float health = getCreatureStats(ptr).getHealth().getCurrent() - damage; - setActorHealth(ptr, health, attacker); + MWMechanics::DynamicStat health(getCreatureStats(ptr).getHealth()); + health.setCurrent(health.getCurrent() - damage); + getCreatureStats(ptr).setHealth(health); } else { @@ -753,64 +740,15 @@ namespace MWClass attacker.getClass().getNpcStats(attacker).addWerewolfKill(); } - // Simple check for who attacked first: if the player attacked first, a crimeId should be set - // Doesn't handle possible edge case where no one reported the assault, but in such a case, - // for bystanders it is not possible to tell who attacked first, anyway. - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (attacker == player && ptr.getClass().getNpcStats(ptr).getCrimeId() != -1 && ptr != player) - MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder); + MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, attacker); } } - void Npc::block(const MWWorld::Ptr &ptr) const - { - MWWorld::InventoryStore& inv = getInventoryStore(ptr); - MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if (shield == inv.end()) - return; - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - switch(shield->getClass().getEquipmentSkill(*shield)) - { - case ESM::Skill::LightArmor: - sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f); - break; - case ESM::Skill::MediumArmor: - sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f); - break; - case ESM::Skill::HeavyArmor: - sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f); - break; - default: - return; - } - } - - void Npc::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const - { - MWMechanics::CreatureStats &crstats = getCreatureStats(ptr); - bool wasDead = crstats.isDead(); - - MWMechanics::DynamicStat stat(crstats.getHealth()); - stat.setCurrent(health); - crstats.setHealth(stat); - - if(!wasDead && crstats.isDead()) - { - // actor was just killed - } - else if(wasDead && !crstats.isDead()) - { - // actor was just resurrected - } - } - - boost::shared_ptr Npc::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { // player got activated by another NPC - if(ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(ptr == MWMechanics::getPlayer()) return boost::shared_ptr(new MWWorld::ActionTalk(actor)); // Werewolfs can't activate NPCs @@ -843,7 +781,7 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore; + return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; } MWWorld::InventoryStore& Npc::getInventoryStore (const MWWorld::Ptr& ptr) @@ -851,13 +789,12 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore; + return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; } - std::string Npc::getScript (const MWWorld::Ptr& ptr) const + std::string Npc::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } @@ -960,33 +897,12 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mMovement; + return ptr.getRefData().getCustomData()->asNpcCustomData().mMovement; } - Ogre::Vector3 Npc::getMovementVector (const MWWorld::Ptr& ptr) const + bool Npc::isEssential (const MWWorld::ConstPtr& ptr) const { - MWMechanics::Movement &movement = getMovementSettings(ptr); - Ogre::Vector3 vec(movement.mPosition); - movement.mPosition[0] = 0.0f; - movement.mPosition[1] = 0.0f; - movement.mPosition[2] = 0.0f; - return vec; - } - - Ogre::Vector3 Npc::getRotationVector (const MWWorld::Ptr& ptr) const - { - MWMechanics::Movement &movement = getMovementSettings(ptr); - Ogre::Vector3 vec(movement.mRotation); - movement.mRotation[0] = 0.0f; - movement.mRotation[1] = 0.0f; - movement.mRotation[2] = 0.0f; - return vec; - } - - bool Npc::isEssential (const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mFlags & ESM::NPC::Essential) != 0; } @@ -997,20 +913,24 @@ namespace MWClass registerClass (typeid (ESM::NPC).name(), instance); } - bool Npc::hasToolTip (const MWWorld::Ptr& ptr) const + bool Npc::hasToolTip(const MWWorld::ConstPtr& ptr) const { - return !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat() || getCreatureStats(ptr).isDead(); + if (!ptr.getRefData().getCustomData()) + return true; + + const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); + return !customData.mNpcStats.getAiSequence().isInCombat() || customData.mNpcStats.isDead(); } - MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); bool fullHelp = MWBase::Environment::get().getWindowManager()->getFullHelp(); MWGui::ToolTipInfo info; info.caption = getName(ptr); - if(fullHelp && getNpcStats(ptr).isWerewolf()) + if(fullHelp && ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) { info.caption += " ("; info.caption += ref->mBase->mName; @@ -1032,21 +952,9 @@ namespace MWClass float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const { - const MWMechanics::NpcStats &stats = getNpcStats(ptr); - // According to UESP, inventory weight is ignored in werewolf form. Does that include // feather and burden effects? - float weight = 0.0f; - if(!stats.isWerewolf()) - { - weight = getContainerStore(ptr).getWeight(); - weight -= stats.getMagicEffects().get(ESM::MagicEffect::Feather).getMagnitude(); - weight += stats.getMagicEffects().get(ESM::MagicEffect::Burden).getMagnitude(); - if(weight < 0.0f) - weight = 0.0f; - } - - return weight; + return getNpcStats(ptr).isWerewolf() ? 0.0f : Actor::getEncumbrance(ptr); } bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id, @@ -1112,23 +1020,35 @@ namespace MWClass + shield; } - void Npc::adjustScale(const MWWorld::Ptr &ptr, float &scale) const + void Npc::adjustScale(const MWWorld::ConstPtr &ptr, osg::Vec3f&scale, bool rendering) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + if (!rendering) + return; // collision meshes are not scaled based on race height + // having the same collision extents for all races makes the environments easier to test + + const MWWorld::LiveCellRef *ref = ptr.get(); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); if (ref->mBase->isMale()) - scale *= race->mData.mHeight.mMale; + { + scale.x() *= race->mData.mWeight.mMale; + scale.y() *= race->mData.mWeight.mMale; + scale.z() *= race->mData.mHeight.mMale; + } else - scale *= race->mData.mHeight.mFemale; + { + scale.x() *= race->mData.mWeight.mFemale; + scale.y() *= race->mData.mWeight.mFemale; + scale.z() *= race->mData.mHeight.mFemale; + } + } - int Npc::getServices(const MWWorld::Ptr &actor) const + int Npc::getServices(const MWWorld::ConstPtr &actor) const { - MWWorld::LiveCellRef* ref = actor.get(); + const MWWorld::LiveCellRef* ref = actor.get(); if (ref->mBase->mHasAI) return ref->mBase->mAiData.mServices; else @@ -1141,7 +1061,7 @@ namespace MWClass if(name == "left" || name == "right") { MWBase::World *world = MWBase::Environment::get().getWorld(); - Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); + osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); if(world->isSwimming(ptr)) return (name == "left") ? "Swim Left" : "Swim Right"; if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) @@ -1178,11 +1098,11 @@ namespace MWClass if(name == "land") { MWBase::World *world = MWBase::Environment::get().getWorld(); - Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); + osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) return "DefaultLandWater"; if(world->isOnGround(ptr)) - return "Body Fall Medium"; + return "DefaultLand"; return ""; } if(name == "swimleft") @@ -1203,13 +1123,11 @@ namespace MWClass throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); } - MWWorld::Ptr - Npc::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Npc::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } int Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const @@ -1217,9 +1135,9 @@ namespace MWClass return ptr.getClass().getNpcStats(ptr).getSkill(skill).getModified(); } - int Npc::getBloodTexture(const MWWorld::Ptr &ptr) const + int Npc::getBloodTexture(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mFlags & ESM::NPC::Skeleton) return 1; @@ -1236,27 +1154,26 @@ namespace MWClass const ESM::NpcState& state2 = dynamic_cast (state); - ensureCustomData(ptr); - // If we do the following instead we get a sizable speedup, but this causes compatibility issues - // with 0.30 savegames, where some state in CreatureStats was not saved yet, - // and therefore needs to be loaded from ESM records. TODO: re-enable this in a future release. - /* - if (!ptr.getRefData().getCustomData()) + if (state.mVersion > 0) { - // Create a CustomData, but don't fill it from ESM records (not needed) - std::auto_ptr data (new NpcCustomData); - ptr.getRefData().setCustomData (data.release()); + if (!ptr.getRefData().getCustomData()) + { + // Create a CustomData, but don't fill it from ESM records (not needed) + std::auto_ptr data (new NpcCustomData); + ptr.getRefData().setCustomData (data.release()); + } } - */ + else + ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. - NpcCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); customData.mInventoryStore.readState (state2.mInventory); customData.mNpcStats.readState (state2.mNpcStats); static_cast (customData.mNpcStats).readState (state2.mCreatureStats); } - void Npc::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + void Npc::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { ESM::NpcState& state2 = dynamic_cast (state); @@ -1267,29 +1184,37 @@ namespace MWClass return; } - ensureCustomData (ptr); - - NpcCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); customData.mInventoryStore.writeState (state2.mInventory); customData.mNpcStats.writeState (state2.mNpcStats); static_cast (customData.mNpcStats).writeState (state2.mCreatureStats); } - int Npc::getBaseGold(const MWWorld::Ptr& ptr) const + int Npc::getBaseGold(const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) return ref->mBase->mNpdt52.mGold; else return ref->mBase->mNpdt12.mGold; } - bool Npc::isClass(const MWWorld::Ptr& ptr, const std::string &className) const + bool Npc::isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const { return Misc::StringUtils::ciEqual(ptr.get()->mBase->mClass, className); } + bool Npc::canSwim(const MWWorld::ConstPtr &ptr) const + { + return true; + } + + bool Npc::canWalk(const MWWorld::ConstPtr &ptr) const + { + return true; + } + void Npc::respawn(const MWWorld::Ptr &ptr) const { if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn) @@ -1304,6 +1229,7 @@ namespace MWClass // Reset to original position ptr.getRefData().setPosition(ptr.getCellRef().getPosition()); + MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(NULL); } } @@ -1317,26 +1243,26 @@ namespace MWClass store.restock(list, ptr, ptr.getCellRef().getRefId()); } - int Npc::getBaseFightRating (const MWWorld::Ptr& ptr) const + int Npc::getBaseFightRating (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mAiData.mFight; } - bool Npc::isBipedal(const MWWorld::Ptr &ptr) const + bool Npc::isBipedal(const MWWorld::ConstPtr &ptr) const { return true; } - std::string Npc::getPrimaryFaction (const MWWorld::Ptr& ptr) const + std::string Npc::getPrimaryFaction (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mFaction; } - int Npc::getPrimaryFactionRank (const MWWorld::Ptr& ptr) const + int Npc::getPrimaryFactionRank (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->getFactionRank(); } } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 27beeb626e..5df34380a5 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWCLASS_NPC_H #define GAME_MWCLASS_NPC_H -#include "../mwworld/class.hpp" +#include "actor.hpp" namespace ESM { @@ -10,12 +10,11 @@ namespace ESM namespace MWClass { - class Npc : public MWWorld::Class + class Npc : public Actor { void ensureCustomData (const MWWorld::Ptr& ptr) const; - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; struct GMST { @@ -45,19 +44,10 @@ namespace MWClass public: - virtual std::string getId (const MWWorld::Ptr& ptr) const; - ///< Return ID of \a ptr - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; - - virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) const; - ///< Adjust position to stand on ground. Must be called post model load - /// @param force do this even if the ptr is flying - - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -70,10 +60,10 @@ namespace MWClass virtual MWWorld::ContainerStore& getContainerStore (const MWWorld::Ptr& ptr) const; ///< Return container store - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip(const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const; @@ -81,19 +71,15 @@ namespace MWClass virtual bool hasInventoryStore(const MWWorld::Ptr &ptr) const { return true; } - virtual void hit(const MWWorld::Ptr& ptr, int type) const; + virtual void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const; virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const; - virtual void block(const MWWorld::Ptr &ptr) const; - - virtual void setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const; - virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr virtual float getSpeed (const MWWorld::Ptr& ptr) const; @@ -105,13 +91,6 @@ namespace MWClass virtual MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const; ///< Return desired movement. - virtual Ogre::Vector3 getMovementVector (const MWWorld::Ptr& ptr) const; - ///< Return desired movement vector (determined based on movement settings, - /// stance and stats). - - virtual Ogre::Vector3 getRotationVector (const MWWorld::Ptr& ptr) const; - ///< Return desired rotations, as euler angles. - virtual float getCapacity (const MWWorld::Ptr& ptr) const; ///< Return total weight that fits into the object. Throws an exception, if the object can't /// hold other objects. @@ -129,28 +108,29 @@ namespace MWClass /// \param actor Actor that is resposible for the ID being applied to \a ptr. /// \return Any effect? - virtual void adjustScale (const MWWorld::Ptr &ptr, float &scale) const; + virtual void adjustScale (const MWWorld::ConstPtr &ptr, osg::Vec3f &scale, bool rendering) const; + /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh virtual void skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor=1.f) const; ///< Inform actor \a ptr that a skill use has succeeded. - virtual bool isEssential (const MWWorld::Ptr& ptr) const; + virtual bool isEssential (const MWWorld::ConstPtr& ptr) const; ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) - virtual int getServices (const MWWorld::Ptr& actor) const; + virtual int getServices (const MWWorld::ConstPtr& actor) const; - virtual bool isPersistent (const MWWorld::Ptr& ptr) const; + virtual bool isPersistent (const MWWorld::ConstPtr& ptr) const; virtual std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const; static void registerSelf(); - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const; /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) - virtual int getBloodTexture (const MWWorld::Ptr& ptr) const; + virtual int getBloodTexture (const MWWorld::ConstPtr& ptr) const; virtual bool isActor() const { return true; @@ -164,32 +144,28 @@ namespace MWClass const; ///< Read additional state from \a state into \a ptr. - virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + virtual void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. - virtual int getBaseGold(const MWWorld::Ptr& ptr) const; + virtual int getBaseGold(const MWWorld::ConstPtr& ptr) const; - virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const; + virtual bool isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const; - virtual bool canSwim (const MWWorld::Ptr &ptr) const { - return true; - } + virtual bool canSwim (const MWWorld::ConstPtr &ptr) const; - virtual bool canWalk (const MWWorld::Ptr &ptr) const { - return true; - } + virtual bool canWalk (const MWWorld::ConstPtr &ptr) const; - virtual bool isBipedal (const MWWorld::Ptr &ptr) const; + virtual bool isBipedal (const MWWorld::ConstPtr &ptr) const; virtual void respawn (const MWWorld::Ptr& ptr) const; virtual void restock (const MWWorld::Ptr& ptr) const; - virtual int getBaseFightRating (const MWWorld::Ptr& ptr) const; + virtual int getBaseFightRating (const MWWorld::ConstPtr& ptr) const; - virtual std::string getPrimaryFaction(const MWWorld::Ptr &ptr) const; - virtual int getPrimaryFactionRank(const MWWorld::Ptr &ptr) const; + virtual std::string getPrimaryFaction(const MWWorld::ConstPtr &ptr) const; + virtual int getPrimaryFactionRank(const MWWorld::ConstPtr &ptr) const; }; } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index ee299ab4f8..40b4d62342 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -1,4 +1,3 @@ - #include "potion.hpp" #include @@ -13,7 +12,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" @@ -25,10 +24,6 @@ namespace MWClass { - std::string Potion::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Potion::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -37,17 +32,14 @@ namespace MWClass } } - void Potion::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Potion::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, true); + // TODO: add option somewhere to enable collision for placeable objects } - std::string Potion::getModel(const MWWorld::Ptr &ptr) const + std::string Potion::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -56,10 +48,9 @@ namespace MWClass return ""; } - std::string Potion::getName (const MWWorld::Ptr& ptr) const + std::string Potion::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -70,18 +61,17 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Potion::getScript (const MWWorld::Ptr& ptr) const + std::string Potion::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - int Potion::getValue (const MWWorld::Ptr& ptr) const + int Potion::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -93,39 +83,36 @@ namespace MWClass registerClass (typeid (ESM::Potion).name(), instance); } - std::string Potion::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Potion::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Potion Up"); } - std::string Potion::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Potion::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Potion Down"); } - std::string Potion::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Potion::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Potion::hasToolTip (const MWWorld::Ptr& ptr) const + bool Potion::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Potion::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Potion::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -174,24 +161,21 @@ namespace MWClass return action; } - MWWorld::Ptr - Potion::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Potion::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Potion::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Potion::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Potions) != 0; } - float Potion::getWeight(const MWWorld::Ptr &ptr) const + float Potion::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 32e3901156..9cdd58058d 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Potion : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,16 +24,16 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; @@ -45,20 +41,20 @@ namespace MWClass static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index da22e9be6c..79d33ba60e 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -1,4 +1,3 @@ - #include "probe.hpp" #include @@ -12,7 +11,7 @@ #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" @@ -22,10 +21,6 @@ namespace MWClass { - std::string Probe::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Probe::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -34,17 +29,14 @@ namespace MWClass } } - void Probe::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Probe::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, true); + // TODO: add option somewhere to enable collision for placeable objects } - std::string Probe::getModel(const MWWorld::Ptr &ptr) const + std::string Probe::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -53,10 +45,9 @@ namespace MWClass return ""; } - std::string Probe::getName (const MWWorld::Ptr& ptr) const + std::string Probe::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -66,15 +57,15 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Probe::getScript (const MWWorld::Ptr& ptr) const + std::string Probe::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Probe::getEquipmentSlots (const MWWorld::Ptr& ptr) const + std::pair, bool> Probe::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { std::vector slots_; @@ -83,10 +74,9 @@ namespace MWClass return std::make_pair (slots_, false); } - int Probe::getValue (const MWWorld::Ptr& ptr) const + int Probe::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -98,39 +88,36 @@ namespace MWClass registerClass (typeid (ESM::Probe).name(), instance); } - std::string Probe::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Probe::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Probe Up"); } - std::string Probe::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Probe::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Probe Down"); } - std::string Probe::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Probe::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Probe::hasToolTip (const MWWorld::Ptr& ptr) const + bool Probe::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Probe::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Probe::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -161,32 +148,28 @@ namespace MWClass return action; } - MWWorld::Ptr - Probe::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Probe::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - bool Probe::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Probe::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Probes) != 0; } - int Probe::getItemMaxHealth (const MWWorld::Ptr& ptr) const + int Probe::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mUses; } - float Probe::getWeight(const MWWorld::Ptr &ptr) const + float Probe::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index bb90ac1531..4fc991a5ff 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Probe : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,47 +24,47 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + virtual std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const; + virtual int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health - virtual bool hasItemHealth (const MWWorld::Ptr& ptr) const { return true; } + virtual bool hasItemHealth (const MWWorld::ConstPtr& ptr) const { return true; } ///< \return Item health data available? (default implementation: false) }; } diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index c02146f122..271f52bde9 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -1,4 +1,3 @@ - #include "repair.hpp" #include @@ -10,7 +9,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/actionrepair.hpp" @@ -21,10 +20,6 @@ namespace MWClass { - std::string Repair::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Repair::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -33,17 +28,14 @@ namespace MWClass } } - void Repair::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Repair::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, true); + // TODO: add option somewhere to enable collision for placeable objects } - std::string Repair::getModel(const MWWorld::Ptr &ptr) const + std::string Repair::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -52,10 +44,9 @@ namespace MWClass return ""; } - std::string Repair::getName (const MWWorld::Ptr& ptr) const + std::string Repair::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -66,18 +57,17 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - std::string Repair::getScript (const MWWorld::Ptr& ptr) const + std::string Repair::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - int Repair::getValue (const MWWorld::Ptr& ptr) const + int Repair::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -89,52 +79,48 @@ namespace MWClass registerClass (typeid (ESM::Repair).name(), instance); } - std::string Repair::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Repair::getUpSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Repair Up"); } - std::string Repair::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Repair::getDownSoundId (const MWWorld::ConstPtr& ptr) const { return std::string("Item Repair Down"); } - std::string Repair::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Repair::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Repair::hasToolTip (const MWWorld::Ptr& ptr) const + bool Repair::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - bool Repair::hasItemHealth (const MWWorld::Ptr& ptr) const + bool Repair::hasItemHealth (const MWWorld::ConstPtr& ptr) const { return true; } - int Repair::getItemMaxHealth (const MWWorld::Ptr& ptr) const + int Repair::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mUses; } - MWGui::ToolTipInfo Repair::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Repair::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -156,13 +142,11 @@ namespace MWClass return info; } - MWWorld::Ptr - Repair::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Repair::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } boost::shared_ptr Repair::use (const MWWorld::Ptr& ptr) const @@ -170,15 +154,14 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionRepair(ptr)); } - bool Repair::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Repair::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::RepairItem) != 0; } - float Repair::getWeight(const MWWorld::Ptr &ptr) const + float Repair::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index 2589a4af35..f0d1292f4d 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -7,20 +7,16 @@ namespace MWClass { class Repair : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,46 +24,46 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu (default implementation: return a /// null action). - virtual bool hasItemHealth (const MWWorld::Ptr& ptr) const; + virtual bool hasItemHealth (const MWWorld::ConstPtr& ptr) const; ///< \return Item health data available? (default implementation: false) - virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const; + virtual int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health /// (default implementation: throw an exception) - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index dbbe7e43a7..65e69ea90a 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -1,10 +1,9 @@ - #include "static.hpp" #include #include "../mwworld/ptr.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/cellstore.hpp" #include "../mwrender/objects.hpp" @@ -12,32 +11,23 @@ namespace MWClass { - std::string Static::getId (const MWWorld::Ptr& ptr) const - { - return ptr.get()->mBase->mId; - } void Static::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - if (!model.empty()) { - renderingInterface.getObjects().insertModel(ptr, model, !ref->mBase->mPersistent); + renderingInterface.getObjects().insertModel(ptr, model); } } - void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) physics.addObject(ptr, model); } - std::string Static::getModel(const MWWorld::Ptr &ptr) const + std::string Static::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -46,7 +36,7 @@ namespace MWClass return ""; } - std::string Static::getName (const MWWorld::Ptr& ptr) const + std::string Static::getName (const MWWorld::ConstPtr& ptr) const { return ""; } @@ -58,12 +48,10 @@ namespace MWClass registerClass (typeid (ESM::Static).name(), instance); } - MWWorld::Ptr - Static::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Static::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } } diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index a94dff394d..076c39cf10 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -7,26 +7,22 @@ namespace MWClass { class Static : public MWWorld::Class { - virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - /// Return ID of \a ptr - virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. static void registerSelf(); - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; }; } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index a484ad6683..f3f8409215 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -1,4 +1,3 @@ - #include "weapon.hpp" #include @@ -13,7 +12,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/physicssystem.hpp" +#include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwgui/tooltips.hpp" @@ -23,12 +22,6 @@ namespace MWClass { - std::string Weapon::getId (const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = ptr.get(); - - return ref->mBase->mId; - } void Weapon::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { @@ -37,17 +30,14 @@ namespace MWClass } } - void Weapon::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const + void Weapon::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { - if(!model.empty()) - physics.addObject(ptr, model, true); + // TODO: add option somewhere to enable collision for placeable objects } - std::string Weapon::getModel(const MWWorld::Ptr &ptr) const + std::string Weapon::getModel(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - assert(ref->mBase != NULL); + const MWWorld::LiveCellRef *ref = ptr.get(); const std::string &model = ref->mBase->mModel; if (!model.empty()) { @@ -56,10 +46,9 @@ namespace MWClass return ""; } - std::string Weapon::getName (const MWWorld::Ptr& ptr) const + std::string Weapon::getName (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mName; } @@ -70,34 +59,31 @@ namespace MWClass return defaultItemActivate(ptr, actor); } - bool Weapon::hasItemHealth (const MWWorld::Ptr& ptr) const + bool Weapon::hasItemHealth (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mData.mType < 11); // thrown weapons and arrows/bolts don't have health, only quantity } - int Weapon::getItemMaxHealth (const MWWorld::Ptr& ptr) const + int Weapon::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mHealth; } - std::string Weapon::getScript (const MWWorld::Ptr& ptr) const + std::string Weapon::getScript (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } - std::pair, bool> Weapon::getEquipmentSlots (const MWWorld::Ptr& ptr) const + std::pair, bool> Weapon::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); std::vector slots_; bool stack = false; @@ -118,10 +104,9 @@ namespace MWClass return std::make_pair (slots_, stack); } - int Weapon::getEquipmentSkill (const MWWorld::Ptr& ptr) const + int Weapon::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); const int size = 12; @@ -148,10 +133,9 @@ namespace MWClass return -1; } - int Weapon::getValue (const MWWorld::Ptr& ptr) const + int Weapon::getValue (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mValue; } @@ -163,10 +147,9 @@ namespace MWClass registerClass (typeid (ESM::Weapon).name(), instance); } - std::string Weapon::getUpSoundId (const MWWorld::Ptr& ptr) const + std::string Weapon::getUpSoundId (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); int type = ref->mBase->mData.mType; // Ammo @@ -208,10 +191,9 @@ namespace MWClass return std::string("Item Misc Up"); } - std::string Weapon::getDownSoundId (const MWWorld::Ptr& ptr) const + std::string Weapon::getDownSoundId (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); int type = ref->mBase->mData.mType; // Ammo @@ -253,29 +235,26 @@ namespace MWClass return std::string("Item Misc Down"); } - std::string Weapon::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Weapon::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mIcon; } - bool Weapon::hasToolTip (const MWWorld::Ptr& ptr) const + bool Weapon::hasToolTip (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return (ref->mBase->mName != ""); } - MWGui::ToolTipInfo Weapon::getToolTipInfo (const MWWorld::Ptr& ptr) const + MWGui::ToolTipInfo Weapon::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); @@ -357,18 +336,16 @@ namespace MWClass return info; } - std::string Weapon::getEnchantment (const MWWorld::Ptr& ptr) const + std::string Weapon::getEnchantment (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mEnchant; } - std::string Weapon::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + std::string Weapon::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); ESM::Weapon newItem = *ref->mBase; newItem.mId=""; @@ -380,7 +357,7 @@ namespace MWClass return record->mId; } - std::pair Weapon::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const + std::pair Weapon::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0) return std::make_pair(0, "#{sInventoryMessage1}"); @@ -413,33 +390,29 @@ namespace MWClass return action; } - MWWorld::Ptr - Weapon::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const + MWWorld::Ptr Weapon::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); - return MWWorld::Ptr(&cell.get().insert(*ref), &cell); + return MWWorld::Ptr(cell.insert(ref), &cell); } - int Weapon::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Weapon::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mEnchant; } - bool Weapon::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Weapon::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return (npcServices & ESM::NPC::Weapon) || ((npcServices & ESM::NPC::MagicItems) && !getEnchantment(item).empty()); } - float Weapon::getWeight(const MWWorld::Ptr &ptr) const + float Weapon::getWeight(const MWWorld::ConstPtr &ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mData.mWeight; } } diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index 47f1c52514..5fc3983f25 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -8,19 +8,16 @@ namespace MWClass class Weapon : public MWWorld::Class { virtual MWWorld::Ptr - copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; + copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; public: - virtual std::string getId (const MWWorld::Ptr& ptr) const; - ///< Return ID of \a ptr - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; - virtual std::string getName (const MWWorld::Ptr& ptr) const; + virtual std::string getName (const MWWorld::ConstPtr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -28,50 +25,50 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation - virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + virtual bool hasToolTip (const MWWorld::ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. - virtual bool hasItemHealth (const MWWorld::Ptr& ptr) const; + virtual bool hasItemHealth (const MWWorld::ConstPtr& ptr) const; ///< \return Item health data available? - virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const; + virtual int getItemMaxHealth (const MWWorld::ConstPtr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health - virtual std::string getScript (const MWWorld::Ptr& ptr) const; + virtual std::string getScript (const MWWorld::ConstPtr& ptr) const; ///< Return name of the script attached to ptr - virtual std::pair, bool> getEquipmentSlots (const MWWorld::Ptr& ptr) const; + virtual std::pair, bool> getEquipmentSlots (const MWWorld::ConstPtr& ptr) const; ///< \return first: Return IDs of the slot this object can be equipped in; second: can object /// stay stacked when equipped? - virtual int getEquipmentSkill (const MWWorld::Ptr& ptr) const; - /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + virtual int getEquipmentSkill (const MWWorld::ConstPtr& ptr) const; + /// Return the index of the skill this item corresponds to when equipped or -1, if there is /// no such skill. - virtual int getValue (const MWWorld::Ptr& ptr) const; + virtual int getValue (const MWWorld::ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. static void registerSelf(); - virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getUpSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the pick up sound Id - virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; + virtual std::string getDownSoundId (const MWWorld::ConstPtr& ptr) const; ///< Return the put down sound Id - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; + virtual std::string getEnchantment (const MWWorld::ConstPtr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - virtual std::string applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + virtual std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. - virtual std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; + virtual std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. /// Second item in the pair specifies the error message @@ -79,13 +76,13 @@ namespace MWClass const; ///< Generate action for using via inventory menu - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const; }; } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 1785575fcf..c99e86c2d0 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -1,4 +1,3 @@ - #include "dialoguemanagerimp.hpp" #include @@ -10,6 +9,7 @@ #include #include #include +#include #include #include @@ -41,6 +41,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "filter.hpp" #include "hypertextparser.hpp" @@ -55,8 +56,6 @@ namespace MWDialogue , mTalkedTo(false) , mTemporaryDispositionChange(0.f) , mPermanentDispositionChange(0.f) - , mScriptVerbose (scriptVerbose) - { mChoice = -1; mIsInChoice = false; @@ -115,6 +114,8 @@ namespace MWDialogue void DialogueManager::startDialogue (const MWWorld::Ptr& actor) { + updateGlobals(); + // Dialogue with dead actor (e.g. through script) should not be allowed. if (actor.getClass().getCreatureStats(actor).isDead()) return; @@ -140,9 +141,6 @@ namespace MWDialogue win->startDialogue(actor, actor.getClass().getName (actor), resetHistory); - //setup the list of topics known by the actor. Topics who are also on the knownTopics list will be added to the GUI - updateTopics(); - //greeting const MWWorld::Store &dialogs = MWBase::Environment::get().getWorld()->getStore().get(); @@ -166,12 +164,19 @@ namespace MWDialogue // TODO play sound } + // first topics update so that parseText knows the keywords to highlight + updateTopics(); + parseText (info->mResponse); MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); executeScript (info->mResultScript); mLastTopic = Misc::StringUtils::lowerCase(it->mId); + + // update topics again to accomodate changes resulting from executeScript + updateTopics(); + return; } } @@ -295,15 +300,18 @@ namespace MWDialogue MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext), title); - // Make sure the returned DialInfo is from the Dialogue we supplied. If could also be from the Info refusal group, - // in which case it should not be added to the journal. - for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); - iter!=dialogue.mInfo.end(); ++iter) + if (dialogue.mType == ESM::Dialogue::Topic) { - if (iter->mId == info->mId) + // Make sure the returned DialInfo is from the Dialogue we supplied. If could also be from the Info refusal group, + // in which case it should not be added to the journal. + for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); + iter!=dialogue.mInfo.end(); ++iter) { - MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor); - break; + if (iter->mId == info->mId) + { + MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor); + break; + } } } @@ -325,6 +333,8 @@ namespace MWDialogue void DialogueManager::updateTopics() { + updateGlobals(); + std::list keywordList; int choice = mChoice; mChoice = -1; @@ -411,8 +421,6 @@ namespace MWDialogue win->setKeywords(keywordList); mChoice = choice; - - updateGlobals(); } void DialogueManager::keywordSelected (const std::string& keyword) @@ -421,7 +429,6 @@ namespace MWDialogue { if(mDialogueMap.find(keyword) != mDialogueMap.end()) { - ESM::Dialogue ndialogue = mDialogueMap[keyword]; if (mDialogueMap[keyword].mType == ESM::Dialogue::Topic) { executeTopic (keyword); @@ -534,7 +541,7 @@ namespace MWDialogue else if (curDisp + mTemporaryDispositionChange > 100) mTemporaryDispositionChange = 100 - curDisp; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1); if (success) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index aec503e87d..086b5ef2f7 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -45,7 +45,6 @@ namespace MWDialogue float mTemporaryDispositionChange; float mPermanentDispositionChange; - bool mScriptVerbose; void parseText (const std::string& text); diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index adb7d38923..43d979ea28 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -1,4 +1,3 @@ - #include "filter.hpp" #include @@ -18,6 +17,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/magiceffects.hpp" +#include "../mwmechanics/actorutil.hpp" #include "selectwrapper.hpp" @@ -28,7 +28,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const // actor id if (!info.mActor.empty()) { - if ( !Misc::StringUtils::ciEqual(info.mActor, mActor.getClass().getId (mActor))) + if ( !Misc::StringUtils::ciEqual(info.mActor, mActor.getCellRef().getRefId())) return false; } else if (isCreature) @@ -41,7 +41,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const if (!info.mRace.empty()) { if (isCreature) - return false; + return true; MWWorld::LiveCellRef *cellRef = mActor.get(); @@ -53,7 +53,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const if (!info.mClass.empty()) { if (isCreature) - return false; + return true; MWWorld::LiveCellRef *cellRef = mActor.get(); @@ -65,7 +65,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const if (!info.mFaction.empty()) { if (isCreature) - return false; + return true; if (!Misc::StringUtils::ciEqual(mActor.getClass().getPrimaryFaction(mActor), info.mFaction)) return false; @@ -77,7 +77,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const else if (info.mData.mRank != -1) { if (isCreature) - return false; + return true; // Rank requirement, but no faction given. Use the actor's faction, if there is one. // check rank @@ -98,7 +98,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const { - const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr player = MWMechanics::getPlayer(); // check player faction if (!info.mPcFaction.empty()) @@ -156,10 +156,8 @@ bool MWDialogue::Filter::testSelectStruct (const SelectWrapper& select) const { if (select.isNpcOnly() && (mActor.getTypeName() != typeid (ESM::NPC).name())) // If the actor is a creature, we do not test the conditions applicable - // only to NPCs. Such conditions can never be satisfied, apart - // inverted ones (NotClass, NotRace, NotFaction return true - // because creatures are not of any race, class or faction). - return select.getType() == SelectWrapper::Type_Inverted; + // only to NPCs. + return true; switch (select.getType()) { @@ -203,6 +201,8 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c return false; // script does not have a variable of this name. int index = localDefs.getIndex (name); + if (index < 0) + return false; // shouldn't happen, we checked that variable has a type above, so must exist const MWScript::Locals& locals = mActor.getRefData().getLocals(); @@ -218,7 +218,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_PcHealthPercent: { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); float ratio = player.getClass().getCreatureStats (player).getHealth().getCurrent() / player.getClass().getCreatureStats (player).getHealth().getModified(); @@ -228,7 +228,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_PcDynamicStat: { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); float value = player.getClass().getCreatureStats (player). getDynamic (select.getArgument()).getCurrent(); @@ -252,7 +252,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) const { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); switch (select.getFunction()) { @@ -311,7 +311,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con int value = 0; - for (int i=0; i<=15; ++i) // everything except thigns held in hands and amunition + for (int i=0; i<=15; ++i) // everything except things held in hands and ammunition { MWWorld::ContainerStoreIterator slot = store.getSlot (i); @@ -428,7 +428,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) const { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); switch (select.getFunction()) { @@ -438,7 +438,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_NotId: - return !Misc::StringUtils::ciEqual(mActor.getClass().getId (mActor), select.getName()); + return !Misc::StringUtils::ciEqual(mActor.getCellRef().getRefId(), select.getName()); case SelectWrapper::Function_NotFaction: @@ -531,7 +531,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_ShouldAttack: return MWBase::Environment::get().getMechanicsManager()->isAggressive(mActor, - MWBase::Environment::get().getWorld()->getPlayerPtr()); + MWMechanics::getPlayer()); case SelectWrapper::Function_Werewolf: diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index 9f07f7b6fc..2f5f02b01f 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -1,4 +1,3 @@ - #include "journalentry.hpp" #include diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 99dab0cf8b..e6ffe22ab2 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -1,4 +1,3 @@ - #include "journalimp.hpp" #include diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index c4e1d75538..3532dc22b8 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -2,7 +2,7 @@ #define GAME_MWDIALOGUE_KEYWORDSEARCH_H #include -#include +#include #include #include #include // std::reverse @@ -44,7 +44,7 @@ public: typename Entry::childen_t::iterator current; typename Entry::childen_t::iterator next; - current = mRoot.mChildren.find (std::tolower (*keyword.begin(), mLocale)); + current = mRoot.mChildren.find (Misc::StringUtils::toLower (*keyword.begin())); if (current == mRoot.mChildren.end()) return false; else if (current->second.mKeyword.size() && Misc::StringUtils::ciEqual(current->second.mKeyword, keyword)) @@ -55,7 +55,7 @@ public: for (Point i = ++keyword.begin(); i != keyword.end(); ++i) { - next = current->second.mChildren.find(std::tolower (*i, mLocale)); + next = current->second.mChildren.find(Misc::StringUtils::toLower (*i)); if (next == current->second.mChildren.end()) return false; if (Misc::StringUtils::ciEqual(next->second.mKeyword, keyword)) @@ -89,7 +89,7 @@ public: // check first character - typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (std::tolower (*i, mLocale)); + typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (Misc::StringUtils::toLower (*i)); // no match, on to next character if (candidate == mRoot.mChildren.end ()) @@ -104,7 +104,7 @@ public: while ((j + 1) != end) { - typename Entry::childen_t::iterator next = candidate->second.mChildren.find (std::tolower (*++j, mLocale)); + typename Entry::childen_t::iterator next = candidate->second.mChildren.find (Misc::StringUtils::toLower (*++j)); if (next == candidate->second.mChildren.end ()) { @@ -136,7 +136,7 @@ public: while (k != end && t != candidate->second.mKeyword.end ()) { - if (std::tolower (*k, mLocale) != std::tolower (*t, mLocale)) + if (Misc::StringUtils::toLower (*k) != Misc::StringUtils::toLower (*t)) break; ++k, ++t; @@ -212,7 +212,7 @@ private: void seed_impl (string_t keyword, value_t value, size_t depth, Entry & entry) { - int ch = tolower (keyword.at (depth), mLocale); + int ch = Misc::StringUtils::toLower (keyword.at (depth)); typename Entry::childen_t::iterator j = entry.mChildren.find (ch); @@ -249,7 +249,6 @@ private: } Entry mRoot; - std::locale mLocale; }; } diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp index a9e39b3798..8465978863 100644 --- a/apps/openmw/mwdialogue/quest.cpp +++ b/apps/openmw/mwdialogue/quest.cpp @@ -1,4 +1,3 @@ - #include "quest.hpp" #include diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index fa0fbfe136..68c88e9437 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -1,4 +1,3 @@ - #include "selectwrapper.hpp" #include @@ -264,10 +263,6 @@ bool MWDialogue::SelectWrapper::isNpcOnly() const { Function_NotFaction, Function_NotClass, Function_NotRace, Function_SameGender, Function_SameRace, Function_SameFaction, - Function_PcSkill, - Function_PcExpelled, - Function_PcVampire, - Function_PcCrimeLevel, Function_RankRequirement, Function_Reputation, Function_FactionRankDiff, Function_Werewolf, Function_WerewolfKills, diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp index c1a45f841c..eb7fbdc1de 100644 --- a/apps/openmw/mwdialogue/topic.cpp +++ b/apps/openmw/mwdialogue/topic.cpp @@ -1,4 +1,3 @@ - #include "topic.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 768ad82e43..d12c22e06d 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -1,7 +1,5 @@ #include "alchemywindow.hpp" -#include - #include #include "../mwbase/environment.hpp" @@ -11,6 +9,7 @@ #include "../mwmechanics/magiceffects.hpp" #include "../mwmechanics/alchemy.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" @@ -66,9 +65,6 @@ namespace MWGui void AlchemyWindow::onCreateButtonClicked(MyGUI::Widget* _sender) { - std::string name = mNameEdit->getCaption(); - boost::algorithm::trim(name); - MWMechanics::Alchemy::Result result = mAlchemy->create (mNameEdit->getCaption ()); if (result == MWMechanics::Alchemy::Result_NoName) @@ -117,16 +113,16 @@ namespace MWGui MWWorld::Ptr ingred = *mIngredients[i]->getUserData(); if (ingred.getRefData().getCount() == 0) removeIngredient(mIngredients[i]); - } + } update(); } void AlchemyWindow::open() { - mAlchemy->setAlchemist (MWBase::Environment::get().getWorld()->getPlayerPtr()); + mAlchemy->setAlchemist (MWMechanics::getPlayer()); - InventoryItemModel* model = new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayerPtr()); + InventoryItemModel* model = new InventoryItemModel(MWMechanics::getPlayer()); mSortModel = new SortFilterItemModel(model); mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients); mItemView->setModel (mSortModel); @@ -135,9 +131,6 @@ namespace MWGui mNameEdit->setCaption(""); int index = 0; - - mAlchemy->setAlchemist (MWBase::Environment::get().getWorld()->getPlayerPtr()); - for (MWMechanics::Alchemy::TToolsIterator iter (mAlchemy->beginTools()); iter!=mAlchemy->endTools() && index (mApparatus.size()); ++iter, ++index) { diff --git a/apps/openmw/mwgui/backgroundimage.cpp b/apps/openmw/mwgui/backgroundimage.cpp index ee966c189c..98828a0411 100644 --- a/apps/openmw/mwgui/backgroundimage.cpp +++ b/apps/openmw/mwgui/backgroundimage.cpp @@ -14,7 +14,7 @@ void BackgroundImage::setBackgroundImage (const std::string& image, bool fixedRa } if (!stretch) { - setImageTexture("black.png"); + setImageTexture("black"); if (fixedRatio) mAspect = 4.0/3.0; diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index 668253712d..de86140042 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -4,9 +4,6 @@ #include #include -#include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -183,7 +180,7 @@ namespace MWGui const ESM::BirthSign *birth = store.get().find(mCurrentBirthId); - mBirthImage->setImageTexture(Misc::ResourceHelpers::correctTexturePath(birth->mTexture)); + mBirthImage->setImageTexture(MWBase::Environment::get().getWindowManager()->correctTexturePath(birth->mTexture)); std::vector abilities, powers, spells; diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index cfb49ebffb..b728b748f7 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -153,6 +153,34 @@ struct TypesetBookImpl : TypesetBook visitRuns (top, bottom, NULL, visitor); } + /// hit test with a margin for error. only hits on interactive text fragments are reported. + StyleImpl * hitTestWithMargin (int left, int top) + { + StyleImpl * hit = hitTest(left, top); + if (hit && hit->mInteractiveId > 0) + return hit; + + const int maxMargin = 10; + for (int margin=1; margin < maxMargin; ++margin) + { + for (int i=0; i<4; ++i) + { + if (i==0) + hit = hitTest(left, top-margin); + else if (i==1) + hit = hitTest(left, top+margin); + else if (i==2) + hit = hitTest(left-margin, top); + else + hit = hitTest(left+margin, top); + + if (hit && hit->mInteractiveId > 0) + return hit; + } + } + return NULL; + } + StyleImpl * hitTest (int left, int top) const { for (Sections::const_iterator i = mSections.begin (); i != mSections.end (); ++i) @@ -916,15 +944,15 @@ public: left -= mCroppedParent->getAbsoluteLeft (); top -= mCroppedParent->getAbsoluteTop (); - Style * Hit = mBook->hitTest (left, mViewTop + top); + Style * hit = mBook->hitTestWithMargin (left, mViewTop + top); if (mLastDown == MyGUI::MouseButton::None) { - if (Hit != mFocusItem) + if (hit != mFocusItem) { dirtyFocusItem (); - mFocusItem = Hit; + mFocusItem = hit; mItemActive = false; dirtyFocusItem (); @@ -933,7 +961,7 @@ public: else if (mFocusItem != 0) { - bool newItemActive = Hit == mFocusItem; + bool newItemActive = hit == mFocusItem; if (newItemActive != mItemActive) { @@ -949,12 +977,18 @@ public: if (!mBook) return; - left -= mCroppedParent->getAbsoluteLeft (); - top -= mCroppedParent->getAbsoluteTop (); + // work around inconsistency in MyGUI where the mouse press coordinates aren't + // transformed by the current Layer (even though mouse *move* events are). + MyGUI::IntPoint pos (left, top); +#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) + pos = mNode->getLayer()->getPosition(left, top); +#endif + pos.left -= mCroppedParent->getAbsoluteLeft (); + pos.top -= mCroppedParent->getAbsoluteTop (); if (mLastDown == MyGUI::MouseButton::None) { - mFocusItem = mBook->hitTest (left, mViewTop + top); + mFocusItem = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top); mItemActive = true; dirtyFocusItem (); @@ -968,14 +1002,21 @@ public: if (!mBook) return; - left -= mCroppedParent->getAbsoluteLeft (); - top -= mCroppedParent->getAbsoluteTop (); + // work around inconsistency in MyGUI where the mouse release coordinates aren't + // transformed by the current Layer (even though mouse *move* events are). + MyGUI::IntPoint pos (left, top); +#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3) + pos = mNode->getLayer()->getPosition(left, top); +#endif + + pos.left -= mCroppedParent->getAbsoluteLeft (); + pos.top -= mCroppedParent->getAbsoluteTop (); if (mLastDown == id) { - Style * mItem = mBook->hitTest (left, mViewTop + top); + Style * item = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top); - bool clicked = mFocusItem == mItem; + bool clicked = mFocusItem == item; mItemActive = false; @@ -983,8 +1024,8 @@ public: mLastDown = MyGUI::MouseButton::None; - if (clicked && mLinkClicked && mItem && mItem->mInteractiveId != 0) - mLinkClicked (mItem->mInteractiveId); + if (clicked && mLinkClicked && item && item->mInteractiveId != 0) + mLinkClicked (item->mInteractiveId); } } diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index 6863994b8a..f3cefb7874 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -9,6 +9,8 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "../mwworld/actiontake.hpp" #include "formatting.hpp" @@ -73,7 +75,7 @@ namespace MWGui mPages.clear(); } - void BookWindow::open (MWWorld::Ptr book, bool showTakeButton) + void BookWindow::openBook (MWWorld::Ptr book, bool showTakeButton) { mBook = book; @@ -123,7 +125,7 @@ namespace MWGui MWBase::Environment::get().getSoundManager()->playSound("Item Book Up", 1.0, 1.0); MWWorld::ActionTake take(mBook); - take.execute (MWBase::Environment::get().getWorld()->getPlayerPtr()); + take.execute (MWMechanics::getPlayer()); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Book); } diff --git a/apps/openmw/mwgui/bookwindow.hpp b/apps/openmw/mwgui/bookwindow.hpp index 8ad4f68300..881b9997ce 100644 --- a/apps/openmw/mwgui/bookwindow.hpp +++ b/apps/openmw/mwgui/bookwindow.hpp @@ -16,7 +16,7 @@ namespace MWGui virtual void exit(); - void open(MWWorld::Ptr book, bool showTakeButton); + void openBook(MWWorld::Ptr book, bool showTakeButton); void setInventoryAllowed(bool allowed); protected: diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index fb00d6a985..d2ce35509d 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -7,6 +7,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/fallback.hpp" @@ -51,7 +52,7 @@ namespace void updatePlayerHealth() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player); npcStats.updateHealth(); } @@ -60,8 +61,10 @@ namespace namespace MWGui { - CharacterCreation::CharacterCreation() - : mNameDialog(0) + CharacterCreation::CharacterCreation(osgViewer::Viewer* viewer, Resource::ResourceSystem* resourceSystem) + : mViewer(viewer) + , mResourceSystem(resourceSystem) + , mNameDialog(0) , mRaceDialog(0) , mClassChoiceDialog(0) , mGenerateClassQuestionDialog(0) @@ -130,141 +133,142 @@ namespace MWGui void CharacterCreation::spawnDialog(const char id) { - switch (id) + try { - case GM_Name: - MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog); - mNameDialog = 0; - mNameDialog = new TextInputDialog(); - mNameDialog->setTextLabel(MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "Name")); - mNameDialog->setTextInput(mPlayerName); - mNameDialog->setNextButtonShow(mCreationStage >= CSE_NameChosen); - mNameDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onNameDialogDone); - mNameDialog->setVisible(true); - break; + switch (id) + { + case GM_Name: + MWBase::Environment::get().getWindowManager()->removeDialog(mNameDialog); + mNameDialog = 0; + mNameDialog = new TextInputDialog(); + mNameDialog->setTextLabel(MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "Name")); + mNameDialog->setTextInput(mPlayerName); + mNameDialog->setNextButtonShow(mCreationStage >= CSE_NameChosen); + mNameDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onNameDialogDone); + mNameDialog->setVisible(true); + break; - case GM_Race: - MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog); - mRaceDialog = 0; - mRaceDialog = new RaceDialog(); - mRaceDialog->setNextButtonShow(mCreationStage >= CSE_RaceChosen); - mRaceDialog->setRaceId(mPlayerRaceId); - mRaceDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogDone); - mRaceDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogBack); - mRaceDialog->setVisible(true); - if (mCreationStage < CSE_NameChosen) - mCreationStage = CSE_NameChosen; - break; + case GM_Race: + MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog); + mRaceDialog = 0; + mRaceDialog = new RaceDialog(mViewer, mResourceSystem); + mRaceDialog->setNextButtonShow(mCreationStage >= CSE_RaceChosen); + mRaceDialog->setRaceId(mPlayerRaceId); + mRaceDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogDone); + mRaceDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onRaceDialogBack); + mRaceDialog->setVisible(true); + if (mCreationStage < CSE_NameChosen) + mCreationStage = CSE_NameChosen; + break; - case GM_Class: - MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog); - mClassChoiceDialog = 0; - mClassChoiceDialog = new ClassChoiceDialog(); - mClassChoiceDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassChoice); - mClassChoiceDialog->setVisible(true); - if (mCreationStage < CSE_RaceChosen) - mCreationStage = CSE_RaceChosen; - break; + case GM_Class: + MWBase::Environment::get().getWindowManager()->removeDialog(mClassChoiceDialog); + mClassChoiceDialog = 0; + mClassChoiceDialog = new ClassChoiceDialog(); + mClassChoiceDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassChoice); + mClassChoiceDialog->setVisible(true); + if (mCreationStage < CSE_RaceChosen) + mCreationStage = CSE_RaceChosen; + break; - case GM_ClassPick: - MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog); - mPickClassDialog = 0; - mPickClassDialog = new PickClassDialog(); - mPickClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); - mPickClassDialog->setClassId(mPlayerClass.mName); - mPickClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogDone); - mPickClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogBack); - mPickClassDialog->setVisible(true); - if (mCreationStage < CSE_RaceChosen) - mCreationStage = CSE_RaceChosen; - break; + case GM_ClassPick: + MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog); + mPickClassDialog = 0; + mPickClassDialog = new PickClassDialog(); + mPickClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); + mPickClassDialog->setClassId(mPlayerClass.mName); + mPickClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogDone); + mPickClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onPickClassDialogBack); + mPickClassDialog->setVisible(true); + if (mCreationStage < CSE_RaceChosen) + mCreationStage = CSE_RaceChosen; + break; - case GM_Birth: - MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog); - mBirthSignDialog = 0; - mBirthSignDialog = new BirthDialog(); - mBirthSignDialog->setNextButtonShow(mCreationStage >= CSE_BirthSignChosen); - mBirthSignDialog->setBirthId(mPlayerBirthSignId); - mBirthSignDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogDone); - mBirthSignDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogBack); - mBirthSignDialog->setVisible(true); - if (mCreationStage < CSE_ClassChosen) - mCreationStage = CSE_ClassChosen; - break; + case GM_Birth: + MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog); + mBirthSignDialog = 0; + mBirthSignDialog = new BirthDialog(); + mBirthSignDialog->setNextButtonShow(mCreationStage >= CSE_BirthSignChosen); + mBirthSignDialog->setBirthId(mPlayerBirthSignId); + mBirthSignDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogDone); + mBirthSignDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogBack); + mBirthSignDialog->setVisible(true); + if (mCreationStage < CSE_ClassChosen) + mCreationStage = CSE_ClassChosen; + break; - case GM_ClassCreate: - if (!mCreateClassDialog) - { - mCreateClassDialog = new CreateClassDialog(); - mCreateClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone); - mCreateClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack); - } - mCreateClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); - mCreateClassDialog->setVisible(true); - if (mCreationStage < CSE_RaceChosen) - mCreationStage = CSE_RaceChosen; - break; - case GM_ClassGenerate: - mGenerateClassStep = 0; - mGenerateClass = ""; - mGenerateClassSpecializations[0] = 0; - mGenerateClassSpecializations[1] = 0; - mGenerateClassSpecializations[2] = 0; - showClassQuestionDialog(); - if (mCreationStage < CSE_RaceChosen) - mCreationStage = CSE_RaceChosen; - break; - case GM_Review: - MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); - mReviewDialog = 0; - mReviewDialog = new ReviewDialog(); - mReviewDialog->setPlayerName(mPlayerName); - mReviewDialog->setRace(mPlayerRaceId); - mReviewDialog->setClass(mPlayerClass); - mReviewDialog->setBirthSign(mPlayerBirthSignId); - - { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - - mReviewDialog->setHealth ( stats.getHealth() ); - mReviewDialog->setMagicka( stats.getMagicka() ); - mReviewDialog->setFatigue( stats.getFatigue() ); - } - - { - std::map attributes = MWBase::Environment::get().getWindowManager()->getPlayerAttributeValues(); - for (std::map::iterator it = attributes.begin(); - it != attributes.end(); ++it) + case GM_ClassCreate: + if (!mCreateClassDialog) { - mReviewDialog->setAttribute(static_cast (it->first), it->second); + mCreateClassDialog = new CreateClassDialog(); + mCreateClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone); + mCreateClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack); } - } + mCreateClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); + mCreateClassDialog->setVisible(true); + if (mCreationStage < CSE_RaceChosen) + mCreationStage = CSE_RaceChosen; + break; + case GM_ClassGenerate: + mGenerateClassStep = 0; + mGenerateClass = ""; + mGenerateClassSpecializations[0] = 0; + mGenerateClassSpecializations[1] = 0; + mGenerateClassSpecializations[2] = 0; + showClassQuestionDialog(); + if (mCreationStage < CSE_RaceChosen) + mCreationStage = CSE_RaceChosen; + break; + case GM_Review: + MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); + mReviewDialog = 0; + mReviewDialog = new ReviewDialog(); + mReviewDialog->setPlayerName(mPlayerName); + mReviewDialog->setRace(mPlayerRaceId); + mReviewDialog->setClass(mPlayerClass); + mReviewDialog->setBirthSign(mPlayerBirthSignId); - { - std::map skills = MWBase::Environment::get().getWindowManager()->getPlayerSkillValues(); - for (std::map::iterator it = skills.begin(); - it != skills.end(); ++it) { - mReviewDialog->setSkillValue(static_cast (it->first), it->second); - } - mReviewDialog->configureSkills(MWBase::Environment::get().getWindowManager()->getPlayerMajorSkills(), MWBase::Environment::get().getWindowManager()->getPlayerMinorSkills()); - } + MWWorld::Ptr player = MWMechanics::getPlayer(); + const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - mReviewDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogDone); - mReviewDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogBack); - mReviewDialog->eventActivateDialog += MyGUI::newDelegate(this, &CharacterCreation::onReviewActivateDialog); - mReviewDialog->setVisible(true); - if (mCreationStage < CSE_BirthSignChosen) - mCreationStage = CSE_BirthSignChosen; - break; + mReviewDialog->setHealth ( stats.getHealth() ); + mReviewDialog->setMagicka( stats.getMagicka() ); + mReviewDialog->setFatigue( stats.getFatigue() ); + } + + { + std::map attributes = MWBase::Environment::get().getWindowManager()->getPlayerAttributeValues(); + for (std::map::iterator it = attributes.begin(); + it != attributes.end(); ++it) + { + mReviewDialog->setAttribute(static_cast (it->first), it->second); + } + } + + { + std::map skills = MWBase::Environment::get().getWindowManager()->getPlayerSkillValues(); + for (std::map::iterator it = skills.begin(); + it != skills.end(); ++it) + { + mReviewDialog->setSkillValue(static_cast (it->first), it->second); + } + mReviewDialog->configureSkills(MWBase::Environment::get().getWindowManager()->getPlayerMajorSkills(), MWBase::Environment::get().getWindowManager()->getPlayerMinorSkills()); + } + + mReviewDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogDone); + mReviewDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogBack); + mReviewDialog->eventActivateDialog += MyGUI::newDelegate(this, &CharacterCreation::onReviewActivateDialog); + mReviewDialog->setVisible(true); + if (mCreationStage < CSE_BirthSignChosen) + mCreationStage = CSE_BirthSignChosen; + break; + } + } + catch (std::exception& e) + { + std::cerr << "Failed to create chargen window: " << e.what() << std::endl; } - } - - void CharacterCreation::doRenderUpdate() - { - if (mRaceDialog) - mRaceDialog->doRenderUpdate(); } void CharacterCreation::onReviewDialogDone(WindowBase* parWindow) @@ -309,7 +313,7 @@ namespace MWGui }; } - void CharacterCreation::onPickClassDialogDone(WindowBase* parWindow) + void CharacterCreation::selectPickedClass() { if (mPickClassDialog) { @@ -329,20 +333,18 @@ namespace MWGui } updatePlayerHealth(); + } + + void CharacterCreation::onPickClassDialogDone(WindowBase* parWindow) + { + selectPickedClass(); handleDialogDone(CSE_ClassChosen, GM_Birth); } void CharacterCreation::onPickClassDialogBack() { - if (mPickClassDialog) - { - const std::string classId = mPickClassDialog->getClassId(); - if (!classId.empty()) - MWBase::Environment::get().getMechanicsManager()->setPlayerClass(classId); - MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog); - mPickClassDialog = 0; - } + selectPickedClass(); MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); @@ -387,29 +389,7 @@ namespace MWGui handleDialogDone(CSE_NameChosen, GM_Race); } - void CharacterCreation::onRaceDialogBack() - { - if (mRaceDialog) - { - const ESM::NPC &data = mRaceDialog->getResult(); - mPlayerRaceId = data.mRace; - if (!mPlayerRaceId.empty()) { - MWBase::Environment::get().getMechanicsManager()->setPlayerRace( - data.mRace, - data.isMale(), - data.mHead, - data.mHair - ); - } - MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog); - mRaceDialog = 0; - } - - MWBase::Environment::get().getWindowManager()->popGuiMode(); - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Name); - } - - void CharacterCreation::onRaceDialogDone(WindowBase* parWindow) + void CharacterCreation::selectRace() { if (mRaceDialog) { @@ -430,11 +410,24 @@ namespace MWGui } updatePlayerHealth(); + } + + void CharacterCreation::onRaceDialogBack() + { + selectRace(); + + MWBase::Environment::get().getWindowManager()->popGuiMode(); + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Name); + } + + void CharacterCreation::onRaceDialogDone(WindowBase* parWindow) + { + selectRace(); handleDialogDone(CSE_RaceChosen, GM_Class); } - void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow) + void CharacterCreation::selectBirthSign() { if (mBirthSignDialog) { @@ -446,24 +439,24 @@ namespace MWGui } updatePlayerHealth(); + } + + void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow) + { + selectBirthSign(); handleDialogDone(CSE_BirthSignChosen, GM_Review); } void CharacterCreation::onBirthSignDialogBack() { - if (mBirthSignDialog) - { - MWBase::Environment::get().getMechanicsManager()->setPlayerBirthsign(mBirthSignDialog->getBirthId()); - MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog); - mBirthSignDialog = 0; - } + selectBirthSign(); MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); } - void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow) + void CharacterCreation::selectCreatedClass() { if (mCreateClassDialog) { @@ -492,19 +485,23 @@ namespace MWGui mPlayerClass = klass; MWBase::Environment::get().getWindowManager()->setPlayerClass(klass); - // Do not delete dialog, so that choices are rembered in case we want to go back and adjust them later + // Do not delete dialog, so that choices are remembered in case we want to go back and adjust them later mCreateClassDialog->setVisible(false); } - updatePlayerHealth(); + } + + void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow) + { + selectCreatedClass(); handleDialogDone(CSE_ClassChosen, GM_Birth); } void CharacterCreation::onCreateClassDialogBack() { - // Do not delete dialog, so that choices are rembered in case we want to go back and adjust them later - mCreateClassDialog->setVisible(false); + // not done in MW, but we do it for consistency with the other dialogs + selectCreatedClass(); MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); @@ -628,18 +625,7 @@ namespace MWGui MWBase::Environment::get().getSoundManager()->say(sGenerateClassSteps(mGenerateClassStep).mSound); } - void CharacterCreation::onGenerateClassBack() - { - MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassResultDialog); - mGenerateClassResultDialog = 0; - - MWBase::Environment::get().getMechanicsManager()->setPlayerClass(mGenerateClass); - - MWBase::Environment::get().getWindowManager()->popGuiMode(); - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); - } - - void CharacterCreation::onGenerateClassDone(WindowBase* parWindow) + void CharacterCreation::selectGeneratedClass() { MWBase::Environment::get().getWindowManager()->removeDialog(mGenerateClassResultDialog); mGenerateClassResultDialog = 0; @@ -653,6 +639,19 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setPlayerClass(mPlayerClass); updatePlayerHealth(); + } + + void CharacterCreation::onGenerateClassBack() + { + selectGeneratedClass(); + + MWBase::Environment::get().getWindowManager()->popGuiMode(); + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); + } + + void CharacterCreation::onGenerateClassDone(WindowBase* parWindow) + { + selectGeneratedClass(); handleDialogDone(CSE_ClassChosen, GM_Birth); } diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp index a4515569db..7fb67caf65 100644 --- a/apps/openmw/mwgui/charactercreation.hpp +++ b/apps/openmw/mwgui/charactercreation.hpp @@ -8,6 +8,16 @@ #include "../mwmechanics/stat.hpp" +namespace osgViewer +{ + class Viewer; +} + +namespace Resource +{ + class ResourceSystem; +} + namespace MWGui { class WindowBase; @@ -29,7 +39,7 @@ namespace MWGui public: typedef std::vector SkillList; - CharacterCreation(); + CharacterCreation(osgViewer::Viewer* viewer, Resource::ResourceSystem* resourceSystem); ~CharacterCreation(); //Show a dialog @@ -39,9 +49,11 @@ namespace MWGui void setValue (const std::string& id, const MWMechanics::DynamicStat& value); void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value); void configureSkills (const SkillList& major, const SkillList& minor); - void doRenderUpdate(); private: + osgViewer::Viewer* mViewer; + Resource::ResourceSystem* mResourceSystem; + //Dialogs TextInputDialog* mNameDialog; RaceDialog* mRaceDialog; @@ -71,6 +83,7 @@ namespace MWGui //Race dialog void onRaceDialogDone(WindowBase* parWindow); void onRaceDialogBack(); + void selectRace(); //Class dialogs void onClassChoice(int _index); @@ -82,10 +95,14 @@ namespace MWGui void onClassQuestionChosen(int _index); void onGenerateClassBack(); void onGenerateClassDone(WindowBase* parWindow); + void selectGeneratedClass(); + void selectCreatedClass(); + void selectPickedClass(); //Birthsign dialog void onBirthSignDialogDone(WindowBase* parWindow); void onBirthSignDialogBack(); + void selectBirthSign(); //Review dialog void onReviewDialogDone(WindowBase* parWindow); diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index 3e8734c716..57cd9ca8eb 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -11,9 +11,6 @@ #include "tooltips.hpp" -#undef min -#undef max - namespace { diff --git a/apps/openmw/mwgui/companionitemmodel.cpp b/apps/openmw/mwgui/companionitemmodel.cpp index 983ef50173..0ff0b648e1 100644 --- a/apps/openmw/mwgui/companionitemmodel.cpp +++ b/apps/openmw/mwgui/companionitemmodel.cpp @@ -1,6 +1,5 @@ #include "companionitemmodel.hpp" -#include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" namespace diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index 69b6aa9f98..fc4a984896 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -3,11 +3,8 @@ #include #include "../mwbase/environment.hpp" -#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwmechanics/npcstats.hpp" - #include "../mwworld/class.hpp" #include "messagebox.hpp" @@ -84,7 +81,7 @@ void CompanionWindow::onItemSelected(int index) if (count > 1 && !shift) { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); - dialog->open(object.getClass().getName(object), "#{sTake}", count); + dialog->openCountDialog(object.getClass().getName(object), "#{sTake}", count); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &CompanionWindow::dragItem); } @@ -106,7 +103,7 @@ void CompanionWindow::onBackgroundSelected() } } -void CompanionWindow::open(const MWWorld::Ptr& npc) +void CompanionWindow::openCompanion(const MWWorld::Ptr& npc) { mPtr = npc; updateEncumbranceBar(); diff --git a/apps/openmw/mwgui/companionwindow.hpp b/apps/openmw/mwgui/companionwindow.hpp index dc460e2fc2..d37f0c4e43 100644 --- a/apps/openmw/mwgui/companionwindow.hpp +++ b/apps/openmw/mwgui/companionwindow.hpp @@ -22,7 +22,7 @@ namespace MWGui virtual void resetReference(); - void open(const MWWorld::Ptr& npc); + void openCompanion(const MWWorld::Ptr& npc); void onFrame (); private: diff --git a/apps/openmw/mwgui/confirmationdialog.cpp b/apps/openmw/mwgui/confirmationdialog.cpp index 83650b1951..e4312914f8 100644 --- a/apps/openmw/mwgui/confirmationdialog.cpp +++ b/apps/openmw/mwgui/confirmationdialog.cpp @@ -16,7 +16,7 @@ namespace MWGui mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ConfirmationDialog::onOkButtonClicked); } - void ConfirmationDialog::open(const std::string& message, const std::string& confirmMessage, const std::string& cancelMessage) + void ConfirmationDialog::askForConfirmation(const std::string& message, const std::string& confirmMessage, const std::string& cancelMessage) { setVisible(true); diff --git a/apps/openmw/mwgui/confirmationdialog.hpp b/apps/openmw/mwgui/confirmationdialog.hpp index 7ff16b10ad..14caa77482 100644 --- a/apps/openmw/mwgui/confirmationdialog.hpp +++ b/apps/openmw/mwgui/confirmationdialog.hpp @@ -9,7 +9,7 @@ namespace MWGui { public: ConfirmationDialog(); - void open(const std::string& message, const std::string& confirmMessage="#{sOk}", const std::string& cancelMessage="#{sCancel}"); + void askForConfirmation(const std::string& message, const std::string& confirmMessage="#{sOk}", const std::string& cancelMessage="#{sCancel}"); virtual void exit(); typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 083dd32b0a..1777d86ad4 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -170,25 +170,25 @@ namespace MWGui mCommandLine->setFontName(fntName); } - void Console::print(const std::string &msg) + void Console::print(const std::string &msg, const std::string& color) { - mHistory->addText(msg); + mHistory->addText(color + MyGUI::TextIterator::toTagsString(msg)); } void Console::printOK(const std::string &msg) { - print("#FF00FF" + msg + "\n"); + print(msg + "\n", "#FF00FF"); } void Console::printError(const std::string &msg) { - print("#FF2222" + msg + "\n"); + print(msg + "\n", "#FF2222"); } void Console::execute (const std::string& command) { // Log the command - print("#FFFFFF> " + command + "\n"); + print("> " + command + "\n"); Compiler::Locals locals; Compiler::Output output (locals); @@ -365,7 +365,7 @@ namespace MWGui /* Is the beginning of the string different from the input string? If yes skip it. */ for( std::string::iterator iter=tmp.begin(), iter2=(*it).begin(); iter < tmp.end();++iter, ++iter2) { - if( tolower(*iter) != tolower(*iter2) ) { + if( Misc::StringUtils::toLower(*iter) != Misc::StringUtils::toLower(*iter2) ) { string_different=true; break; } @@ -405,7 +405,7 @@ namespace MWGui for(std::string::iterator iter=matches.front().begin()+tmp.length(); iter < matches.front().end(); ++iter, ++i) { for(std::vector::iterator it=matches.begin(); it < matches.end();++it) { - if( tolower((*it)[i]) != tolower(*iter) ) { + if( Misc::StringUtils::toLower((*it)[i]) != Misc::StringUtils::toLower(*iter) ) { /* Append the longest match to the end of the output string*/ output.append(matches.front().substr( 0, i)); return output; diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index 09a05be487..98e46a5596 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -48,9 +48,8 @@ namespace MWGui void onResChange(int width, int height); - // Print a message to the console. Messages may contain color - // code, eg. "#FFFFFF this is white". - void print(const std::string &msg); + // Print a message to the console, in specified color. + void print(const std::string &msg, const std::string& color = "#FFFFFF"); // These are pre-colored versions that you should use. diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 76e9cbb5b6..5d71fc445d 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -9,15 +9,14 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/containerstore.hpp" #include "../mwmechanics/pickpocket.hpp" #include "../mwmechanics/creaturestats.hpp" #include "countdialog.hpp" -#include "tradewindow.hpp" #include "inventorywindow.hpp" #include "itemview.hpp" @@ -83,7 +82,7 @@ namespace MWGui if (count > 1 && !shift) { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); - dialog->open(object.getClass().getName(object), "#{sTake}", count); + dialog->openCountDialog(object.getClass().getName(object), "#{sTake}", count); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerWindow::dragItem); } @@ -131,7 +130,7 @@ namespace MWGui dropItem(); } - void ContainerWindow::open(const MWWorld::Ptr& container, bool loot) + void ContainerWindow::openContainer(const MWWorld::Ptr& container, bool loot) { mPickpocketDetected = false; mPtr = container; @@ -139,7 +138,7 @@ namespace MWGui if (mPtr.getTypeName() == typeid(ESM::NPC).name() && !loot) { // we are stealing stuff - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); mModel = new PickpocketItemModel(player, new InventoryItemModel(container), !mPtr.getClass().getCreatureStats(mPtr).getKnockedDown()); } @@ -185,7 +184,7 @@ namespace MWGui && !mPickpocketDetected ) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::Pickpocket pickpocket(player, mPtr); if (pickpocket.finish()) { @@ -262,7 +261,7 @@ namespace MWGui bool ContainerWindow::onTakeItem(const ItemStack &item, int count) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); // TODO: move to ItemModels if (dynamic_cast(mModel) && !mPtr.getClass().getCreatureStats(mPtr).getKnockedDown()) diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 87ae993a54..520bce9d9d 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -33,7 +33,7 @@ namespace MWGui public: ContainerWindow(DragAndDrop* dragAndDrop); - void open(const MWWorld::Ptr& container, bool loot=false); + void openContainer(const MWWorld::Ptr& container, bool loot=false); virtual void close(); virtual void resetReference(); diff --git a/apps/openmw/mwgui/countdialog.cpp b/apps/openmw/mwgui/countdialog.cpp index c6f2180c92..03cf1cab65 100644 --- a/apps/openmw/mwgui/countdialog.cpp +++ b/apps/openmw/mwgui/countdialog.cpp @@ -29,7 +29,7 @@ namespace MWGui mItemEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &CountDialog::onEnterKeyPressed); } - void CountDialog::open(const std::string& item, const std::string& message, const int maxCount) + void CountDialog::openCountDialog(const std::string& item, const std::string& message, const int maxCount) { setVisible(true); diff --git a/apps/openmw/mwgui/countdialog.hpp b/apps/openmw/mwgui/countdialog.hpp index a54e99cf4b..7014b5fad9 100644 --- a/apps/openmw/mwgui/countdialog.hpp +++ b/apps/openmw/mwgui/countdialog.hpp @@ -14,7 +14,7 @@ namespace MWGui { public: CountDialog(); - void open(const std::string& item, const std::string& message, const int maxCount); + void openCountDialog(const std::string& item, const std::string& message, const int maxCount); void cancel(); virtual void exit(); diff --git a/apps/openmw/mwgui/cursor.cpp b/apps/openmw/mwgui/cursor.cpp index 9c64f94caa..82e0b9699b 100644 --- a/apps/openmw/mwgui/cursor.cpp +++ b/apps/openmw/mwgui/cursor.cpp @@ -5,8 +5,6 @@ #include #include -#include - namespace MWGui { diff --git a/apps/openmw/mwgui/debugwindow.cpp b/apps/openmw/mwgui/debugwindow.cpp index a6dab66c10..37ea3470d4 100644 --- a/apps/openmw/mwgui/debugwindow.cpp +++ b/apps/openmw/mwgui/debugwindow.cpp @@ -18,9 +18,9 @@ namespace float accumulated_time=0,parent_time = pit->Is_Root() ? CProfileManager::Get_Time_Since_Reset() : pit->Get_Current_Parent_Total_Time(); int i,j; int frames_since_reset = CProfileManager::Get_Frame_Count_Since_Reset(); - for (i=0;iGet_Current_Parent_Name())+" (total running time: "+MyGUI::utility::toString(parent_time,3)+" ms) ---\n"; os << s; @@ -35,7 +35,7 @@ namespace accumulated_time += current_total_time; float fraction = parent_time > SIMD_EPSILON ? (current_total_time / parent_time) * 100 : 0.f; - for (j=0;jGet_Current_Name()+" ("+MyGUI::utility::toString(fraction,2)+" %) :: "+MyGUI::utility::toString(ms,3)+" ms / frame ("+MyGUI::utility::toString(pit->Get_Current_Total_Calls())+" calls)\n"; os << s; @@ -47,7 +47,7 @@ namespace { os << "what's wrong\n"; } - for (i=0;i SIMD_EPSILON ? ((parent_time - accumulated_time) / parent_time) * 100 : 0.f; s = "Unaccounted: ("+MyGUI::utility::toString(unaccounted,3)+" %) :: "+MyGUI::utility::toString(parent_time - accumulated_time,3)+" ms\n"; os << s; diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 0cb0475c91..d325886316 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -16,12 +16,13 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/dialoguemanager.hpp" -#include "../mwmechanics/npcstats.hpp" - #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "widgets.hpp" #include "bookpage.hpp" @@ -30,7 +31,7 @@ namespace { - MyGUI::Colour getTextColour (const std::string& type) + MyGUI::Colour getDialogueTextColour (const std::string& type) { return MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=" + type + "}")); } @@ -89,7 +90,7 @@ namespace MWGui WindowModal::open(); center(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mBribe10Button->setEnabled (playerGold >= 10); @@ -114,7 +115,7 @@ namespace MWGui void Response::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const { - BookTypesetter::Style* title = typesetter->createStyle("", getTextColour("header")); + BookTypesetter::Style* title = typesetter->createStyle("", getDialogueTextColour("header")); typesetter->sectionBreak(9); if (mTitle != "") typesetter->write(title, to_utf8_span(mTitle.c_str())); @@ -158,14 +159,14 @@ namespace MWGui if (hyperLinks.size() && MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().hasTranslation()) { - BookTypesetter::Style* style = typesetter->createStyle("", getTextColour("normal")); + BookTypesetter::Style* style = typesetter->createStyle("", getDialogueTextColour("normal")); size_t formatted = 0; // points to the first character that is not laid out yet for (std::map::iterator it = hyperLinks.begin(); it != hyperLinks.end(); ++it) { intptr_t topicId = it->second; - const MyGUI::Colour linkHot (getTextColour("link_over")); - const MyGUI::Colour linkNormal (getTextColour("link")); - const MyGUI::Colour linkActive (getTextColour("link_pressed")); + const MyGUI::Colour linkHot(getDialogueTextColour("link_over")); + const MyGUI::Colour linkNormal(getDialogueTextColour("link")); + const MyGUI::Colour linkActive(getDialogueTextColour("link_pressed")); BookTypesetter::Style* hotStyle = typesetter->createHotStyle (style, linkNormal, linkHot, linkActive, topicId); if (formatted < it->first.first) typesetter->write(style, formatted, it->first.first); @@ -198,11 +199,11 @@ namespace MWGui void Response::addTopicLink(BookTypesetter::Ptr typesetter, intptr_t topicId, size_t begin, size_t end) const { - BookTypesetter::Style* style = typesetter->createStyle("", getTextColour("normal")); + BookTypesetter::Style* style = typesetter->createStyle("", getDialogueTextColour("normal")); - const MyGUI::Colour linkHot (getTextColour("link_over")); - const MyGUI::Colour linkNormal (getTextColour("link")); - const MyGUI::Colour linkActive (getTextColour("link_pressed")); + const MyGUI::Colour linkHot(getDialogueTextColour("link_over")); + const MyGUI::Colour linkNormal(getDialogueTextColour("link")); + const MyGUI::Colour linkActive(getDialogueTextColour("link_pressed")); if (topicId) style = typesetter->createHotStyle (style, linkNormal, linkHot, linkActive, topicId); @@ -216,7 +217,7 @@ namespace MWGui void Message::write(BookTypesetter::Ptr typesetter, KeywordSearchT* keywordSearch, std::map& topicLinks) const { - BookTypesetter::Style* title = typesetter->createStyle("", getTextColour("notify")); + BookTypesetter::Style* title = typesetter->createStyle("", getDialogueTextColour("notify")); typesetter->sectionBreak(9); typesetter->write(title, to_utf8_span(mText.c_str())); } @@ -485,9 +486,9 @@ namespace MWGui typesetter->sectionBreak(9); // choices - const MyGUI::Colour linkHot (getTextColour("answer_over")); - const MyGUI::Colour linkNormal (getTextColour("answer")); - const MyGUI::Colour linkActive (getTextColour("answer_pressed")); + const MyGUI::Colour linkHot(getDialogueTextColour("answer_over")); + const MyGUI::Colour linkNormal(getDialogueTextColour("answer")); + const MyGUI::Colour linkActive(getDialogueTextColour("answer_pressed")); for (std::vector >::iterator it = mChoices.begin(); it != mChoices.end(); ++it) { Choice* link = new Choice(it->second); diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 43f2493a98..c182a0a527 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include "../mwbase/environment.hpp" @@ -19,6 +18,8 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "itemselection.hpp" #include "itemwidget.hpp" @@ -161,7 +162,7 @@ namespace MWGui void EnchantingDialog::startSelfEnchanting(MWWorld::Ptr soulgem) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); mEnchanting.setSelfEnchanting(true); mEnchanting.setEnchanter(player); @@ -209,7 +210,7 @@ namespace MWGui mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onItemSelected); mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onItemCancel); mItemSelectionDialog->setVisible(true); - mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr()); + mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyEnchantable); } else @@ -264,7 +265,7 @@ namespace MWGui mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onSoulSelected); mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onSoulCancel); mItemSelectionDialog->setVisible(true); - mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr()); + mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyChargedSoulstones); //MWBase::Environment::get().getWindowManager()->messageBox("#{sInventorySelectNoSoul}"); @@ -325,7 +326,7 @@ namespace MWGui mEnchanting.setNewItemName(mName->getCaption()); mEnchanting.setEffect(mEffectList); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); if (mPtr != player && mEnchanting.getEnchantPrice() > playerGold) { diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 761a89ca62..6a8145e970 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -1,19 +1,19 @@ #include "formatting.hpp" -#include -#include - #include #include #include #include #include +// correctBookartPath +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + #include #include #include -#include #include #include "../mwscript/interpretercontext.hpp" @@ -129,7 +129,7 @@ namespace MWGui size_t tagNameEndPos = tag.find(' '); mAttributes.clear(); mTag = tag.substr(0, tagNameEndPos); - Misc::StringUtils::toLower(mTag); + Misc::StringUtils::lowerCaseInPlace(mTag); if (mTag.empty()) return; @@ -151,7 +151,7 @@ namespace MWGui return; std::string key = tag.substr(0, sepPos); - Misc::StringUtils::toLower(key); + Misc::StringUtils::lowerCaseInPlace(key); tag.erase(0, sepPos+1); std::string value; @@ -466,7 +466,7 @@ namespace MWGui MyGUI::IntCoord(left, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top, parent->getName() + MyGUI::utility::toString(parent->getChildCount())); - std::string image = Misc::ResourceHelpers::correctBookartPath(src, width, mImageHeight); + std::string image = MWBase::Environment::get().getWindowManager()->correctBookartPath(src, width, mImageHeight); mImageBox->setImageTexture(image); mImageBox->setProperty("NeedMouse", "false"); } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 1f24b58d82..cf0fa3414a 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -1,7 +1,5 @@ #include "hud.hpp" -#include - #include #include #include @@ -9,11 +7,9 @@ #include #include -#include #include #include "../mwbase/environment.hpp" -#include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -22,6 +18,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "inventorywindow.hpp" #include "spellicons.hpp" @@ -70,9 +67,9 @@ namespace MWGui }; - HUD::HUD(CustomMarkerCollection &customMarkers, int fpsLevel, DragAndDrop* dragAndDrop) + HUD::HUD(CustomMarkerCollection &customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender) : Layout("openmw_hud.layout") - , LocalMapBase(customMarkers) + , LocalMapBase(customMarkers, localMapRender) , mHealth(NULL) , mMagicka(NULL) , mStamina(NULL) @@ -88,10 +85,6 @@ namespace MWGui , mCellNameBox(NULL) , mDrowningFrame(NULL) , mDrowningFlash(NULL) - , mFpsBox(NULL) - , mFpsCounter(NULL) - , mTriangleCounter(NULL) - , mBatchCounter(NULL) , mHealthManaStaminaBaseLeft(0) , mWeapBoxBaseLeft(0) , mSpellBoxBaseLeft(0) @@ -166,11 +159,6 @@ namespace MWGui getWidget(mCrosshair, "Crosshair"); - setFpsLevel(fpsLevel); - - getWidget(mTriangleCounter, "TriangleCounter"); - getWidget(mBatchCounter, "BatchCounter"); - LocalMapBase::init(mMinimap, mCompass, Settings::Manager::getInt("local map hud widget size", "Map")); mMainWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWorldClicked); @@ -189,46 +177,6 @@ namespace MWGui delete mSpellIcons; } - void HUD::setFpsLevel(int level) - { - mFpsCounter = 0; - - MyGUI::Widget* fps; - getWidget(fps, "FPSBoxAdv"); - fps->setVisible(false); - getWidget(fps, "FPSBox"); - fps->setVisible(false); - - if (level == 2) - { - getWidget(mFpsBox, "FPSBoxAdv"); - mFpsBox->setVisible(true); - getWidget(mFpsCounter, "FPSCounterAdv"); - } - else if (level == 1) - { - getWidget(mFpsBox, "FPSBox"); - mFpsBox->setVisible(true); - getWidget(mFpsCounter, "FPSCounter"); - } - } - - void HUD::setFPS(float fps) - { - if (mFpsCounter) - mFpsCounter->setCaption(MyGUI::utility::toString((int)fps)); - } - - void HUD::setTriangleCount(unsigned int count) - { - mTriangleCounter->setCaption(MyGUI::utility::toString(count)); - } - - void HUD::setBatchCount(unsigned int count) - { - mBatchCounter->setCaption(MyGUI::utility::toString(count)); - } - void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& value) { int current = std::max(0, static_cast(value.getCurrent())); @@ -248,7 +196,7 @@ namespace MWGui mMagicka->setProgressRange (modified); mMagicka->setProgressPosition (current); getWidget(w, "MagickaFrame"); - w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); + w->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr); } else if (id == "FBar") { @@ -286,7 +234,7 @@ namespace MWGui { // drop item into the gameworld MWBase::Environment::get().getWorld()->breakInvisibility( - MWBase::Environment::get().getWorld()->getPlayerPtr()); + MWMechanics::getPlayer()); MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); MyGUI::IntPoint cursorPosition = MyGUI::InputManager::getInstance().getMousePosition(); @@ -365,7 +313,7 @@ namespace MWGui void HUD::onWeaponClicked(MyGUI::Widget* _sender) { - const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr& player = MWMechanics::getPlayer(); if (player.getClass().getNpcStats(player).isWerewolf()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); @@ -377,7 +325,7 @@ namespace MWGui void HUD::onMagicClicked(MyGUI::Widget* _sender) { - const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr& player = MWMechanics::getPlayer(); if (player.getClass().getNpcStats(player).isWerewolf()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); @@ -418,7 +366,7 @@ namespace MWGui } if (mIsDrowning) - mDrowningFlashTheta += dt * Ogre::Math::TWO_PI; + mDrowningFlashTheta += dt * osg::PI*2; } void HUD::setSelectedSpell(const std::string& spellId, int successChancePercent) @@ -448,7 +396,7 @@ namespace MWGui std::string icon = effect->mIcon; int slashPos = icon.rfind('\\'); icon.insert(slashPos+1, "b_"); - icon = Misc::ResourceHelpers::correctIconPath(icon); + icon = MWBase::Environment::get().getWindowManager()->correctIconPath(icon); mSpellImage->setItem(MWWorld::Ptr()); mSpellImage->setIcon(icon); @@ -544,7 +492,19 @@ namespace MWGui { mCrosshair->setVisible (visible); } - + + void HUD::setCrosshairOwned(bool owned) + { + if(owned) + { + mCrosshair->changeWidgetSkin("HUD_Crosshair_Owned"); + } + else + { + mCrosshair->changeWidgetSkin("HUD_Crosshair"); + } + } + void HUD::setHmsVisible(bool visible) { mHealth->setVisible(visible); diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 263c087740..c5ae457891 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -16,15 +16,12 @@ namespace MWGui class SpellIcons; class ItemWidget; - class HUD : public OEngine::GUI::Layout, public LocalMapBase + class HUD : public Layout, public LocalMapBase { public: - HUD(CustomMarkerCollection& customMarkers, int fpsLevel, DragAndDrop* dragAndDrop); + HUD(CustomMarkerCollection& customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender); virtual ~HUD(); void setValue (const std::string& id, const MWMechanics::DynamicStat& value); - void setFPS(float fps); - void setTriangleCount(unsigned int count); - void setBatchCount(unsigned int count); /// Set time left for the player to start drowning /// @param time time left to start drowning @@ -40,8 +37,6 @@ namespace MWGui void setEffectVisible(bool visible); void setMinimapVisible(bool visible); - void setFpsLevel(const int level); - void setSelectedSpell(const std::string& spellId, int successChancePercent); void setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent); void setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent); @@ -49,6 +44,7 @@ namespace MWGui void unsetSelectedWeapon(); void setCrosshairVisible(bool visible); + void setCrosshairOwned(bool owned); void onFrame(float dt); @@ -78,11 +74,6 @@ namespace MWGui MyGUI::TextBox* mWeaponSpellBox; MyGUI::Widget *mDrowningFrame, *mDrowningFlash; - MyGUI::Widget* mFpsBox; - MyGUI::TextBox* mFpsCounter; - MyGUI::TextBox* mTriangleCounter; - MyGUI::TextBox* mBatchCounter; - // bottom left elements int mHealthManaStaminaBaseLeft, mWeapBoxBaseLeft, mSpellBoxBaseLeft, mSneakBoxBaseLeft; // bottom right elements diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index e8354b7406..b80850ba1b 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -4,8 +4,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwmechanics/creaturestats.hpp" - namespace MWGui { diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index af24f3f794..7678cb006c 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -8,6 +8,12 @@ #include #include +#include + +#include + +#include + #include #include "../mwbase/world.hpp" @@ -15,14 +21,16 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/scriptmanager.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/action.hpp" #include "../mwscript/interpretercontext.hpp" -#include "../mwbase/scriptmanager.hpp" #include "../mwrender/characterpreview.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "itemview.hpp" #include "inventoryitemmodel.hpp" #include "sortfilteritemmodel.hpp" @@ -48,20 +56,21 @@ namespace namespace MWGui { - InventoryWindow::InventoryWindow(DragAndDrop* dragAndDrop) + InventoryWindow::InventoryWindow(DragAndDrop* dragAndDrop, osgViewer::Viewer* viewer, Resource::ResourceSystem* resourceSystem) : WindowPinnableBase("openmw_inventory_window.layout") , mDragAndDrop(dragAndDrop) - , mPreviewDirty(true) - , mPreviewResize(true) , mSelectedItem(-1) , mSortModel(NULL) , mTradeModel(NULL) , mGuiMode(GM_Inventory) , mLastXSize(0) , mLastYSize(0) - , mPreview(new MWRender::InventoryPreview(MWBase::Environment::get().getWorld ()->getPlayerPtr())) + , mPreview(new MWRender::InventoryPreview(viewer, resourceSystem, MWMechanics::getPlayer())) , mTrading(false) { + mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); + mPreview->rebuild(); + mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize); getWidget(mAvatar, "Avatar"); @@ -77,6 +86,8 @@ namespace MWGui getWidget(mArmorRating, "ArmorRating"); mAvatarImage->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked); + mAvatarImage->setRenderItemTexture(mPreviewTexture.get()); + mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); getWidget(mItemView, "ItemView"); mItemView->eventItemClicked += MyGUI::newDelegate(this, &InventoryWindow::onItemSelected); @@ -110,20 +121,21 @@ namespace MWGui { mPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); mTradeModel = new TradeItemModel(new InventoryItemModel(mPtr), MWWorld::Ptr()); - mSortModel = new SortFilterItemModel(mTradeModel); + + if (mSortModel) // reuse existing SortModel when possible to keep previous category/filter settings + mSortModel->setSourceModel(mTradeModel); + else + mSortModel = new SortFilterItemModel(mTradeModel); + mItemView->setModel(mSortModel); - mPreview.reset(NULL); - mAvatarImage->setImageTexture(""); - MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().getTexture("CharacterPreview"); - if (tex) - MyGUI::RenderManager::getInstance().destroyTexture(tex); + mPreview->updatePtr(mPtr); + mPreview->rebuild(); + mPreview->update(); - mPreview.reset(new MWRender::InventoryPreview(mPtr)); - mPreview->setup(); + dirtyPreview(); - mPreviewDirty = true; - mPreviewResize = true; + updatePreviewSize(); } void InventoryWindow::setGuiMode(GuiMode mode) @@ -155,12 +167,15 @@ namespace MWGui MyGUI::IntSize size(static_cast(Settings::Manager::getFloat(setting + " w", "Windows") * viewSize.width), static_cast(Settings::Manager::getFloat(setting + " h", "Windows") * viewSize.height)); - if (size.width != mMainWidget->getWidth() || size.height != mMainWidget->getHeight()) - mPreviewResize = true; + bool needUpdate = (size.width != mMainWidget->getWidth() || size.height != mMainWidget->getHeight()); mMainWidget->setPosition(pos); mMainWidget->setSize(size); + adjustPanes(); + + if (needUpdate) + updatePreviewSize(); } SortFilterItemModel* InventoryWindow::getSortFilterModel() @@ -232,7 +247,7 @@ namespace MWGui { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); std::string message = mTrading ? "#{sQuanityMenuMessage01}" : "#{sTake}"; - dialog->open(object.getClass().getName(object), message, count); + dialog->openCountDialog(object.getClass().getName(object), message, count); dialog->eventOkClicked.clear(); if (mTrading) dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::sellItem); @@ -317,12 +332,13 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->updateSpellWindow(); mItemView->update(); - mPreviewDirty = true; + + dirtyPreview(); } void InventoryWindow::open() { - mPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + mPtr = MWMechanics::getPlayer(); updateEncumbranceBar(); @@ -364,10 +380,31 @@ namespace MWGui { mLastXSize = mMainWidget->getSize().width; mLastYSize = mMainWidget->getSize().height; - mPreviewResize = true; + + updatePreviewSize(); + updateArmorRating(); } } + void InventoryWindow::updateArmorRating() + { + mArmorRating->setCaptionWithReplacing ("#{sArmor}: " + + MyGUI::utility::toString(static_cast(mPtr.getClass().getArmorRating(mPtr)))); + if (mArmorRating->getTextSize().width > mArmorRating->getSize().width) + mArmorRating->setCaptionWithReplacing (MyGUI::utility::toString(static_cast(mPtr.getClass().getArmorRating(mPtr)))); + } + + void InventoryWindow::updatePreviewSize() + { + MyGUI::IntSize size = mAvatarImage->getSize(); + int width = std::min(mPreview->getTextureWidth(), size.width); + int height = std::min(mPreview->getTextureHeight(), size.height); + mPreview->setViewport(width, height); + + mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, height/float(mPreview->getTextureHeight()), + width/float(mPreview->getTextureWidth()), 0.f)); + } + void InventoryWindow::onFilterChanged(MyGUI::Widget* _sender) { if (_sender == mFilterAll) @@ -407,6 +444,20 @@ namespace MWGui { const std::string& script = ptr.getClass().getScript(ptr); + MWWorld::Ptr player = MWMechanics::getPlayer(); + + // early-out for items that need to be equipped, but can't be equipped: we don't want to set OnPcEquip in that case + if (!ptr.getClass().getEquipmentSlots(ptr).first.empty()) + { + std::pair canEquip = ptr.getClass().canBeEquipped(ptr, player); + if (canEquip.first == 0) + { + MWBase::Environment::get().getWindowManager()->messageBox(canEquip.second); + updateItemView(); + return; + } + } + // If the item has a script, set its OnPcEquip to 1 if (!script.empty() // Another morrowind oddity: when an item has skipped equipping and pcskipequip is reset to 0 afterwards, @@ -422,16 +473,18 @@ namespace MWGui MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); } - if (script.empty() || ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 0) + mSkippedToEquip = MWWorld::Ptr(); + if (ptr.getRefData().getCount()) // make sure the item is still there, the script might have removed it { - boost::shared_ptr action = ptr.getClass().use(ptr); + if (script.empty() || ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 0) + { + boost::shared_ptr action = ptr.getClass().use(ptr); - action->execute (MWBase::Environment::get().getWorld()->getPlayerPtr()); - - mSkippedToEquip = MWWorld::Ptr(); + action->execute (player); + } + else + mSkippedToEquip = ptr; } - else - mSkippedToEquip = ptr; if (isVisible()) { @@ -460,10 +513,8 @@ namespace MWGui { MyGUI::IntPoint mousePos = MyGUI::InputManager::getInstance ().getLastPressedPosition (MyGUI::MouseButton::Left); MyGUI::IntPoint relPos = mousePos - mAvatarImage->getAbsolutePosition (); - int realX = int(float(relPos.left) / float(mAvatarImage->getSize().width) * 512.f ); - int realY = int(float(relPos.top) / float(mAvatarImage->getSize().height) * 1024.f ); - MWWorld::Ptr itemSelected = getAvatarSelectedItem (realX, realY); + MWWorld::Ptr itemSelected = getAvatarSelectedItem (relPos.left, relPos.top); if (itemSelected.isEmpty ()) return; @@ -481,6 +532,8 @@ namespace MWGui MWWorld::Ptr InventoryWindow::getAvatarSelectedItem(int x, int y) { + // convert to OpenGL lower-left origin + y = (mAvatarImage->getHeight()-1) - y; int slot = mPreview->getSlotSelected (x, y); if (slot == -1) @@ -503,7 +556,7 @@ namespace MWGui void InventoryWindow::updateEncumbranceBar() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); float capacity = player.getClass().getCapacity(player); float encumbrance = player.getClass().getEncumbrance(player); @@ -524,34 +577,11 @@ namespace MWGui mTrading = trading; } - void InventoryWindow::doRenderUpdate () + void InventoryWindow::dirtyPreview() { - mPreview->onFrame(); + mPreview->update(); - if (mPreviewResize || mPreviewDirty) - { - mArmorRating->setCaptionWithReplacing ("#{sArmor}: " - + MyGUI::utility::toString(static_cast(mPtr.getClass().getArmorRating(mPtr)))); - if (mArmorRating->getTextSize().width > mArmorRating->getSize().width) - mArmorRating->setCaptionWithReplacing (MyGUI::utility::toString(static_cast(mPtr.getClass().getArmorRating(mPtr)))); - } - if (mPreviewResize) - { - mPreviewResize = false; - MyGUI::IntSize size = mAvatarImage->getSize(); - mPreview->resize(size.width, size.height); - - mAvatarImage->setImageTexture("CharacterPreview"); - mAvatarImage->setImageCoord(MyGUI::IntCoord(0, 0, std::min(512, size.width), std::min(1024, size.height))); - mAvatarImage->setImageTile(MyGUI::IntSize(std::min(512, size.width), std::min(1024, size.height))); - } - if (mPreviewDirty) - { - mPreviewDirty = false; - mPreview->update (); - - mAvatarImage->setImageTexture("CharacterPreview"); - } + updateArmorRating(); } void InventoryWindow::notifyContentChanged() @@ -560,9 +590,9 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->updateSpellWindow(); MWBase::Environment::get().getMechanicsManager()->updateMagicEffects( - MWBase::Environment::get().getWorld()->getPlayerPtr()); + MWMechanics::getPlayer()); - mPreviewDirty = true; + dirtyPreview(); } void InventoryWindow::pickUpObject (MWWorld::Ptr object) @@ -591,7 +621,7 @@ namespace MWGui int count = object.getRefData().getCount(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getWorld()->breakInvisibility(player); MWBase::Environment::get().getMechanicsManager()->itemTaken(player, object, MWWorld::Ptr(), count); @@ -621,7 +651,7 @@ namespace MWGui { ItemModel::ModelIndex selected = -1; // not using mSortFilterModel as we only need sorting, not filtering - SortFilterItemModel model(new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + SortFilterItemModel model(new InventoryItemModel(MWMechanics::getPlayer())); model.setSortByType(false); model.update(); if (model.getItemCount() == 0) diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index ee71d9b04c..a8a1b15a13 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -6,6 +6,16 @@ #include "../mwworld/ptr.hpp" +namespace osgViewer +{ + class Viewer; +} + +namespace Resource +{ + class ResourceSystem; +} + namespace MWRender { class InventoryPreview; @@ -27,12 +37,10 @@ namespace MWGui class InventoryWindow : public WindowPinnableBase { public: - InventoryWindow(DragAndDrop* dragAndDrop); + InventoryWindow(DragAndDrop* dragAndDrop, osgViewer::Viewer* viewer, Resource::ResourceSystem* resourceSystem); virtual void open(); - void doRenderUpdate(); - /// start trading, disables item drag&drop void setTrading(bool trading); @@ -62,8 +70,6 @@ namespace MWGui private: DragAndDrop* mDragAndDrop; - bool mPreviewDirty; - bool mPreviewResize; int mSelectedItem; MWWorld::Ptr mPtr; @@ -93,6 +99,7 @@ namespace MWGui int mLastXSize; int mLastYSize; + std::auto_ptr mPreviewTexture; std::auto_ptr mPreview; bool mTrading; @@ -113,6 +120,9 @@ namespace MWGui void updateEncumbranceBar(); void notifyContentChanged(); + void dirtyPreview(); + void updatePreviewSize(); + void updateArmorRating(); void adjustPanes(); diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 9fce6e84dd..2c382f3cf4 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -35,7 +35,7 @@ namespace MWGui { const ESM::GameSetting ¤tSetting = *currentIteration; std::string currentGMSTID = currentSetting.mId; - Misc::StringUtils::toLower(currentGMSTID); + Misc::StringUtils::lowerCaseInPlace(currentGMSTID); // Don't bother checking this GMST if it's not a sMagicBound* one. const std::string& toFind = "smagicbound"; @@ -44,7 +44,7 @@ namespace MWGui // All sMagicBound* GMST's should be of type string std::string currentGMSTValue = currentSetting.getString(); - Misc::StringUtils::toLower(currentGMSTValue); + Misc::StringUtils::lowerCaseInPlace(currentGMSTValue); boundItemIDCache.insert(currentGMSTValue); } @@ -52,7 +52,7 @@ namespace MWGui // Perform bound item check and assign the Flag_Bound bit if it passes std::string tempItemID = base.getCellRef().getRefId(); - Misc::StringUtils::toLower(tempItemID); + Misc::StringUtils::lowerCaseInPlace(tempItemID); if (boundItemIDCache.count(tempItemID) != 0) mFlags |= Flag_Bound; @@ -120,6 +120,11 @@ namespace MWGui } + ProxyItemModel::ProxyItemModel() + : mSourceModel(NULL) + { + } + ProxyItemModel::~ProxyItemModel() { delete mSourceModel; @@ -164,4 +169,18 @@ namespace MWGui return mSourceModel->getIndex(item); } + void ProxyItemModel::setSourceModel(ItemModel *sourceModel) + { + if (mSourceModel == sourceModel) + return; + + if (mSourceModel) + { + delete mSourceModel; + mSourceModel = NULL; + } + + mSourceModel = sourceModel; + } + } diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp index 53c7018e22..2019c10426 100644 --- a/apps/openmw/mwgui/itemmodel.hpp +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -80,11 +80,15 @@ namespace MWGui class ProxyItemModel : public ItemModel { public: + ProxyItemModel(); virtual ~ProxyItemModel(); virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false); virtual void removeItem (const ItemStack& item, size_t count); virtual ModelIndex getIndex (ItemStack item); + /// @note Takes ownership of the passed pointer. + void setSourceModel(ItemModel* sourceModel); + ModelIndex mapToSource (ModelIndex index); ModelIndex mapFromSource (ModelIndex index); protected: diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index c5320a2be4..2cdfcada4d 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -30,8 +30,12 @@ ItemView::~ItemView() void ItemView::setModel(ItemModel *model) { + if (mModel == model) + return; + delete mModel; mModel = model; + update(); } @@ -103,7 +107,7 @@ void ItemView::update() MyGUI::Align::Stretch); dragArea->setNeedMouseFocus(true); dragArea->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemView::onSelectedBackground); - dragArea->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheel); + dragArea->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheelMoved); for (ItemModel::ModelIndex i=0; i(mModel->getItemCount()); ++i) { @@ -122,7 +126,7 @@ void ItemView::update() itemWidget->setCount(item.mCount); itemWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemView::onSelectedItem); - itemWidget->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheel); + itemWidget->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheelMoved); } layoutWidgets(); @@ -144,7 +148,7 @@ void ItemView::onSelectedBackground(MyGUI::Widget *sender) eventBackgroundClicked(); } -void ItemView::onMouseWheel(MyGUI::Widget *_sender, int _rel) +void ItemView::onMouseWheelMoved(MyGUI::Widget *_sender, int _rel) { if (mScrollView->getViewOffset().left + _rel*0.3f > 0) mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); diff --git a/apps/openmw/mwgui/itemview.hpp b/apps/openmw/mwgui/itemview.hpp index f87a48aa60..fa6ef29f90 100644 --- a/apps/openmw/mwgui/itemview.hpp +++ b/apps/openmw/mwgui/itemview.hpp @@ -42,7 +42,7 @@ namespace MWGui void onSelectedItem (MyGUI::Widget* sender); void onSelectedBackground (MyGUI::Widget* sender); - void onMouseWheel(MyGUI::Widget* _sender, int _rel); + void onMouseWheelMoved(MyGUI::Widget* _sender, int _rel); ItemModel* mModel; MyGUI::ScrollView* mScrollView; diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index 36940113e1..75436e797d 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -4,7 +4,9 @@ #include #include -#include +// correctIconPath +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" @@ -77,7 +79,7 @@ namespace MWGui void ItemWidget::setIcon(const MWWorld::Ptr &ptr) { - setIcon(Misc::ResourceHelpers::correctIconPath(ptr.getClass().getInventoryIcon(ptr))); + setIcon(MWBase::Environment::get().getWindowManager()->correctIconPath(ptr.getClass().getInventoryIcon(ptr))); } diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index 936da2d8e2..7eaf5eb303 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -8,6 +8,7 @@ #include "../mwbase/environment.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/store.hpp" @@ -57,7 +58,7 @@ namespace MWGui if (mFadeTimeRemaining <= 0) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getWorld()->teleportToClosestMarker(player, "prisonmarker"); setVisible(true); @@ -76,7 +77,7 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Jail); MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getWorld()->advanceTime(mDays * 24); for (int i=0; i skills; for (int day=0; daytopicBegin (); i != journal->topicEnd (); ++i) { - if (i->first [0] != std::tolower (character, mLocale)) + if (i->first [0] != Misc::StringUtils::toLower(character)) continue; visitor (i->second.getName()); diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 4cfcc80641..9af87c7ae3 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -61,6 +61,7 @@ namespace DisplayStateStack mStates; Book mTopicIndexBook; bool mQuestMode; + bool mOptionsMode; bool mAllQuests; template @@ -95,7 +96,7 @@ namespace return getWidget (name); } - JournalWindowImpl (MWGui::JournalViewModel::Ptr Model) + JournalWindowImpl (MWGui::JournalViewModel::Ptr Model, bool questList) : WindowBase("openmw_journal.layout"), JournalBooks (Model) { mMainWidget->setVisible(false); @@ -141,17 +142,17 @@ namespace getPage (RightTopicIndex)->adviseLinkClicked (callback); } - adjustButton(OptionsBTN, true); adjustButton(PrevPageBTN); adjustButton(NextPageBTN); adjustButton(CloseBTN); adjustButton(CancelBTN); - adjustButton(ShowAllBTN, true); - adjustButton(ShowActiveBTN, true); adjustButton(JournalBTN); Gui::ImageButton* optionsButton = getWidget(OptionsBTN); - if (optionsButton->getWidth() == 0) + Gui::ImageButton* showActiveButton = getWidget(ShowActiveBTN); + Gui::ImageButton* showAllButton = getWidget(ShowAllBTN); + Gui::ImageButton* questsButton = getWidget(QuestsBTN); + if (!questList) { // If tribunal is not installed (-> no options button), we still want the Topics button available, // so place it where the options button would have been @@ -161,6 +162,23 @@ namespace topicsButton->setPosition(optionsButton->getPosition()); topicsButton->eventMouseButtonClick.clear(); topicsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &JournalWindowImpl::notifyOptions); + + optionsButton->setVisible(false); + showActiveButton->setVisible(false); + showAllButton->setVisible(false); + questsButton->setVisible(false); + } + else + { + optionsButton->setImage("textures/tx_menubook_options.dds"); + showActiveButton->setImage("textures/tx_menubook_quests_active.dds"); + showAllButton->setImage("textures/tx_menubook_quests_all.dds"); + questsButton->setImage("textures/tx_menubook_quests.dds"); + + adjustButton(ShowAllBTN); + adjustButton(ShowActiveBTN); + adjustButton(OptionsBTN); + adjustButton(QuestsBTN); } Gui::ImageButton* nextButton = getWidget(NextPageBTN); @@ -172,7 +190,6 @@ namespace } adjustButton(TopicsBTN); - adjustButton(QuestsBTN, true); int width = getWidget(TopicsBTN)->getSize().width + getWidget(QuestsBTN)->getSize().width; int topicsWidth = getWidget(TopicsBTN)->getSize().width; int pageWidth = getWidget(RightBookPage)->getSize().width; @@ -182,14 +199,15 @@ namespace mQuestMode = false; mAllQuests = false; + mOptionsMode = false; } - void adjustButton (char const * name, bool optional = false) + void adjustButton (char const * name) { Gui::ImageButton* button = getWidget(name); - MyGUI::IntSize diff = button->getSize() - button->getRequestedSize(!optional); - button->setSize(button->getRequestedSize(!optional)); + MyGUI::IntSize diff = button->getSize() - button->getRequestedSize(); + button->setSize(button->getRequestedSize()); if (button->getAlign().isRight()) button->setPosition(button->getPosition() + MyGUI::IntPoint(diff.width,0)); @@ -244,6 +262,7 @@ namespace void setBookMode () { + mOptionsMode = false; setVisible (OptionsBTN, true); setVisible (OptionsOverlay, false); @@ -253,6 +272,8 @@ namespace void setOptionsMode () { + mOptionsMode = true; + setVisible (OptionsBTN, false); setVisible (OptionsOverlay, true); @@ -351,6 +372,8 @@ namespace setVisible (OptionsOverlay, false); setVisible (OptionsBTN, true); setVisible (JournalBTN, true); + + mOptionsMode = false; } void notifyTopicSelected (const std::string& topic, int id) @@ -378,6 +401,8 @@ namespace setVisible (OptionsOverlay, false); setVisible (OptionsBTN, true); setVisible (JournalBTN, true); + + mOptionsMode = false; } void notifyOptions(MyGUI::Widget* _sender) @@ -508,6 +533,8 @@ namespace void notifyNextPage(MyGUI::Widget* _sender) { + if (mOptionsMode) + return; if (!mStates.empty ()) { unsigned int & page = mStates.top ().mPage; @@ -523,6 +550,8 @@ namespace void notifyPrevPage(MyGUI::Widget* _sender) { + if (mOptionsMode) + return; if (!mStates.empty ()) { unsigned int & page = mStates.top ().mPage; @@ -538,7 +567,7 @@ namespace } // glue the implementation to the interface -MWGui::JournalWindow * MWGui::JournalWindow::create (JournalViewModel::Ptr Model) +MWGui::JournalWindow * MWGui::JournalWindow::create (JournalViewModel::Ptr Model, bool questList) { - return new JournalWindowImpl (Model); + return new JournalWindowImpl (Model, questList); } diff --git a/apps/openmw/mwgui/journalwindow.hpp b/apps/openmw/mwgui/journalwindow.hpp index 5d2a5318ac..740284e203 100644 --- a/apps/openmw/mwgui/journalwindow.hpp +++ b/apps/openmw/mwgui/journalwindow.hpp @@ -12,7 +12,7 @@ namespace MWGui struct JournalWindow { /// construct a new instance of the one JournalWindow implementation - static JournalWindow * create (boost::shared_ptr Model); + static JournalWindow * create (boost::shared_ptr Model, bool questList); /// destroy this instance of the JournalWindow implementation virtual ~JournalWindow () {}; diff --git a/libs/openengine/gui/layout.cpp b/apps/openmw/mwgui/layout.cpp similarity index 98% rename from libs/openengine/gui/layout.cpp rename to apps/openmw/mwgui/layout.cpp index cd94387f95..6bdb1a9f4d 100644 --- a/libs/openengine/gui/layout.cpp +++ b/apps/openmw/mwgui/layout.cpp @@ -6,9 +6,7 @@ #include #include -namespace OEngine -{ -namespace GUI +namespace MWGui { void Layout::initialise(const std::string& _layout, MyGUI::Widget* _parent) { @@ -78,4 +76,3 @@ namespace GUI } } -} diff --git a/libs/openengine/gui/layout.hpp b/apps/openmw/mwgui/layout.hpp similarity index 94% rename from libs/openengine/gui/layout.hpp rename to apps/openmw/mwgui/layout.hpp index b0515ccbb0..0e7cf3faa7 100644 --- a/libs/openengine/gui/layout.hpp +++ b/apps/openmw/mwgui/layout.hpp @@ -1,12 +1,11 @@ -#ifndef OENGINE_MYGUI_LAYOUT_H -#define OENGINE_MYGUI_LAYOUT_H +#ifndef OPENMW_MWGUI_LAYOUT_H +#define OPENMW_MWGUI_LAYOUT_H #include #include #include -namespace OEngine { -namespace GUI +namespace MWGui { /** The Layout class is an utility class used to load MyGUI layouts from xml files, and to manipulate member widgets. @@ -60,5 +59,5 @@ namespace GUI std::string mLayoutName; MyGUI::VectorWidgetPtr mListWindowRoot; }; -}} +} #endif diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index c392372ff3..c832a78792 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -16,6 +16,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" namespace MWGui { @@ -203,7 +204,7 @@ namespace MWGui void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); if (mSpentAttributes.size() < mCoinCount) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index db7b320183..e32140cb35 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -1,44 +1,43 @@ #include "loadingscreen.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include #include #include #include #include -#include +#include + +#include #include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/statemanager.hpp" - #include "../mwbase/windowmanager.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwrender/vismask.hpp" + #include "backgroundimage.hpp" namespace MWGui { - LoadingScreen::LoadingScreen(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* rw) + LoadingScreen::LoadingScreen(const VFS::Manager* vfs, osgViewer::Viewer* viewer) : WindowBase("openmw_loading_screen.layout") - , mSceneMgr(sceneMgr) - , mWindow(rw) - , mLastWallpaperChangeTime(0) - , mLastRenderTime(0) + , mVFS(vfs) + , mViewer(viewer) + , mTargetFrameRate(120.0) + , mLastWallpaperChangeTime(0.0) + , mLastRenderTime(0.0) + , mLoadingOnTime(0.0) + , mImportantLabel(false) , mProgress(0) - , mVSyncWasEnabled(false) { mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); @@ -52,10 +51,42 @@ namespace MWGui MyGUI::Align::Stretch, "Menu"); setVisible(false); + + findSplashScreens(); } - void LoadingScreen::setLabel(const std::string &label) + LoadingScreen::~LoadingScreen() { + } + + void LoadingScreen::findSplashScreens() + { + const std::map& index = mVFS->getIndex(); + std::string pattern = "Splash/"; + mVFS->normalizeFilename(pattern); + + std::map::const_iterator found = index.lower_bound(pattern); + while (found != index.end()) + { + const std::string& name = found->first; + if (name.size() >= pattern.size() && name.substr(0, pattern.size()) == pattern) + { + size_t pos = name.find_last_of('.'); + if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".tga") == 0) + mSplashScreens.push_back(found->first); + } + else + break; + ++found; + } + if (mSplashScreens.empty()) + std::cerr << "No splash screens found!" << std::endl; + } + + void LoadingScreen::setLabel(const std::string &label, bool important) + { + mImportantLabel = important; + mLoadingText->setCaptionWithReplacing(label); int padding = mLoadingBox->getWidth() - mLoadingText->getWidth(); MyGUI::IntSize size(mLoadingText->getTextSize().width+padding, mLoadingBox->getHeight()); @@ -64,53 +95,76 @@ namespace MWGui mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mLoadingBox->getTop()); } - LoadingScreen::~LoadingScreen() - { - } - void LoadingScreen::setVisible(bool visible) { WindowBase::setVisible(visible); mBackgroundImage->setVisible(visible); } + class CopyFramebufferToTextureCallback : public osg::Camera::DrawCallback + { + public: + CopyFramebufferToTextureCallback(osg::Texture2D* texture, int w, int h) + : mTexture(texture), mWidth(w), mHeight(h) + { + } + + virtual void operator () (osg::RenderInfo& renderInfo) const + { + mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, mWidth, mHeight); + + // Callback removes itself when done + if (renderInfo.getCurrentCamera()) + renderInfo.getCurrentCamera()->setInitialDrawCallback(NULL); + } + + private: + osg::ref_ptr mTexture; + int mWidth, mHeight; + }; + void LoadingScreen::loadingOn() { + mLoadingOnTime = mTimer.time_m(); // Early-out if already on if (mMainWidget->getVisible()) return; - // Temporarily turn off VSync, we want to do actual loading rather than waiting for the screen to sync. - // Threaded loading would be even better, of course - especially because some drivers force VSync to on and we can't change it. - mVSyncWasEnabled = mWindow->isVSyncEnabled(); - mWindow->setVSyncEnabled(false); + if (mViewer->getIncrementalCompileOperation()) + { + mViewer->getIncrementalCompileOperation()->setMaximumNumOfObjectsToCompilePerFrame(100); + mViewer->getIncrementalCompileOperation()->setTargetFrameRate(mTargetFrameRate); + } bool showWallpaper = (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame); - if (!showWallpaper) { - mBackgroundImage->setImageTexture(""); - int width = mWindow->getWidth(); - int height = mWindow->getHeight(); - const std::string textureName = "@loading_background"; - Ogre::TexturePtr texture; - texture = Ogre::TextureManager::getSingleton().getByName(textureName); - if (texture.isNull()) + // Copy the current framebuffer onto a texture and display that texture as the background image + // Note, we could also set the camera to disable clearing and have the background image transparent, + // but then we get shaking effects on buffer swaps. + + if (!mTexture) { - texture = Ogre::TextureManager::getSingleton().createManual(textureName, - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, - width, height, 0, mWindow->suggestPixelFormat(), Ogre::TU_DYNAMIC_WRITE_ONLY); + mTexture = new osg::Texture2D; + mTexture->setInternalFormat(GL_RGB); + mTexture->setResizeNonPowerOfTwoHint(false); } - texture->unload(); - texture->setWidth(width); - texture->setHeight(height); - texture->createInternalResources(); - mWindow->copyContentsToMemory(texture->getBuffer()->lock(Ogre::Image::Box(0,0,width,height), Ogre::HardwareBuffer::HBL_DISCARD)); - texture->getBuffer()->unlock(); - mBackgroundImage->setBackgroundImage(texture->getName(), false, false); + + int width = mViewer->getCamera()->getViewport()->width(); + int height = mViewer->getCamera()->getViewport()->height(); + mViewer->getCamera()->setInitialDrawCallback(new CopyFramebufferToTextureCallback(mTexture, width, height)); + + if (!mGuiTexture.get()) + { + mGuiTexture.reset(new osgMyGUI::OSGTexture(mTexture)); + } + + mBackgroundImage->setBackgroundImage(""); + + mBackgroundImage->setRenderItemTexture(mGuiTexture.get()); + mBackgroundImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); } setVisible(true); @@ -125,9 +179,20 @@ namespace MWGui void LoadingScreen::loadingOff() { - // Re-enable vsync now. - mWindow->setVSyncEnabled(mVSyncWasEnabled); + if (mLastRenderTime < mLoadingOnTime) + { + // the loading was so fast that we didn't show loading screen at all + // we may still want to show the label if the caller requested it + if (mImportantLabel) + { + MWBase::Environment::get().getWindowManager()->messageBox(mLoadingText->getCaption()); + mImportantLabel = false; + } + } + else + mImportantLabel = false; // label was already shown on loading screen + //std::cout << "loading took " << mTimer.time_m() - mLoadingOnTime << std::endl; setVisible(false); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Loading); @@ -136,29 +201,15 @@ namespace MWGui void LoadingScreen::changeWallpaper () { - if (mResources.empty()) + if (!mSplashScreens.empty()) { - Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups (); - for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it) - { - Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "Splash/*.tga"); - mResources.insert(mResources.end(), resourcesInThisGroup->begin(), resourcesInThisGroup->end()); - } - } - - if (!mResources.empty()) - { - std::string const & randomSplash = mResources.at(OEngine::Misc::Rng::rollDice(mResources.size())); - - Ogre::TextureManager::getSingleton ().load (randomSplash, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); + std::string const & randomSplash = mSplashScreens.at(Misc::Rng::rollDice(mSplashScreens.size())); // TODO: add option (filename pattern?) to use image aspect ratio instead of 4:3 // we can't do this by default, because the Morrowind splash screens are 1024x1024, but should be displayed as 4:3 bool stretch = Settings::Manager::getBool("stretch menu background", "GUI"); mBackgroundImage->setBackgroundImage(randomSplash, true, stretch); } - else - std::cerr << "No loading screens found!" << std::endl; } void LoadingScreen::setProgressRange (size_t range) @@ -171,8 +222,10 @@ namespace MWGui void LoadingScreen::setProgress (size_t value) { - if (value - mProgress < mProgressBar->getScrollRange()/100.f) + // skip expensive update if there isn't enough visible progress + if (value - mProgress < mProgressBar->getScrollRange()/200.f) return; + value = std::min(value, mProgressBar->getScrollRange()-1); mProgress = value; mProgressBar->setScrollPosition(0); mProgressBar->setTrackSize(static_cast(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize())); @@ -183,63 +236,76 @@ namespace MWGui { mProgressBar->setScrollPosition(0); size_t value = mProgress + increase; + value = std::min(value, mProgressBar->getScrollRange()-1); mProgress = value; mProgressBar->setTrackSize(static_cast(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize())); draw(); } - void LoadingScreen::indicateProgress() + bool LoadingScreen::needToDrawLoadingScreen() { - float time = (mTimer.getMilliseconds() % 2001) / 1000.f; - if (time > 1) - time = (time-2)*-1; + if ( mTimer.time_m() <= mLastRenderTime + (1.0/mTargetFrameRate) * 1000.0) + return false; - mProgressBar->setTrackSize(50); - mProgressBar->setScrollPosition(static_cast(time * (mProgressBar->getScrollRange() - 1))); - draw(); + // the minimal delay before a loading screen shows + const float initialDelay = 0.05; + + bool alreadyShown = (mLastRenderTime > mLoadingOnTime); + float diff = (mTimer.time_m() - mLoadingOnTime); + + if (!alreadyShown) + { + // bump the delay by the current progress - i.e. if during the initial delay the loading + // has almost finished, no point showing the loading screen now + diff -= mProgress / static_cast(mProgressBar->getScrollRange()) * 100.f; + } + + bool showWallpaper = (MWBase::Environment::get().getStateManager()->getState() + == MWBase::StateManager::State_NoGame); + if (!showWallpaper && diff < initialDelay*1000) + return false; + return true; } void LoadingScreen::draw() { - const float loadingScreenFps = 20.f; + if (!needToDrawLoadingScreen()) + return; - if (mTimer.getMilliseconds () > mLastRenderTime + (1.f/loadingScreenFps) * 1000.f) + bool showWallpaper = (MWBase::Environment::get().getStateManager()->getState() + == MWBase::StateManager::State_NoGame); + if (showWallpaper && mTimer.time_m() > mLastWallpaperChangeTime + 5000*1) { - mLastRenderTime = mTimer.getMilliseconds (); - - bool showWallpaper = (MWBase::Environment::get().getStateManager()->getState() - == MWBase::StateManager::State_NoGame); - - if (showWallpaper && mTimer.getMilliseconds () > mLastWallpaperChangeTime + 5000*1) - { - mLastWallpaperChangeTime = mTimer.getMilliseconds (); - changeWallpaper(); - } - - // Turn off rendering except the GUI - mSceneMgr->clearSpecialCaseRenderQueues(); - // SCRQM_INCLUDE with RENDER_QUEUE_OVERLAY does not work. - for (int i = 0; i < Ogre::RENDER_QUEUE_MAX; ++i) - { - if (i > 0 && i < 96) - mSceneMgr->addSpecialCaseRenderQueue(i); - } - mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); - - MWBase::Environment::get().getInputManager()->update(0, true, true); - - // First, swap buffers from last draw, then, queue an update of the - // window contents, but don't swap buffers (which would have - // caused a sync / flush and would be expensive). - // We're doing this so we can do some actual loading while the GPU is busy with the render. - // This means the render is lagging a frame behind, but this is hardly noticable. - mWindow->swapBuffers(); - - mWindow->update(false); - - // resume 3d rendering - mSceneMgr->clearSpecialCaseRenderQueues(); - mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); + mLastWallpaperChangeTime = mTimer.time_m(); + changeWallpaper(); } + + // Turn off rendering except the GUI + int oldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask(); + int oldCullMask = mViewer->getCamera()->getCullMask(); + mViewer->getUpdateVisitor()->setTraversalMask(MWRender::Mask_GUI); + mViewer->getCamera()->setCullMask(MWRender::Mask_GUI); + + MWBase::Environment::get().getInputManager()->update(0, true, true); + + //osg::Timer timer; + // at the time this function is called we are in the middle of a frame, + // so out of order calls are necessary to get a correct frameNumber for the next frame. + // refer to the advance() and frame() order in Engine::go() + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + //std::cout << "frame took " << timer.time_m() << std::endl; + + //if (mViewer->getIncrementalCompileOperation()) + //std::cout << "num to compile " << mViewer->getIncrementalCompileOperation()->getToCompile().size() << std::endl; + + // resume 3d rendering + mViewer->getUpdateVisitor()->setTraversalMask(oldUpdateMask); + mViewer->getCamera()->setCullMask(oldCullMask); + + mLastRenderTime = mTimer.time_m(); } + } diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 0d3ffbbec7..a7f7d3619c 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -1,16 +1,26 @@ #ifndef MWGUI_LOADINGSCREEN_H #define MWGUI_LOADINGSCREEN_H -#include -#include +#include +#include #include "windowbase.hpp" #include -namespace Ogre +namespace osgViewer { - class SceneManager; + class Viewer; +} + +namespace osg +{ + class Texture2D; +} + +namespace VFS +{ + class Manager; } namespace MWGui @@ -20,35 +30,34 @@ namespace MWGui class LoadingScreen : public WindowBase, public Loading::Listener { public: - virtual void setLabel (const std::string& label); - - /// Indicate that some progress has been made, without specifying how much - virtual void indicateProgress (); + LoadingScreen(const VFS::Manager* vfs, osgViewer::Viewer* viewer); + virtual ~LoadingScreen(); + /// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details + virtual void setLabel (const std::string& label, bool important); virtual void loadingOn(); virtual void loadingOff(); - virtual void setProgressRange (size_t range); virtual void setProgress (size_t value); virtual void increaseProgress (size_t increase=1); virtual void setVisible(bool visible); - LoadingScreen(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* rw); - virtual ~LoadingScreen(); - - void setLoadingProgress (const std::string& stage, int depth, int current, int total); - void loadingDone(); - - void updateWindow(Ogre::RenderWindow* rw) { mWindow = rw; } - private: - Ogre::SceneManager* mSceneMgr; - Ogre::RenderWindow* mWindow; + void findSplashScreens(); + bool needToDrawLoadingScreen(); - unsigned long mLastWallpaperChangeTime; - unsigned long mLastRenderTime; - Ogre::Timer mTimer; + const VFS::Manager* mVFS; + osg::ref_ptr mViewer; + + double mTargetFrameRate; + + double mLastWallpaperChangeTime; + double mLastRenderTime; + osg::Timer mTimer; + double mLoadingOnTime; + + bool mImportantLabel; size_t mProgress; @@ -58,9 +67,12 @@ namespace MWGui MyGUI::ScrollBar* mProgressBar; BackgroundImage* mBackgroundImage; - Ogre::StringVector mResources; + std::vector mSplashScreens; - bool mVSyncWasEnabled; + // TODO: add releaseGLObjects() for mTexture + + osg::ref_ptr mTexture; + std::auto_ptr mGuiTexture; void changeWallpaper(); diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 9a737af64c..258f0dfb08 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -1,26 +1,19 @@ #include "mainmenu.hpp" -#include - #include #include #include -#include - #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwbase/journal.hpp" -#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/statemanager.hpp" -#include "../mwstate/character.hpp" - #include "savegamedialog.hpp" #include "confirmationdialog.hpp" #include "backgroundimage.hpp" @@ -29,31 +22,19 @@ namespace MWGui { - MainMenu::MainMenu(int w, int h) - : OEngine::GUI::Layout("openmw_mainmenu.layout") - , mWidth (w), mHeight (h), mButtonBox(0) + MainMenu::MainMenu(int w, int h, const VFS::Manager* vfs, const std::string& versionDescription) + : Layout("openmw_mainmenu.layout") + , mWidth (w), mHeight (h) + , mVFS(vfs), mButtonBox(0) , mBackground(NULL) , mVideoBackground(NULL) , mVideo(NULL) , mSaveGameDialog(NULL) { getWidget(mVersionText, "VersionText"); - std::stringstream sstream; - sstream << "OpenMW Version: " << OPENMW_VERSION; + mVersionText->setCaption(versionDescription); - // adding info about git hash if available - std::string rev = OPENMW_VERSION_COMMITHASH; - std::string tag = OPENMW_VERSION_TAGHASH; - if (!rev.empty() && !tag.empty()) - { - rev = rev.substr(0,10); - sstream << "\nRevision: " << rev; - } - - std::string output = sstream.str(); - mVersionText->setCaption(output); - - mHasAnimatedMenu = (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("video\\menu_background.bik")); + mHasAnimatedMenu = mVFS->exists("video/menu_background.bik"); updateMenu(); } @@ -80,7 +61,7 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) && MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame); - OEngine::GUI::Layout::setVisible (visible); + Layout::setVisible (visible); } void MainMenu::onNewGameConfirmed() @@ -112,7 +93,7 @@ namespace MWGui else { ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - dialog->open("#{sMessage2}"); + dialog->askForConfirmation("#{sMessage2}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &MainMenu::onExitConfirmed); dialog->eventCancelClicked.clear(); @@ -125,7 +106,7 @@ namespace MWGui else { ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - dialog->open("#{sNotifyMessage54}"); + dialog->askForConfirmation("#{sNotifyMessage54}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &MainMenu::onNewGameConfirmed); dialog->eventCancelClicked.clear(); @@ -170,10 +151,11 @@ namespace MWGui // Use black background to correct aspect ratio mVideoBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Default, "Menu"); - mVideoBackground->setImageTexture("black.png"); + mVideoBackground->setImageTexture("black"); mVideo = mVideoBackground->createWidget("ImageBox", 0,0,1,1, MyGUI::Align::Stretch, "Menu"); + mVideo->setVFS(mVFS); mVideo->playVideo("video\\menu_background.bik"); } diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index cd2050d0ff..fe256dc8ed 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -1,13 +1,18 @@ #ifndef OPENMW_GAME_MWGUI_MAINMENU_H #define OPENMW_GAME_MWGUI_MAINMENU_H -#include +#include "layout.hpp" namespace Gui { class ImageButton; } +namespace VFS +{ + class Manager; +} + namespace MWGui { @@ -15,7 +20,7 @@ namespace MWGui class SaveGameDialog; class VideoWidget; - class MainMenu : public OEngine::GUI::Layout + class MainMenu : public Layout { int mWidth; int mHeight; @@ -24,7 +29,7 @@ namespace MWGui public: - MainMenu(int w, int h); + MainMenu(int w, int h, const VFS::Manager* vfs, const std::string& versionDescription); ~MainMenu(); void onResChange(int w, int h); @@ -34,6 +39,7 @@ namespace MWGui void update(float dt); private: + const VFS::Manager* mVFS; MyGUI::Widget* mButtonBox; MyGUI::TextBox* mVersionText; diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index c594327961..0ebb595ddf 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -1,7 +1,6 @@ #include "mapwindow.hpp" -#include -#include +#include #include #include @@ -13,7 +12,9 @@ #include #include +#include #include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -24,8 +25,8 @@ #include "../mwworld/esmstore.hpp" #include "../mwrender/globalmap.hpp" +#include "../mwrender/localmap.hpp" -#include "widgets.hpp" #include "confirmationdialog.hpp" #include "tooltips.hpp" @@ -36,8 +37,8 @@ namespace enum LocalMapWidgetDepth { - Local_CompassLayer = 0, - Local_MarkerAboveFogLayer = 1, + Local_MarkerAboveFogLayer = 0, + Local_CompassLayer = 1, Local_FogLayer = 2, Local_MarkerLayer = 3, Local_MapLayer = 4 @@ -90,31 +91,41 @@ namespace MWGui void CustomMarkerCollection::addMarker(const ESM::CustomMarker &marker, bool triggerEvent) { - mMarkers.push_back(marker); + mMarkers.insert(std::make_pair(marker.mCell, marker)); if (triggerEvent) eventMarkersChanged(); } void CustomMarkerCollection::deleteMarker(const ESM::CustomMarker &marker) { - std::vector::iterator it = std::find(mMarkers.begin(), mMarkers.end(), marker); - if (it != mMarkers.end()) - mMarkers.erase(it); - else - throw std::runtime_error("can't find marker to delete"); + std::pair range = mMarkers.equal_range(marker.mCell); - eventMarkersChanged(); + for (ContainerType::iterator it = range.first; it != range.second; ++it) + { + if (it->second == marker) + { + mMarkers.erase(it); + eventMarkersChanged(); + return; + } + } + throw std::runtime_error("can't find marker to delete"); } void CustomMarkerCollection::updateMarker(const ESM::CustomMarker &marker, const std::string &newNote) { - std::vector::iterator it = std::find(mMarkers.begin(), mMarkers.end(), marker); - if (it != mMarkers.end()) - it->mNote = newNote; - else - throw std::runtime_error("can't find marker to update"); + std::pair range = mMarkers.equal_range(marker.mCell); - eventMarkersChanged(); + for (ContainerType::iterator it = range.first; it != range.second; ++it) + { + if (it->second == marker) + { + it->second.mNote = newNote; + eventMarkersChanged(); + return; + } + } + throw std::runtime_error("can't find marker to update"); } void CustomMarkerCollection::clear() @@ -123,16 +134,21 @@ namespace MWGui eventMarkersChanged(); } - std::vector::const_iterator CustomMarkerCollection::begin() const + CustomMarkerCollection::ContainerType::const_iterator CustomMarkerCollection::begin() const { return mMarkers.begin(); } - std::vector::const_iterator CustomMarkerCollection::end() const + CustomMarkerCollection::ContainerType::const_iterator CustomMarkerCollection::end() const { return mMarkers.end(); } + CustomMarkerCollection::RangeType CustomMarkerCollection::getMarkers(const ESM::CellId &cellId) const + { + return mMarkers.equal_range(cellId); + } + size_t CustomMarkerCollection::size() const { return mMarkers.size(); @@ -140,8 +156,9 @@ namespace MWGui // ------------------------------------------------------ - LocalMapBase::LocalMapBase(CustomMarkerCollection &markers) - : mCurX(0) + LocalMapBase::LocalMapBase(CustomMarkerCollection &markers, MWRender::LocalMap* localMapRender) + : mLocalMapRender(localMapRender) + , mCurX(0) , mCurY(0) , mInterior(false) , mLocalMap(NULL) @@ -212,19 +229,37 @@ namespace MWGui void LocalMapBase::applyFogOfWar() { + TextureVector fogTextures; for (int mx=0; mx<3; ++mx) { for (int my=0; my<3; ++my) { - std::string image = mPrefix+"_"+ MyGUI::utility::toString(mCurX + (mx-1)) + "_" - + MyGUI::utility::toString(mCurY + (-1*(my-1))); + int x = mCurX + (mx-1); + int y = mCurY + (-1*(my-1)); MyGUI::ImageBox* fog = mFogWidgets[my + 3*mx]; - fog->setImageTexture(mFogOfWar ? - ((MyGUI::RenderManager::getInstance().getTexture(image+"_fog") != 0) ? image+"_fog" - : "black.png" ) - : ""); + + if (!mFogOfWar) + { + fog->setImageTexture(""); + continue; + } + + osg::ref_ptr tex = mLocalMapRender->getFogOfWarTexture(x, y); + if (tex) + { + boost::shared_ptr myguitex (new osgMyGUI::OSGTexture(tex)); + fog->setRenderItemTexture(myguitex.get()); + fog->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); + fogTextures.push_back(myguitex); + } + else + fog->setImageTexture("black"); } } + // Move the textures we just set into mFogTextures, and move the previous textures into fogTextures, for deletion when this function ends. + // Note, above we need to ensure that all widgets are getting a new texture set, lest we delete textures that are still in use. + mFogTextures.swap(fogTextures); + redraw(); } @@ -256,8 +291,8 @@ namespace MWGui else { int cellX, cellY; - Ogre::Vector2 worldPos (worldX, worldY); - MWBase::Environment::get().getWorld ()->worldToInteriorMapPosition (worldPos, nX, nY, cellX, cellY); + osg::Vec2f worldPos (worldX, worldY); + mLocalMapRender->worldToInteriorMapPosition(worldPos, nX, nY, cellX, cellY); markerPos.cellX = cellX; markerPos.cellY = cellY; @@ -278,44 +313,43 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(*it); mCustomMarkerWidgets.clear(); - for (std::vector::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) + for (int dX = -1; dX <= 1; ++dX) { - const ESM::CustomMarker& marker = *it; - - if (marker.mCell.mPaged != !mInterior) - continue; - if (mInterior) + for (int dY =-1; dY <= 1; ++dY) { - if (marker.mCell.mWorldspace != mPrefix) - continue; - } - else - { - if (std::abs(marker.mCell.mIndex.mX - mCurX) > 1) - continue; - if (std::abs(marker.mCell.mIndex.mY - mCurY) > 1) - continue; - } + ESM::CellId cellId; + cellId.mPaged = !mInterior; + cellId.mWorldspace = (mInterior ? mPrefix : "sys::default"); + cellId.mIndex.mX = mCurX+dX; + cellId.mIndex.mY = mCurY+dY; - MarkerUserData markerPos; - MyGUI::IntPoint widgetPos = getMarkerPosition(marker.mWorldX, marker.mWorldY, markerPos); + CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellId); + for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it) + { + const ESM::CustomMarker& marker = it->second; - MyGUI::IntCoord widgetCoord(widgetPos.left - 8, - widgetPos.top - 8, - 16, 16); - MarkerWidget* markerWidget = mLocalMap->createWidget("CustomMarkerButton", - widgetCoord, MyGUI::Align::Default); - markerWidget->setDepth(Local_MarkerAboveFogLayer); - markerWidget->setUserString("ToolTipType", "Layout"); - markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); - markerWidget->setUserString("Caption_TextOneLine", MyGUI::TextIterator::toTagsString(marker.mNote)); - markerWidget->setNormalColour(MyGUI::Colour(0.6f, 0.6f, 0.6f)); - markerWidget->setHoverColour(MyGUI::Colour(1.0f, 1.0f, 1.0f)); - markerWidget->setUserData(marker); - markerWidget->setNeedMouseFocus(true); - customMarkerCreated(markerWidget); - mCustomMarkerWidgets.push_back(markerWidget); + MarkerUserData markerPos (mLocalMapRender); + MyGUI::IntPoint widgetPos = getMarkerPosition(marker.mWorldX, marker.mWorldY, markerPos); + + MyGUI::IntCoord widgetCoord(widgetPos.left - 8, + widgetPos.top - 8, + 16, 16); + MarkerWidget* markerWidget = mLocalMap->createWidget("CustomMarkerButton", + widgetCoord, MyGUI::Align::Default); + markerWidget->setDepth(Local_MarkerAboveFogLayer); + markerWidget->setUserString("ToolTipType", "Layout"); + markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); + markerWidget->setUserString("Caption_TextOneLine", MyGUI::TextIterator::toTagsString(marker.mNote)); + markerWidget->setNormalColour(MyGUI::Colour(0.6f, 0.6f, 0.6f)); + markerWidget->setHoverColour(MyGUI::Colour(1.0f, 1.0f, 1.0f)); + markerWidget->setUserData(marker); + markerWidget->setNeedMouseFocus(true); + customMarkerCreated(markerWidget); + mCustomMarkerWidgets.push_back(markerWidget); + } + } } + redraw(); } @@ -338,22 +372,29 @@ namespace MWGui mDoorMarkerWidgets.clear(); // Update the map textures + TextureVector textures; for (int mx=0; mx<3; ++mx) { for (int my=0; my<3; ++my) { - // map - std::string image = mPrefix+"_"+ MyGUI::utility::toString(x + (mx-1)) + "_" - + MyGUI::utility::toString(y + (-1*(my-1))); + int mapX = x + (mx-1); + int mapY = y + (-1*(my-1)); MyGUI::ImageBox* box = mMapWidgets[my + 3*mx]; - if (MyGUI::RenderManager::getInstance().getTexture(image) != 0) - box->setImageTexture(image); + osg::ref_ptr texture = mLocalMapRender->getMapTexture(mapX, mapY); + if (texture) + { + boost::shared_ptr guiTex (new osgMyGUI::OSGTexture(texture)); + textures.push_back(guiTex); + box->setRenderItemTexture(guiTex.get()); + box->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + } else - box->setImageTexture("black.png"); + box->setRenderItemTexture(NULL); } } + mMapTextures.swap(textures); MWBase::World* world = MWBase::Environment::get().getWorld(); @@ -383,13 +424,11 @@ namespace MWGui MWBase::World::DoorMarker marker = *it; std::vector destNotes; - for (std::vector::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) - { - if (it->mCell == marker.dest) - destNotes.push_back(it->mNote); - } + CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(marker.dest); + for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it) + destNotes.push_back(it->second.mNote); - MarkerUserData data; + MarkerUserData data (mLocalMapRender); data.notes = destNotes; data.caption = marker.name; MyGUI::IntPoint widgetPos = getMarkerPosition(marker.x, marker.y, data); @@ -485,7 +524,7 @@ namespace MWGui for (std::vector::iterator it = markers.begin(); it != markers.end(); ++it) { const ESM::Position& worldPos = it->getRefData().getPosition(); - MarkerUserData markerPos; + MarkerUserData markerPos (mLocalMapRender); MyGUI::IntPoint widgetPos = getMarkerPosition(worldPos.pos[0], worldPos.pos[1], markerPos); MyGUI::IntCoord widgetCoord(widgetPos.left - 4, widgetPos.top - 4, @@ -530,7 +569,7 @@ namespace MWGui if (markedCell && markedCell->isExterior() == !mInterior && (!mInterior || Misc::StringUtils::ciEqual(markedCell->getCell()->mName, mPrefix))) { - MarkerUserData markerPos; + MarkerUserData markerPos (mLocalMapRender); MyGUI::IntPoint widgetPos = getMarkerPosition(markedPosition.pos[0], markedPosition.pos[1], markerPos); MyGUI::IntCoord widgetCoord(widgetPos.left - 4, widgetPos.top - 4, @@ -548,9 +587,9 @@ namespace MWGui // ------------------------------------------------------------------------------------------ - MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, const std::string& cacheDir) + MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender) : WindowPinnableBase("openmw_map_window.layout") - , LocalMapBase(customMarkers) + , LocalMapBase(customMarkers, localMapRender) , NoDrop(drag, mMainWidget) , mGlobalMap(0) , mGlobalMapImage(NULL) @@ -558,7 +597,7 @@ namespace MWGui , mGlobal(false) , mEventBoxGlobal(NULL) , mEventBoxLocal(NULL) - , mGlobalMapRender(0) + , mGlobalMapRender(new MWRender::GlobalMap(localMapRender->getRoot())) , mEditNoteDialog() { static bool registered = false; @@ -623,7 +662,7 @@ namespace MWGui void MapWindow::onNoteEditDelete() { ConfirmationDialog* confirmation = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - confirmation->open("#{sDeleteNote}", "#{sYes}", "#{sNo}"); + confirmation->askForConfirmation("#{sDeleteNote}", "#{sYes}", "#{sNo}"); confirmation->eventCancelClicked.clear(); confirmation->eventOkClicked.clear(); confirmation->eventOkClicked += MyGUI::newDelegate(this, &MapWindow::onNoteEditDeleteConfirm); @@ -656,19 +695,19 @@ namespace MWGui x += mCurX; y += mCurY; - Ogre::Vector2 worldPos; + osg::Vec2f worldPos; if (mInterior) { - worldPos = MWBase::Environment::get().getWorld()->interiorMapToWorldPosition(nX, nY, x, y); + worldPos = mLocalMapRender->interiorMapToWorldPosition(nX, nY, x, y); } else { - worldPos.x = (x + nX) * cellSize; - worldPos.y = (y + (1.0f-nY)) * cellSize; + worldPos.x() = (x + nX) * cellSize; + worldPos.y() = (y + (1.0f-nY)) * cellSize; } - mEditingMarker.mWorldX = worldPos.x; - mEditingMarker.mWorldY = worldPos.y; + mEditingMarker.mWorldX = worldPos.x(); + mEditingMarker.mWorldY = worldPos.y(); mEditingMarker.mCell.mPaged = !mInterior; if (mInterior) @@ -701,13 +740,17 @@ namespace MWGui void MapWindow::renderGlobalMap(Loading::Listener* loadingListener) { - mGlobalMapRender = new MWRender::GlobalMap(""); mGlobalMapRender->render(loadingListener); mGlobalMap->setCanvasSize (mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); mGlobalMapImage->setSize(mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); - mGlobalMapImage->setImageTexture("GlobalMap.png"); - mGlobalMapOverlay->setImageTexture("GlobalMapOverlay"); + mGlobalMapTexture.reset(new osgMyGUI::OSGTexture(mGlobalMapRender->getBaseTexture())); + mGlobalMapImage->setRenderItemTexture(mGlobalMapTexture.get()); + mGlobalMapImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + + mGlobalMapOverlayTexture.reset(new osgMyGUI::OSGTexture(mGlobalMapRender->getOverlayTexture())); + mGlobalMapOverlay->setRenderItemTexture(mGlobalMapOverlayTexture.get()); + mGlobalMapOverlay->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); } MapWindow::~MapWindow() @@ -739,14 +782,19 @@ namespace MWGui MyGUI::Widget* markerWidget = mGlobalMap->createWidget("MarkerButton", widgetCoord, MyGUI::Align::Default); + + markerWidget->setUserString("Caption_TextOneLine", "#{sCell=" + name + "}"); + + setGlobalMapMarkerTooltip(markerWidget, x, y); + + markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); + markerWidget->setNeedMouseFocus(true); markerWidget->setColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); - markerWidget->setUserString("ToolTipType", "Layout"); - markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); - markerWidget->setUserString("Caption_TextOneLine", name); markerWidget->setDepth(Global_MarkerLayer); markerWidget->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); markerWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); + mGlobalMapMarkers[std::make_pair(x,y)] = markerWidget; } } @@ -759,15 +807,57 @@ namespace MWGui { LocalMapBase::onFrame(dt); + mGlobalMapRender->cleanupCameras(); + for (std::vector::iterator it = mQueuedToExplore.begin(); it != mQueuedToExplore.end(); ++it) { - mGlobalMapRender->exploreCell(it->first, it->second); + mGlobalMapRender->exploreCell(it->first, it->second, mLocalMapRender->getMapTexture(it->first, it->second)); } + mQueuedToExplore.clear(); NoDrop::onFrame(dt); } + void MapWindow::setGlobalMapMarkerTooltip(MyGUI::Widget* markerWidget, int x, int y) + { + ESM::CellId cellId; + cellId.mIndex.mX = x; + cellId.mIndex.mY = y; + cellId.mWorldspace = "sys::default"; + cellId.mPaged = true; + CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellId); + std::vector destNotes; + for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; ++it) + destNotes.push_back(it->second.mNote); + + if (!destNotes.empty()) + { + MarkerUserData data (NULL); + data.notes = destNotes; + data.caption = markerWidget->getUserString("Caption_TextOneLine"); + markerWidget->setUserData(data); + markerWidget->setUserString("ToolTipType", "MapMarker"); + } + else + { + markerWidget->setUserString("ToolTipType", "Layout"); + } + } + + void MapWindow::updateCustomMarkers() + { + LocalMapBase::updateCustomMarkers(); + + for (std::map, MyGUI::Widget*>::iterator widgetIt = mGlobalMapMarkers.begin(); widgetIt != mGlobalMapMarkers.end(); ++widgetIt) + { + int x = widgetIt->first.first; + int y = widgetIt->first.second; + MyGUI::Widget* markerWidget = widgetIt->second; + setGlobalMapMarkerTooltip(markerWidget, x, y); + } + } + void MapWindow::onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { if (_id!=MyGUI::MouseButton::Left) return; @@ -822,14 +912,16 @@ namespace MWGui // For interiors, position is set by WindowManager via setGlobalMapPlayerPosition if (MWBase::Environment::get().getWorld ()->isCellExterior ()) { - Ogre::Vector3 pos = MWBase::Environment::get().getWorld ()->getPlayerPtr().getRefData ().getBaseNode ()->_getDerivedPosition (); - setGlobalMapPlayerPosition(pos.x, pos.y); + osg::Vec3f pos = MWBase::Environment::get().getWorld ()->getPlayerPtr().getRefData().getPosition().asVec3(); + setGlobalMapPlayerPosition(pos.x(), pos.y()); } } void MapWindow::notifyPlayerUpdate () { globalMapUpdatePlayer (); + + setGlobalMapPlayerDir(mLastDirectionX, mLastDirectionY); } void MapWindow::setGlobalMapPlayerPosition(float worldX, float worldY) @@ -859,11 +951,13 @@ namespace MWGui void MapWindow::clear() { mMarkers.clear(); + mGlobalMapRender->clear(); mChanged = true; - while (mEventBoxGlobal->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(mEventBoxGlobal->getChildAt(0)); + for (std::map, MyGUI::Widget*>::iterator it = mGlobalMapMarkers.begin(); it != mGlobalMapMarkers.end(); ++it) + MyGUI::Gui::getInstance().destroyWidget(it->second); + mGlobalMapMarkers.clear(); } void MapWindow::write(ESM::ESMWriter &writer, Loading::Listener& progress) @@ -980,4 +1074,11 @@ namespace MWGui eventDeleteClicked(); } + bool LocalMapBase::MarkerUserData::isPositionExplored() const + { + if (!mLocalMapRender) + return true; + return mLocalMapRender->isPositionExplored(nX, nY, cellX, cellY, interior); + } + } diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index a80b3e4c59..a41d5d5270 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -3,6 +3,8 @@ #include +#include + #include "windowpinnablebase.hpp" #include @@ -12,6 +14,7 @@ namespace MWRender { class GlobalMap; + class LocalMap; } namespace ESM @@ -39,20 +42,26 @@ namespace MWGui size_t size() const; - std::vector::const_iterator begin() const; - std::vector::const_iterator end() const; + typedef std::multimap ContainerType; + + typedef std::pair RangeType; + + ContainerType::const_iterator begin() const; + ContainerType::const_iterator end() const; + + RangeType getMarkers(const ESM::CellId& cellId) const; typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; EventHandle_Void eventMarkersChanged; private: - std::vector mMarkers; + ContainerType mMarkers; }; class LocalMapBase { public: - LocalMapBase(CustomMarkerCollection& markers); + LocalMapBase(CustomMarkerCollection& markers, MWRender::LocalMap* localMapRender); virtual ~LocalMapBase(); void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int mapWidgetSize); @@ -67,6 +76,19 @@ namespace MWGui struct MarkerUserData { + MarkerUserData(MWRender::LocalMap* map) + : mLocalMapRender(map) + , interior(false) + , cellX(0) + , cellY(0) + , nX(0.f) + , nY(0.f) + { + } + + bool isPositionExplored() const; + + MWRender::LocalMap* mLocalMapRender; bool interior; int cellX; int cellY; @@ -77,6 +99,8 @@ namespace MWGui }; protected: + MWRender::LocalMap* mLocalMapRender; + int mCurX, mCurY; bool mInterior; MyGUI::ScrollView* mLocalMap; @@ -93,12 +117,16 @@ namespace MWGui std::vector mMapWidgets; std::vector mFogWidgets; + typedef std::vector > TextureVector; + TextureVector mMapTextures; + TextureVector mFogTextures; + // Keep track of created marker widgets, just to easily remove them later. std::vector mDoorMarkerWidgets; std::vector mMagicMarkerWidgets; std::vector mCustomMarkerWidgets; - void updateCustomMarkers(); + virtual void updateCustomMarkers(); void applyFogOfWar(); @@ -153,7 +181,7 @@ namespace MWGui class MapWindow : public MWGui::WindowPinnableBase, public LocalMapBase, public NoDrop { public: - MapWindow(CustomMarkerCollection& customMarkers, DragAndDrop* drag, const std::string& cacheDir); + MapWindow(CustomMarkerCollection& customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender); virtual ~MapWindow(); void setCellName(const std::string& cellName); @@ -162,7 +190,8 @@ namespace MWGui void renderGlobalMap(Loading::Listener* loadingListener); - // adds the marker to the global map + /// adds the marker to the global map + /// @param name The ESM::Cell::mName void addVisitedLocation(const std::string& name, int x, int y); // reveals this cell's map on the global map @@ -175,6 +204,8 @@ namespace MWGui void onFrame(float dt); + virtual void updateCustomMarkers(); + /// Clear all savegame-specific data void clear(); @@ -193,8 +224,11 @@ namespace MWGui void onNoteDoubleClicked(MyGUI::Widget* sender); void onChangeScrollWindowCoord(MyGUI::Widget* sender); void globalMapUpdatePlayer(); + void setGlobalMapMarkerTooltip(MyGUI::Widget* widget, int x, int y); MyGUI::ScrollView* mGlobalMap; + std::auto_ptr mGlobalMapTexture; + std::auto_ptr mGlobalMapOverlayTexture; MyGUI::ImageBox* mGlobalMapImage; MyGUI::ImageBox* mGlobalMapOverlay; MyGUI::ImageBox* mPlayerArrowLocal; @@ -218,6 +252,8 @@ namespace MWGui MWRender::GlobalMap* mGlobalMapRender; + std::map, MyGUI::Widget*> mGlobalMapMarkers; + EditNoteDialog mEditNoteDialog; ESM::CustomMarker mEditingMarker; diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 862b719d48..481e1ceb47 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -13,6 +13,7 @@ #include "../mwbase/soundmanager.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -40,7 +41,7 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor) int currentY = 0; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); @@ -125,7 +126,7 @@ void MerchantRepair::exit() void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int price = MyGUI::utility::parseInt(sender->getUserString("Price")); if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId)) diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 6e81ed6262..f8ddeba3ee 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -117,8 +117,11 @@ namespace MWGui bool MessageBoxManager::createInteractiveMessageBox (const std::string& message, const std::vector& buttons) { - if(mInterMessageBoxe != NULL) { - throw std::runtime_error("There is a message box already"); + if (mInterMessageBoxe != NULL) + { + std::cerr << "Warning: replacing an interactive message box that was not answered yet" << std::endl; + delete mInterMessageBoxe; + mInterMessageBoxe = NULL; } mInterMessageBoxe = new InteractiveMessageBox(*this, message, buttons); @@ -195,7 +198,6 @@ namespace MWGui InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector& buttons) : WindowModal("openmw_interactive_messagebox.layout") , mMessageBoxManager(parMessageBoxManager) - , mTextButtonPadding(0) , mButtonPressed(-1) { WindowModal::open(); diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 48a92c844b..b4121fed3b 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -51,7 +51,7 @@ namespace MWGui int mLastButtonPressed; }; - class MessageBox : public OEngine::GUI::Layout + class MessageBox : public Layout { public: MessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message); @@ -88,7 +88,6 @@ namespace MWGui MyGUI::Widget* mButtonsWidget; std::vector mButtons; - int mTextButtonPadding; int mButtonPressed; }; diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index b8ac20f99d..ab0d02f950 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -1,8 +1,8 @@ #include "pickpocketitemmodel.hpp" -#include +#include +#include -#include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" namespace MWGui @@ -20,7 +20,7 @@ namespace MWGui { for (size_t i = 0; igetItemCount(); ++i) { - if (OEngine::Misc::Rng::roll0to99() > chance) + if (Misc::Rng::roll0to99() > chance) mHiddenItems.push_back(mSourceModel->getItem(i)); } } diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 685c7d45c1..f2ae8dd837 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -3,9 +3,10 @@ #include #include #include +#include +#include #include -#include #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" @@ -14,20 +15,14 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" -#include "../mwgui/inventorywindow.hpp" -#include "../mwgui/bookwindow.hpp" -#include "../mwgui/scrollwindow.hpp" - -#include "windowmanagerimp.hpp" #include "itemselection.hpp" - #include "spellview.hpp" - - #include "itemwidget.hpp" #include "sortfilteritemmodel.hpp" @@ -151,7 +146,7 @@ namespace MWGui mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &QuickKeysMenu::onAssignItemCancel); } mItemSelectionDialog->setVisible(true); - mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr()); + mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyUsableItems); mAssignDialog->setVisible (false); @@ -245,7 +240,7 @@ namespace MWGui std::string path = effect->mIcon; int slashPos = path.rfind('\\'); path.insert(slashPos+1, "b_"); - path = Misc::ResourceHelpers::correctIconPath(path); + path = MWBase::Environment::get().getWindowManager()->correctIconPath(path); button->setFrame("textures\\menu_icon_select_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); button->setIcon(path); @@ -266,7 +261,7 @@ namespace MWGui QuickKeyType type = mAssigned[index-1]; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); if (type == Type_Item || type == Type_MagicItem) @@ -314,7 +309,7 @@ namespace MWGui else if (type == Type_Item) { MWWorld::Ptr item = *button->getUserData(); - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); + MWBase::Environment::get().getWindowManager()->useItem(item); MWWorld::ContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); // change draw state only if the item is in player's right hand if (rightHand != store.end() && item == *rightHand) @@ -340,7 +335,7 @@ namespace MWGui // equip, if it can be equipped if (!item.getClass().getEquipmentSlots(item).first.empty()) { - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); + MWBase::Environment::get().getWindowManager()->useItem(item); // make sure that item was successfully equipped if (!store.isEquipped(item)) @@ -471,13 +466,14 @@ namespace MWGui switch (keyType) { case Type_Magic: - onAssignMagic(id); + if (MWBase::Environment::get().getWorld()->getStore().get().search(id)) + onAssignMagic(id); break; case Type_Item: case Type_MagicItem: { // Find the item by id - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); MWWorld::Ptr item; for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) @@ -547,7 +543,7 @@ namespace MWGui { WindowModal::open(); - mMagicList->setModel(new SpellModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + mMagicList->setModel(new SpellModel(MWMechanics::getPlayer())); mMagicList->resetScrollbars(); } diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index f908a9dd0d..a65379fca4 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -5,8 +5,12 @@ #include #include +#include + #include +#include + #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -37,8 +41,10 @@ namespace namespace MWGui { - RaceDialog::RaceDialog() + RaceDialog::RaceDialog(osgViewer::Viewer* viewer, Resource::ResourceSystem* resourceSystem) : WindowModal("openmw_chargen_race.layout") + , mViewer(viewer) + , mResourceSystem(resourceSystem) , mGenderIndex(0) , mFaceIndex(0) , mHairIndex(0) @@ -125,18 +131,20 @@ namespace MWGui updateSkills(); updateSpellPowers(); + mPreviewImage->setRenderItemTexture(NULL); + mPreview.reset(NULL); + mPreviewTexture.reset(NULL); - mPreviewImage->setImageTexture(""); + mPreview.reset(new MWRender::RaceSelectionPreview(mViewer, mResourceSystem)); + mPreview->rebuild(); + mPreview->setAngle (mCurrentAngle); - const std::string textureName = "CharacterHeadPreview"; - MyGUI::RenderManager::getInstance().destroyTexture(MyGUI::RenderManager::getInstance().getTexture(textureName)); + mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); + mPreviewImage->setRenderItemTexture(mPreviewTexture.get()); + mPreviewImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); - mPreview.reset(new MWRender::RaceSelectionPreview()); - mPreview->setup(); - mPreview->update (mCurrentAngle); - - const ESM::NPC proto = mPreview->getPrototype(); + const ESM::NPC& proto = mPreview->getPrototype(); setRaceId(proto.mRace); recountParts(); @@ -152,8 +160,6 @@ namespace MWGui mHairIndex = i; } - mPreviewImage->setImageTexture (textureName); - mPreviewDirty = true; size_t initialPos = mHeadRotate->getScrollRange()/2+mHeadRotate->getScrollRange()/10; @@ -181,9 +187,9 @@ namespace MWGui void RaceDialog::close() { - mPreviewImage->setImageTexture(""); - const std::string textureName = "CharacterHeadPreview"; - MyGUI::RenderManager::getInstance().destroyTexture(MyGUI::RenderManager::getInstance().getTexture(textureName)); + mPreviewImage->setRenderItemTexture(NULL); + + mPreviewTexture.reset(NULL); mPreview.reset(NULL); } @@ -204,8 +210,8 @@ namespace MWGui void RaceDialog::onHeadRotate(MyGUI::ScrollBar* scroll, size_t _position) { float angle = (float(_position) / (scroll->getScrollRange()-1) - 0.5f) * 3.14f * 2; - mPreview->update (angle); - mPreviewDirty = true; + mPreview->setAngle (angle); + mCurrentAngle = angle; } @@ -331,21 +337,6 @@ namespace MWGui { std::cerr << "Error creating preview: " << e.what() << std::endl; } - - mPreviewDirty = true; - } - - void RaceDialog::doRenderUpdate() - { - if (!mPreview.get()) - return; - - mPreview->onFrame(); - if (mPreviewDirty) - { - mPreview->render(); - mPreviewDirty = false; - } } void RaceDialog::updateRaces() diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp index be16af5d1e..b3de9bef06 100644 --- a/apps/openmw/mwgui/race.hpp +++ b/apps/openmw/mwgui/race.hpp @@ -19,12 +19,22 @@ namespace ESM struct NPC; } +namespace osgViewer +{ + class Viewer; +} + +namespace Resource +{ + class ResourceSystem; +} + namespace MWGui { class RaceDialog : public WindowModal { public: - RaceDialog(); + RaceDialog(osgViewer::Viewer* viewer, Resource::ResourceSystem* resourceSystem); enum Gender { @@ -35,13 +45,9 @@ namespace MWGui const ESM::NPC &getResult() const; const std::string &getRaceId() const { return mCurrentRaceId; } Gender getGender() const { return mGenderIndex == 0 ? GM_Male : GM_Female; } - // getFace() - // getHair() void setRaceId(const std::string &raceId); void setGender(Gender gender) { mGenderIndex = gender == GM_Male ? 0 : 1; } - // setFace() - // setHair() void setNextButtonShow(bool shown); virtual void open(); @@ -60,8 +66,6 @@ namespace MWGui */ EventHandle_WindowBase eventDone; - void doRenderUpdate(); - protected: void onHeadRotate(MyGUI::ScrollBar* _sender, size_t _position); @@ -89,6 +93,9 @@ namespace MWGui void getBodyParts (int part, std::vector& out); + osgViewer::Viewer* mViewer; + Resource::ResourceSystem* mResourceSystem; + std::vector mAvailableHeads; std::vector mAvailableHairs; @@ -109,6 +116,7 @@ namespace MWGui float mCurrentAngle; std::auto_ptr mPreview; + std::auto_ptr mPreviewTexture; bool mPreviewDirty; }; diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index b7280565b7..1dac7138fb 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -5,9 +5,7 @@ #include #include -#include - -#include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -19,6 +17,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "widgets.hpp" #include "itemwidget.hpp" @@ -94,7 +93,7 @@ void Recharge::updateView() int currentY = 0; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) @@ -149,7 +148,7 @@ void Recharge::onItemClicked(MyGUI::Widget *sender) MWWorld::Ptr item = *sender->getUserData(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player); @@ -165,7 +164,7 @@ void Recharge::onItemClicked(MyGUI::Widget *sender) intelligenceTerm = 1; float x = (npcStats.getSkill(ESM::Skill::Enchant).getModified() + intelligenceTerm + luckTerm) * stats.getFatigueTerm(); - int roll = OEngine::Misc::Rng::roll0to99(); + int roll = Misc::Rng::roll0to99(); if (roll < x) { std::string soul = gem.getCellRef().getSoul(); diff --git a/apps/openmw/mwgui/referenceinterface.cpp b/apps/openmw/mwgui/referenceinterface.cpp index 2ea0db64ac..76bb4f53fa 100644 --- a/apps/openmw/mwgui/referenceinterface.cpp +++ b/apps/openmw/mwgui/referenceinterface.cpp @@ -3,6 +3,8 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwmechanics/actorutil.hpp" + namespace MWGui { ReferenceInterface::ReferenceInterface() @@ -16,7 +18,7 @@ namespace MWGui void ReferenceInterface::checkReferenceAvailable() { - MWWorld::CellStore* playerCell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); + MWWorld::CellStore* playerCell = MWMechanics::getPlayer().getCell(); // check if player has changed cell, or count of the reference has become 0 if ((playerCell != mCurrentPlayerCell && mCurrentPlayerCell != NULL) diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index 534226aebd..49d5735a46 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -9,6 +9,8 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" @@ -94,7 +96,7 @@ void Repair::updateRepairView() int currentY = 0; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor; for (MWWorld::ContainerStoreIterator iter (store.begin(categories)); diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index 47c7cef219..d7d6c3cdb1 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -1,5 +1,7 @@ #include "review.hpp" +#include + #include #include #include @@ -11,9 +13,6 @@ #include "tooltips.hpp" -#undef min -#undef max - namespace { void adjustButtonSize(MyGUI::Button *button) @@ -155,7 +154,7 @@ namespace MWGui { mMagicka->setValue(static_cast(value.getCurrent()), static_cast(value.getModified())); std::string valStr = MyGUI::utility::toString(value.getCurrent()) + "/" + MyGUI::utility::toString(value.getModified()); - mMagicka->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); + mMagicka->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr); } void ReviewDialog::setFatigue(const MWMechanics::DynamicStat& value) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index d022c8e251..53c2807374 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -1,18 +1,24 @@ #include "savegamedialog.hpp" -#include "widgets.hpp" -#include -#include +#include +#include #include #include #include #include +#include +#include + +#include + #include #include +#include + #include "../mwbase/statemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -22,6 +28,7 @@ #include "../mwstate/character.hpp" #include "confirmationdialog.hpp" +#include "widgets.hpp" namespace MWGui { @@ -69,7 +76,7 @@ namespace MWGui void SaveGameDialog::confirmDeleteSave() { ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - dialog->open("#{sMessage3}"); + dialog->askForConfirmation("#{sMessage3}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteSlotConfirmed); dialog->eventCancelClicked.clear(); @@ -228,7 +235,7 @@ namespace MWGui if (mCurrentSlot != NULL && !reallySure) { ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - dialog->open("#{sMessage4}"); + dialog->askForConfirmation("#{sMessage4}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onConfirmationGiven); dialog->eventCancelClicked.clear(); @@ -305,6 +312,22 @@ namespace MWGui onSlotSelected(mSaveList, MyGUI::ITEM_NONE); } + std::string formatTimeplayed(const double timeInSeconds) + { + int timePlayed = (int)floor(timeInSeconds); + int days = timePlayed / 60 / 60 / 24; + int hours = (timePlayed / 60 / 60) % 24; + int minutes = (timePlayed / 60) % 60; + int seconds = timePlayed % 60; + + std::stringstream stream; + stream << std::setfill('0') << std::setw(2) << days << ":"; + stream << std::setfill('0') << std::setw(2) << hours << ":"; + stream << std::setfill('0') << std::setw(2) << minutes << ":"; + stream << std::setfill('0') << std::setw(2) << seconds; + return stream.str(); + } + void SaveGameDialog::onSlotSelected(MyGUI::ListBox *sender, size_t pos) { mOkButton->setEnabled(pos != MyGUI::ITEM_NONE || mSaving); @@ -337,15 +360,19 @@ namespace MWGui timeinfo = localtime(&time); // Use system/environment locale settings for datetime formatting + char* oldLctime = setlocale(LC_TIME, NULL); setlocale(LC_TIME, ""); const int size=1024; char buffer[size]; if (std::strftime(buffer, size, "%x %X", timeinfo) > 0) text << buffer << "\n"; - text << "Level " << mCurrentSlot->mProfile.mPlayerLevel << "\n"; - text << mCurrentSlot->mProfile.mPlayerCell << "\n"; - // text << "Time played: " << slot->mProfile.mTimePlayed << "\n"; + + // reset + setlocale(LC_TIME, oldLctime); + + text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n"; + text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCell << "}\n"; int hour = int(mCurrentSlot->mProfile.mInGameTime.mGameHour); bool pm = hour >= 12; @@ -357,30 +384,44 @@ namespace MWGui << MWBase::Environment::get().getWorld()->getMonthName(mCurrentSlot->mProfile.mInGameTime.mMonth) << " " << hour << " " << (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}"); + if (Settings::Manager::getBool("timeplayed","Saves")) + { + text << "\n" << "Time played: " << formatTimeplayed(mCurrentSlot->mProfile.mTimePlayed); + } + mInfoText->setCaptionWithReplacing(text.str()); + // Decode screenshot - std::vector data = mCurrentSlot->mProfile.mScreenshot; // MemoryDataStream doesn't work with const data :( - Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size())); - Ogre::Image image; - image.load(stream, "jpg"); + const std::vector& data = mCurrentSlot->mProfile.mScreenshot; + Files::IMemStream instream (&data[0], data.size()); - const std::string textureName = "@savegame_screenshot"; - Ogre::TexturePtr texture; - texture = Ogre::TextureManager::getSingleton().getByName(textureName); - mScreenshot->setImageTexture(""); - if (texture.isNull()) + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg"); + if (!readerwriter) { - texture = Ogre::TextureManager::getSingleton().createManual(textureName, - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, - image.getWidth(), image.getHeight(), 0, Ogre::PF_BYTE_RGBA, Ogre::TU_DYNAMIC_WRITE_ONLY); + std::cerr << "Can't open savegame screenshot, no jpg readerwriter found" << std::endl; + return; } - texture->unload(); - texture->setWidth(image.getWidth()); - texture->setHeight(image.getHeight()); - texture->loadImage(image); - mScreenshot->setImageTexture(textureName); + osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(instream); + if (!result.success()) + { + std::cerr << "Failed to read savegame screenshot: " << result.message() << " code " << result.status() << std::endl; + return; + } + + osg::ref_ptr texture (new osg::Texture2D); + texture->setImage(result.getImage()); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + texture->setResizeNonPowerOfTwoHint(false); + texture->setUnRefImageDataAfterApply(true); + + mScreenshotTexture.reset(new osgMyGUI::OSGTexture(texture)); + + mScreenshot->setRenderItemTexture(mScreenshotTexture.get()); + mScreenshot->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); } } diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp index 2192adbdec..6a9e59cc60 100644 --- a/apps/openmw/mwgui/savegamedialog.hpp +++ b/apps/openmw/mwgui/savegamedialog.hpp @@ -48,6 +48,7 @@ namespace MWGui void fillSaveList(); + std::auto_ptr mScreenshotTexture; MyGUI::ImageBox* mScreenshot; bool mSaving; diff --git a/apps/openmw/mwgui/screenfader.cpp b/apps/openmw/mwgui/screenfader.cpp index 473776a82a..be0346e882 100644 --- a/apps/openmw/mwgui/screenfader.cpp +++ b/apps/openmw/mwgui/screenfader.cpp @@ -1,6 +1,7 @@ #include "screenfader.hpp" #include +#include namespace MWGui { @@ -66,20 +67,25 @@ namespace MWGui mFader->notifyOperationFinished(); } - ScreenFader::ScreenFader(const std::string & texturePath) - : WindowBase("openmw_screen_fader.layout") + ScreenFader::ScreenFader(const std::string & texturePath, const std::string& layout, const MyGUI::FloatCoord& texCoordOverride) + : WindowBase(layout) , mCurrentAlpha(0.f) , mFactor(1.f) , mRepeat(false) { mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); - setTexture(texturePath); setVisible(false); - } - void ScreenFader::setTexture(const std::string & texturePath) - { - mMainWidget->setProperty("ImageTexture", texturePath); + MyGUI::ImageBox* imageBox = mMainWidget->castType(false); + if (imageBox) + { + imageBox->setImageTexture(texturePath); + const MyGUI::IntSize imageSize = imageBox->getImageSize(); + imageBox->setImageCoord(MyGUI::IntCoord(texCoordOverride.left * imageSize.width, + texCoordOverride.top * imageSize.height, + texCoordOverride.width * imageSize.width, + texCoordOverride.height * imageSize.height)); + } } void ScreenFader::update(float dt) diff --git a/apps/openmw/mwgui/screenfader.hpp b/apps/openmw/mwgui/screenfader.hpp index 402554555e..f87fd84432 100644 --- a/apps/openmw/mwgui/screenfader.hpp +++ b/apps/openmw/mwgui/screenfader.hpp @@ -36,9 +36,7 @@ namespace MWGui class ScreenFader : public WindowBase { public: - ScreenFader(const std::string & texturePath); - - void setTexture(const std::string & texturePath); + ScreenFader(const std::string & texturePath, const std::string& layout = "openmw_screen_fader.layout", const MyGUI::FloatCoord& texCoordOverride = MyGUI::FloatCoord(0,0,1,1)); void update(float dt); diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index 85d1c8c4ee..ccc07174dc 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -10,6 +10,8 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "../mwworld/actiontake.hpp" #include "formatting.hpp" @@ -48,7 +50,7 @@ namespace MWGui center(); } - void ScrollWindow::open (MWWorld::Ptr scroll, bool showTakeButton) + void ScrollWindow::openScroll (MWWorld::Ptr scroll, bool showTakeButton) { // no 3d sounds because the object could be in a container. MWBase::Environment::get().getSoundManager()->playSound ("scroll", 1.0, 1.0); @@ -103,7 +105,7 @@ namespace MWGui MWBase::Environment::get().getSoundManager()->playSound("Item Book Up", 1.0, 1.0); MWWorld::ActionTake take(mScroll); - take.execute (MWBase::Environment::get().getWorld()->getPlayerPtr()); + take.execute (MWMechanics::getPlayer()); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll); } diff --git a/apps/openmw/mwgui/scrollwindow.hpp b/apps/openmw/mwgui/scrollwindow.hpp index 3c9e718b61..961f1b675f 100644 --- a/apps/openmw/mwgui/scrollwindow.hpp +++ b/apps/openmw/mwgui/scrollwindow.hpp @@ -17,7 +17,7 @@ namespace MWGui public: ScrollWindow (); - void open (MWWorld::Ptr scroll, bool showTakeButton); + void openScroll (MWWorld::Ptr scroll, bool showTakeButton); virtual void exit(); void setInventoryAllowed(bool allowed); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 6991bb2946..fbfbd0e702 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -1,7 +1,5 @@ #include "settingswindow.hpp" -#include - #include #include #include @@ -33,20 +31,17 @@ namespace { if (level == 0) return "#{sOff}"; - else if (level == 1) - return "Basic"; - else - return "Detailed"; + else //if (level == 1) + return "#{sOn}"; } - std::string textureFilteringToStr(const std::string& val) + std::string textureMipmappingToStr(const std::string& val) { - if (val == "anisotropic") - return "Anisotropic"; - else if (val == "bilinear") - return "Bilinear"; - else - return "Trilinear"; + if (val == "linear") return "Trilinear"; + if (val == "nearest") return "Bilinear"; + if (val != "none") + std::cerr<< "Invalid texture mipmap option: "<getName ().find("OpenGL") != std::string::npos) ? "glsl" : "hlsl"; - } - const char* checkButtonType = "CheckButton"; const char* sliderType = "Slider"; @@ -177,22 +167,20 @@ namespace MWGui getWidget(mFullscreenButton, "FullscreenButton"); getWidget(mVSyncButton, "VSyncButton"); getWidget(mWindowBorderButton, "WindowBorderButton"); - getWidget(mFPSButton, "FPSButton"); getWidget(mFOVSlider, "FOVSlider"); getWidget(mAnisotropySlider, "AnisotropySlider"); getWidget(mTextureFilteringButton, "TextureFilteringButton"); getWidget(mAnisotropyLabel, "AnisotropyLabel"); getWidget(mAnisotropyBox, "AnisotropyBox"); getWidget(mShadersButton, "ShadersButton"); - getWidget(mShaderModeButton, "ShaderModeButton"); getWidget(mShadowsEnabledButton, "ShadowsEnabledButton"); getWidget(mShadowsTextureSize, "ShadowsTextureSize"); getWidget(mControlsBox, "ControlsBox"); getWidget(mResetControlsButton, "ResetControlsButton"); - getWidget(mRefractionButton, "RefractionButton"); getWidget(mDifficultySlider, "DifficultySlider"); getWidget(mKeyboardSwitch, "KeyboardButton"); getWidget(mControllerSwitch, "ControllerButton"); + getWidget(mWaterTextureSize, "WaterTextureSize"); #ifndef WIN32 // hide gamma controls since it currently does not work under Linux @@ -212,11 +200,11 @@ namespace MWGui mSettingsTab->eventTabChangeSelect += MyGUI::newDelegate(this, &SettingsWindow::onTabChanged); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); - mShaderModeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShaderModeToggled); mTextureFilteringButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringChanged); - mFPSButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onFpsToggled); mResolutionList->eventListChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onResolutionSelected); + mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); + mShadowsTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onShadowTextureSizeChanged); mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked); @@ -246,26 +234,30 @@ namespace MWGui if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE) mResolutionList->addItem(str); } + highlightCurrentResolution(); - std::string tf = Settings::Manager::getString("texture filtering", "General"); - mTextureFilteringButton->setCaption(textureFilteringToStr(tf)); + std::string tmip = Settings::Manager::getString("texture mipmap", "General"); + mTextureFilteringButton->setCaption(textureMipmappingToStr(tmip)); mAnisotropyLabel->setCaption("Anisotropy (" + MyGUI::utility::toString(Settings::Manager::getInt("anisotropy", "General")) + ")"); + int waterTextureSize = Settings::Manager::getInt ("rtt size", "Water"); + if (waterTextureSize >= 512) + mWaterTextureSize->setIndexSelected(0); + if (waterTextureSize >= 1024) + mWaterTextureSize->setIndexSelected(1); + if (waterTextureSize >= 2048) + mWaterTextureSize->setIndexSelected(2); + mShadowsTextureSize->setCaption (Settings::Manager::getString ("texture size", "Shadows")); - mShaderModeButton->setCaption (Settings::Manager::getString("shader mode", "General")); - if (!Settings::Manager::getBool("shaders", "Objects")) { - mRefractionButton->setEnabled(false); mShadowsEnabledButton->setEnabled(false); } - mFPSButton->setCaptionWithReplacing(fpsLevelToStr(Settings::Manager::getInt("fps", "HUD"))); - MyGUI::TextBox* fovText; getWidget(fovText, "FovText"); - fovText->setCaption("Field of View (" + MyGUI::utility::toString(int(Settings::Manager::getInt("field of view", "General"))) + ")"); + fovText->setCaption("Field of View (" + MyGUI::utility::toString(int(Settings::Manager::getInt("field of view", "Camera"))) + ")"); MyGUI::TextBox* diffText; getWidget(diffText, "DifficultyText"); @@ -294,7 +286,7 @@ namespace MWGui return; ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - dialog->open("#{sNotifyMessage67}"); + dialog->askForConfirmation("#{sNotifyMessage67}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SettingsWindow::onResolutionAccept); dialog->eventCancelClicked.clear(); @@ -314,8 +306,41 @@ namespace MWGui } void SettingsWindow::onResolutionCancel() + { + highlightCurrentResolution(); + } + + void SettingsWindow::highlightCurrentResolution() { mResolutionList->setIndexSelected(MyGUI::ITEM_NONE); + + int currentX = Settings::Manager::getInt("resolution x", "Video"); + int currentY = Settings::Manager::getInt("resolution y", "Video"); + + for (size_t i=0; igetItemCount(); ++i) + { + int resX, resY; + parseResolution (resX, resY, mResolutionList->getItemNameAt(i)); + + if (resX == currentX && resY == currentY) + { + mResolutionList->setIndexSelected(i); + break; + } + } + } + + void SettingsWindow::onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos) + { + int size = 0; + if (pos == 0) + size = 512; + else if (pos == 1) + size = 1024; + else if (pos == 2) + size = 2048; + Settings::Manager::setInt("rtt size", "Water", size); + apply(); } void SettingsWindow::onShadowTextureSizeChanged(MyGUI::ComboBox *_sender, size_t pos) @@ -344,12 +369,6 @@ namespace MWGui { if (newState == false) { - // refraction needs shaders to display underwater fog - mRefractionButton->setCaptionWithReplacing("#{sOff}"); - mRefractionButton->setEnabled(false); - - Settings::Manager::setBool("refraction", "Water", false); - // shadows not supported mShadowsEnabledButton->setEnabled(false); mShadowsEnabledButton->setCaptionWithReplacing("#{sOff}"); @@ -357,9 +376,6 @@ namespace MWGui } else { - // re-enable - mRefractionButton->setEnabled(true); - mShadowsEnabledButton->setEnabled(true); } } @@ -408,28 +424,14 @@ namespace MWGui } } - void SettingsWindow::onShaderModeToggled(MyGUI::Widget* _sender) - { - std::string val = hlslGlsl(); - - _sender->castType()->setCaption(val); - - Settings::Manager::setString("shader mode", "General", val); - - apply(); - } - - void SettingsWindow::onFpsToggled(MyGUI::Widget* _sender) - { - int newLevel = (Settings::Manager::getInt("fps", "HUD") + 1) % 3; - Settings::Manager::setInt("fps", "HUD", newLevel); - mFPSButton->setCaptionWithReplacing(fpsLevelToStr(newLevel)); - apply(); - } - void SettingsWindow::onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos) { - Settings::Manager::setString("texture filtering", "General", Misc::StringUtils::lowerCase(_sender->getItemNameAt(pos))); + if(pos == 0) + Settings::Manager::setString("texture mipmap", "General", "nearest"); + else if(pos == 1) + Settings::Manager::setString("texture mipmap", "General", "linear"); + else + std::cerr<< "Unexpected option pos "<getConfirmationDialog(); - dialog->open("#{sNotifyMessage66}"); + dialog->askForConfirmation("#{sNotifyMessage66}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindingsAccept); dialog->eventCancelClicked.clear(); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 8910960b24..99553808b4 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -30,7 +30,6 @@ namespace MWGui MyGUI::Button* mFullscreenButton; MyGUI::Button* mVSyncButton; MyGUI::Button* mWindowBorderButton; - MyGUI::Button* mFPSButton; MyGUI::ScrollBar* mFOVSlider; MyGUI::ScrollBar* mDifficultySlider; MyGUI::ScrollBar* mAnisotropySlider; @@ -38,8 +37,8 @@ namespace MWGui MyGUI::TextBox* mAnisotropyLabel; MyGUI::Widget* mAnisotropyBox; MyGUI::Button* mShadersButton; - MyGUI::Button* mShaderModeButton; - MyGUI::Button* mRefractionButton; + + MyGUI::ComboBox* mWaterTextureSize; MyGUI::Button* mShadowsEnabledButton; MyGUI::ComboBox* mShadowsTextureSize; @@ -53,15 +52,16 @@ namespace MWGui void onTabChanged(MyGUI::TabControl* _sender, size_t index); void onOkButtonClicked(MyGUI::Widget* _sender); - void onFpsToggled(MyGUI::Widget* _sender); void onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos); void onSliderChangePosition(MyGUI::ScrollBar* scroller, size_t pos); void onButtonToggled(MyGUI::Widget* _sender); void onResolutionSelected(MyGUI::ListBox* _sender, size_t index); void onResolutionAccept(); void onResolutionCancel(); + void highlightCurrentResolution(); + + void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); - void onShaderModeToggled(MyGUI::Widget* _sender); void onShadowTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); void onRebindAction(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 183ab07ff9..1d086f2ee3 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -71,7 +71,6 @@ namespace MWGui SortFilterItemModel::SortFilterItemModel(ItemModel *sourceModel) : mCategory(Category_All) , mFilter(0) - , mShowEquipped(true) , mSortByType(true) { mSourceModel = sourceModel; @@ -91,9 +90,6 @@ namespace MWGui { MWWorld::Ptr base = item.mBase; - if (item.mType == ItemStack::Type_Equipped && !mShowEquipped) - return false; - int category = 0; if (base.getTypeName() == typeid(ESM::Armor).name() || base.getTypeName() == typeid(ESM::Clothing).name()) diff --git a/apps/openmw/mwgui/sortfilteritemmodel.hpp b/apps/openmw/mwgui/sortfilteritemmodel.hpp index 1b68bdd4f1..064f62e771 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.hpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp @@ -24,7 +24,6 @@ namespace MWGui void setCategory (int category); void setFilter (int filter); - void setShowEquipped (bool show) { mShowEquipped = show; } /// Use ItemStack::Type for sorting? void setSortByType(bool sort) { mSortByType = sort; } @@ -49,7 +48,6 @@ namespace MWGui int mCategory; int mFilter; - bool mShowEquipped; bool mSortByType; }; diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index ae7b7588ab..843731cff8 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -12,10 +12,10 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" namespace MWGui { @@ -49,7 +49,7 @@ namespace MWGui int price = static_cast(spell->mData.mCost*store.get().find("fSpellValueMult")->getFloat()); price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); // TODO: refactor to use MyGUI::ListBox @@ -95,8 +95,7 @@ namespace MWGui for (MWMechanics::Spells::TIterator iter = merchantSpells.begin(); iter!=merchantSpells.end(); ++iter) { - const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + const ESM::Spell* spell = iter->first; if (spell->mData.mType!=ESM::Spell::ST_Spell) continue; // don't try to sell diseases, curses or powers @@ -110,10 +109,10 @@ namespace MWGui continue; } - if (playerHasSpell(iter->first)) + if (playerHasSpell(iter->first->mId)) continue; - addSpell (iter->first); + addSpell (iter->first->mId); } updateLabels(); @@ -126,7 +125,7 @@ namespace MWGui bool SpellBuyingWindow::playerHasSpell(const std::string &id) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); return player.getClass().getCreatureStats(player).getSpells().hasSpell(id); } @@ -134,7 +133,7 @@ namespace MWGui { int price = *_sender->getUserData(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId)) return; @@ -159,7 +158,7 @@ namespace MWGui void SpellBuyingWindow::updateLabels() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index a492ea7aaf..64d4d86c68 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include @@ -20,6 +19,7 @@ #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/spells.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "tooltips.hpp" #include "class.hpp" @@ -180,7 +180,7 @@ namespace MWGui void EditEffectDialog::setMagicEffect (const ESM::MagicEffect *effect) { - mEffectImage->setImageTexture(Misc::ResourceHelpers::correctIconPath(effect->mIcon)); + mEffectImage->setImageTexture(MWBase::Environment::get().getWindowManager()->correctIconPath(effect->mIcon)); mEffectName->setCaptionWithReplacing("#{"+ESM::MagicEffect::effectIdToString (effect->mIndex)+"}"); @@ -377,7 +377,7 @@ namespace MWGui return; } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); if (MyGUI::utility::parseInt(mPriceLabel->getCaption()) > playerGold) @@ -475,7 +475,7 @@ namespace MWGui mPriceLabel->setCaption(MyGUI::utility::toString(int(price))); - float chance = MWMechanics::getSpellSuccessChance(&mSpell, MWBase::Environment::get().getWorld()->getPlayerPtr()); + float chance = MWMechanics::getSpellSuccessChance(&mSpell, MWMechanics::getPlayer()); mSuccessChance->setCaption(MyGUI::utility::toString(int(chance))); } @@ -508,7 +508,7 @@ namespace MWGui { // get the list of magic effects that are known to the player - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); @@ -516,8 +516,7 @@ namespace MWGui for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { - const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find (it->first); + const ESM::Spell* spell = it->first; // only normal spells count if (spell->mData.mType != ESM::Spell::ST_Spell) diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index c597cfaebe..27896381ef 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -17,6 +17,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "tooltips.hpp" @@ -44,7 +45,7 @@ namespace MWGui { // TODO: Tracking add/remove/expire would be better than force updating every frame - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); @@ -134,6 +135,24 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->getGameSettingString("spoint", "") ); } } + if (effectIt->mRemainingTime > -1 && + Settings::Manager::getBool("show effect duration","Game")) { + sourcesDescription += " #{sDuration}: "; + float duration = effectIt->mRemainingTime; + if (duration > 3600) { + int hour = duration / 3600; + duration -= hour*3600; + sourcesDescription += MWGui::ToolTips::toString(hour) + "h"; + } + if (duration > 60) { + int minute = duration / 60; + duration -= minute*60; + sourcesDescription += MWGui::ToolTips::toString(minute) + "m"; + } + if (duration > 0.1) { + sourcesDescription += MWGui::ToolTips::toString(duration) + "s"; + } + } } if (remainingDuration > 0.f) @@ -145,7 +164,7 @@ namespace MWGui ("ImageBox", MyGUI::IntCoord(w,2,16,16), MyGUI::Align::Default); mWidgetMap[it->first] = image; - image->setImageTexture(Misc::ResourceHelpers::correctIconPath(effect->mIcon)); + image->setImageTexture(MWBase::Environment::get().getWindowManager()->correctIconPath(effect->mIcon)); std::string name = ESM::MagicEffect::effectIdToString (it->first); diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 91512a0116..c775330084 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -1,5 +1,7 @@ #include "spellmodel.hpp" +#include + #include #include "../mwbase/environment.hpp" @@ -50,7 +52,7 @@ namespace MWGui for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { - const ESM::Spell* spell = esmStore.get().find(it->first); + const ESM::Spell* spell = it->first; if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) continue; @@ -65,9 +67,9 @@ namespace MWGui } else newSpell.mType = Spell::Type_Power; - newSpell.mId = it->first; + newSpell.mId = spell->mId; - newSpell.mSelected = (MWBase::Environment::get().getWindowManager()->getSelectedSpell() == it->first); + newSpell.mSelected = (MWBase::Environment::get().getWindowManager()->getSelectedSpell() == spell->mId); newSpell.mActive = true; mSpells.push_back(newSpell); } @@ -79,14 +81,19 @@ namespace MWGui const std::string enchantId = item.getClass().getEnchantment(item); if (enchantId.empty()) continue; - const ESM::Enchantment* enchant = - esmStore.get().find(item.getClass().getEnchantment(item)); + const ESM::Enchantment* enchant = esmStore.get().search(enchantId); + if (!enchant) + { + std::cerr << "Can't find enchantment '" << enchantId << "' on item " << item.getCellRef().getRefId() << std::endl; + continue; + } + if (enchant->mData.mType != ESM::Enchantment::WhenUsed && enchant->mData.mType != ESM::Enchantment::CastOnce) continue; Spell newSpell; newSpell.mItem = item; - newSpell.mId = item.getClass().getId(item); + newSpell.mId = item.getCellRef().getRefId(); newSpell.mName = item.getClass().getName(item); newSpell.mType = Spell::Type_EnchantedItem; newSpell.mSelected = invStore.getSelectedEnchantItem() == it; diff --git a/apps/openmw/mwgui/spellview.cpp b/apps/openmw/mwgui/spellview.cpp index af6ff00a53..06809bb49f 100644 --- a/apps/openmw/mwgui/spellview.cpp +++ b/apps/openmw/mwgui/spellview.cpp @@ -289,7 +289,7 @@ namespace MWGui widget->setUserString(sSpellModelIndex, MyGUI::utility::toString(index)); - widget->eventMouseWheel += MyGUI::newDelegate(this, &SpellView::onMouseWheel); + widget->eventMouseWheel += MyGUI::newDelegate(this, &SpellView::onMouseWheelMoved); widget->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellView::onSpellSelected); } @@ -303,7 +303,7 @@ namespace MWGui eventSpellClicked(getSpellModelIndex(_sender)); } - void SpellView::onMouseWheel(MyGUI::Widget* _sender, int _rel) + void SpellView::onMouseWheelMoved(MyGUI::Widget* _sender, int _rel) { if (mScrollView->getViewOffset().top + _rel*0.3f > 0) mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); diff --git a/apps/openmw/mwgui/spellview.hpp b/apps/openmw/mwgui/spellview.hpp index 0f0b8a7d25..a4f8ddf39b 100644 --- a/apps/openmw/mwgui/spellview.hpp +++ b/apps/openmw/mwgui/spellview.hpp @@ -86,7 +86,7 @@ namespace MWGui void adjustSpellWidget(const Spell& spell, SpellModel::ModelIndex index, MyGUI::Widget* widget); void onSpellSelected(MyGUI::Widget* _sender); - void onMouseWheel(MyGUI::Widget* _sender, int _rel); + void onMouseWheelMoved(MyGUI::Widget* _sender, int _rel); SpellModel::ModelIndex getSpellModelIndex(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index ca5ec20bdf..8422bb33fb 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -15,9 +15,9 @@ #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/spells.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "spellicons.hpp" -#include "inventorywindow.hpp" #include "confirmationdialog.hpp" #include "spellview.hpp" @@ -79,12 +79,12 @@ namespace MWGui { mSpellIcons->updateWidgets(mEffectBox, false); - mSpellView->setModel(new SpellModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + mSpellView->setModel(new SpellModel(MWMechanics::getPlayer())); } void SpellWindow::onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); // retrieve ContainerStoreIterator to the item @@ -103,7 +103,7 @@ namespace MWGui if (!alreadyEquipped && !item.getClass().getEquipmentSlots(item).first.empty()) { - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); + MWBase::Environment::get().getWindowManager()->useItem(item); // make sure that item was successfully equipped if (!store.isEquipped(item)) return; @@ -134,7 +134,7 @@ namespace MWGui ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); std::string question = MWBase::Environment::get().getWindowManager()->getGameSettingString("sQuestionDeleteSpell", "Delete %s?"); question = boost::str(boost::format(question) % spell->mName); - dialog->open(question); + dialog->askForConfirmation(question); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SpellWindow::onDeleteSpellAccept); dialog->eventCancelClicked.clear(); @@ -159,7 +159,7 @@ namespace MWGui void SpellWindow::onSpellSelected(const std::string& spellId) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); store.setSelectedEnchantItem(store.end()); MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); @@ -169,7 +169,7 @@ namespace MWGui void SpellWindow::onDeleteSpellAccept() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); @@ -183,7 +183,7 @@ namespace MWGui void SpellWindow::cycle(bool next) { - mSpellView->setModel(new SpellModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + mSpellView->setModel(new SpellModel(MWMechanics::getPlayer())); SpellModel::ModelIndex selected = 0; for (SpellModel::ModelIndex i = 0; igetModel()->getItemCount()); ++i) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index cb1bf6f371..e66e012386 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -15,6 +15,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "tooltips.hpp" @@ -158,7 +159,7 @@ namespace MWGui else if (id == "MBar") { getWidget(w, "Magicka"); - w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); + w->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr); } else if (id == "FBar") { @@ -234,7 +235,7 @@ namespace MWGui NoDrop::onFrame(dt); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::NpcStats &PCstats = player.getClass().getNpcStats(player); // level progress @@ -383,7 +384,7 @@ namespace MWGui int base = stat.getBase(); int modified = stat.getModified(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -493,7 +494,7 @@ namespace MWGui if (!mFactions.empty()) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::NpcStats &PCstats = player.getClass().getNpcStats(player); const std::set &expelled = PCstats.getExpelled(); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 34896c0bd2..cb3d0d0a1f 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include "../mwbase/world.hpp" @@ -18,6 +17,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/actorutil.hpp" #include "mapwindow.hpp" #include "inventorywindow.hpp" @@ -39,6 +39,7 @@ namespace MWGui , mLastMouseY(0) , mEnabled(true) , mFullHelp(false) + , mShowOwned(0) { getWidget(mDynamicToolTipBox, "DynamicToolTipBox"); @@ -56,6 +57,8 @@ namespace MWGui { mMainWidget->getChildAt(i)->setVisible(false); } + + mShowOwned = Settings::Manager::getInt("show owned", "Game"); } void ToolTips::setEnabled(bool enabled) @@ -84,9 +87,9 @@ namespace MWGui return; } - bool gameMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); + bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); - if (gameMode) + if (guiMode) { const MyGUI::IntPoint& mousePos = MyGUI::InputManager::getInstance().getMousePosition(); @@ -109,10 +112,10 @@ namespace MWGui if (info.caption.empty()) info.caption=mFocusObject.getCellRef().getRefId(); info.icon=""; - tooltipSize = createToolTip(info); + tooltipSize = createToolTip(info, true); } else - tooltipSize = getToolTipViaPtr(true); + tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), true); MyGUI::IntPoint tooltipPosition = MyGUI::InputManager::getInstance().getMousePosition(); position(tooltipPosition, tooltipSize, viewSize); @@ -169,44 +172,38 @@ namespace MWGui { LocalMapBase::MarkerUserData data = *focus->getUserData(); - if (!MWBase::Environment::get().getWorld ()->isPositionExplored (data.nX, data.nY, data.cellX, data.cellY, data.interior)) + if (!data.isPositionExplored()) return; ToolTipInfo info; info.text = data.caption; info.notes = data.notes; - tooltipSize = createToolTip(info); + tooltipSize = createToolTip(info, false); } else if (type == "ItemPtr") { mFocusObject = *focus->getUserData(); - tooltipSize = getToolTipViaPtr(false); + tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false); } else if (type == "ItemModelIndex") { std::pair pair = *focus->getUserData >(); mFocusObject = pair.second->getItem(pair.first).mBase; - // HACK: To get the correct count for multiple item stack sources - int oldCount = mFocusObject.getRefData().getCount(); - mFocusObject.getRefData().setCount(pair.second->getItem(pair.first).mCount); - tooltipSize = getToolTipViaPtr(false); - mFocusObject.getRefData().setCount(oldCount); + tooltipSize = getToolTipViaPtr(pair.second->getItem(pair.first).mCount, false); } else if (type == "ToolTipInfo") { - tooltipSize = createToolTip(*focus->getUserData()); + tooltipSize = createToolTip(*focus->getUserData(), false); } else if (type == "AvatarItemSelection") { MyGUI::IntCoord avatarPos = focus->getAbsoluteCoord(); MyGUI::IntPoint relMousePos = MyGUI::InputManager::getInstance ().getMousePosition () - MyGUI::IntPoint(avatarPos.left, avatarPos.top); - int realX = int(float(relMousePos.left) / float(avatarPos.width) * 512.f ); - int realY = int(float(relMousePos.top) / float(avatarPos.height) * 1024.f ); - MWWorld::Ptr item = MWBase::Environment::get().getWindowManager()->getInventoryWindow ()->getAvatarSelectedItem (realX, realY); + MWWorld::Ptr item = MWBase::Environment::get().getWindowManager()->getInventoryWindow ()->getAvatarSelectedItem (relMousePos.left, relMousePos.top); mFocusObject = item; if (!mFocusObject.isEmpty ()) - tooltipSize = getToolTipViaPtr(false); + tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false); } else if (type == "Spell") { @@ -234,12 +231,12 @@ namespace MWGui } if (MWMechanics::spellIncreasesSkill(spell)) // display school of spells that contribute to skill progress { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int school = MWMechanics::getSpellSchool(spell, player); info.text = "#{sSchool}: " + sSchoolNames[school]; } info.effects = effects; - tooltipSize = createToolTip(info); + tooltipSize = createToolTip(info, false); } else if (type == "Layout") { @@ -293,7 +290,7 @@ namespace MWGui { if (!mFocusObject.isEmpty()) { - MyGUI::IntSize tooltipSize = getToolTipViaPtr(); + MyGUI::IntSize tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount()); setCoord(viewSize.width/2 - tooltipSize.width/2, std::max(0, int(mFocusToolTipY*viewSize.height - tooltipSize.height)), @@ -320,12 +317,12 @@ namespace MWGui } } - void ToolTips::setFocusObject(const MWWorld::Ptr& focus) + void ToolTips::setFocusObject(const MWWorld::ConstPtr& focus) { mFocusObject = focus; } - MyGUI::IntSize ToolTips::getToolTipViaPtr (bool image) + MyGUI::IntSize ToolTips::getToolTipViaPtr (int count, bool image) { // this the maximum width of the tooltip before it starts word-wrapping setCoord(0, 0, 300, 300); @@ -341,18 +338,49 @@ namespace MWGui { mDynamicToolTipBox->setVisible(true); - ToolTipInfo info = object.getToolTipInfo(mFocusObject); + ToolTipInfo info = object.getToolTipInfo(mFocusObject, count); if (!image) info.icon = ""; - tooltipSize = createToolTip(info); + tooltipSize = createToolTip(info, true); } return tooltipSize; } + + bool ToolTips::checkOwned() + { + if(!mFocusObject.isEmpty()) + { + const MWWorld::CellRef& cellref = mFocusObject.getCellRef(); + MWWorld::Ptr ptr = MWMechanics::getPlayer(); + MWWorld::Ptr victim; + + MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager(); + bool allowed = mm->isAllowedToUse(ptr, cellref, victim); - MyGUI::IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) + return !allowed; + } + else + { + return false; + } + } + + MyGUI::IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info, bool isFocusObject) { mDynamicToolTipBox->setVisible(true); + + if(mShowOwned == 1 || mShowOwned == 3) + { + if(isFocusObject && checkOwned()) + { + mDynamicToolTipBox->changeWidgetSkin("HUD_Box_NoTransp_Owned"); + } + else + { + mDynamicToolTipBox->changeWidgetSkin("HUD_Box_NoTransp"); + } + } std::string caption = info.caption; std::string image = info.icon; @@ -388,7 +416,7 @@ namespace MWGui const int imageCaptionHPadding = (caption != "" ? 8 : 0); const int imageCaptionVPadding = (caption != "" ? 4 : 0); - std::string realImage = Misc::ResourceHelpers::correctIconPath(image); + std::string realImage = MWBase::Environment::get().getWindowManager()->correctIconPath(image); MyGUI::EditBox* captionWidget = mDynamicToolTipBox->createWidget("NormalText", MyGUI::IntCoord(0, 0, 300, 300), MyGUI::Align::Left | MyGUI::Align::Top, "ToolTipCaption"); captionWidget->setProperty("Static", "true"); @@ -689,7 +717,7 @@ namespace MWGui widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "BirthSignToolTip"); - widget->setUserString("ImageTexture_BirthSignImage", Misc::ResourceHelpers::correctTexturePath(sign->mTexture)); + widget->setUserString("ImageTexture_BirthSignImage", MWBase::Environment::get().getWindowManager()->correctTexturePath(sign->mTexture)); std::string text; text += sign->mName; @@ -784,7 +812,7 @@ namespace MWGui std::string icon = effect->mIcon; int slashPos = icon.rfind('\\'); icon.insert(slashPos+1, "b_"); - icon = Misc::ResourceHelpers::correctIconPath(icon); + icon = MWBase::Environment::get().getWindowManager()->correctIconPath(icon); widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "MagicEffectToolTip"); diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index c50d47ef57..1fc736bffd 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -1,8 +1,7 @@ - #ifndef MWGUI_TOOLTIPS_H #define MWGUI_TOOLTIPS_H -#include +#include "layout.hpp" #include "../mwworld/ptr.hpp" #include "widgets.hpp" @@ -45,7 +44,7 @@ namespace MWGui bool wordWrap; }; - class ToolTips : public OEngine::GUI::Layout + class ToolTips : public Layout { public: ToolTips(); @@ -59,7 +58,7 @@ namespace MWGui void setDelay(float delay); - void setFocusObject(const MWWorld::Ptr& focus); + void setFocusObject(const MWWorld::ConstPtr& focus); void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y); ///< set the screen-space position of the tooltip for focused object @@ -87,17 +86,21 @@ namespace MWGui static void createRaceToolTip(MyGUI::Widget* widget, const ESM::Race* playerRace); static void createClassToolTip(MyGUI::Widget* widget, const ESM::Class& playerClass); static void createMagicEffectToolTip(MyGUI::Widget* widget, short id); - + + bool checkOwned(); + /// Returns True if taking mFocusObject would be crime + private: MyGUI::Widget* mDynamicToolTipBox; - MWWorld::Ptr mFocusObject; + MWWorld::ConstPtr mFocusObject; - MyGUI::IntSize getToolTipViaPtr (bool image=true); + MyGUI::IntSize getToolTipViaPtr (int count, bool image=true); ///< @return requested tooltip size - MyGUI::IntSize createToolTip(const ToolTipInfo& info); + MyGUI::IntSize createToolTip(const ToolTipInfo& info, bool isFocusObject); ///< @return requested tooltip size + /// @param isFocusObject Is the object this tooltips originates from mFocusObject? float mFocusToolTipX; float mFocusToolTipY; @@ -107,7 +110,7 @@ namespace MWGui static std::string sSchoolNames[6]; - int mHorizontalScrollIndex; + int mHorizontalScrollIndex; float mDelay; @@ -119,6 +122,8 @@ namespace MWGui bool mEnabled; bool mFullHelp; + + int mShowOwned; }; } #endif diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index bdcf28bf20..6f45ed0053 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include @@ -15,12 +15,12 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" -#include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "inventorywindow.hpp" #include "itemview.hpp" @@ -195,7 +195,7 @@ namespace MWGui { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); std::string message = "#{sQuanityMenuMessage02}"; - dialog->open(object.getClass().getName(object), message, count); + dialog->openCountDialog(object.getClass().getName(object), message, count); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &TradeWindow::sellItem); mItemToSell = mSortModel->mapToSource(index); @@ -283,7 +283,7 @@ namespace MWGui return; } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); // check if the player can afford this @@ -368,7 +368,7 @@ namespace MWGui else x += abs(int(npcTerm - pcTerm)); - int roll = OEngine::Misc::Rng::rollDice(100) + 1; + int roll = Misc::Rng::rollDice(100) + 1; if(roll > x || (mCurrentMerchantOffer < 0) != (mCurrentBalance < 0)) //trade refused { MWBase::Environment::get().getWindowManager()-> @@ -491,7 +491,7 @@ namespace MWGui void TradeWindow::updateLabels() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mPlayerGold->setCaptionWithReplacing("#{sYourGold} " + MyGUI::utility::toString(playerGold)); diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 6376ced574..30ad27e46e 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -13,6 +13,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "tooltips.hpp" @@ -68,7 +69,7 @@ namespace MWGui { mPtr = actor; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index ba6fc2a786..8c55c37329 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -4,8 +4,6 @@ #include #include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -14,6 +12,7 @@ #include "../mwbase/dialoguemanager.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -146,7 +145,7 @@ namespace MWGui int price; iss >> price; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); if (playerGold(d /MWBase::Environment::get().getWorld()->getStore().get().find("fTravelTimeMult")->getFloat()); for(int i = 0;i < hours;i++) { diff --git a/apps/openmw/mwgui/videowidget.cpp b/apps/openmw/mwgui/videowidget.cpp index f865de377d..d28ea0b660 100644 --- a/apps/openmw/mwgui/videowidget.cpp +++ b/apps/openmw/mwgui/videowidget.cpp @@ -1,26 +1,56 @@ #include "videowidget.hpp" -#include +#include #include +#include + +#include +#include + #include "../mwsound/movieaudiofactory.hpp" namespace MWGui { VideoWidget::VideoWidget() + : mVFS(NULL) { mPlayer.reset(new Video::VideoPlayer()); setNeedKeyFocus(true); } +void VideoWidget::setVFS(const VFS::Manager *vfs) +{ + mVFS = vfs; +} + void VideoWidget::playVideo(const std::string &video) { mPlayer->setAudioFactory(new MWSound::MovieAudioFactory()); - mPlayer->playVideo(video); - setImageTexture(mPlayer->getTextureName()); + Files::IStreamPtr videoStream; + try + { + videoStream = mVFS->get(video); + } + catch (std::exception& e) + { + std::cerr << "Failed to open video: " << e.what() << std::endl; + return; + } + + mPlayer->playVideo(videoStream, video); + + osg::ref_ptr texture = mPlayer->getVideoTexture(); + if (!texture) + return; + + mTexture.reset(new osgMyGUI::OSGTexture(texture)); + + setRenderItemTexture(mTexture.get()); + getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); } int VideoWidget::getVideoWidth() diff --git a/apps/openmw/mwgui/videowidget.hpp b/apps/openmw/mwgui/videowidget.hpp index ee44328eb6..6b265628e6 100644 --- a/apps/openmw/mwgui/videowidget.hpp +++ b/apps/openmw/mwgui/videowidget.hpp @@ -1,26 +1,34 @@ #ifndef OPENMW_MWGUI_VIDEOWIDGET_H #define OPENMW_MWGUI_VIDEOWIDGET_H -#include +#include namespace Video { class VideoPlayer; } +namespace VFS +{ + class Manager; +} + namespace MWGui { /** * Widget that plays a video. */ - class VideoWidget : public MyGUI::ImageBox + class VideoWidget : public MyGUI::Widget { public: MYGUI_RTTI_DERIVED(VideoWidget) VideoWidget(); + /// Set the VFS (virtual file system) to find the videos on. + void setVFS(const VFS::Manager* vfs); + void playVideo (const std::string& video); int getVideoWidth(); @@ -42,6 +50,8 @@ namespace MWGui void autoResize (bool stretch); private: + const VFS::Manager* mVFS; + std::auto_ptr mTexture; std::auto_ptr mPlayer; }; diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index ed261e7eb0..e8ac63bf6b 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include #include @@ -19,6 +19,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwstate/charactermanager.hpp" @@ -155,13 +156,17 @@ namespace MWGui if (!region->mSleepList.empty()) { // figure out if player will be woken while sleeping - int x = OEngine::Misc::Rng::rollDice(hoursToWait); + int x = Misc::Rng::rollDice(hoursToWait); float fSleepRandMod = world->getStore().get().find("fSleepRandMod")->getFloat(); if (x < fSleepRandMod * hoursToWait) { float fSleepRestMod = world->getStore().get().find("fSleepRestMod")->getFloat(); - mInterruptAt = hoursToWait - int(fSleepRestMod * hoursToWait); - mInterruptCreatureList = region->mSleepList; + int interruptAtHoursRemaining = int(fSleepRestMod * hoursToWait); + if (interruptAtHoursRemaining != 0) + { + mInterruptAt = hoursToWait - interruptAtHoursRemaining; + mInterruptCreatureList = region->mSleepList; + } } } } @@ -199,7 +204,7 @@ namespace MWGui { stopWaiting(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::NpcStats &pcstats = player.getClass().getNpcStats(player); // trigger levelup if possible @@ -213,7 +218,7 @@ namespace MWGui void WaitDialog::setCanRest (bool canRest) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); bool full = (stats.getHealth().getCurrent() >= stats.getHealth().getModified()) && (stats.getMagicka().getCurrent() >= stats.getMagicka().getModified()); diff --git a/apps/openmw/mwgui/waitdialog.hpp b/apps/openmw/mwgui/waitdialog.hpp index e8f58c574d..13df1f8aec 100644 --- a/apps/openmw/mwgui/waitdialog.hpp +++ b/apps/openmw/mwgui/waitdialog.hpp @@ -8,11 +8,6 @@ namespace MWGui { - namespace Widgets - { - class MWScrollBar; - } - class WaitDialogProgressBar : public WindowBase { public: @@ -51,7 +46,7 @@ namespace MWGui MyGUI::Button* mUntilHealedButton; MyGUI::Button* mWaitButton; MyGUI::Button* mCancelButton; - MWGui::Widgets::MWScrollBar* mHourSlider; + MyGUI::ScrollBar* mHourSlider; TimeAdvancer mTimeAdvancer; bool mSleeping; diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 718624a16f..695337cde4 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -7,8 +7,6 @@ #include #include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -17,9 +15,6 @@ #include "controllers.hpp" -#undef min -#undef max - namespace MWGui { namespace Widgets @@ -474,7 +469,7 @@ namespace MWGui mTextWidget->setCaptionWithReplacing(spellLine); mRequestedWidth = mTextWidget->getTextSize().width + 24; - mImageWidget->setImageTexture(Misc::ResourceHelpers::correctIconPath(magicEffect->mIcon)); + mImageWidget->setImageTexture(MWBase::Environment::get().getWindowManager()->correctIconPath(magicEffect->mIcon)); } MWSpellEffect::~MWSpellEffect() @@ -544,6 +539,9 @@ namespace MWGui , mRepeatStepTime(0.1f) , mIsIncreasing(true) { +#if MYGUI_VERSION >= MYGUI_DEFINE_VERSION(3,2,2) + ScrollBar::setRepeatEnabled(false); +#endif } MWScrollBar::~MWScrollBar() @@ -607,7 +605,7 @@ namespace MWGui controller->eventRepeatClick += newDelegate(this, &MWScrollBar::repeatClick); controller->setEnabled(mEnableRepeat); controller->setRepeat(mRepeatTriggerTime, mRepeatStepTime); - MyGUI::ControllerManager::getInstance().addItem(this, controller); + MyGUI::ControllerManager::getInstance().addItem(this, controller); } void MWScrollBar::onDecreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) @@ -623,7 +621,7 @@ namespace MWGui controller->eventRepeatClick += newDelegate(this, &MWScrollBar::repeatClick); controller->setEnabled(mEnableRepeat); controller->setRepeat(mRepeatTriggerTime, mRepeatStepTime); - MyGUI::ControllerManager::getInstance().addItem(this, controller); + MyGUI::ControllerManager::getInstance().addItem(this, controller); } void MWScrollBar::onIncreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index 899f8a5e84..c9d1b06175 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -1,6 +1,7 @@ #include "windowbase.hpp" #include +#include #include @@ -48,15 +49,13 @@ void WindowBase::center() { // Centre dialog - // MyGUI::IntSize gameWindowSize = MyGUI::RenderManager::getInstance().getViewSize(); - // Note by scrawl: The following works more reliably in the case when the window was _just_ - // resized and MyGUI RenderManager doesn't know about the new size yet - MyGUI::IntSize gameWindowSize = MyGUI::IntSize(Settings::Manager::getInt("resolution x", "Video"), - Settings::Manager::getInt("resolution y", "Video")); + MyGUI::IntSize layerSize = MyGUI::RenderManager::getInstance().getViewSize(); + if (mMainWidget->getLayer()) + layerSize = mMainWidget->getLayer()->getSize(); MyGUI::IntCoord coord = mMainWidget->getCoord(); - coord.left = (gameWindowSize.width - coord.width)/2; - coord.top = (gameWindowSize.height - coord.height)/2; + coord.left = (layerSize.width - coord.width)/2; + coord.top = (layerSize.height - coord.height)/2; mMainWidget->setCoord(coord); } diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index bf74c8bf0f..43fb8901e7 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -1,7 +1,7 @@ #ifndef MWGUI_WINDOW_BASE_H #define MWGUI_WINDOW_BASE_H -#include +#include "layout.hpp" namespace MWBase { @@ -13,7 +13,7 @@ namespace MWGui class WindowManager; class DragAndDrop; - class WindowBase: public OEngine::GUI::Layout + class WindowBase: public Layout { public: WindowBase(const std::string& parLayout); @@ -21,16 +21,16 @@ namespace MWGui // Events typedef MyGUI::delegates::CMultiDelegate1 EventHandle_WindowBase; - ///Unhides the window + /// Notify that window has been made visible virtual void open() {} - ///Hides the window + /// Notify that window has been hidden virtual void close () {} - ///Gracefully exits the window + /// Gracefully exits the window virtual void exit() {} - ///Sets the visibility of the window + /// Sets the visibility of the window virtual void setVisible(bool visible); - ///Returns the visibility state of the window - virtual bool isVisible(); + /// Returns the visibility state of the window + bool isVisible(); void center(); }; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 015af00432..f151bee80b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -3,9 +3,7 @@ #include #include -#include -#include -#include +#include #include #include @@ -22,20 +20,37 @@ #include #include -#include -#include +#include -#include +#include +#include #include +#include +#include + #include +#include +#include +#include +#include + +#include + #include #include +#include + +#include + #include "../mwbase/inputmanager.hpp" #include "../mwbase/statemanager.hpp" +#include "../mwbase/soundmanager.hpp" + +#include "../mwrender/vismask.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" @@ -44,8 +59,9 @@ #include "../mwmechanics/stat.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" -#include "../mwsound/soundmanagerimp.hpp" +#include "../mwrender/localmap.hpp" #include "console.hpp" #include "journalwindow.hpp" @@ -98,15 +114,17 @@ namespace MWGui { WindowManager::WindowManager( - const Compiler::Extensions& extensions, OEngine::Render::OgreRenderer *ogre, - const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, - Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap) - : mConsoleOnlyScripts(consoleOnlyScripts) + osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem + , const std::string& logpath, const std::string& resourcePath, bool consoleOnlyScripts, + Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap, const std::string& versionDescription) + : mStore(NULL) + , mResourceSystem(resourceSystem) + , mViewer(viewer) + , mConsoleOnlyScripts(consoleOnlyScripts) , mCurrentModals() - , mGuiManager(NULL) - , mRendering(ogre) , mHud(NULL) , mMap(NULL) + , mLocalMapRender(NULL) , mMenu(NULL) , mToolTips(NULL) , mStatsWindow(NULL) @@ -171,19 +189,24 @@ namespace MWGui , mForceHidden(GW_None) , mAllowed(GW_ALL) , mRestAllowed(true) - , mFPS(0.0f) - , mTriangleCount(0) - , mBatchCount(0) , mFallbackMap(fallbackMap) + , mShowOwned(0) + , mVersionDescription(versionDescription) { - // Set up the GUI system - mGuiManager = new OEngine::GUI::MyGUIManager(mRendering->getWindow(), mRendering->getScene(), false, logpath); + float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); + mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getTextureManager(), uiScale); + mGuiPlatform->initialise(resourcePath, logpath); + + mGui = new MyGUI::Gui; + mGui->initialise(""); + + createTextures(); MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag); // Load fonts - Gui::FontLoader fontLoader (encoding); - fontLoader.loadAllFonts(exportFonts); + mFontLoader.reset(new Gui::FontLoader(encoding, resourceSystem->getVFS())); + mFontLoader->loadAllFonts(exportFonts); //Register own widgets with MyGUI MyGUI::FactoryManager::getInstance().registerFactory("Widget"); @@ -196,6 +219,8 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Layer"); + MyGUI::FactoryManager::getInstance().registerFactory("Layer"); BookPage::registerMyGUIComponents (); ItemView::registerComponents(); ItemWidget::registerComponents(); @@ -208,25 +233,26 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); MyGUI::ResourceManager::getInstance().load("core.xml"); - mLoadingScreen = new LoadingScreen(mRendering->getScene (), mRendering->getWindow ()); + mLoadingScreen = new LoadingScreen(mResourceSystem->getVFS(), mViewer); //set up the hardware cursor manager - mCursorManager = new SFO::SDLCursorManager(); + mCursorManager = new SDLUtil::SDLCursorManager(); MyGUI::PointerManager::getInstance().eventChangeMousePointer += MyGUI::newDelegate(this, &WindowManager::onCursorChange); MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged); + // Create all cursors in advance + createCursors(); onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); - mCursorManager->setEnabled(true); // hide mygui's pointer MyGUI::PointerManager::getInstance().setVisible(false); mVideoBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Default, "Overlay"); - mVideoBackground->setImageTexture("black.png"); + MyGUI::Align::Default, "InputBlocker"); + mVideoBackground->setImageTexture("black"); mVideoBackground->setVisible(false); mVideoBackground->setNeedMouseFocus(true); mVideoBackground->setNeedKeyFocus(true); @@ -234,6 +260,7 @@ namespace MWGui mVideoWidget = mVideoBackground->createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Default); mVideoWidget->setNeedMouseFocus(true); mVideoWidget->setNeedKeyFocus(true); + mVideoWidget->setVFS(resourceSystem->getVFS()); // Removes default MyGUI system clipboard implementation, which supports windows only MyGUI::ClipboardManager::getInstance().eventClipboardChanged.clear(); @@ -241,6 +268,8 @@ namespace MWGui MyGUI::ClipboardManager::getInstance().eventClipboardChanged += MyGUI::newDelegate(this, &WindowManager::onClipboardChanged); MyGUI::ClipboardManager::getInstance().eventClipboardRequested += MyGUI::newDelegate(this, &WindowManager::onClipboardRequested); + + mShowOwned = Settings::Manager::getInt("show owned", "Game"); } void WindowManager::initUI() @@ -252,17 +281,19 @@ namespace MWGui mDragAndDrop = new DragAndDrop(); mRecharge = new Recharge(); - mMenu = new MainMenu(w,h); - mMap = new MapWindow(mCustomMarkers, mDragAndDrop, ""); + mMenu = new MainMenu(w, h, mResourceSystem->getVFS(), mVersionDescription); + mLocalMapRender = new MWRender::LocalMap(mViewer); + mMap = new MapWindow(mCustomMarkers, mDragAndDrop, mLocalMapRender); trackWindow(mMap, "map"); mStatsWindow = new StatsWindow(mDragAndDrop); trackWindow(mStatsWindow, "stats"); mConsole = new Console(w,h, mConsoleOnlyScripts); trackWindow(mConsole, "console"); - mJournal = JournalWindow::create(JournalViewModel::create ()); - mMessageBoxManager = new MessageBoxManager( - MWBase::Environment::get().getWorld()->getStore().get().find("fMessageTimePerChar")->getFloat()); - mInventoryWindow = new InventoryWindow(mDragAndDrop); + + bool questList = mResourceSystem->getVFS()->exists("textures/tx_menubook_options_over.dds"); + mJournal = JournalWindow::create(JournalViewModel::create (), questList); + mMessageBoxManager = new MessageBoxManager(mStore->get().find("fMessageTimePerChar")->getFloat()); + mInventoryWindow = new InventoryWindow(mDragAndDrop, mViewer, mResourceSystem); mTradeWindow = new TradeWindow(); trackWindow(mTradeWindow, "barter"); mSpellBuyingWindow = new SpellBuyingWindow(); @@ -271,7 +302,7 @@ namespace MWGui trackWindow(mDialogueWindow, "dialogue"); mContainerWindow = new ContainerWindow(mDragAndDrop); trackWindow(mContainerWindow, "container"); - mHud = new HUD(mCustomMarkers, Settings::Manager::getInt("fps", "HUD"), mDragAndDrop); + mHud = new HUD(mCustomMarkers, mDragAndDrop, mLocalMapRender); mToolTips = new ToolTips(); mScrollWindow = new ScrollWindow(); mBookWindow = new BookWindow(); @@ -295,23 +326,31 @@ namespace MWGui trackWindow(mCompanionWindow, "companion"); mJailScreen = new JailScreen(); - mWerewolfFader = new ScreenFader("textures\\werewolfoverlay.dds"); - mBlindnessFader = new ScreenFader("black.png"); - std::string hitFaderTexture = "textures\\bm_player_hit_01.dds"; + std::string werewolfFaderTex = "textures\\werewolfoverlay.dds"; + if (mResourceSystem->getVFS()->exists(werewolfFaderTex)) + mWerewolfFader = new ScreenFader(werewolfFaderTex); + mBlindnessFader = new ScreenFader("black"); + // fall back to player_hit_01.dds if bm_player_hit_01.dds is not available - // TODO: check if non-BM versions actually use player_hit_01.dds - if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(hitFaderTexture)) + std::string hitFaderTexture = "textures\\bm_player_hit_01.dds"; + const std::string hitFaderLayout = "openmw_screen_fader_hit.layout"; + MyGUI::FloatCoord hitFaderCoord (0,0,1,1); + if(!mResourceSystem->getVFS()->exists(hitFaderTexture)) + { hitFaderTexture = "textures\\player_hit_01.dds"; - mHitFader = new ScreenFader(hitFaderTexture); - mScreenFader = new ScreenFader("black.png"); + hitFaderCoord = MyGUI::FloatCoord(0.2, 0.25, 0.6, 0.5); + } + mHitFader = new ScreenFader(hitFaderTexture, hitFaderLayout, hitFaderCoord); + + mScreenFader = new ScreenFader("black"); mDebugWindow = new DebugWindow(); - mInputBlocker = MyGUI::Gui::getInstance().createWidget("",0,0,w,h,MyGUI::Align::Stretch,"Overlay"); + mInputBlocker = MyGUI::Gui::getInstance().createWidget("",0,0,w,h,MyGUI::Align::Stretch,"InputBlocker"); mHud->setVisible(mHudEnabled); - mCharGen = new CharacterCreation(); + mCharGen = new CharacterCreation(mViewer, mResourceSystem); // Setup player stats for (int i = 0; i < ESM::Attribute::Length; ++i) @@ -345,7 +384,7 @@ namespace MWGui { disallowAll(); delete mCharGen; - mCharGen = new CharacterCreation(); + mCharGen = new CharacterCreation(mViewer, mResourceSystem); mGuiModes.clear(); MWBase::Environment::get().getInputManager()->changeInputMode(false); mHud->unsetSelectedWeapon(); @@ -370,6 +409,7 @@ namespace MWGui delete mMessageBoxManager; delete mHud; delete mMap; + delete mLocalMapRender; delete mMenu; delete mStatsWindow; delete mJournal; @@ -412,7 +452,18 @@ namespace MWGui cleanupGarbage(); - delete mGuiManager; + mFontLoader.reset(); + + mGui->shutdown(); + delete mGui; + + mGuiPlatform->shutdown(); + delete mGuiPlatform; + } + + void WindowManager::setStore(const MWWorld::ESMStore &store) + { + mStore = &store; } void WindowManager::cleanupGarbage() @@ -420,7 +471,7 @@ namespace MWGui // Delete any dialogs which are no longer in use if (!mGarbageDialogs.empty()) { - for (std::vector::iterator it = mGarbageDialogs.begin(); it != mGarbageDialogs.end(); ++it) + for (std::vector::iterator it = mGarbageDialogs.begin(); it != mGarbageDialogs.end(); ++it) { delete *it; } @@ -432,10 +483,6 @@ namespace MWGui { cleanupGarbage(); - mHud->setFPS(mFPS); - mHud->setTriangleCount(mTriangleCount); - mHud->setBatchCount(mBatchCount); - mHud->update(); } @@ -720,7 +767,7 @@ namespace MWGui mStatsWindow->updateSkillArea(); } - void WindowManager::removeDialog(OEngine::GUI::Layout*dialog) + void WindowManager::removeDialog(Layout*dialog) { if (!dialog) return; @@ -829,7 +876,13 @@ namespace MWGui mMessageBoxManager->onFrame(0.f); MWBase::Environment::get().getInputManager()->update(0, true, false); - mRendering->getWindow()->update(); + // at the time this function is called we are in the middle of a frame, + // so out of order calls are necessary to get a correct frameNumber for the next frame. + // refer to the advance() and frame() order in Engine::go() + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); } } } @@ -860,8 +913,7 @@ namespace MWGui std::string WindowManager::getGameSettingString(const std::string &id, const std::string &default_) { - const ESM::GameSetting *setting = - MWBase::Environment::get().getWorld()->getStore().get().search(id); + const ESM::GameSetting *setting = mStore->get().search(id); if (setting && setting->mValue.getType()==ESM::VT_String) return setting->mValue.getString(); @@ -869,6 +921,34 @@ namespace MWGui return default_; } + void WindowManager::updateMap() + { + if (!mLocalMapRender) + return; + + MWWorld::Ptr player = MWMechanics::getPlayer(); + + osg::Vec3f playerPosition = player.getRefData().getPosition().asVec3(); + osg::Quat playerOrientation (-player.getRefData().getPosition().rot[2], osg::Vec3(0,0,1)); + + osg::Vec3f playerdirection; + int x,y; + float u,v; + mLocalMapRender->updatePlayer(playerPosition, playerOrientation, u, v, x, y, playerdirection); + + if (!player.getCell()->isExterior()) + { + mMap->setActiveCell(x, y, true); + mHud->setActiveCell(x, y, true); + } + // else: need to know the current grid center, call setActiveCell from MWWorld::Scene + + mMap->setPlayerDir(playerdirection.x(), playerdirection.y()); + mMap->setPlayerPos(x, y, u, v); + mHud->setPlayerDir(playerdirection.x(), playerdirection.y()); + mHud->setPlayerPos(x, y, u, v); + } + void WindowManager::onFrame (float frameDuration) { mMessageBoxManager->onFrame(frameDuration); @@ -877,6 +957,9 @@ namespace MWGui mMenu->update(frameDuration); + if (mLocalMapRender) + mLocalMapRender->cleanupCameras(); + if (MWBase::Environment::get().getStateManager()->getState()== MWBase::StateManager::State_NoGame) return; @@ -891,6 +974,8 @@ namespace MWGui mInventoryWindow->onFrame(); + updateMap(); + mStatsWindow->onFrame(frameDuration); mMap->onFrame(frameDuration); mSpellWindow->onFrame(frameDuration); @@ -913,7 +998,8 @@ namespace MWGui mCompanionWindow->onFrame(); mJailScreen->onFrame(frameDuration); - mWerewolfFader->update(frameDuration); + if (mWerewolfFader) + mWerewolfFader->update(frameDuration); mBlindnessFader->update(frameDuration); mHitFader->update(frameDuration); mScreenFader->update(frameDuration); @@ -931,7 +1017,7 @@ namespace MWGui if (cell->getCell()->isExterior()) { if (!cell->getCell()->mName.empty()) - mMap->addVisitedLocation ("#{sCell=" + name + "}", cell->getCell()->getGridX (), cell->getCell()->getGridY ()); + mMap->addVisitedLocation (name, cell->getCell()->getGridX (), cell->getCell()->getGridY ()); mMap->cellExplored (cell->getCell()->getGridX(), cell->getCell()->getGridY()); } @@ -940,40 +1026,21 @@ namespace MWGui mMap->setCellPrefix (cell->getCell()->mName ); mHud->setCellPrefix (cell->getCell()->mName ); - Ogre::Vector3 worldPos; + osg::Vec3f worldPos; if (!MWBase::Environment::get().getWorld()->findInteriorPositionInWorldSpace(cell, worldPos)) worldPos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); else MWBase::Environment::get().getWorld()->getPlayer().setLastKnownExteriorPosition(worldPos); - mMap->setGlobalMapPlayerPosition(worldPos.x, worldPos.y); + mMap->setGlobalMapPlayerPosition(worldPos.x(), worldPos.y()); } } void WindowManager::setActiveMap(int x, int y, bool interior) { - if (!interior) - { - mMap->setCellPrefix("Cell"); - mHud->setCellPrefix("Cell"); - } - mMap->setActiveCell(x,y, interior); mHud->setActiveCell(x,y, interior); } - void WindowManager::setPlayerPos(int cellX, int cellY, const float x, const float y) - { - mMap->setPlayerPos(cellX, cellY, x, y); - mHud->setPlayerPos(cellX, cellY, x, y); - } - - void WindowManager::setPlayerDir(const float x, const float y) - { - mMap->setPlayerDir(x,y); - mMap->setGlobalMapPlayerDir(x, y); - mHud->setPlayerDir(x,y); - } - void WindowManager::setDrowningBarVisibility(bool visible) { mHud->setDrowningBarVisible(visible); @@ -998,6 +1065,12 @@ namespace MWGui void WindowManager::setFocusObject(const MWWorld::Ptr& focus) { mToolTips->setFocusObject(focus); + + if(mHud && (mShowOwned == 2 || mShowOwned == 3)) + { + bool owned = mToolTips->checkOwned(); + mHud->setCrosshairOwned(owned); + } } void WindowManager::setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) @@ -1045,11 +1118,22 @@ namespace MWGui void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result) { std::string tag(_tag); + + std::string MyGuiPrefix = "setting="; + size_t MyGuiPrefixLength = MyGuiPrefix.length(); std::string tokenToFind = "sCell="; size_t tokenLength = tokenToFind.length(); - - if (tag.compare(0, tokenLength, tokenToFind) == 0) + + if(tag.compare(0, MyGuiPrefixLength, MyGuiPrefix) == 0) + { + tag = tag.substr(MyGuiPrefixLength, tag.length()); + std::string settingSection = tag.substr(0, tag.find(",")); + std::string settingTag = tag.substr(tag.find(",")+1, tag.length()); + + _result = Settings::Manager::getString(settingTag, settingSection); + } + else if (tag.compare(0, tokenLength, tokenToFind) == 0) { _result = mTranslationDataStorage.translateCellName(tag.substr(tokenLength)); } @@ -1059,8 +1143,12 @@ namespace MWGui } else { - const ESM::GameSetting *setting = - MWBase::Environment::get().getWorld()->getStore().get().find(tag); + if (!mStore) + { + std::cerr << "WindowManager::onRetrieveTag: no Store set up yet, can not replace '" << tag << "'" << std::endl; + return; + } + const ESM::GameSetting *setting = mStore->get().find(tag); if (setting && setting->mValue.getType()==ESM::VT_String) _result = setting->mValue.getString(); @@ -1071,7 +1159,6 @@ namespace MWGui void WindowManager::processChangedSettings(const Settings::CategorySettingVector& changed) { - mHud->setFpsLevel(Settings::Manager::getInt("fps", "HUD")); mToolTips->setDelay(Settings::Manager::getFloat("tooltip delay", "GUI")); for (Settings::CategorySettingVector::const_iterator it = changed.begin(); @@ -1081,13 +1168,22 @@ namespace MWGui mCrosshairEnabled = Settings::Manager::getBool ("crosshair", "HUD"); else if (it->first == "GUI" && it->second == "subtitles") mSubtitlesEnabled = Settings::Manager::getBool ("subtitles", "GUI"); + else if (it->first == "GUI" && it->second == "menu transparency") + setMenuTransparency(Settings::Manager::getFloat("menu transparency", "GUI")); } } void WindowManager::windowResized(int x, int y) { + mGuiPlatform->getRenderManagerPtr()->setViewSize(x, y); + + // scaled size + const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + x = viewSize.width; + y = viewSize.height; + sizeVideo(x, y); - mGuiManager->windowResized(); + if (!mHud) return; // UI not initialized yet @@ -1133,30 +1229,7 @@ namespace MWGui void WindowManager::onCursorChange(const std::string &name) { - if(!mCursorManager->cursorChanged(name)) - return; //the cursor manager doesn't want any more info about this cursor - //See if we can get the information we need out of the cursor resource - ResourceImageSetPointerFix* imgSetPtr = dynamic_cast(MyGUI::PointerManager::getInstance().getByName(name)); - if(imgSetPtr != NULL) - { - MyGUI::ResourceImageSet* imgSet = imgSetPtr->getImageSet(); - - std::string tex_name = imgSet->getIndexInfo(0,0).texture; - - Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().getByName(tex_name); - - //everything looks good, send it to the cursor manager - if(!tex.isNull()) - { - Uint8 size_x = imgSetPtr->getSize().width; - Uint8 size_y = imgSetPtr->getSize().height; - Uint8 hotspot_x = imgSetPtr->getHotSpot().left; - Uint8 hotspot_y = imgSetPtr->getHotSpot().top; - int rotation = imgSetPtr->getRotation(); - - mCursorManager->receiveCursorInfo(name, rotation, tex, size_x, size_y, hotspot_x, hotspot_y); - } - } + mCursorManager->cursorChanged(name); } void WindowManager::popGuiMode() @@ -1198,8 +1271,7 @@ namespace MWGui mSelectedSpell = spellId; mHud->setSelectedSpell(spellId, successChancePercent); - const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + const ESM::Spell* spell = mStore->get().find(spellId); mSpellWindow->setTitle(spell->mName); } @@ -1207,7 +1279,7 @@ namespace MWGui void WindowManager::setSelectedEnchantItem(const MWWorld::Ptr& item) { mSelectedSpell = ""; - const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get() + const ESM::Enchantment* ench = mStore->get() .find(item.getClass().getEnchantment(item)); int chargePercent = (item.getCellRef().getEnchantmentCharge() == -1) ? 100 @@ -1269,19 +1341,18 @@ namespace MWGui mConsole->executeFile (path); } - void WindowManager::wmUpdateFps(float fps, unsigned int triangleCount, unsigned int batchCount) - { - mFPS = fps; - mTriangleCount = triangleCount; - mBatchCount = batchCount; - } - MWGui::DialogueWindow* WindowManager::getDialogueWindow() { return mDialogueWindow; } MWGui::InventoryWindow* WindowManager::getInventoryWindow() { return mInventoryWindow; } MWGui::CountDialog* WindowManager::getCountDialog() { return mCountDialog; } MWGui::ConfirmationDialog* WindowManager::getConfirmationDialog() { return mConfirmationDialog; } MWGui::TradeWindow* WindowManager::getTradeWindow() { return mTradeWindow; } + void WindowManager::useItem(const MWWorld::Ptr &item) + { + if (mInventoryWindow) + mInventoryWindow->useItem(item); + } + bool WindowManager::isAllowed (GuiWindow wnd) const { return (mAllowed & wnd) != 0; @@ -1483,7 +1554,7 @@ namespace MWGui void WindowManager::showCompanionWindow(MWWorld::Ptr actor) { pushGuiMode(MWGui::GM_Companion); - mCompanionWindow->open(actor); + mCompanionWindow->openCompanion(actor); } void WindowManager::changePointer(const std::string &name) @@ -1499,17 +1570,11 @@ namespace MWGui updateVisible(); } - void WindowManager::frameStarted (float dt) - { - mInventoryWindow->doRenderUpdate (); - mCharGen->doRenderUpdate(); - } - void WindowManager::updatePlayer() { mInventoryWindow->updatePlayer(); - const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr player = MWMechanics::getPlayer(); if (player.getClass().getNpcStats(player).isWerewolf()) { setWerewolfOverlay(true); @@ -1555,7 +1620,7 @@ namespace MWGui return mCursorVisible; } - void WindowManager::trackWindow(OEngine::GUI::Layout *layout, const std::string &name) + void WindowManager::trackWindow(Layout *layout, const std::string &name) { MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); MyGUI::IntPoint pos(static_cast(Settings::Manager::getFloat(name + " x", "Windows") * viewSize.width), @@ -1588,6 +1653,9 @@ namespace MWGui void WindowManager::clear() { + if (mLocalMapRender) + mLocalMapRender->clear(); + mMap->clear(); mQuickKeysMenu->clear(); mMessageBoxManager->clear(); @@ -1628,10 +1696,10 @@ namespace MWGui writer.endRecord(ESM::REC_ASPL); } - for (std::vector::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) + for (CustomMarkerCollection::ContainerType::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) { writer.startRecord(ESM::REC_MARK); - (*it).save(writer); + it->second.save(writer); writer.endRecord(ESM::REC_MARK); } } @@ -1645,7 +1713,9 @@ namespace MWGui else if (type == ESM::REC_ASPL) { reader.getSubNameIs("ID__"); - mSelectedSpell = reader.getHString(); + std::string spell = reader.getHString(); + if (mStore->get().search(spell)) + mSelectedSpell = spell; } else if (type == ESM::REC_MARK) { @@ -1683,18 +1753,15 @@ namespace MWGui } // Turn off all rendering except for the GUI - mRendering->getScene()->clearSpecialCaseRenderQueues(); - // SCRQM_INCLUDE with RENDER_QUEUE_OVERLAY does not work? - for(int i = 0;i < Ogre::RENDER_QUEUE_MAX;++i) - { - if(i > 0 && i < 96) - mRendering->getScene()->addSpecialCaseRenderQueue(i); - } - mRendering->getScene()->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); + int oldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask(); + int oldCullMask = mViewer->getCamera()->getCullMask(); + mViewer->getUpdateVisitor()->setTraversalMask(MWRender::Mask_GUI); + mViewer->getCamera()->setCullMask(MWRender::Mask_GUI); MyGUI::IntSize screenSize = MyGUI::RenderManager::getInstance().getViewSize(); sizeVideo(screenSize.width, screenSize.height); + MyGUI::Widget* oldKeyFocus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); setKeyFocusWidget(mVideoWidget); mVideoBackground->setVisible(true); @@ -1710,17 +1777,25 @@ namespace MWGui { MWBase::Environment::get().getInputManager()->update(0, true, false); - mRendering->getWindow()->update(); + // at the time this function is called we are in the middle of a frame, + // so out of order calls are necessary to get a correct frameNumber for the next frame. + // refer to the advance() and frame() order in Engine::go() + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); } mVideoWidget->stop(); MWBase::Environment::get().getSoundManager()->resumeSounds(); + setKeyFocusWidget(oldKeyFocus); + setCursorVisible(cursorWasVisible); // Restore normal rendering - mRendering->getScene()->clearSpecialCaseRenderQueues(); - mRendering->getScene()->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); + mViewer->getUpdateVisitor()->setTraversalMask(oldUpdateMask); + mViewer->getCamera()->setCullMask(oldCullMask); mVideoBackground->setVisible(false); } @@ -1821,7 +1896,8 @@ namespace MWGui if (!mWerewolfOverlayEnabled) return; - mWerewolfFader->notifyAlphaChanged(set ? 1.0f : 0.0f); + if (mWerewolfFader) + mWerewolfFader->notifyAlphaChanged(set ? 1.0f : 0.0f); } void WindowManager::onClipboardChanged(const std::string &_type, const std::string &_data) @@ -1893,19 +1969,128 @@ namespace MWGui void WindowManager::openContainer(const MWWorld::Ptr &container, bool loot) { pushGuiMode(GM_Container); - mContainerWindow->open(container, loot); + mContainerWindow->openContainer(container, loot); } void WindowManager::showBook(const MWWorld::Ptr &item, bool showTakeButton) { pushGuiMode(GM_Book); - mBookWindow->open(item, showTakeButton); + mBookWindow->openBook(item, showTakeButton); } void WindowManager::showScroll(const MWWorld::Ptr &item, bool showTakeButton) { pushGuiMode(GM_Scroll); - mScrollWindow->open(item, showTakeButton); + mScrollWindow->openScroll(item, showTakeButton); + } + + std::string WindowManager::correctIconPath(const std::string& path) + { + return Misc::ResourceHelpers::correctIconPath(path, mResourceSystem->getVFS()); + } + + std::string WindowManager::correctBookartPath(const std::string& path, int width, int height) + { + return Misc::ResourceHelpers::correctBookartPath(path, width, height, mResourceSystem->getVFS()); + } + + std::string WindowManager::correctTexturePath(const std::string& path) + { + return Misc::ResourceHelpers::correctTexturePath(path, mResourceSystem->getVFS()); + } + + void WindowManager::createCursors() + { + MyGUI::ResourceManager::EnumeratorPtr enumerator = MyGUI::ResourceManager::getInstance().getEnumerator(); + while (enumerator.next()) + { + MyGUI::IResource* resource = enumerator.current().second; + ResourceImageSetPointerFix* imgSetPointer = dynamic_cast(resource); + if (!imgSetPointer) + continue; + std::string tex_name = imgSetPointer->getImageSet()->getIndexInfo(0,0).texture; + + osg::ref_ptr tex = mResourceSystem->getTextureManager()->getTexture2D(tex_name, osg::Texture::CLAMP, osg::Texture::CLAMP); + + if(tex.valid()) + { + //everything looks good, send it to the cursor manager + Uint8 size_x = imgSetPointer->getSize().width; + Uint8 size_y = imgSetPointer->getSize().height; + Uint8 hotspot_x = imgSetPointer->getHotSpot().left; + Uint8 hotspot_y = imgSetPointer->getHotSpot().top; + int rotation = imgSetPointer->getRotation(); + + mCursorManager->createCursor(imgSetPointer->getResourceName(), rotation, tex->getImage(), size_x, size_y, hotspot_x, hotspot_y); + } + } + } + + void WindowManager::createTextures() + { + { + MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().createTexture("white"); + tex->createManual(8, 8, MyGUI::TextureUsage::Write, MyGUI::PixelFormat::R8G8B8); + unsigned char* data = reinterpret_cast(tex->lock(MyGUI::TextureUsage::Write)); + for (int x=0; x<8; ++x) + for (int y=0; y<8; ++y) + { + *(data++) = 255; + *(data++) = 255; + *(data++) = 255; + } + tex->unlock(); + } + + { + MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().createTexture("black"); + tex->createManual(8, 8, MyGUI::TextureUsage::Write, MyGUI::PixelFormat::R8G8B8); + unsigned char* data = reinterpret_cast(tex->lock(MyGUI::TextureUsage::Write)); + for (int x=0; x<8; ++x) + for (int y=0; y<8; ++y) + { + *(data++) = 0; + *(data++) = 0; + *(data++) = 0; + } + tex->unlock(); + } + + { + MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().createTexture("transparent"); + tex->createManual(8, 8, MyGUI::TextureUsage::Write, MyGUI::PixelFormat::R8G8B8A8); + setMenuTransparency(Settings::Manager::getFloat("menu transparency", "GUI")); + } + } + + void WindowManager::setMenuTransparency(float value) + { + MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().getTexture("transparent"); + unsigned char* data = reinterpret_cast(tex->lock(MyGUI::TextureUsage::Write)); + for (int x=0; x<8; ++x) + for (int y=0; y<8; ++y) + { + *(data++) = 255; + *(data++) = 255; + *(data++) = 255; + *(data++) = static_cast(value*255); + } + tex->unlock(); + } + + void WindowManager::requestMap(std::set cells) + { + mLocalMapRender->requestMap(cells); + } + + void WindowManager::removeCell(MWWorld::CellStore *cell) + { + mLocalMapRender->removeCell(cell); + } + + void WindowManager::writeFog(MWWorld::CellStore *cell) + { + mLocalMapRender->saveFogOfWar(cell); } } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 297480d20f..6edb4660cf 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -28,6 +28,11 @@ namespace MyGUI class ImageBox; } +namespace MWWorld +{ + class ESMStore; +} + namespace Compiler { class Extensions; @@ -38,23 +43,38 @@ namespace Translation class Storage; } -namespace OEngine +namespace osg { - namespace GUI - { - class Layout; - class MyGUIManager; - } - - namespace Render - { - class OgreRenderer; - } + class Group; +} +namespace osgViewer +{ + class Viewer; } -namespace SFO +namespace Resource { - class CursorManager; + class ResourceSystem; +} + +namespace SDLUtil +{ + class SDLCursorManager; +} + +namespace osgMyGUI +{ + class Platform; +} + +namespace Gui +{ + class FontLoader; +} + +namespace MWRender +{ + class LocalMap; } namespace MWGui @@ -99,12 +119,14 @@ namespace MWGui typedef std::pair Faction; typedef std::vector FactionList; - WindowManager(const Compiler::Extensions& extensions, - OEngine::Render::OgreRenderer *mOgre, const std::string& logpath, - const std::string& cacheDir, bool consoleOnlyScripts, - Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap); + WindowManager(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem, + const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, + Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap, const std::string& versionDescription); virtual ~WindowManager(); + /// Set the ESMStore to use for retrieving of GUI-related strings. + void setStore (const MWWorld::ESMStore& store); + void initUI(); void renderWorldMap(); @@ -159,12 +181,13 @@ namespace MWGui virtual MWGui::ConfirmationDialog* getConfirmationDialog(); virtual MWGui::TradeWindow* getTradeWindow(); + /// Make the player use an item, while updating GUI state accordingly + virtual void useItem(const MWWorld::Ptr& item); + virtual void updateSpellWindow(); virtual void setConsoleSelectedObject(const MWWorld::Ptr& object); - virtual void wmUpdateFps(float fps, unsigned int triangleCount, unsigned int batchCount); - ///< Set value for the given ID. virtual void setValue (const std::string& id, const MWMechanics::AttributeValue& value); virtual void setValue (int parSkill, const MWMechanics::SkillValue& value); @@ -182,8 +205,6 @@ namespace MWGui virtual void updateSkillArea(); ///< update display of skills, factions, birth sign, reputation and bounty virtual void changeCell(MWWorld::CellStore* cell); ///< change the active cell - virtual void setPlayerPos(int cellX, int cellY, const float x, const float y); ///< set player position in map space - virtual void setPlayerDir(const float x, const float y); ///< set player view direction in map space virtual void setFocusObject(const MWWorld::Ptr& focus); virtual void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y); @@ -233,7 +254,7 @@ namespace MWGui virtual void addVisitedLocation(const std::string& name, int x, int y); ///Hides dialog and schedules dialog to be deleted. - virtual void removeDialog(OEngine::GUI::Layout* dialog); + virtual void removeDialog(Layout* dialog); ///Gracefully attempts to exit the topmost GUI mode virtual void exitCurrentGuiMode(); @@ -294,8 +315,6 @@ namespace MWGui virtual void showBook(const MWWorld::Ptr& item, bool showTakeButton); virtual void showScroll(const MWWorld::Ptr& item, bool showTakeButton); - virtual void frameStarted(float dt); - virtual void showSoulgemDialog (MWWorld::Ptr item); virtual void changePointer (const std::string& name); @@ -351,11 +370,28 @@ namespace MWGui /// Cycle to next or previous weapon virtual void cycleWeapon(bool next); + // In WindowManager for now since there isn't a VFS singleton + virtual std::string correctIconPath(const std::string& path); + virtual std::string correctBookartPath(const std::string& path, int width, int height); + virtual std::string correctTexturePath(const std::string& path); + + void requestMap(std::set cells); + void removeCell(MWWorld::CellStore* cell); + void writeFog(MWWorld::CellStore* cell); + private: + const MWWorld::ESMStore* mStore; + Resource::ResourceSystem* mResourceSystem; + + osgMyGUI::Platform* mGuiPlatform; + osgViewer::Viewer* mViewer; + + std::auto_ptr mFontLoader; + bool mConsoleOnlyScripts; std::map mTrackedWindows; - void trackWindow(OEngine::GUI::Layout* layout, const std::string& name); + void trackWindow(Layout* layout, const std::string& name); void onWindowChangeCoord(MyGUI::Window* _sender); std::string mSelectedSpell; @@ -365,10 +401,9 @@ namespace MWGui // Markers placed manually by the player. Must be shared between both map views (the HUD map and the map window). CustomMarkerCollection mCustomMarkers; - OEngine::GUI::MyGUIManager *mGuiManager; - OEngine::Render::OgreRenderer *mRendering; HUD *mHud; MapWindow *mMap; + MWRender::LocalMap* mLocalMapRender; MainMenu *mMenu; ToolTips *mToolTips; StatsWindow *mStatsWindow; @@ -437,9 +472,9 @@ namespace MWGui MyGUI::Gui *mGui; // Gui std::vector mGuiModes; - SFO::CursorManager* mCursorManager; + SDLUtil::SDLCursorManager* mCursorManager; - std::vector mGarbageDialogs; + std::vector mGarbageDialogs; void cleanupGarbage(); GuiWindow mShown; // Currently shown windows in inventory mode @@ -456,16 +491,19 @@ namespace MWGui void updateVisible(); // Update visibility of all windows based on mode, shown and allowed settings - float mFPS; - unsigned int mTriangleCount; - unsigned int mBatchCount; + void updateMap(); std::map mFallbackMap; + + int mShowOwned; + + std::string mVersionDescription; /** * Called when MyGUI tries to retrieve a tag's value. Tags must be denoted in #{tag} notation and will be replaced upon setting a user visible text/property. * Supported syntax: * #{GMSTName}: retrieves String value of the GMST called GMSTName + * #{setting=CATEGORY_NAME,SETTING_NAME}: retrieves String value of SETTING_NAME under category CATEGORY_NAME from settings.cfg * #{sCell=CellID}: retrieves translated name of the given CellID (used only by some Morrowind localisations, in others cell ID is == cell name) * #{fontcolour=FontColourName}: retrieves the value of the fallback setting "FontColor_color_" from openmw.cfg, * in the format "r g b a", float values in range 0-1. Useful for "Colour" and "TextColour" properties in skins. @@ -484,6 +522,10 @@ namespace MWGui void onClipboardChanged(const std::string& _type, const std::string& _data); void onClipboardRequested(const std::string& _type, std::string& _data); + + void createTextures(); + void createCursors(); + void setMenuTransparency(float value); }; } diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 2e82faa6dc..a46f1778bb 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1,11 +1,11 @@ #include "inputmanagerimp.hpp" -#include -#include #include #include +#include + #include #include #include @@ -14,15 +14,14 @@ #include -#include - -#include "../engine.hpp" +#include +#include #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/statemanager.hpp" -#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/environment.hpp" #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" @@ -30,89 +29,30 @@ #include "../mwworld/esmstore.hpp" #include "../mwmechanics/npcstats.hpp" - -#include "../mwdialogue/dialoguemanagerimp.hpp" - -#include - -using namespace ICS; - -namespace -{ - std::vector utf8ToUnicode(const std::string& utf8) - { - std::vector unicode; - size_t i = 0; - while (i < utf8.size()) - { - unsigned long uni; - size_t todo; - unsigned char ch = utf8[i++]; - if (ch <= 0x7F) - { - uni = ch; - todo = 0; - } - else if (ch <= 0xBF) - { - throw std::logic_error("not a UTF-8 string"); - } - else if (ch <= 0xDF) - { - uni = ch&0x1F; - todo = 1; - } - else if (ch <= 0xEF) - { - uni = ch&0x0F; - todo = 2; - } - else if (ch <= 0xF7) - { - uni = ch&0x07; - todo = 3; - } - else - { - throw std::logic_error("not a UTF-8 string"); - } - for (size_t j = 0; j < todo; ++j) - { - if (i == utf8.size()) - throw std::logic_error("not a UTF-8 string"); - unsigned char ch = utf8[i++]; - if (ch < 0x80 || ch > 0xBF) - throw std::logic_error("not a UTF-8 string"); - uni <<= 6; - uni += ch & 0x3F; - } - if (uni >= 0xD800 && uni <= 0xDFFF) - throw std::logic_error("not a UTF-8 string"); - if (uni > 0x10FFFF) - throw std::logic_error("not a UTF-8 string"); - unicode.push_back(uni); - } - return unicode; - } -} +#include "../mwmechanics/actorutil.hpp" namespace MWInput { - InputManager::InputManager(OEngine::Render::OgreRenderer &ogre, - OMW::Engine& engine, + InputManager::InputManager( + SDL_Window* window, + osg::ref_ptr viewer, + osg::ref_ptr screenCaptureHandler, const std::string& userFile, bool userFileExists, const std::string& controllerBindingsFile, bool grab) - : mJoystickLastUsed(false) - , mOgre(ogre) + : mWindow(window) + , mWindowVisible(true) + , mViewer(viewer) + , mScreenCaptureHandler(screenCaptureHandler) + , mJoystickLastUsed(false) , mPlayer(NULL) - , mEngine(engine) + , mInputManager(NULL) + , mVideoWrapper(NULL) , mUserFile(userFile) , mDragDrop(false) , mGrabCursor (Settings::Manager::getBool("grab cursor", "Input")) , mInvertY (Settings::Manager::getBool("invert y axis", "Input")) , mControlsDisabled(false) , mCameraSensitivity (Settings::Manager::getFloat("camera sensitivity", "Input")) - , mUISensitivity (Settings::Manager::getFloat("ui sensitivity", "Input")) , mCameraYMultiplier (Settings::Manager::getFloat("camera y multiplier", "Input")) , mPreviewPOVDelay(0.f) , mTimeIdle(0.f) @@ -120,28 +60,29 @@ namespace MWInput , mGuiCursorEnabled(true) , mDetectingKeyboard(false) , mOverencumberedMessageDelay(0.f) - , mMouseX(ogre.getWindow()->getWidth ()/2.f) - , mMouseY(ogre.getWindow()->getHeight ()/2.f) + , mGuiCursorX(0) + , mGuiCursorY(0) , mMouseWheel(0) , mUserFileExists(userFileExists) , mAlwaysRunActive(Settings::Manager::getBool("always run", "Input")) , mSneakToggles(Settings::Manager::getBool("toggle sneak", "Input")) , mSneaking(false) , mAttemptJump(false) + , mInvUiScalingFactor(1.f) , mFakeDeviceID(1) { - - Ogre::RenderWindow* window = ogre.getWindow (); - - mInputManager = new SFO::InputWrapper(mOgre.getSDLWindow(), mOgre.getWindow(), grab); + mInputManager = new SDLUtil::InputWrapper(window, viewer, grab); mInputManager->setMouseEventCallback (this); mInputManager->setKeyboardEventCallback (this); mInputManager->setWindowEventCallback(this); mInputManager->setControllerEventCallback(this); + mVideoWrapper = new SDLUtil::VideoWrapper(window, viewer); + mVideoWrapper->setGammaContrast(Settings::Manager::getFloat("gamma", "Video"), + Settings::Manager::getFloat("contrast", "Video")); + std::string file = userFileExists ? userFile : ""; mInputBinder = new ICS::InputControlSystem(file, true, this, NULL, A_Last); - adjustMouseRegion (window->getWidth(), window->getHeight()); loadKeyDefaults(); loadControllerDefaults(); @@ -159,9 +100,9 @@ namespace MWInput mControlSwitch["playerviewswitch"] = true; mControlSwitch["vanitymode"] = true; - /* Joystick Init */ + /* Joystick Init */ - //Load controller mappings + // Load controller mappings #if SDL_VERSION_ATLEAST(2,0,2) if(controllerBindingsFile!="") { @@ -169,21 +110,32 @@ namespace MWInput } #endif - //Open all presently connected sticks - int numSticks = SDL_NumJoysticks(); - for(int i = 0; i < numSticks; i++) - { + // Open all presently connected sticks + int numSticks = SDL_NumJoysticks(); + for(int i = 0; i < numSticks; i++) + { if(SDL_IsGameController(i)) { SDL_ControllerDeviceEvent evt; evt.which = i; controllerAdded(mFakeDeviceID, evt); + std::cout << "Detected game controller: " << SDL_GameControllerNameForIndex(i) << std::endl; } else { - //ICS_LOG(std::string("Unusable controller plugged in: ")+SDL_JoystickNameForIndex(i)); + std::cout << "Detected unusable controller: " << SDL_JoystickNameForIndex(i) << std::endl; } - } + } + + float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); + if (uiScale != 0.f) + mInvUiScalingFactor = 1.f / uiScale; + + int w,h; + SDL_GetWindowSize(window, &w, &h); + + mGuiCursorX = mInvUiScalingFactor * w / 2.f; + mGuiCursorY = mInvUiScalingFactor * h / 2.f; } void InputManager::clear() @@ -200,6 +152,13 @@ namespace MWInput delete mInputBinder; delete mInputManager; + + delete mVideoWrapper; + } + + bool InputManager::isWindowVisible() + { + return mWindowVisible; } void InputManager::setPlayerControlsEnabled(bool enabled) @@ -250,7 +209,7 @@ namespace MWInput if (mControlSwitch["playercontrols"]) { if (action == A_Use) - mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).setAttackingOrSpell(currentValue != 0); + mPlayer->setAttackingOrSpell(currentValue != 0); else if (action == A_Jump) mAttemptJump = (currentValue == 1.0 && previousValue == 0.0); } @@ -343,16 +302,20 @@ namespace MWInput quickLoad(); break; case A_CycleSpellLeft: - MWBase::Environment::get().getWindowManager()->cycleSpell(false); + if (checkAllowedToUseItems()) + MWBase::Environment::get().getWindowManager()->cycleSpell(false); break; case A_CycleSpellRight: - MWBase::Environment::get().getWindowManager()->cycleSpell(true); + if (checkAllowedToUseItems()) + MWBase::Environment::get().getWindowManager()->cycleSpell(true); break; case A_CycleWeaponLeft: - MWBase::Environment::get().getWindowManager()->cycleWeapon(false); + if (checkAllowedToUseItems()) + MWBase::Environment::get().getWindowManager()->cycleWeapon(false); break; case A_CycleWeaponRight: - MWBase::Environment::get().getWindowManager()->cycleWeapon(true); + if (checkAllowedToUseItems()) + MWBase::Environment::get().getWindowManager()->cycleWeapon(true); break; case A_Sneak: if (mSneakToggles) @@ -383,10 +346,22 @@ namespace MWInput //cursor is if( !is_relative && was_relative != is_relative ) { - mInputManager->warpMouse(static_cast(mMouseX), static_cast(mMouseY)); + mInputManager->warpMouse(static_cast(mGuiCursorX/mInvUiScalingFactor), static_cast(mGuiCursorY/mInvUiScalingFactor)); } } + bool InputManager::checkAllowedToUseItems() const + { + MWWorld::Ptr player = MWMechanics::getPlayer(); + if (player.getClass().getNpcStats(player).isWerewolf()) + { + // Cannot use items or spells while in werewolf form + MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); + return false; + } + return true; + } + void InputManager::update(float dt, bool disableControls, bool disableEvents) { mControlsDisabled = disableControls; @@ -395,7 +370,7 @@ namespace MWInput mInputManager->capture(disableEvents); // inject some fake mouse movement to force updating MyGUI's widget states - MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX), int(mMouseY), mMouseWheel); + MyGUI::InputManager::getInstance().injectMouseMove( int(mGuiCursorX), int(mGuiCursorY), mMouseWheel); if (mControlsDisabled) { @@ -417,17 +392,20 @@ namespace MWInput float zAxis = mInputBinder->getChannel(A_LookUpDown)->getValue()*2.0f-1.0f; const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + xAxis *= (1.5f - mInputBinder->getChannel(A_Use)->getValue()); + yAxis *= (1.5f - mInputBinder->getChannel(A_Use)->getValue()); + // We keep track of our own mouse position, so that moving the mouse while in // game mode does not move the position of the GUI cursor - mMouseX += xAxis * dt * 1500.0f; - mMouseY += yAxis * dt * 1500.0f; + mGuiCursorX += xAxis * dt * 1500.0f * mInvUiScalingFactor; + mGuiCursorY += yAxis * dt * 1500.0f * mInvUiScalingFactor; mMouseWheel -= static_cast(zAxis * dt * 1500.0f); - mMouseX = std::max(0.f, std::min(mMouseX, float(viewSize.width))); - mMouseY = std::max(0.f, std::min(mMouseY, float(viewSize.height))); + mGuiCursorX = std::max(0.f, std::min(mGuiCursorX, float(viewSize.width))); + mGuiCursorY = std::max(0.f, std::min(mGuiCursorY, float(viewSize.height))); - MyGUI::InputManager::getInstance().injectMouseMove(static_cast(mMouseX), static_cast(mMouseY), mMouseWheel); - mInputManager->warpMouse(static_cast(mMouseX), static_cast(mMouseY)); + MyGUI::InputManager::getInstance().injectMouseMove(static_cast(mGuiCursorX), static_cast(mGuiCursorY), mMouseWheel); + mInputManager->warpMouse(static_cast(mGuiCursorX/mInvUiScalingFactor), static_cast(mGuiCursorY/mInvUiScalingFactor)); } if (mMouseLookEnabled) { @@ -613,6 +591,8 @@ namespace MWInput void InputManager::processChangedSettings(const Settings::CategorySettingVector& changed) { + bool changeRes = false; + for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) { @@ -622,12 +602,30 @@ namespace MWInput if (it->first == "Input" && it->second == "camera sensitivity") mCameraSensitivity = Settings::Manager::getFloat("camera sensitivity", "Input"); - if (it->first == "Input" && it->second == "ui sensitivity") - mUISensitivity = Settings::Manager::getFloat("ui sensitivity", "Input"); - if (it->first == "Input" && it->second == "grab cursor") mGrabCursor = Settings::Manager::getBool("grab cursor", "Input"); + if (it->first == "Video" && ( + it->second == "resolution x" + || it->second == "resolution y" + || it->second == "fullscreen" + || it->second == "window border")) + changeRes = true; + + if (it->first == "Video" && it->second == "vsync") + mVideoWrapper->setSyncToVBlank(Settings::Manager::getBool("vsync", "Video")); + + if (it->first == "Video" && (it->second == "gamma" || it->second == "contrast")) + mVideoWrapper->setGammaContrast(Settings::Manager::getFloat("gamma", "Video"), + Settings::Manager::getFloat("contrast", "Video")); + } + + if (changeRes) + { + mVideoWrapper->setVideoMode(Settings::Manager::getInt("resolution x", "Video"), + Settings::Manager::getInt("resolution y", "Video"), + Settings::Manager::getBool("fullscreen", "Video"), + Settings::Manager::getBool("window border", "Video")); } } @@ -658,11 +656,6 @@ namespace MWInput mControlSwitch[sw] = value; } - void InputManager::adjustMouseRegion(int width, int height) - { - mInputBinder->adjustMouseRegion(width, height); - } - void InputManager::keyPressed( const SDL_KeyboardEvent &arg ) { // HACK: to make Morrowind's default keybinding for the console work without printing an extra "^" upon closing @@ -689,9 +682,9 @@ namespace MWInput void InputManager::textInput(const SDL_TextInputEvent &arg) { - const char* text = &arg.text[0]; - std::vector unicode = utf8ToUnicode(std::string(text)); - for (std::vector::iterator it = unicode.begin(); it != unicode.end(); ++it) + MyGUI::UString ustring(&arg.text[0]); + MyGUI::UString::utf32string utf32string = ustring.asUTF32(); + for (MyGUI::UString::utf32string::const_iterator it = utf32string.begin(); it != utf32string.end(); ++it) MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::None, *it); } @@ -712,7 +705,7 @@ namespace MWInput if (id == SDL_BUTTON_LEFT || id == SDL_BUTTON_RIGHT) // MyGUI only uses these mouse events { guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); - guiMode = MyGUI::InputManager::getInstance().injectMousePress(static_cast(mMouseX), static_cast(mMouseY), sdlButtonToMyGUI(id)) && guiMode; + guiMode = MyGUI::InputManager::getInstance().injectMousePress(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI(id)) && guiMode; if (MyGUI::InputManager::getInstance ().getMouseFocusWidget () != 0) { MyGUI::Button* b = MyGUI::InputManager::getInstance ().getMouseFocusWidget ()->castType(false); @@ -739,7 +732,7 @@ namespace MWInput mInputBinder->mouseReleased (arg, id); } else { bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); - guiMode = MyGUI::InputManager::getInstance().injectMouseRelease(static_cast(mMouseX), static_cast(mMouseY), sdlButtonToMyGUI(id)) && guiMode; + guiMode = MyGUI::InputManager::getInstance().injectMouseRelease(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI(id)) && guiMode; if(mInputBinder->detectingBindingState()) return; // don't allow same mouseup to bind as initiated bind @@ -748,7 +741,7 @@ namespace MWInput } } - void InputManager::mouseMoved(const SFO::MouseMotionEvent &arg ) + void InputManager::mouseMoved(const SDLUtil::MouseMotionEvent &arg ) { mInputBinder->mouseMoved (arg); @@ -757,19 +750,14 @@ namespace MWInput if (mGuiCursorEnabled) { - const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); - // We keep track of our own mouse position, so that moving the mouse while in // game mode does not move the position of the GUI cursor - mMouseX = static_cast(arg.x); - mMouseY = static_cast(arg.y); - - mMouseX = std::max(0.f, std::min(mMouseX, float(viewSize.width))); - mMouseY = std::max(0.f, std::min(mMouseY, float(viewSize.height))); + mGuiCursorX = static_cast(arg.x) * mInvUiScalingFactor; + mGuiCursorY = static_cast(arg.y) * mInvUiScalingFactor; mMouseWheel = int(arg.z); - MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX), int(mMouseY), mMouseWheel); + MyGUI::InputManager::getInstance().injectMouseMove( int(mGuiCursorX), int(mGuiCursorY), mMouseWheel); } if (mMouseLookEnabled && !mControlsDisabled) @@ -811,7 +799,7 @@ namespace MWInput guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); if(!mInputBinder->detectingBindingState()) { - guiMode = MyGUI::InputManager::getInstance().injectMousePress(static_cast(mMouseX), static_cast(mMouseY), + guiMode = MyGUI::InputManager::getInstance().injectMousePress(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI((arg.button == SDL_CONTROLLER_BUTTON_B) ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT)) && guiMode; if (MyGUI::InputManager::getInstance ().getMouseFocusWidget () != 0) { @@ -843,7 +831,7 @@ namespace MWInput else if(arg.button == SDL_CONTROLLER_BUTTON_A || arg.button == SDL_CONTROLLER_BUTTON_B) { bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); - guiMode = MyGUI::InputManager::getInstance().injectMouseRelease(static_cast(mMouseX), static_cast(mMouseY), sdlButtonToMyGUI((arg.button == SDL_CONTROLLER_BUTTON_B) ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT)) && guiMode; + guiMode = MyGUI::InputManager::getInstance().injectMouseRelease(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI((arg.button == SDL_CONTROLLER_BUTTON_B) ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT)) && guiMode; if(mInputBinder->detectingBindingState()) return; // don't allow same mouseup to bind as initiated bind @@ -853,7 +841,7 @@ namespace MWInput else mInputBinder->buttonReleased(deviceID, arg); - //to escape initial movie + ///to escape initial movie OIS::KeyCode kc = mInputManager->sdl2OISKeyCode(SDLK_ESCAPE); setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::Enum(kc))); } @@ -880,12 +868,15 @@ namespace MWInput void InputManager::windowVisibilityChange(bool visible) { - //TODO: Pause game? + mWindowVisible = visible; } void InputManager::windowResized(int x, int y) { - mOgre.windowResized(x,y); + Settings::Manager::setInt("resolution x", "Video", x); + Settings::Manager::setInt("resolution y", "Video", y); + + MWBase::Environment::get().getWindowManager()->windowResized(x, y); } void InputManager::windowClosed() @@ -927,6 +918,9 @@ namespace MWInput if (!mControlSwitch["playermagic"] || !mControlSwitch["playercontrols"]) return; + if (!checkAllowedToUseItems()) + return; + // Not allowed if no spell selected MWWorld::InventoryStore& inventory = mPlayer->getPlayer().getClass().getInventoryStore(mPlayer->getPlayer()); if (MWBase::Environment::get().getWindowManager()->getSelectedSpell().empty() && @@ -973,7 +967,8 @@ namespace MWInput void InputManager::screenshot() { - mEngine.screenshot(); + mScreenCaptureHandler->setFramesToCapture(1); + mScreenCaptureHandler->captureNextFrame(*mViewer); MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved"); } @@ -1040,13 +1035,8 @@ namespace MWInput { if (!mControlSwitch["playercontrols"]) return; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (player.getClass().getNpcStats(player).isWerewolf()) - { - // Cannot use items or spells while in werewolf form - MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); + if (!checkAllowedToUseItems()) return; - } if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) MWBase::Environment::get().getWindowManager()->activateQuickKey (index); @@ -1057,13 +1047,8 @@ namespace MWInput if (!MWBase::Environment::get().getWindowManager()->isGuiMode () && MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (player.getClass().getNpcStats(player).isWerewolf()) - { - // Cannot use items or spells while in werewolf form - MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); + if (!checkAllowedToUseItems()) return; - } MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_QuickKeysMenu); @@ -1079,7 +1064,7 @@ namespace MWInput void InputManager::activate() { if (mControlSwitch["playercontrols"]) - mEngine.activate(); + mPlayer->activate(); } void InputManager::toggleAutoMove() diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 5588010236..6443916883 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -3,19 +3,17 @@ #include "../mwgui/mode.hpp" +#include + +#include +#include + #include #include +#include #include "../mwbase/inputmanager.hpp" -#include -namespace OEngine -{ - namespace Render - { - class OgreRenderer; - } -} namespace MWWorld { @@ -27,11 +25,6 @@ namespace MWBase class WindowManager; } -namespace OMW -{ - class Engine; -} - namespace ICS { class InputControlSystem; @@ -47,8 +40,19 @@ namespace Files struct ConfigurationManager; } -#include -#include +namespace SDLUtil +{ + class InputWrapper; + class VideoWrapper; +} + +namespace osgViewer +{ + class Viewer; + class ScreenCaptureHandler; +} + +struct SDL_Window; namespace MWInput { @@ -58,21 +62,25 @@ namespace MWInput */ class InputManager : public MWBase::InputManager, - public SFO::KeyListener, - public SFO::MouseListener, - public SFO::WindowListener, - public SFO::ControllerListener, + public SDLUtil::KeyListener, + public SDLUtil::MouseListener, + public SDLUtil::WindowListener, + public SDLUtil::ControllerListener, public ICS::ChannelListener, public ICS::DetectingBindingListener { public: - InputManager(OEngine::Render::OgreRenderer &_ogre, - OMW::Engine& engine, + InputManager( + SDL_Window* window, + osg::ref_ptr viewer, + osg::ref_ptr screenCaptureHandler, const std::string& userFile, bool userFileExists, const std::string& controllerBindingsFile, bool grab); virtual ~InputManager(); + virtual bool isWindowVisible(); + /// Clear all savegame-specific data virtual void clear(); @@ -108,7 +116,7 @@ namespace MWInput virtual void mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ); virtual void mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ); - virtual void mouseMoved( const SFO::MouseMotionEvent &arg ); + virtual void mouseMoved( const SDLUtil::MouseMotionEvent &arg ); virtual void buttonPressed(int deviceID, const SDL_ControllerButtonEvent &arg); virtual void buttonReleased(int deviceID, const SDL_ControllerButtonEvent &arg); @@ -142,15 +150,18 @@ namespace MWInput void clearAllControllerBindings (ICS::Control* control); private: + SDL_Window* mWindow; + bool mWindowVisible; + osg::ref_ptr mViewer; + osg::ref_ptr mScreenCaptureHandler; + bool mJoystickLastUsed; - OEngine::Render::OgreRenderer &mOgre; MWWorld::Player* mPlayer; - OMW::Engine& mEngine; ICS::InputControlSystem* mInputBinder; - - SFO::InputWrapper* mInputManager; + SDLUtil::InputWrapper* mInputManager; + SDLUtil::VideoWrapper* mVideoWrapper; std::string mUserFile; @@ -163,7 +174,6 @@ namespace MWInput bool mControlsDisabled; float mCameraSensitivity; - float mUISensitivity; float mCameraYMultiplier; float mPreviewPOVDelay; float mTimeIdle; @@ -175,8 +185,8 @@ namespace MWInput float mOverencumberedMessageDelay; - float mMouseX; - float mMouseY; + float mGuiCursorX; + float mGuiCursorY; int mMouseWheel; bool mUserFileExists; bool mAlwaysRunActive; @@ -186,8 +196,11 @@ namespace MWInput std::map mControlSwitch; + float mInvUiScalingFactor; + private: - void adjustMouseRegion(int width, int height); + void convertMousePosForMyGUI(int& x, int& y); + MyGUI::MouseButton sdlButtonToMyGUI(Uint8 button); virtual std::string sdlControllerAxisToString(int axis); @@ -200,6 +213,8 @@ namespace MWInput void updateCursorMode(); + bool checkAllowedToUseItems() const; + private: void toggleMainMenu(); void toggleSpell(); diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index a6cc9af8ef..6a247622c5 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -1,6 +1,6 @@ #include "activespells.hpp" -#include +#include #include @@ -132,15 +132,11 @@ namespace MWMechanics return scaledDuration-usedUp; } - bool ActiveSpells::isSpellActive(std::string id) const + bool ActiveSpells::isSpellActive(const std::string& id) const { - Misc::StringUtils::toLower(id); for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter) { - std::string left = iter->first; - Misc::StringUtils::toLower(left); - - if (iter->first == id) + if (Misc::StringUtils::ciEqual(iter->first, id)) return true; } return false; @@ -231,7 +227,7 @@ namespace MWMechanics { for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ) { - if (OEngine::Misc::Rng::roll0to99() < chance) + if (Misc::Rng::roll0to99() < chance) mSpells.erase(it++); else ++it; diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 2a4d75d402..3842ee61c5 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -97,7 +97,7 @@ namespace MWMechanics /// Remove all spells void clear(); - bool isSpellActive (std::string id) const; + bool isSpellActive (const std::string& id) const; ///< case insensitive const MagicEffects& getMagicEffects() const; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index c6df241546..4046010d79 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1,43 +1,39 @@ - #include "actors.hpp" #include +#include -#include -#include - +#include +#include #include +#include #include "../mwworld/esmstore.hpp" - #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwworld/manualref.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/player.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" -#include "../mwrender/animation.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "npcstats.hpp" #include "creaturestats.hpp" #include "movement.hpp" #include "character.hpp" - -#include "../mwbase/environment.hpp" -#include "../mwbase/mechanicsmanager.hpp" - #include "aicombat.hpp" #include "aifollow.hpp" #include "aipursue.hpp" - #include "actor.hpp" #include "summoning.hpp" #include "combat.hpp" +#include "actorutil.hpp" namespace { @@ -60,7 +56,7 @@ void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& a action.execute(actor); MWWorld::ContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); // change draw state only if the item is in player's right hand - if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr() + if (actor == MWMechanics::getPlayer() && rightHand != store.end() && newPtr == *rightHand) { MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); @@ -73,44 +69,6 @@ void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& a } } -bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate) -{ - if (ptr.getClass().hasInventoryStore(ptr)) - { - MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); - MWWorld::ContainerStoreIterator item = - inv.getSlot(slot); - if (item != inv.end()) - { - if (!item->getClass().hasItemHealth(*item)) - return false; - int charge = item->getClass().getItemHealth(*item); - - if (charge == 0) - return false; - - // FIXME: charge should be a float, not int so that damage < 1 per frame can be applied. - // This was also a bug in the original engine. - charge -= - std::min(static_cast(disintegrate), - charge); - item->getCellRef().setCharge(charge); - - if (charge == 0) - { - // Will unequip the broken item and try to find a replacement - if (ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) - inv.autoEquip(ptr); - else - inv.unequipItem(*item, ptr); - } - - return true; - } - } - return false; -} - class CheckActorCommanded : public MWMechanics::EffectSourceVisitor { MWWorld::Ptr mActor; @@ -124,7 +82,7 @@ public: const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); if ( ((key.mId == ESM::MagicEffect::CommandHumanoid && mActor.getClass().isNpc()) || (key.mId == ESM::MagicEffect::CommandCreature && mActor.getTypeName() == typeid(ESM::Creature).name())) && casterActorId == player.getClass().getCreatureStats(player).getActorId() @@ -191,14 +149,20 @@ namespace MWMechanics { MWWorld::Ptr mCreature; MWWorld::Ptr mActor; + bool mTrapped; public: SoulTrap(MWWorld::Ptr trappedCreature) - : mCreature(trappedCreature) {} + : mCreature(trappedCreature) + , mTrapped(false) + { + } virtual void visit (MWMechanics::EffectKey key, const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1) { + if (mTrapped) + return; if (key.mId != ESM::MagicEffect::Soultrap) return; if (magnitude <= 0) @@ -245,17 +209,20 @@ namespace MWMechanics gem->getContainerStore()->unstack(*gem, caster); gem->getCellRef().setSoul(mCreature.getCellRef().getRefId()); - if (caster == MWBase::Environment::get().getWorld()->getPlayerPtr()) + mTrapped = true; + + if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sSoultrapSuccess}"); const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() .search("VFX_Soul_Trap"); if (fx) MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, - "", Ogre::Vector3(mCreature.getRefData().getPosition().pos)); + "", mCreature.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getSoundManager()->playSound3D(mCreature, "conjuration hit", 1.f, 1.f, - MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); + MWBase::Environment::get().getSoundManager()->playSound3D( + mCreature.getRefData().getPosition().asVec3(), "conjuration hit", 1.f, 1.f + ); } }; @@ -285,7 +252,7 @@ namespace MWMechanics const ESM::Position& actor1Pos = actor.getRefData().getPosition(); const ESM::Position& actor2Pos = targetActor.getRefData().getPosition(); - float sqrDist = Ogre::Vector3(actor1Pos.pos).squaredDistance(Ogre::Vector3(actor2Pos.pos)); + float sqrDist = (actor1Pos.asVec3() - actor2Pos.asVec3()).length2(); if (sqrDist > maxDistance*maxDistance) return; @@ -293,12 +260,17 @@ namespace MWMechanics if (targetActor.getClass().getCreatureStats(targetActor).isDead()) return; + if (!actor.getRefData().getBaseNode()) + return; + // stop tracking when target is behind the actor - Ogre::Vector3 actorDirection (actor.getRefData().getBaseNode()->getOrientation().yAxis()); - Ogre::Vector3 targetDirection (Ogre::Vector3(actor2Pos.pos) - Ogre::Vector3(actor1Pos.pos)); - actorDirection.z = 0; - targetDirection.z = 0; - if (actorDirection.angleBetween(targetDirection) < Ogre::Degree(90) + osg::Vec3f actorDirection = actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); + osg::Vec3f targetDirection (actor2Pos.asVec3() - actor1Pos.asVec3()); + actorDirection.z() = 0; + targetDirection.z() = 0; + actorDirection.normalize(); + targetDirection.normalize(); + if (std::acos(actorDirection * targetDirection) < osg::DegreesToRadians(90.f) && sqrDist <= sqrHeadTrackDistance && MWBase::Environment::get().getWorld()->getLOS(actor, targetActor) // check LOS and awareness last as it's the most expensive function && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(targetActor, actor)) @@ -318,7 +290,7 @@ namespace MWMechanics const ESM::Position& actor1Pos = actor1.getRefData().getPosition(); const ESM::Position& actor2Pos = actor2.getRefData().getPosition(); - float sqrDist = Ogre::Vector3(actor1Pos.pos).squaredDistance(Ogre::Vector3(actor2Pos.pos)); + float sqrDist = (actor1Pos.asVec3() - actor2Pos.asVec3()).length2(); if (sqrDist > 7168*7168) return; @@ -330,12 +302,16 @@ namespace MWMechanics return; } + // no combat for totally static creatures (they have no movement or attack animations anyway) + if (!actor1.getClass().isMobile(actor1)) + return; + bool aggressive; if (againstPlayer) { // followers with high fight should not engage in combat with the player (e.g. bm_bear_black_summon) - const std::list& followers = getActorsFollowing(actor2); + const std::list& followers = getActorsSidingWith(actor2); if (std::find(followers.begin(), followers.end(), actor1) != followers.end()) return; @@ -354,7 +330,7 @@ namespace MWMechanics } // start combat if target actor is in combat with one of our followers - const std::list& followers = getActorsFollowing(actor1); + const std::list& followers = getActorsSidingWith(actor1); const CreatureStats& creatureStats2 = actor2.getClass().getCreatureStats(actor2); for (std::list::const_iterator it = followers.begin(); it != followers.end(); ++it) { @@ -368,20 +344,21 @@ namespace MWMechanics // start combat if target actor is in combat with someone we are following for (std::list::const_iterator it = creatureStats.getAiSequence().begin(); it != creatureStats.getAiSequence().end(); ++it) { - if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow) - { - MWWorld::Ptr followTarget = static_cast(*it)->getTarget(); - if (followTarget.isEmpty()) - continue; + if (!(*it)->sideWithTarget()) + continue; - if (creatureStats.getAiSequence().isInCombat(followTarget)) - continue; + MWWorld::Ptr followTarget = (*it)->getTarget(); - // need to check both ways since player doesn't use AI packages - if (creatureStats2.getAiSequence().isInCombat(followTarget) - || followTarget.getClass().getCreatureStats(followTarget).getAiSequence().isInCombat(actor2)) - aggressive = true; - } + if (followTarget.isEmpty()) + continue; + + if (creatureStats.getAiSequence().isInCombat(followTarget)) + continue; + + // need to check both ways since player doesn't use AI packages + if (creatureStats2.getAiSequence().isInCombat(followTarget) + || followTarget.getClass().getCreatureStats(followTarget).getAiSequence().isInCombat(actor2)) + aggressive = true; } if(aggressive) @@ -434,7 +411,7 @@ namespace MWMechanics int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified(); float base = 1.f; - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == getPlayer()) base = MWBase::Environment::get().getWorld()->getStore().get().find("fPCbaseMagickaMult")->getFloat(); else base = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCbaseMagickaMult")->getFloat(); @@ -517,8 +494,26 @@ namespace MWMechanics bool wasDead = creatureStats.isDead(); - // FIXME: effect ticks should go into separate functions so they can be used with either - // magnitude (instant effect) or magnitude*duration + if (duration > 0) + { + for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) + { + // tickable effects (i.e. effects having a lasting impact after expiry) + effectTick(creatureStats, ptr, it->first, it->second.getMagnitude() * duration); + + // instant effects are already applied on spell impact in spellcasting.cpp, but may also come from permanent abilities + if (it->second.getMagnitude() > 0) + { + CastSpell cast(ptr, ptr); + if (cast.applyInstantEffect(ptr, ptr, it->first, it->second.getMagnitude())) + { + creatureStats.getActiveSpells().purgeEffect(it->first.mId); + if (ptr.getClass().hasInventoryStore(ptr)) + ptr.getClass().getInventoryStore(ptr).purgeEffect(it->first.mId); + } + } + } + } // attributes for(int i = 0;i < ESM::Attribute::Length;++i) @@ -528,9 +523,6 @@ namespace MWMechanics effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).getMagnitude() - effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).getMagnitude())); - stat.damage(effects.get(EffectKey(ESM::MagicEffect::DamageAttribute, i)).getMagnitude() * duration); - stat.restore(effects.get(EffectKey(ESM::MagicEffect::RestoreAttribute, i)).getMagnitude() * duration); - creatureStats.setAttribute(i, stat); } @@ -544,7 +536,7 @@ namespace MWMechanics { spells.worsenCorprus(it->first); - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); } } @@ -560,12 +552,6 @@ namespace MWMechanics // Fatigue can be decreased below zero meaning the actor will be knocked out i == 2); - - float currentDiff = creatureStats.getMagicEffects().get(ESM::MagicEffect::RestoreHealth+i).getMagnitude() - - creatureStats.getMagicEffects().get(ESM::MagicEffect::DamageHealth+i).getMagnitude() - - creatureStats.getMagicEffects().get(ESM::MagicEffect::AbsorbHealth+i).getMagnitude(); - stat.setCurrent(stat.getCurrent() + currentDiff * duration, i == 2); - creatureStats.setDynamic(i, stat); } @@ -577,107 +563,28 @@ namespace MWMechanics if (!creature || ptr.get()->mBase->mData.mType == ESM::Creature::Creatures) { Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Fight); - stat.setModifier(static_cast(creatureStats.getMagicEffects().get(ESM::MagicEffect::FrenzyHumanoid + creature).getMagnitude() - - creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid+creature).getMagnitude())); + stat.setModifier(static_cast(effects.get(ESM::MagicEffect::FrenzyHumanoid + creature).getMagnitude() + - effects.get(ESM::MagicEffect::CalmHumanoid+creature).getMagnitude())); creatureStats.setAiSetting(CreatureStats::AI_Fight, stat); stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); - stat.setModifier(static_cast(creatureStats.getMagicEffects().get(ESM::MagicEffect::DemoralizeHumanoid + creature).getMagnitude() - - creatureStats.getMagicEffects().get(ESM::MagicEffect::RallyHumanoid+creature).getMagnitude())); + stat.setModifier(static_cast(effects.get(ESM::MagicEffect::DemoralizeHumanoid + creature).getMagnitude() + - effects.get(ESM::MagicEffect::RallyHumanoid+creature).getMagnitude())); creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); } if (creature && ptr.get()->mBase->mData.mType == ESM::Creature::Undead) { Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); - stat.setModifier(static_cast(creatureStats.getMagicEffects().get(ESM::MagicEffect::TurnUndead).getMagnitude())); + stat.setModifier(static_cast(effects.get(ESM::MagicEffect::TurnUndead).getMagnitude())); creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); } - // Apply disintegration (reduces item health) - float disintegrateWeapon = effects.get(ESM::MagicEffect::DisintegrateWeapon).getMagnitude(); - if (disintegrateWeapon > 0) - disintegrateSlot(ptr, MWWorld::InventoryStore::Slot_CarriedRight, disintegrateWeapon*duration); - float disintegrateArmor = effects.get(ESM::MagicEffect::DisintegrateArmor).getMagnitude(); - if (disintegrateArmor > 0) - { - // According to UESP - int priorities[] = { - MWWorld::InventoryStore::Slot_CarriedLeft, - MWWorld::InventoryStore::Slot_Cuirass, - MWWorld::InventoryStore::Slot_LeftPauldron, - MWWorld::InventoryStore::Slot_RightPauldron, - MWWorld::InventoryStore::Slot_LeftGauntlet, - MWWorld::InventoryStore::Slot_RightGauntlet, - MWWorld::InventoryStore::Slot_Helmet, - MWWorld::InventoryStore::Slot_Greaves, - MWWorld::InventoryStore::Slot_Boots - }; - - for (unsigned int i=0; i 0.0f - || creatureStats.getMagicEffects().get(ESM::MagicEffect::AbsorbHealth).getMagnitude() > 0.0f) - receivedMagicDamage = true; - - // Apply damage ticks - int damageEffects[] = { - ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison, - ESM::MagicEffect::SunDamage - }; - - DynamicStat health = creatureStats.getHealth(); - for (unsigned int i=0; iisExterior()) - continue; - float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour(); - float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); - float damageScale = 1.f - timeDiff / 7.f; - // When cloudy, the sun damage effect is halved - static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get().find( - "fMagicSunBlockedMult")->getFloat(); - - int weather = MWBase::Environment::get().getWorld()->getCurrentWeather(); - if (weather > 1) - damageScale *= fMagicSunBlockedMult; - health.setCurrent(health.getCurrent() - magnitude * duration * damageScale); - - if (magnitude * damageScale > 0.0f) - receivedMagicDamage = true; - } - else - { - health.setCurrent(health.getCurrent() - magnitude * duration); - - if (magnitude > 0.0f) - receivedMagicDamage = true; - } - } - - if (receivedMagicDamage && ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) - MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); - - creatureStats.setHealth(health); - if (!wasDead && creatureStats.isDead()) { // The actor was killed by a magic effect. Figure out if the player was responsible for it. const ActiveSpells& spells = creatureStats.getActiveSpells(); bool killedByPlayer = false; - bool murderedByPlayer = false; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it) { const ActiveSpells::ActiveSpellParams& spell = it->second; @@ -686,40 +593,36 @@ namespace MWMechanics { int effectId = effectIt->mEffectId; bool isDamageEffect = false; + + int damageEffects[] = { + ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison, + ESM::MagicEffect::SunDamage, ESM::MagicEffect::DamageHealth, ESM::MagicEffect::AbsorbHealth + }; + for (unsigned int i=0; isearchPtrViaActorId(spell.mCasterActorId); if (isDamageEffect && caster == player) - { killedByPlayer = true; - // Simple check for who attacked first: if the player attacked first, a crimeId should be set - // Doesn't handle possible edge case where no one reported the assault, but in such a case, - // for bystanders it is not possible to tell who attacked first, anyway. - if (ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).getCrimeId() != -1 - && ptr != player) - murderedByPlayer = true; - } } } - if (murderedByPlayer) - MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder); - if (killedByPlayer && player.getClass().getNpcStats(player).isWerewolf()) - player.getClass().getNpcStats(player).addWerewolfKill(); + if (killedByPlayer) + { + MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, player); + if (player.getClass().getNpcStats(player).isWerewolf()) + player.getClass().getNpcStats(player).addWerewolfKill(); + } } // TODO: dirty flag for magic effects to avoid some unnecessary work below? // any value of calm > 0 will stop the actor from fighting - if ((creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() > 0 && ptr.getClass().isNpc()) - || (creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmCreature).getMagnitude() > 0 && !ptr.getClass().isNpc())) + if ((effects.get(ESM::MagicEffect::CalmHumanoid).getMagnitude() > 0 && ptr.getClass().isNpc()) + || (effects.get(ESM::MagicEffect::CalmCreature).getMagnitude() > 0 && !ptr.getClass().isNpc())) { for (std::list::const_iterator it = creatureStats.getAiSequence().begin(); it != creatureStats.getAiSequence().end(); ) { @@ -752,7 +655,7 @@ namespace MWMechanics for (std::map::iterator it = boundItemsMap.begin(); it != boundItemsMap.end(); ++it) { bool found = creatureStats.mBoundItems.find(it->first) != creatureStats.mBoundItems.end(); - float magnitude = creatureStats.getMagicEffects().get(it->first).getMagnitude(); + float magnitude = effects.get(it->first).getMagnitude(); if (found != (magnitude > 0)) { std::string itemGmst = it->second; @@ -796,9 +699,6 @@ namespace MWMechanics skill.setModifier(static_cast(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).getMagnitude() - effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).getMagnitude() - effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).getMagnitude())); - - skill.damage(effects.get(EffectKey(ESM::MagicEffect::DamageSkill, i)).getMagnitude() * duration); - skill.restore(effects.get(EffectKey(ESM::MagicEffect::RestoreSkill, i)).getMagnitude() * duration); } } @@ -811,7 +711,7 @@ namespace MWMechanics NpcStats &stats = ptr.getClass().getNpcStats(ptr); MWBase::World *world = MWBase::Environment::get().getWorld(); - bool knockedOutUnderwater = (ctrl->isKnockedOut() && world->isUnderwater(ptr.getCell(), Ogre::Vector3(ptr.getRefData().getPosition().pos))); + bool knockedOutUnderwater = (ctrl->isKnockedOut() && world->isUnderwater(ptr.getCell(), osg::Vec3f(ptr.getRefData().getPosition().asVec3()))); if((world->isSubmerged(ptr) || knockedOutUnderwater) && stats.getMagicEffects().get(ESM::MagicEffect::WaterBreathing).getMagnitude() == 0) { @@ -829,7 +729,9 @@ namespace MWMechanics { // If drowning, apply 3 points of damage per second static const float fSuffocationDamage = world->getStore().get().find("fSuffocationDamage")->getFloat(); - ptr.getClass().setActorHealth(ptr, stats.getHealth().getCurrent() - fSuffocationDamage*duration); + DynamicStat health = stats.getHealth(); + health.setCurrent(health.getCurrent() - fSuffocationDamage*duration); + stats.setHealth(health); // Play a drowning sound MWBase::SoundManager *sndmgr = MWBase::Environment::get().getSoundManager(); @@ -849,7 +751,7 @@ namespace MWMechanics void Actors::updateEquippedLight (const MWWorld::Ptr& ptr, float duration) { - bool isPlayer = (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()); + bool isPlayer = (ptr == getPlayer()); MWWorld::InventoryStore &inventoryStore = ptr.getClass().getInventoryStore(ptr); MWWorld::ContainerStoreIterator heldIter = @@ -946,7 +848,7 @@ namespace MWMechanics void Actors::updateCrimePersuit(const MWWorld::Ptr& ptr, float duration) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); if (ptr != player && ptr.getClass().isNpc()) { // get stats of witness @@ -959,7 +861,7 @@ namespace MWMechanics if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.getAiSequence().isInCombat()) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - int cutoff = esmStore.get().find("iCrimeThreshold")->getInt(); + static const int cutoff = esmStore.get().find("iCrimeThreshold")->getInt(); // Force dialogue on sight if bounty is greater than the cutoff // In vanilla morrowind, the greeting dialogue is scripted to either arrest the player (< 5000 bounty) or attack (>= 5000 bounty) if ( player.getClass().getNpcStats(player).getBounty() >= cutoff @@ -967,7 +869,7 @@ namespace MWMechanics && MWBase::Environment::get().getWorld()->getLOS(ptr, player) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr)) { - static int iCrimeThresholdMultiplier = esmStore.get().find("iCrimeThresholdMultiplier")->getInt(); + static const int iCrimeThresholdMultiplier = esmStore.get().find("iCrimeThresholdMultiplier")->getInt(); if (player.getClass().getNpcStats(player).getBounty() >= cutoff * iCrimeThresholdMultiplier) MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player); else @@ -1012,6 +914,8 @@ namespace MWMechanics removeActor(ptr); MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); + if (!anim) + return; mActors.insert(std::make_pair(ptr, new Actor(ptr, anim))); if (updateImmediately) mActors[ptr]->getCharacterController()->update(0); @@ -1045,7 +949,7 @@ namespace MWMechanics PtrActorMap::iterator iter = mActors.begin(); while(iter != mActors.end()) { - if(iter->first.getCell()==cellStore && iter->first != ignore) + if((iter->first.isInCell() && iter->first.getCell()==cellStore) && iter->first != ignore) { delete iter->second; mActors.erase(iter++); @@ -1066,7 +970,7 @@ namespace MWMechanics if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0; if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); int hostilesCount = 0; // need to know this to play Battle music @@ -1081,12 +985,18 @@ namespace MWMechanics // AI and magic effects update for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { + bool inProcessingRange = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() + <= sqrProcessingDistance; + + iter->second->getCharacterController()->setActive(inProcessingRange); + + if (iter->first == player) + iter->second->getCharacterController()->setAttackingOrSpell(MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell()); + if (!iter->first.getClass().getCreatureStats(iter->first).isDead()) { updateActor(iter->first, duration); - if (MWBase::Environment::get().getMechanicsManager()->isAIActive() && - Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(iter->first.getRefData().getPosition().pos)) - <= sqrProcessingDistance) + if (MWBase::Environment::get().getMechanicsManager()->isAIActive() && inProcessingRange) { if (timerUpdateAITargets == 0) { @@ -1121,7 +1031,7 @@ namespace MWMechanics { CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); if (isConscious(iter->first)) - stats.getAiSequence().execute(iter->first,iter->second->getAiState(), duration); + stats.getAiSequence().execute(iter->first, *iter->second->getCharacterController(), iter->second->getAiState(), duration); if (stats.getAiSequence().isInCombat() && !stats.isDead()) hostilesCount++; } @@ -1148,17 +1058,16 @@ namespace MWMechanics for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { if (iter->first != player && - Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(iter->first.getRefData().getPosition().pos)) + (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() > sqrProcessingDistance) continue; - if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects().get( - ESM::MagicEffect::Paralyze).getMagnitude() > 0) + if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) iter->second->getCharacterController()->skipAnim(); // Handle player last, in case a cell transition occurs by casting a teleportation spell // (would invalidate the iterator) - if (iter->first == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (iter->first == getPlayer()) { playerCharacter = iter->second->getCharacterController(); continue; @@ -1191,24 +1100,24 @@ namespace MWMechanics killDeadActors(); // check if we still have any player enemies to switch music - static bool isBattleMusic = false; + static int currentMusic = 0; - if (isBattleMusic && hostilesCount == 0 && !(player.getClass().getCreatureStats(player).isDead() && + if (currentMusic != 1 && hostilesCount == 0 && !(player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getSoundManager()->isMusicPlaying())) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); - isBattleMusic = false; + currentMusic = 1; } - else if (!isBattleMusic && hostilesCount > 0) + else if (currentMusic != 2 && hostilesCount > 0) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); - isBattleMusic = true; + currentMusic = 2; } static float sneakTimer = 0.f; // times update of sneak icon // if player is in sneak state see if anyone detects him - if (player.getClass().getCreatureStats(player).getMovementFlag(MWMechanics::CreatureStats::Flag_Sneak)) + if (playerCharacter && playerCharacter->isSneaking()) { static float sneakSkillTimer = 0.f; // times sneak skill progress from "avoid notice" @@ -1233,7 +1142,7 @@ namespace MWMechanics continue; // is the player in range and can they be detected - if (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(player.getRefData().getPosition().pos)) <= radius*radius + if ((iter->first.getRefData().getPosition().asVec3() - player.getRefData().getPosition().asVec3()).length2() <= radius*radius && MWBase::Environment::get().getWorld()->getLOS(player, iter->first)) { if (MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, iter->first)) @@ -1290,6 +1199,12 @@ namespace MWMechanics if (iter->second->getCharacterController()->kill()) { + // TODO: It's not known whether the soundgen tags scream, roar, and moan are reliable + // for NPCs since some of the npc death animation files are missing them. + + // Play dying words + MWBase::Environment::get().getDialogueManager()->say(iter->first, "hit"); + iter->first.getClass().getCreatureStats(iter->first).notifyDied(); ++mDeathCount[Misc::StringUtils::lowerCase(iter->first.getCellRef().getRefId())]; @@ -1361,11 +1276,18 @@ namespace MWMechanics iter->second->getCharacterController()->forceStateUpdate(); } - void Actors::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) + bool Actors::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) { PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) - iter->second->getCharacterController()->playGroup(groupName, mode, number); + { + return iter->second->getCharacterController()->playGroup(groupName, mode, number); + } + else + { + std::cerr<< "Error in Actors::playAnimationGroup: Unable to find " << ptr.getCellRef().getRefId() << std::endl; + return false; + } } void Actors::skipAnimation(const MWWorld::Ptr& ptr) { @@ -1382,15 +1304,37 @@ namespace MWMechanics return false; } - void Actors::getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector& out) + void Actors::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) { for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) { - if (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(position) <= radius*radius) + if ((iter->first.getRefData().getPosition().asVec3() - position).length2() <= radius*radius) out.push_back(iter->first); } } + std::list Actors::getActorsSidingWith(const MWWorld::Ptr& actor) + { + std::list list; + for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + { + const MWWorld::Class &cls = iter->first.getClass(); + CreatureStats &stats = cls.getCreatureStats(iter->first); + if (stats.isDead()) + continue; + + // An actor counts as following if AiFollow or AiEscort is the current AiPackage, or there are only Combat packages before the AiFollow/AiEscort package + for (std::list::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) + { + if ((*it)->sideWithTarget() && (*it)->getTarget() == actor) + list.push_back(iter->first); + else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat) + break; + } + } + return list; + } + std::list Actors::getActorsFollowing(const MWWorld::Ptr& actor) { std::list list; @@ -1404,16 +1348,8 @@ namespace MWMechanics // An actor counts as following if AiFollow is the current AiPackage, or there are only Combat packages before the AiFollow package for (std::list::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) { - if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow) - { - MWWorld::Ptr followTarget = static_cast(*it)->getTarget(); - if (followTarget.isEmpty()) - continue; - if (followTarget == actor) - list.push_back(iter->first); - else - break; - } + if ((*it)->followTargetThroughDoors() && (*it)->getTarget() == actor) + list.push_back(iter->first); else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat) break; } @@ -1441,8 +1377,7 @@ namespace MWMechanics continue; if (followTarget == actor) list.push_back(static_cast(*it)->getFollowIndex()); - else - break; + break; } else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat) break; @@ -1454,7 +1389,7 @@ namespace MWMechanics std::list Actors::getActorsFighting(const MWWorld::Ptr& actor) { std::list list; std::vector neighbors; - Ogre::Vector3 position = Ogre::Vector3(actor.getRefData().getPosition().pos); + osg::Vec3f position (actor.getRefData().getPosition().asVec3()); getObjectsInRange(position, MWBase::Environment::get().getWorld()->getStore().get().find("fAlarmRadius")->getFloat(), neighbors); //only care about those within the alarm disance @@ -1532,9 +1467,9 @@ namespace MWMechanics for (PtrActorMap::iterator it = map.begin(); it != map.end(); ++it) { MWWorld::Ptr ptr = it->first; - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr() + if (ptr == getPlayer() || !isConscious(ptr) - || ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + || ptr.getClass().getCreatureStats(ptr).isParalyzed()) continue; MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); seq.fastForward(ptr, it->second->getAiState()); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 70f1b47d91..0bdf611d2a 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -10,11 +10,6 @@ #include "movement.hpp" #include "../mwbase/world.hpp" -namespace Ogre -{ - class Vector3; -} - namespace MWWorld { class Ptr; @@ -110,14 +105,15 @@ namespace MWMechanics void forceStateUpdate(const MWWorld::Ptr &ptr); - void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); + bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); void skipAnimation(const MWWorld::Ptr& ptr); bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName); - void getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector& out); + void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out); - ///Returns the list of actors which are following the given actor - /**ie AiFollow is active and the target is the actor **/ + ///Returns the list of actors which are siding with the given actor in fights + /**ie AiFollow or AiEscort is active and the target is the actor **/ + std::list getActorsSidingWith(const MWWorld::Ptr& actor); std::list getActorsFollowing(const MWWorld::Ptr& actor); /// Get the list of AiFollow::mFollowIndex for all actors following this target diff --git a/apps/openmw/mwmechanics/actorutil.cpp b/apps/openmw/mwmechanics/actorutil.cpp new file mode 100644 index 0000000000..537f271974 --- /dev/null +++ b/apps/openmw/mwmechanics/actorutil.cpp @@ -0,0 +1,19 @@ +#include "actorutil.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwworld/player.hpp" + +namespace MWMechanics +{ + MWWorld::Ptr getPlayer() + { + return MWBase::Environment::get().getWorld()->getPlayerPtr(); + } + + bool isPlayerInCombat() + { + return MWBase::Environment::get().getWorld()->getPlayer().isInCombat(); + } +} diff --git a/apps/openmw/mwmechanics/actorutil.hpp b/apps/openmw/mwmechanics/actorutil.hpp new file mode 100644 index 0000000000..510e41db35 --- /dev/null +++ b/apps/openmw/mwmechanics/actorutil.hpp @@ -0,0 +1,12 @@ +#ifndef OPENMW_MWMECHANICS_ACTORUTIL_H +#define OPENMW_MWMECHANICS_ACTORUTIL_H + +#include "../mwworld/ptr.hpp" + +namespace MWMechanics +{ + MWWorld::Ptr getPlayer(); + bool isPlayerInCombat(); +} + +#endif diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index aa937022d2..348ddd692a 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -8,7 +8,6 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/cellstore.hpp" #include "steering.hpp" #include "movement.hpp" @@ -21,7 +20,7 @@ MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const { return new AiActivate(*this); } -bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor, AiState& state, float duration) +bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { ESM::Position pos = actor.getRefData().getPosition(); //position of the actor const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index e25afe2853..2ca985be9a 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -28,7 +28,7 @@ namespace MWMechanics AiActivate(const ESM::AiSequence::AiActivate* activate); virtual AiActivate *clone() const; - virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); + virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); virtual int getTypeId() const; virtual void writeState(ESM::AiSequence::AiSequence& sequence) const; diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index fccace55c2..100b666e89 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -1,24 +1,23 @@ #include "aiavoiddoor.hpp" -#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/cellstore.hpp" #include "creaturestats.hpp" #include "movement.hpp" -#include "mechanicsmanagerimp.hpp" +#include "actorutil.hpp" -#include #include "steering.hpp" -MWMechanics::AiAvoidDoor::AiAvoidDoor(const MWWorld::Ptr& doorPtr) +MWMechanics::AiAvoidDoor::AiAvoidDoor(const MWWorld::ConstPtr& doorPtr) : AiPackage(), mDuration(1), mDoorPtr(doorPtr), mAdjAngle(0) { } -bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, AiState& state, float duration) +bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { ESM::Position pos = actor.getRefData().getPosition(); @@ -55,7 +54,7 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, AiState& stat actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); // Turn away from the door and move when turn completed - if (zTurn(actor, Ogre::Radian(std::atan2(x,y) + mAdjAngle), Ogre::Degree(5))) + if (zTurn(actor, std::atan2(x,y) + mAdjAngle, osg::DegreesToRadians(5.f))) actor.getClass().getMovementSettings(actor).mPosition[1] = 1; else actor.getClass().getMovementSettings(actor).mPosition[1] = 0; @@ -63,9 +62,9 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, AiState& stat // Make all nearby actors also avoid the door std::vector actors; - MWBase::Environment::get().getMechanicsManager()->getActorsInRange(Ogre::Vector3(pos.pos[0],pos.pos[1],pos.pos[2]),100,actors); + MWBase::Environment::get().getMechanicsManager()->getActorsInRange(pos.asVec3(),100,actors); for(std::vector::iterator it = actors.begin(); it != actors.end(); ++it) { - if(*it != MWBase::Environment::get().getWorld()->getPlayerPtr()) { //Not the player + if(*it != getPlayer()) { //Not the player MWMechanics::AiSequence& seq = it->getClass().getCreatureStats(*it).getAiSequence(); if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) { //Only add it once seq.stack(MWMechanics::AiAvoidDoor(mDoorPtr),*it); diff --git a/apps/openmw/mwmechanics/aiavoiddoor.hpp b/apps/openmw/mwmechanics/aiavoiddoor.hpp index 7590c8fcba..9d63c63e08 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.hpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.hpp @@ -16,11 +16,11 @@ namespace MWMechanics { public: /// Avoid door until the door is fully open - AiAvoidDoor(const MWWorld::Ptr& doorPtr); + AiAvoidDoor(const MWWorld::ConstPtr& doorPtr); virtual AiAvoidDoor *clone() const; - virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); + virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); virtual int getTypeId() const; @@ -28,7 +28,7 @@ namespace MWMechanics private: float mDuration; - MWWorld::Ptr mDoorPtr; + MWWorld::ConstPtr mDoorPtr; ESM::Position mLastPos; float mAdjAngle; }; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index fb47c11096..6251cff8df 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -1,28 +1,21 @@ #include "aicombat.hpp" -#include - -#include +#include #include #include "../mwworld/class.hpp" -#include "../mwworld/timestamp.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/cellstore.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwrender/animation.hpp" - #include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" -#include "character.hpp" // fixme: for getActiveWeapon +#include "character.hpp" #include "aicombataction.hpp" #include "combat.hpp" @@ -33,42 +26,35 @@ namespace //chooses an attack depending on probability to avoid uniformity ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); - void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations)[2]); - - Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos, + osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength); } namespace MWMechanics { - static const float DOOR_CHECK_INTERVAL = 1.5f; // same as AiWander - // NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp - /// \brief This class holds the variables AiCombat needs which are deleted if the package becomes inactive. struct AiCombatStorage : AiTemporaryBase { - float mTimerAttack; + float mAttackCooldown; float mTimerReact; float mTimerCombatMove; bool mReadyToAttack; bool mAttack; bool mFollowTarget; bool mCombatMove; - Ogre::Vector3 mLastTargetPos; + osg::Vec3f mLastTargetPos; const MWWorld::CellStore* mCell; boost::shared_ptr mCurrentAction; float mActionCooldown; float mStrength; - float mMinMaxAttackDuration[3][2]; - bool mMinMaxAttackDurationInitialised; bool mForceNoShortcut; ESM::Position mShortcutFailPos; - Ogre::Vector3 mLastActorPos; + osg::Vec3f mLastActorPos; MWMechanics::Movement mMovement; AiCombatStorage(): - mTimerAttack(0), + mAttackCooldown(0), mTimerReact(0), mTimerCombatMove(0), mReadyToAttack(false), @@ -80,10 +66,17 @@ namespace MWMechanics mCurrentAction(), mActionCooldown(0), mStrength(), - mMinMaxAttackDurationInitialised(false), mForceNoShortcut(false), mLastActorPos(0,0,0), mMovement(){} + + void startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack); + void updateCombatMove(float duration); + void stopCombatMove(); + void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, + const ESM::Weapon* weapon, bool distantCombat); + void updateAttack(CharacterController& characterController); + void stopAttack(); }; AiCombat::AiCombat(const MWWorld::Ptr& actor) : @@ -146,15 +139,13 @@ namespace MWMechanics * Use the Observer Pattern to co-ordinate attacks, provide intelligence on * whether the target was hit, etc. */ - bool AiCombat::execute (const MWWorld::Ptr& actor, AiState& state, float duration) + bool AiCombat::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { // get or create temporary storage AiCombatStorage& storage = state.get(); - - //General description - if(actor.getClass().getCreatureStats(actor).isDead()) + if (actor.getClass().getCreatureStats(actor).isDead()) return true; MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); @@ -166,98 +157,36 @@ namespace MWMechanics || target.getClass().getCreatureStats(target).isDead()) return true; - const MWWorld::Class& actorClass = actor.getClass(); - MWBase::World* world = MWBase::Environment::get().getWorld(); - - //Update every frame - bool& combatMove = storage.mCombatMove; - float& timerCombatMove = storage.mTimerCombatMove; - MWMechanics::Movement& movement = storage.mMovement; - if(combatMove) - { - timerCombatMove -= duration; - if( timerCombatMove <= 0) - { - timerCombatMove = 0; - movement.mPosition[1] = movement.mPosition[0] = 0; - combatMove = false; - } - } - - actorClass.getMovementSettings(actor) = movement; - actorClass.getMovementSettings(actor).mRotation[0] = 0; - actorClass.getMovementSettings(actor).mRotation[2] = 0; - - if(movement.mRotation[2] != 0) - { - if(zTurn(actor, Ogre::Degree(movement.mRotation[2]))) movement.mRotation[2] = 0; - } - - if(movement.mRotation[0] != 0) - { - if(smoothTurn(actor, Ogre::Degree(movement.mRotation[0]), 0)) movement.mRotation[0] = 0; - } - - //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f - float attacksPeriod = 1.0f; - - ESM::Weapon::AttackType attackType; - - - - - bool& attack = storage.mAttack; - bool& readyToAttack = storage.mReadyToAttack; - float& timerAttack = storage.mTimerAttack; - - bool& minMaxAttackDurationInitialised = storage.mMinMaxAttackDurationInitialised; - float (&minMaxAttackDuration)[3][2] = storage.mMinMaxAttackDuration; - - if(readyToAttack) - { - if (!minMaxAttackDurationInitialised) - { - // TODO: this must be updated when a different weapon is equipped - getMinMaxAttackDuration(actor, minMaxAttackDuration); - minMaxAttackDurationInitialised = true; - } - - if (timerAttack < 0) attack = false; - - timerAttack -= duration; - } - else - { - timerAttack = -attacksPeriod; - attack = false; - } - - actorClass.getCreatureStats(actor).setAttackingOrSpell(attack); - - - float& actionCooldown = storage.mActionCooldown; - actionCooldown -= duration; + storage.updateCombatMove(duration); + updateActorsMovement(actor, duration, storage.mMovement); + storage.updateAttack(characterController); + storage.mActionCooldown -= duration; float& timerReact = storage.mTimerReact; - float tReaction = 0.25f; - if(timerReact < tReaction) + if(timerReact < AI_REACTION_TIME) { timerReact += duration; return false; } - - //Update with period = tReaction - - // Stop attacking if target is not seen - if (target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude() > 0 - || target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude() > 75) + else { - movement.mPosition[1] = movement.mPosition[0] = 0; + timerReact = 0; + return reactionTimeActions(actor, characterController, storage, target); + } + } + + bool AiCombat::reactionTimeActions(const MWWorld::Ptr& actor, CharacterController& characterController, + AiCombatStorage& storage, MWWorld::Ptr target) + { + MWMechanics::Movement& movement = storage.mMovement; + + if (isTargetMagicallyHidden(target)) + { + storage.stopAttack(); return false; // TODO: run away instead of doing nothing } - timerReact = 0; const MWWorld::CellStore*& currentCell = storage.mCell; bool cellChange = currentCell && (actor.getCell() != currentCell); if(!currentCell || cellChange) @@ -265,19 +194,17 @@ namespace MWMechanics currentCell = actor.getCell(); } - MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(actor); - if (!anim) // shouldn't happen - return false; - + const MWWorld::Class& actorClass = actor.getClass(); actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); + float& actionCooldown = storage.mActionCooldown; if (actionCooldown > 0) return false; float rangeAttack = 0; float rangeFollow = 0; boost::shared_ptr& currentAction = storage.mCurrentAction; - if (anim->upperBodyReady()) + if (characterController.readyToPrepareAttack()) { currentAction = prepareNextAction(actor, target); actionCooldown = currentAction->getActionCooldown(); @@ -292,12 +219,10 @@ namespace MWMechanics float weapRange = 1.0f; // Get weapon characteristics + MWBase::World* world = MWBase::Environment::get().getWorld(); if (actorClass.hasInventoryStore(actor)) { - // TODO: Check equipped weapon and equip a different one if we can't attack with it - // (e.g. no ammunition, or wrong type of ammunition equipped, etc. autoEquip is not very smart in this regard)) - - //Get weapon speed and range + //Get weapon range MWWorld::ContainerStoreIterator weaponSlot = MWMechanics::getActiveWeapon(actorClass.getCreatureStats(actor), actorClass.getInventoryStore(actor), &weaptype); @@ -344,36 +269,9 @@ namespace MWMechanics } - float& strength = storage.mStrength; + bool& readyToAttack = storage.mReadyToAttack; // start new attack - if(readyToAttack) - { - if(timerAttack <= -attacksPeriod) - { - attack = true; // attack starts just now - - if (!distantCombat) attackType = chooseBestAttack(weapon, movement); - else attackType = ESM::Weapon::AT_Chop; // cause it's =0 - - strength = OEngine::Misc::Rng::rollClosedProbability(); - - // Note: may be 0 for some animations - timerAttack = minMaxAttackDuration[attackType][0] + - (minMaxAttackDuration[attackType][1] - minMaxAttackDuration[attackType][0]) * strength; - - //say a provoking combat phrase - if (actor.getClass().isNpc()) - { - const MWWorld::ESMStore &store = world->getStore(); - int chance = store.get().find("iVoiceAttackOdds")->getInt(); - if (OEngine::Misc::Rng::roll0to99() < chance) - { - MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); - } - } - } - } - + storage.startAttackIfReady(actor, characterController, weapon, distantCombat); /* * Some notes on meanings of variables: @@ -405,17 +303,18 @@ namespace MWMechanics */ ESM::Position pos = actor.getRefData().getPosition(); - Ogre::Vector3 vActorPos(pos.pos); - Ogre::Vector3 vTargetPos(target.getRefData().getPosition().pos); - Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos; - float distToTarget = vDirToTarget.length(); + osg::Vec3f vActorPos(pos.asVec3()); + osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); + + osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); + float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); - Ogre::Vector3& lastActorPos = storage.mLastActorPos; + osg::Vec3f& lastActorPos = storage.mLastActorPos; bool& followTarget = storage.mFollowTarget; bool isStuck = false; float speed = 0.0f; - if(movement.mPosition[1] && (lastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * tReaction / 2) + if(movement.mPosition[1] && (lastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * AI_REACTION_TIME / 2) isStuck = true; lastActorPos = vActorPos; @@ -424,25 +323,22 @@ namespace MWMechanics bool canMoveByZ = (actorClass.canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor); - // for distant combat we should know if target is in LOS even if distToTarget < rangeAttack - bool inLOS = distantCombat ? world->getLOS(actor, target) : true; - // can't fight if attacker can't go where target is. E.g. A fish can't attack person on land. if (distToTarget >= rangeAttack && !actorClass.isNpc() && !MWMechanics::isEnvironmentCompatible(actor, target)) { // TODO: start fleeing? - movement.mPosition[0] = 0; - movement.mPosition[1] = 0; - movement.mPosition[2] = 0; - readyToAttack = false; - actorClass.getCreatureStats(actor).setAttackingOrSpell(false); + storage.stopAttack(); return false; } + // for distant combat we should know if target is in LOS even if distToTarget < rangeAttack + bool inLOS = distantCombat ? world->getLOS(actor, target) : true; + // (within attack dist) || (not quite attack dist while following) if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && followTarget && !isStuck))) { + mPathFinder.clearPath(); //Melee and Close-up combat // getXAngleToDir determines vertical angle to target: @@ -451,47 +347,29 @@ namespace MWMechanics // note: in getZAngleToDir if we preserve dir.z then horizontal angle can be inaccurate if (distantCombat) { - Ogre::Vector3& lastTargetPos = storage.mLastTargetPos; - Ogre::Vector3 vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, tReaction, weaptype, strength); + osg::Vec3f& lastTargetPos = storage.mLastTargetPos; + vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, weaptype, + storage.mStrength); lastTargetPos = vTargetPos; movement.mRotation[0] = getXAngleToDir(vAimDir); movement.mRotation[2] = getZAngleToDir(vAimDir); } else { - movement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); - movement.mRotation[2] = getZAngleToDir(vDirToTarget); + movement.mRotation[0] = getXAngleToDir(vAimDir); + movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated } // (not quite attack dist while following) if (followTarget && distToTarget > rangeAttack) { //Close-up combat: just run up on target + storage.stopCombatMove(); movement.mPosition[1] = 1; } else // (within attack dist) { - if(movement.mPosition[0] || movement.mPosition[1]) - { - timerCombatMove = 0.1f + 0.1f * OEngine::Misc::Rng::rollClosedProbability(); - combatMove = true; - } - // only NPCs are smart enough to use dodge movements - else if(actorClass.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2))) - { - //apply sideway movement (kind of dodging) with some probability - if (OEngine::Misc::Rng::rollClosedProbability() < 0.25) - { - movement.mPosition[0] = OEngine::Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; - timerCombatMove = 0.05f + 0.15f * OEngine::Misc::Rng::rollClosedProbability(); - combatMove = true; - } - } - - if(distantCombat && distToTarget < rangeAttack/4) - { - movement.mPosition[1] = -1; - } + storage.startCombatMove(actorClass.isNpc(), distantCombat, distToTarget, rangeAttack); readyToAttack = true; //only once got in melee combat, actor is allowed to use close-up shortcutting @@ -508,22 +386,24 @@ namespace MWMechanics ESM::Position& shortcutFailPos = storage.mShortcutFailPos; if(inLOS && (!isStuck || readyToAttack) - && (!forceNoShortcut || (Ogre::Vector3(shortcutFailPos.pos) - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) + && (!forceNoShortcut || (shortcutFailPos.asVec3() - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) { if(speed == 0.0f) speed = actorClass.getSpeed(actor); // maximum dist before pit/obstacle for actor to avoid them depending on his speed - float maxAvoidDist = tReaction * speed + speed / MAX_VEL_ANGULAR.valueRadians() * 2; // *2 - for reliability - preferShortcut = checkWayIsClear(vActorPos, vTargetPos, Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); + float maxAvoidDist = AI_REACTION_TIME * speed + speed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability + preferShortcut = checkWayIsClear(vActorPos, vTargetPos, osg::Vec3f(vAimDir.x(), vAimDir.y(), 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); } // don't use pathgrid when actor can move in 3 dimensions - if(canMoveByZ) preferShortcut = true; + if (canMoveByZ) + { + preferShortcut = true; + movement.mRotation[0] = getXAngleToDir(vAimDir); + } if(preferShortcut) { - if (canMoveByZ) - movement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); - movement.mRotation[2] = getZAngleToDir(vDirToTarget); + movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); forceNoShortcut = false; shortcutFailPos.pos[0] = shortcutFailPos.pos[1] = shortcutFailPos.pos[2] = 0; mPathFinder.clearPath(); @@ -538,111 +418,68 @@ namespace MWMechanics followTarget = false; - buildNewPath(actor, target); //may fail to build a path, check before use + buildNewPath(actor, target); - //delete visited path node - mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1]); - - // This works on the borders between the path grid and areas with no waypoints. - if(inLOS && mPathFinder.getPath().size() > 1) - { - // get point just before target - std::list::const_iterator pntIter = --mPathFinder.getPath().end(); - --pntIter; - Ogre::Vector3 vBeforeTarget(PathFinder::MakeOgreVector3(*pntIter)); - - // if current actor pos is closer to target then last point of path (excluding target itself) then go straight on target - if(distToTarget <= (vTargetPos - vBeforeTarget).length()) - { - movement.mRotation[2] = getZAngleToDir(vDirToTarget); - preferShortcut = true; - } - } - - // if there is no new path, then go straight on target - if(!preferShortcut) - { - if(!mPathFinder.getPath().empty()) - movement.mRotation[2] = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - else - movement.mRotation[2] = getZAngleToDir(vDirToTarget); - } + // should always return a path (even if it's just go straight on target.) + assert(mPathFinder.isPathConstructed()); } - movement.mPosition[1] = 1; if (readyToAttack) { // to stop possible sideway moving after target moved out of attack range - combatMove = true; - timerCombatMove = 0; + storage.stopCombatMove(); + readyToAttack = false; } - readyToAttack = false; - } - - if(!isStuck && distToTarget > rangeAttack && !distantCombat) - { - //special run attack; it shouldn't affect melee combat tactics - if(actorClass.getMovementSettings(actor).mPosition[1] == 1) - { - /* check if actor can overcome the distance = distToTarget - attackerWeapRange - less than in time of swinging with weapon (t_swing), then start attacking - */ - float speed1 = actorClass.getSpeed(actor); - float speed2 = target.getClass().getSpeed(target); - if(target.getClass().getMovementSettings(target).mPosition[0] == 0 - && target.getClass().getMovementSettings(target).mPosition[1] == 0) - speed2 = 0; - - float s1 = distToTarget - weapRange; - float t = s1/speed1; - float s2 = speed2 * t; - float t_swing = - minMaxAttackDuration[ESM::Weapon::AT_Thrust][0] + - (minMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - minMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * OEngine::Misc::Rng::rollClosedProbability(); - - if (t + s2/speed1 <= t_swing) - { - readyToAttack = true; - if(timerAttack <= -attacksPeriod) - { - timerAttack = t_swing; - attack = true; - } - } - } - } - - // NOTE: This section gets updated every tReaction, which is currently hard - // coded at 250ms or 1/4 second - // - // TODO: Add a parameter to vary DURATION_SAME_SPOT? - if((distToTarget > rangeAttack || followTarget) && - mObstacleCheck.check(actor, tReaction)) // check if evasive action needed - { - // probably walking into another NPC TODO: untested in combat situation - // TODO: diagonal should have same animation as walk forward - // but doesn't seem to do that? - actor.getClass().getMovementSettings(actor).mPosition[0] = 1; - actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; - // change the angle a bit, too - if(mPathFinder.isPathConstructed()) - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); - - if(followTarget) - followTarget = false; - // FIXME: can fool actors to stay behind doors, etc. - // Related to Bug#1102 and to some degree #1155 as well + movement.mPosition[1] = 1; } return false; } + void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, MWMechanics::Movement& desiredMovement) + { + MWMechanics::Movement& actorMovementSettings = actor.getClass().getMovementSettings(actor); + if (mPathFinder.isPathConstructed()) + { + const ESM::Position& pos = actor.getRefData().getPosition(); + if (mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) + { + actorMovementSettings.mPosition[1] = 0; + } + else + { + evadeObstacles(actor, duration, pos); + } + } + else + { + actorMovementSettings = desiredMovement; + rotateActorOnAxis(actor, 2, actorMovementSettings, desiredMovement); + rotateActorOnAxis(actor, 0, actorMovementSettings, desiredMovement); + } + } + + void AiCombat::rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, + MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement) + { + actorMovementSettings.mRotation[axis] = 0; + float& targetAngleRadians = desiredMovement.mRotation[axis]; + if (targetAngleRadians != 0) + { + if (smoothTurn(actor, targetAngleRadians, axis)) + { + // actor now facing desired direction, no need to turn any more + targetAngleRadians = 0; + } + } + } + bool AiCombat::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) { if (!mPathFinder.getPath().empty()) { - Ogre::Vector3 currPathTarget(PathFinder::MakeOgreVector3(mPathFinder.getPath().back())); - Ogre::Vector3 newPathTarget = PathFinder::MakeOgreVector3(dest); + osg::Vec3f currPathTarget(PathFinder::MakeOsgVec3(mPathFinder.getPath().back())); + osg::Vec3f newPathTarget = PathFinder::MakeOsgVec3(dest); float dist = (newPathTarget - currPathTarget).length(); float targetPosThreshold = (cell->isExterior()) ? 300.0f : 100.0f; return dist > targetPosThreshold; @@ -697,6 +534,104 @@ namespace MWMechanics package.mPackage = combat.release(); sequence.mPackages.push_back(package); } + + void AiCombatStorage::startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack) + { + if (mMovement.mPosition[0] || mMovement.mPosition[1]) + { + mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); + mCombatMove = true; + } + // only NPCs are smart enough to use dodge movements + else if (isNpc && (!isDistantCombat || (distToTarget < rangeAttack / 2))) + { + //apply sideway movement (kind of dodging) with some probability + if (Misc::Rng::rollClosedProbability() < 0.25) + { + mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; + mTimerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability(); + mCombatMove = true; + } + } + + if (isDistantCombat && distToTarget < rangeAttack / 4) + { + mMovement.mPosition[1] = -1; + } + } + + void AiCombatStorage::updateCombatMove(float duration) + { + if (mCombatMove) + { + mTimerCombatMove -= duration; + if (mTimerCombatMove <= 0) + { + stopCombatMove(); + } + } + } + + void AiCombatStorage::stopCombatMove() + { + mTimerCombatMove = 0; + mMovement.mPosition[1] = mMovement.mPosition[0] = 0; + mCombatMove = false; + } + + void AiCombatStorage::startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, + const ESM::Weapon* weapon, bool distantCombat) + { + if (mReadyToAttack && characterController.readyToStartAttack()) + { + if (mAttackCooldown <= 0) + { + mAttack = true; // attack starts just now + characterController.setAttackingOrSpell(true); + + if (!distantCombat) + chooseBestAttack(weapon, mMovement); + + mStrength = Misc::Rng::rollClosedProbability(); + + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + + float baseDelay = store.get().find("fCombatDelayCreature")->getFloat(); + if (actor.getClass().isNpc()) + { + baseDelay = store.get().find("fCombatDelayNPC")->getFloat(); + + //say a provoking combat phrase + int chance = store.get().find("iVoiceAttackOdds")->getInt(); + if (Misc::Rng::roll0to99() < chance) + { + MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); + } + } + mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(), baseDelay + 0.9); + } + else + mAttackCooldown -= AI_REACTION_TIME; + } + } + + void AiCombatStorage::updateAttack(CharacterController& characterController) + { + if (mAttack && (characterController.getAttackStrength() >= mStrength || characterController.readyToPrepareAttack())) + { + mAttack = false; + } + characterController.setAttackingOrSpell(mAttack); + } + + void AiCombatStorage::stopAttack() + { + mMovement.mPosition[0] = 0; + mMovement.mPosition[1] = 0; + mMovement.mPosition[2] = 0; + mReadyToAttack = false; + mAttack = false; + } } @@ -710,10 +645,10 @@ ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics: if (weapon == NULL) { //hand-to-hand deal equal damage for each type - float roll = OEngine::Misc::Rng::rollClosedProbability(); + float roll = Misc::Rng::rollClosedProbability(); if(roll <= 0.333f) //side punch { - movement.mPosition[0] = OEngine::Misc::Rng::rollClosedProbability() ? 1.0f : -1.0f; + movement.mPosition[0] = Misc::Rng::rollClosedProbability() ? 1.0f : -1.0f; movement.mPosition[1] = 0; attackType = ESM::Weapon::AT_Slash; } @@ -735,16 +670,14 @@ ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics: int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; - float total = static_cast(slash + chop + thrust); - - float roll = OEngine::Misc::Rng::rollClosedProbability(); - if(roll <= (slash/total)) + float roll = Misc::Rng::rollClosedProbability() * (slash + chop + thrust); + if(roll <= slash) { - movement.mPosition[0] = (OEngine::Misc::Rng::rollClosedProbability() < 0.5f) ? 1.0f : -1.0f; + movement.mPosition[0] = (Misc::Rng::rollClosedProbability() < 0.5f) ? 1.0f : -1.0f; movement.mPosition[1] = 0; attackType = ESM::Weapon::AT_Slash; } - else if(roll <= (slash + (thrust/total))) + else if(roll <= (slash + thrust)) { movement.mPosition[1] = 1; attackType = ESM::Weapon::AT_Thrust; @@ -759,72 +692,7 @@ ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics: return attackType; } -void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations)[2]) -{ - if (!actor.getClass().hasInventoryStore(actor)) // creatures - { - fMinMaxDurations[0][0] = fMinMaxDurations[0][1] = 0.1f; - fMinMaxDurations[1][0] = fMinMaxDurations[1][1] = 0.1f; - fMinMaxDurations[2][0] = fMinMaxDurations[2][1] = 0.1f; - - return; - } - - // get weapon information: type and speed - const ESM::Weapon *weapon = NULL; - MWMechanics::WeaponType weaptype = MWMechanics::WeapType_None; - - MWWorld::ContainerStoreIterator weaponSlot = - MWMechanics::getActiveWeapon(actor.getClass().getCreatureStats(actor), actor.getClass().getInventoryStore(actor), &weaptype); - - float weapSpeed; - if (weaptype != MWMechanics::WeapType_HandToHand - && weaptype != MWMechanics::WeapType_Spell - && weaptype != MWMechanics::WeapType_None) - { - weapon = weaponSlot->get()->mBase; - weapSpeed = weapon->mData.mSpeed; - } - else weapSpeed = 1.0f; - - MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(actor); - - std::string weapGroup; - MWMechanics::getWeaponGroup(weaptype, weapGroup); - weapGroup = weapGroup + ": "; - - bool bRangedWeap = (weaptype >= MWMechanics::WeapType_BowAndArrow && weaptype <= MWMechanics::WeapType_Thrown); - - const char *attackType[] = {"chop ", "slash ", "thrust ", "shoot "}; - - std::string textKey = "start"; - std::string textKey2; - - // get durations for each attack type - for (int i = 0; i < (bRangedWeap ? 1 : 3); i++) - { - float start1 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey); - - if (start1 < 0) - { - fMinMaxDurations[i][0] = fMinMaxDurations[i][1] = 0.1f; - continue; - } - - textKey2 = "min attack"; - float start2 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2); - - fMinMaxDurations[i][0] = (start2 - start1) / weapSpeed; - - textKey2 = "max attack"; - start1 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2); - - fMinMaxDurations[i][1] = fMinMaxDurations[i][0] + (start1 - start2) / weapSpeed; - } - -} - -Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos, +osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength) { float projSpeed; @@ -853,31 +721,39 @@ Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr // idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same - Ogre::Vector3 vActorPos = Ogre::Vector3(actor.getRefData().getPosition().pos); - Ogre::Vector3 vTargetPos = Ogre::Vector3(target.getRefData().getPosition().pos); - Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos; + osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3(); + osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); float distToTarget = vDirToTarget.length(); - Ogre::Vector3 vTargetMoveDir = vTargetPos - vLastTargetPos; + osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos; vTargetMoveDir /= duration; // |vTargetMoveDir| is target real speed in units/sec now - Ogre::Vector3 vPerpToDir = vDirToTarget.crossProduct(Ogre::Vector3::UNIT_Z); + osg::Vec3f vPerpToDir = vDirToTarget ^ osg::Vec3f(0,0,1); // cross product - float velPerp = vTargetMoveDir.dotProduct(vPerpToDir.normalisedCopy()); - float velDir = vTargetMoveDir.dotProduct(vDirToTarget.normalisedCopy()); + vPerpToDir.normalize(); + osg::Vec3f vDirToTargetNormalized = vDirToTarget; + vDirToTargetNormalized.normalize(); + + // dot product + float velPerp = vTargetMoveDir * vPerpToDir; + float velDir = vTargetMoveDir * vDirToTargetNormalized; // time to collision between target and projectile float t_collision; float projVelDirSquared = projSpeed * projSpeed - velPerp * velPerp; - float projDistDiff = vDirToTarget.dotProduct(vTargetMoveDir.normalisedCopy()); - projDistDiff = sqrt(distToTarget * distToTarget - projDistDiff * projDistDiff); + + osg::Vec3f vTargetMoveDirNormalized = vTargetMoveDir; + vTargetMoveDirNormalized.normalize(); + + float projDistDiff = vDirToTarget * vTargetMoveDirNormalized; // dot product + projDistDiff = std::sqrt(distToTarget * distToTarget - projDistDiff * projDistDiff); if (projVelDirSquared > 0) - t_collision = projDistDiff / (sqrt(projVelDirSquared) - velDir); + t_collision = projDistDiff / (std::sqrt(projVelDirSquared) - velDir); else t_collision = 0; // speed of projectile is not enough to reach moving target - return vTargetPos + vTargetMoveDir * t_collision - vActorPos; + return vDirToTarget + vTargetMoveDir * t_collision; } } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 8248975b58..93d6305291 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -8,8 +8,6 @@ #include "movement.hpp" #include "obstacle.hpp" -#include - #include "../mwworld/cellstore.hpp" // for Doors #include "../mwbase/world.hpp" @@ -28,6 +26,8 @@ namespace MWMechanics { class Action; + struct AiCombatStorage; + /// \brief Causes the actor to fight another actor class AiCombat : public AiPackage { @@ -42,7 +42,7 @@ namespace MWMechanics virtual AiCombat *clone() const; - virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); + virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); virtual int getTypeId() const; @@ -60,8 +60,14 @@ namespace MWMechanics int mTargetActorId; - void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + bool reactionTimeActions(const MWWorld::Ptr& actor, CharacterController& characterController, + AiCombatStorage& storage, MWWorld::Ptr target); + + /// Transfer desired movement (from AiCombatStorage) to Actor + void updateActorsMovement(const MWWorld::Ptr& actor, float duration, MWMechanics::Movement& movement); + void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, + MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement); }; diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 33e3c3d679..60446e524d 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -541,7 +541,7 @@ namespace MWMechanics for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); + const ESM::Spell* spell = it->first; float rating = rateSpell(spell, actor, target); if (rating > bestActionRating) diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index f9ebefe136..baf4f1be77 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -1,13 +1,14 @@ #include "aiescort.hpp" #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -63,7 +64,7 @@ namespace MWMechanics return new AiEscort(*this); } - bool AiEscort::execute (const MWWorld::Ptr& actor, AiState& state, float duration) + bool AiEscort::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { // If AiEscort has ran for as long or longer then the duration specified // and the duration is not infinite, the package is complete. @@ -74,6 +75,12 @@ namespace MWMechanics return true; } + if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), actor.getRefData().getPosition().asVec3())) + return false; + + if (!mCellId.empty() && mCellId != actor.getCell()->getCell()->getCellId().mWorldspace) + return false; // Not in the correct cell, pause and rely on the player to go back through a teleport door + actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); @@ -115,6 +122,11 @@ namespace MWMechanics return TypeIdEscort; } + MWWorld::Ptr AiEscort::getTarget() + { + return MWBase::Environment::get().getWorld()->getPtr(mActorId, false); + } + void AiEscort::writeState(ESM::AiSequence::AiSequence &sequence) const { std::auto_ptr escort(new ESM::AiSequence::AiEscort()); diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index f02cdba220..cdb0f79360 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -33,10 +33,13 @@ namespace MWMechanics virtual AiEscort *clone() const; - virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); + virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); virtual int getTypeId() const; + MWWorld::Ptr getTarget(); + virtual bool sideWithTarget() const { return true; } + void writeState(ESM::AiSequence::AiSequence &sequence) const; private: diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 55414bfff3..f0e67af085 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -1,8 +1,7 @@ #include "aifollow.hpp" -#include - #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -12,9 +11,6 @@ #include "creaturestats.hpp" #include "movement.hpp" -#include -#include - #include "steering.hpp" namespace MWMechanics @@ -24,8 +20,9 @@ namespace MWMechanics struct AiFollowStorage : AiTemporaryBase { float mTimer; + bool mMoving; - AiFollowStorage() : mTimer(0.f) {} + AiFollowStorage() : mTimer(0.f), mMoving(false) {} }; int AiFollow::mFollowIndexCounter = 0; @@ -57,7 +54,7 @@ AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) } -bool AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, float duration) +bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { MWWorld::Ptr target = getTarget(); @@ -68,15 +65,16 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, float duratio actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + AiFollowStorage& storage = state.get(); + // AiFollow requires the target to be in range and within sight for the initial activation if (!mActive) { - AiFollowStorage& storage = state.get(); storage.mTimer -= duration; if (storage.mTimer < 0) { - if (Ogre::Vector3(actor.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(target.getRefData().getPosition().pos)) + if ((actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length2() < 500*500 && MWBase::Environment::get().getWorld()->getLOS(actor, target)) mActive = true; @@ -129,13 +127,20 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, float duratio //Set the target destination from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; + float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]); + //const float threshold = 10; - pathTo(actor, dest, duration, followDistance); //Go to the destination + //if (storage.mMoving) //Stop when you get close + // storage.mMoving = (dist > followDistance); + //else + // storage.mMoving = (dist > followDistance + threshold); + + storage.mMoving = !pathTo(actor, dest, duration, followDistance); // Go to the destination //Check if you're far away - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) > 450) + if(dist > 450) actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run - else if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold + else if(dist < 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk return false; diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 68a1f0ea5f..017ea7122d 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -32,10 +32,12 @@ namespace MWMechanics AiFollow(const ESM::AiSequence::AiFollow* follow); MWWorld::Ptr getTarget(); + virtual bool sideWithTarget() const { return true; } + virtual bool followTargetThroughDoors() const { return true; } virtual AiFollow *clone() const; - virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); + virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 425593624e..571bf0ac83 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -1,7 +1,11 @@ - #include "aipackage.hpp" #include + +#include +#include +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" @@ -10,74 +14,71 @@ #include "movement.hpp" #include "../mwworld/action.hpp" -#include - #include "steering.hpp" +#include "actorutil.hpp" +#include "coordinateconverter.hpp" MWMechanics::AiPackage::~AiPackage() {} MWMechanics::AiPackage::AiPackage() : - mTimer(0.26f), mStuckTimer(0), //mTimer starts at .26 to force initial pathbuild + mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild mShortcutProhibited(false), mShortcutFailPos() { } +MWWorld::Ptr MWMechanics::AiPackage::getTarget() +{ + return MWWorld::Ptr(); +} + +bool MWMechanics::AiPackage::sideWithTarget() const +{ + return false; +} + +bool MWMechanics::AiPackage::followTargetThroughDoors() const +{ + return false; +} + bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance) { //Update various Timers mTimer += duration; //Update timer - mStuckTimer += duration; //Update stuck timer ESM::Position pos = actor.getRefData().getPosition(); //position of the actor /// Stops the actor when it gets too close to a unloaded cell - const ESM::Cell *cell = actor.getCell()->getCell(); + //... At current time, this test is unnecessary. AI shuts down when actor is more than 7168 + //... units from player, and exterior cells are 8192 units long and wide. + //... But AI processing distance may increase in the future. + if (isNearInactiveCell(pos)) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - Movement &movement = actor.getClass().getMovementSettings(actor); - - //Ensure pursuer doesn't leave loaded cells - if(cell->mData.mX != player.getCell()->getCell()->mData.mX) - { - int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); - //check if actor is near the border of an inactive cell. If so, stop walking. - if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) - { - movement.mPosition[1] = 0; - return false; - } - } - if(cell->mData.mY != player.getCell()->getCell()->mData.mY) - { - int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); - //check if actor is near the border of an inactive cell. If so, stop walking. - if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) - { - movement.mPosition[1] = 0; - return false; - } - } + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + return false; } - //Start position - ESM::Pathgrid::Point start = pos.pos; - //*********************** /// Checks if you can't get to the end position at all, adds end position to end of path /// Rebuilds path every [AI_REACTION_TIME] seconds, in case the target has moved //*********************** - bool isStuck = distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < actor.getClass().getSpeed(actor)*0.05 - && distance(dest, start) > 20; + ESM::Pathgrid::Point start = pos.pos; float distToNextWaypoint = distance(start, dest); - bool isDestReached = (distToNextWaypoint <= destTolerance); if (!isDestReached && mTimer > AI_REACTION_TIME) { - bool needPathRecalc = doesPathNeedRecalc(dest, cell); + osg::Vec3f& lastActorPos = mLastActorPos; + bool isStuck = distance(start, lastActorPos.x(), lastActorPos.y(), lastActorPos.z()) < actor.getClass().getSpeed(actor)*mTimer + && distance(dest, start) > 20; + + lastActorPos = pos.asVec3(); + + const ESM::Cell *cell = actor.getCell()->getCell(); + bool needPathRecalc = doesPathNeedRecalc(dest, cell); //Only rebuild path if dest point has changed bool isWayClear = true; @@ -133,48 +134,48 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr actor.getClass().getMovementSettings(actor).mPosition[2] = 0; // turn to destination point - zTurn(actor, Ogre::Degree(getZAngleToPoint(start, dest))); - smoothTurn(actor, Ogre::Degree(getXAngleToPoint(start, dest)), 0); + zTurn(actor, getZAngleToPoint(start, dest)); + smoothTurn(actor, getXAngleToPoint(start, dest), 0); return true; } - else if(mStuckTimer>0.5) //Every half second see if we need to take action to avoid something + else { -/// TODO (tluppi#1#): Use ObstacleCheck here. Not working for some reason - //if(mObstacleCheck.check(actor, duration)) { - if (isStuck) { //Actually stuck, and far enough away from destination to care - // first check if we're walking into a door - MWWorld::Ptr door = getNearbyDoor(actor); - if(door != MWWorld::Ptr()) // NOTE: checks interior cells only - { - if(!door.getCellRef().getTeleport() && door.getCellRef().getTrap().empty() && door.getClass().getDoorState(door) == 0) { //Open the door if untrapped - MWBase::Environment::get().getWorld()->activateDoor(door, 1); - } - } - else // probably walking into another NPC - { - actor.getClass().getMovementSettings(actor).mPosition[0] = 1; - actor.getClass().getMovementSettings(actor).mPosition[1] = 1; - // change the angle a bit, too - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); - } - } - else { //Not stuck, so reset things - mStuckTimer = 0; - mStuckPos = pos; - actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward - } - } - else { - actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward the rest of the time + evadeObstacles(actor, duration, pos); } // turn to next path point by X,Z axes - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); - smoothTurn(actor, Ogre::Degree(mPathFinder.getXAngleToNext(pos.pos[0], pos.pos[1], pos.pos[2])), 0); + zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); + smoothTurn(actor, mPathFinder.getXAngleToNext(pos.pos[0], pos.pos[1], pos.pos[2]), 0); return false; } +void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos) +{ + zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); + + MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); + if (mObstacleCheck.check(actor, duration)) + { + // first check if we're walking into a door + MWWorld::Ptr door = getNearbyDoor(actor); + if (door != MWWorld::Ptr()) // NOTE: checks interior cells only + { + if (!door.getCellRef().getTeleport() && door.getCellRef().getTrap().empty() + && door.getCellRef().getLockLevel() <= 0 && door.getClass().getDoorState(door) == 0) { + MWBase::Environment::get().getWorld()->activateDoor(door, 1); + } + } + else // probably walking into another NPC + { + mObstacleCheck.takeEvasiveAction(movement); + } + } + else { //Not stuck, so reset things + movement.mPosition[1] = 1; //Just run forward + } +} + bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS) { const MWWorld::Class& actorClass = actor.getClass(); @@ -188,7 +189,7 @@ bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint bool isPathClear = actorCanMoveByZ; if (!isPathClear - && (!mShortcutProhibited || (PathFinder::MakeOgreVector3(mShortcutFailPos) - PathFinder::MakeOgreVector3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) + && (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) { // take the direct path only if there aren't any obstacles isPathClear = !MWBase::Environment::get().getWorld()->castRay( @@ -223,12 +224,12 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& return true; float actorSpeed = actor.getClass().getSpeed(actor); - float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR.valueRadians() * 2; // *2 - for reliability - Ogre::Real distToTarget = Ogre::Vector3(static_cast(endPoint.mX), static_cast(endPoint.mY), 0).length(); + float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability + osg::Vec3f::value_type distToTarget = osg::Vec3f(static_cast(endPoint.mX), static_cast(endPoint.mY), 0).length(); float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2; - bool isClear = checkWayIsClear(PathFinder::MakeOgreVector3(startPoint), PathFinder::MakeOgreVector3(endPoint), offsetXY); + bool isClear = checkWayIsClear(PathFinder::MakeOsgVec3(startPoint), PathFinder::MakeOsgVec3(endPoint), offsetXY); // update shortcut prohibit state if (isClear) @@ -258,4 +259,35 @@ bool MWMechanics::AiPackage::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const mPrevDest = dest; return needRecalc; -} \ No newline at end of file +} + +bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target) +{ + const MagicEffects& magicEffects(target.getClass().getCreatureStats(target).getMagicEffects()); + return (magicEffects.get(ESM::MagicEffect::Invisibility).getMagnitude() > 0) + || (magicEffects.get(ESM::MagicEffect::Chameleon).getMagnitude() > 75); +} + +bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos) +{ + const ESM::Cell* playerCell(getPlayer().getCell()->getCell()); + if (playerCell->isExterior()) + { + // get actor's distance from origin of center cell + osg::Vec3f actorOffset(actorPos.asVec3()); + CoordinateConverter(playerCell).toLocal(actorOffset); + + // currently assumes 3 x 3 grid for exterior cells, with player at center cell. + // ToDo: (Maybe) use "exterior cell load distance" setting to get count of actual active cells + // While AI Process distance is 7168, AI shuts down actors before they reach edges of 3 x 3 grid. + const float distanceFromEdge = 200.0; + float minThreshold = (-1.0f * ESM::Land::REAL_SIZE) + distanceFromEdge; + float maxThreshold = (2.0f * ESM::Land::REAL_SIZE) - distanceFromEdge; + return (actorOffset[0] < minThreshold) || (maxThreshold < actorOffset[0]) + || (actorOffset[1] < minThreshold) || (maxThreshold < actorOffset[1]); + } + else + { + return false; + } +} diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index a22ac9357b..67c2b0c1b3 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -26,6 +26,8 @@ namespace MWMechanics { const float AI_REACTION_TIME = 0.25f; + class CharacterController; + /// \brief Base class for AI packages class AiPackage { @@ -54,7 +56,7 @@ namespace MWMechanics /// Updates and runs the package (Should run every frame) /// \return Package completed? - virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration) = 0; + virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) = 0; /// Returns the TypeID of the AiPackage /// \see enum TypeId @@ -68,6 +70,17 @@ namespace MWMechanics /// Simulates the passing of time virtual void fastForward(const MWWorld::Ptr& actor, AiState& state) {} + /// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr) + virtual MWWorld::Ptr getTarget(); + + /// Return true if having this AiPackage makes the actor side with the target in fights (default false) + virtual bool sideWithTarget() const; + + /// Return true if the actor should follow the target through teleport doors (default false) + virtual bool followTargetThroughDoors() const; + + bool isTargetMagicallyHidden(const MWWorld::Ptr& target); + protected: /// Causes the actor to attempt to walk to the specified location /** \return If the actor has arrived at his destination **/ @@ -84,19 +97,32 @@ namespace MWMechanics virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); + void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); + // TODO: all this does not belong here, move into temporary storage PathFinder mPathFinder; ObstacleCheck mObstacleCheck; float mTimer; - float mStuckTimer; - ESM::Position mStuckPos; ESM::Pathgrid::Point mPrevDest; + osg::Vec3f mLastActorPos; bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail + + bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) const + { + // Maximum travel distance for vanilla compatibility. + // Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well. + // We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways. + return (pos1 - pos2).length2() <= 7168*7168; + } + + private: + bool isNearInactiveCell(const ESM::Position& actorPos); }; + } #endif diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index af007231b8..649a72cc0e 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -1,12 +1,12 @@ #include "aipursue.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" #include "../mwworld/action.hpp" -#include "../mwworld/cellstore.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -30,7 +30,7 @@ AiPursue *MWMechanics::AiPursue::clone() const { return new AiPursue(*this); } -bool AiPursue::execute (const MWWorld::Ptr& actor, AiState& state, float duration) +bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { if(actor.getClass().getCreatureStats(actor).isDead()) return true; @@ -43,8 +43,7 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, AiState& state, float duratio ) return true; //Target doesn't exist - if (target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude() > 0 - || target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude() > 75) + if (isTargetMagicallyHidden(target)) return true; if(target.getClass().getCreatureStats(target).isDead()) diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index 493a279859..813b87cff0 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -31,7 +31,7 @@ namespace MWMechanics AiPursue(const ESM::AiSequence::AiPursue* pursue); virtual AiPursue *clone() const; - virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); + virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); virtual int getTypeId() const; MWWorld::Ptr getTarget() const; diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index bb078f8834..d55dc240e0 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -1,6 +1,7 @@ - #include "aisequence.hpp" +#include + #include "aipackage.hpp" #include "aistate.hpp" @@ -11,12 +12,10 @@ #include "aiactivate.hpp" #include "aicombat.hpp" #include "aipursue.hpp" +#include "actorutil.hpp" #include -#include "../mwworld/class.hpp" -#include "creaturestats.hpp" -#include "npcstats.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -73,7 +72,7 @@ bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const targetActor = combat->getTarget(); - return true; + return !targetActor.isEmpty(); } std::list::const_iterator AiSequence::begin() const @@ -150,9 +149,9 @@ bool AiSequence::isPackageDone() const return mDone; } -void AiSequence::execute (const MWWorld::Ptr& actor, AiState& state,float duration) +void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { - if(actor != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(actor != getPlayer()) { if (!mPackages.empty()) { @@ -165,7 +164,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, AiState& state,float durati std::list::iterator itActualCombat; float nearestDist = std::numeric_limits::max(); - Ogre::Vector3 vActorPos = Ogre::Vector3(actor.getRefData().getPosition().pos); + osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) { @@ -183,7 +182,12 @@ void AiSequence::execute (const MWWorld::Ptr& actor, AiState& state,float durati { const ESM::Position &targetPos = target.getRefData().getPosition(); - float distTo = (Ogre::Vector3(targetPos.pos) - vActorPos).length(); + float distTo = (targetPos.asVec3() - vActorPos).length(); + + // Small threshold for changing target + if (it == mPackages.begin()) + distTo = std::max(0.f, distTo - 50.f); + if (distTo < nearestDist) { nearestDist = distTo; @@ -211,7 +215,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, AiState& state,float durati } } - if (package->execute (actor,state,duration)) + if (package->execute (actor,characterController,state,duration)) { // To account for the rare case where AiPackage::execute() queued another AI package // (e.g. AiPursue executing a dialogue script that uses startCombat) @@ -239,7 +243,7 @@ void AiSequence::clear() void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) { - if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (actor == getPlayer()) throw std::runtime_error("Can't add AI packages to player"); if (package.getTypeId() == AiPackage::TypeIdCombat || package.getTypeId() == AiPackage::TypeIdPursue) @@ -258,7 +262,7 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) return; // already in combat with this actor } else if ((*iter)->getTypeId() == AiPackage::TypeIdWander) - static_cast(*iter)->setReturnPosition(Ogre::Vector3(actor.getRefData().getPosition().pos)); + static_cast(*iter)->setReturnPosition(actor.getRefData().getPosition().asVec3()); } } diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 19f1e14545..1eefe7c697 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -24,6 +24,7 @@ namespace ESM namespace MWMechanics { class AiPackage; + class CharacterController; template< class Base > class DerivedClassStorage; struct AiTemporaryBase; @@ -95,7 +96,7 @@ namespace MWMechanics void stopPursuit(); /// Execute current package, switching if needed. - void execute (const MWWorld::Ptr& actor, MWMechanics::AiState& state, float duration); + void execute (const MWWorld::Ptr& actor, CharacterController& characterController, MWMechanics::AiState& state, float duration); /// Simulate the passing of time using the currently active AI package void fastForward(const MWWorld::Ptr &actor, AiState &state); diff --git a/apps/openmw/mwmechanics/aistate.hpp b/apps/openmw/mwmechanics/aistate.hpp index 581f45d078..19f0ecf994 100644 --- a/apps/openmw/mwmechanics/aistate.hpp +++ b/apps/openmw/mwmechanics/aistate.hpp @@ -4,15 +4,11 @@ #include #include -// c++11 replacement -#include -#include - namespace MWMechanics { /** \brief stores one object of any class derived from Base. - * Requesting a certain dereived class via get() either returns + * Requesting a certain derived class via get() either returns * the stored object if it has the correct type or otherwise replaces * it with an object of the requested type. */ @@ -22,17 +18,6 @@ namespace MWMechanics private: Base* mStorage; - // assert that Derived is derived from Base. - template< class Derived > - void assert_derived() - { - // c++11: - // static_assert( std::is_base_of , "DerivedClassStorage may only store derived classes" ); - - // boost: - BOOST_STATIC_ASSERT((boost::is_base_of::value));//,"DerivedClassStorage may only store derived classes"); - } - //if needed you have to provide a clone member function DerivedClassStorage( const DerivedClassStorage& other ); DerivedClassStorage& operator=( const DerivedClassStorage& ); @@ -42,8 +27,6 @@ namespace MWMechanics template< class Derived > Derived& get() { - assert_derived(); - Derived* result = dynamic_cast(mStorage); if(!result) @@ -60,7 +43,6 @@ namespace MWMechanics template< class Derived > void store( const Derived& payload ) { - assert_derived(); if(mStorage) delete mStorage; mStorage = new Derived(payload); @@ -70,7 +52,6 @@ namespace MWMechanics template< class Derived > void moveIn( Derived* p ) { - assert_derived(); if(mStorage) delete mStorage; mStorage = p; @@ -87,12 +68,12 @@ namespace MWMechanics } - DerivedClassStorage():mStorage(NULL){}; + DerivedClassStorage():mStorage(NULL){} ~DerivedClassStorage() { if(mStorage) delete mStorage; - }; + } @@ -108,7 +89,7 @@ namespace MWMechanics * */ struct AiTemporaryBase { - virtual ~AiTemporaryBase(){}; + virtual ~AiTemporaryBase(){} }; /// \brief Container for AI package status. diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 4f4d4c79ff..c29861f4ee 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -1,8 +1,7 @@ #include "aitravel.hpp" -#include - #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -14,18 +13,6 @@ #include "movement.hpp" #include "creaturestats.hpp" -namespace -{ - -bool isWithinMaxRange(const Ogre::Vector3& pos1, const Ogre::Vector3& pos2) -{ - // Maximum travel distance for vanilla compatibility. - // Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well. - // We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways. - return (pos1.squaredDistance(pos2) <= 7168*7168); -} - -} namespace MWMechanics { @@ -49,14 +36,14 @@ namespace MWMechanics return new AiTravel(*this); } - bool AiTravel::execute (const MWWorld::Ptr& actor, AiState& state, float duration) + bool AiTravel::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { ESM::Position pos = actor.getRefData().getPosition(); actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); - if (!isWithinMaxRange(Ogre::Vector3(mX, mY, mZ), Ogre::Vector3(pos.pos))) + if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), pos.asVec3())) return false; if (pathTo(actor, ESM::Pathgrid::Point(static_cast(mX), static_cast(mY), static_cast(mZ)), duration)) @@ -86,7 +73,7 @@ namespace MWMechanics void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state) { - if (!isWithinMaxRange(Ogre::Vector3(mX, mY, mZ), Ogre::Vector3(actor.getRefData().getPosition().pos))) + if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), actor.getRefData().getPosition().asVec3())) return; // does not do any validation on the travel target (whether it's in air, inside collision geometry, etc), // that is the user's responsibility diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index a5a4577e60..9f263fd46c 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -30,7 +30,7 @@ namespace MWMechanics virtual AiTravel *clone() const; - virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); + virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 4566740e3a..16c8d31567 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -1,9 +1,9 @@ #include "aiwander.hpp" -#include -#include +#include +#include -#include +#include #include @@ -20,17 +20,24 @@ #include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" +#include "coordinateconverter.hpp" +#include "actorutil.hpp" namespace MWMechanics { - static const int COUNT_BEFORE_RESET = 200; // TODO: maybe no longer needed + static const int COUNT_BEFORE_RESET = 10; static const float DOOR_CHECK_INTERVAL = 1.5f; - static const float REACTION_INTERVAL = 0.25f; static const int GREETING_SHOULD_START = 4; //how many reaction intervals should pass before NPC can greet player static const int GREETING_SHOULD_END = 10; + // to prevent overcrowding + static const int DESTINATION_TOLERANCE = 64; + + // distance must be long enough that NPC will need to move to get there. + static const int MINIMUM_WANDER_DISTANCE = DESTINATION_TOLERANCE * 2; + const std::string AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = { std::string("idle2"), @@ -46,40 +53,35 @@ namespace MWMechanics /// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive. struct AiWanderStorage : AiTemporaryBase { - // the z rotation angle (degrees) we want to reach - // used every frame when mRotate is true - Ogre::Radian mTargetAngle; - bool mRotate; + // the z rotation angle to reach + // when mTurnActorGivingGreetingToFacePlayer is true + float mTargetAngleRadians; + bool mTurnActorGivingGreetingToFacePlayer; float mReaction; // update some actions infrequently - AiWander::GreetingState mSaidGreeting; int mGreetingTimer; const MWWorld::CellStore* mCell; // for detecting cell change // AiWander states - bool mChooseAction; - bool mIdleNow; - bool mMoveNow; - bool mWalking; + AiWander::WanderState mState; - unsigned short mPlayedIdle; + unsigned short mIdleAnimation; + std::vector mBadIdles; // Idle animations that when called cause errors PathFinder mPathFinder; AiWanderStorage(): - mTargetAngle(0), - mRotate(false), + mTargetAngleRadians(0), + mTurnActorGivingGreetingToFacePlayer(false), mReaction(0), mSaidGreeting(AiWander::Greet_None), mGreetingTimer(0), mCell(NULL), - mChooseAction(true), - mIdleNow(false), - mMoveNow(false), - mWalking(false), - mPlayedIdle(0) + mState(AiWander::Wander_ChooseAction), + mIdleAnimation(0), + mBadIdles() {}; }; @@ -101,7 +103,7 @@ namespace MWMechanics mTrimCurrentNode = false; mHasReturnPosition = false; - mReturnPosition = Ogre::Vector3(0,0,0); + mReturnPosition = osg::Vec3f(0,0,0); if(mDistance < 0) mDistance = 0; @@ -112,7 +114,7 @@ namespace MWMechanics mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); - mStoredAvailableNodes = false; + mPopulateAvailableNodes = true; } @@ -171,7 +173,7 @@ namespace MWMechanics * actors will enter combat (i.e. no longer wandering) and different pathfinding * will kick in. */ - bool AiWander::execute (const MWWorld::Ptr& actor, AiState& state, float duration) + bool AiWander::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { // get or create temporary storage AiWanderStorage& storage = state.get(); @@ -186,7 +188,7 @@ namespace MWMechanics if(!currentCell || cellChange) { currentCell = actor.getCell(); - mStoredAvailableNodes = false; // prob. not needed since mDistance = 0 + mPopulateAvailableNodes = true; } cStats.setDrawState(DrawState_Nothing); @@ -194,197 +196,31 @@ namespace MWMechanics ESM::Position pos = actor.getRefData().getPosition(); - - bool& idleNow = storage.mIdleNow; - bool& moveNow = storage.mMoveNow; - bool& walking = storage.mWalking; - // Check if an idle actor is too close to a door - if so start walking - mDoorCheckDuration += duration; - if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL) - { - mDoorCheckDuration = 0; // restart timer - if(mDistance && // actor is not intended to be stationary - idleNow && // but is in idle - !walking && // FIXME: some actors are idle while walking - proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6f*1.6f)) // NOTE: checks interior cells only - { - idleNow = false; - moveNow = true; - mTrimCurrentNode = false; // just in case - } - } + doPerFrameActionsForState(actor, duration, storage, pos); - // Are we there yet? - bool& chooseAction = storage.mChooseAction; - if(walking && - storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], 64.f)) - { - stopWalking(actor, storage); - moveNow = false; - walking = false; - chooseAction = true; - mHasReturnPosition = false; - } - - - - if(walking) // have not yet reached the destination - { - // turn towards the next point in mPath - zTurn(actor, Ogre::Degree(storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); - actor.getClass().getMovementSettings(actor).mPosition[1] = 1; - - // Returns true if evasive action needs to be taken - if(mObstacleCheck.check(actor, duration)) - { - // first check if we're walking into a door - if(proximityToDoor(actor)) // NOTE: checks interior cells only - { - // remove allowed points then select another random destination - mTrimCurrentNode = true; - trimAllowedNodes(mAllowedNodes, storage.mPathFinder); - mObstacleCheck.clear(); - storage.mPathFinder.clearPath(); - walking = false; - moveNow = true; - } - else // probably walking into another NPC - { - // TODO: diagonal should have same animation as walk forward - // but doesn't seem to do that? - actor.getClass().getMovementSettings(actor).mPosition[0] = 1; - actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; - // change the angle a bit, too - zTurn(actor, Ogre::Degree(storage.mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); - } - mStuckCount++; // TODO: maybe no longer needed - } -//#if 0 - // TODO: maybe no longer needed - if(mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset - { - //std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl; - mObstacleCheck.clear(); - - stopWalking(actor, storage); - moveNow = false; - walking = false; - chooseAction = true; - } -//#endif - } - - - Ogre::Radian& targetAngle = storage.mTargetAngle; - bool& rotate = storage.mRotate; - if (rotate) - { - // Reduce the turning animation glitch by using a *HUGE* value of - // epsilon... TODO: a proper fix might be in either the physics or the - // animation subsystem - if (zTurn(actor, targetAngle, Ogre::Degree(5))) - rotate = false; - } - - // Check if idle animation finished - short unsigned& playedIdle = storage.mPlayedIdle; - GreetingState& greetingState = storage.mSaidGreeting; - if(idleNow && !checkIdle(actor, playedIdle) && (greetingState == Greet_Done || greetingState == Greet_None)) - { - playedIdle = 0; - idleNow = false; - chooseAction = true; - } - - MWBase::World *world = MWBase::Environment::get().getWorld(); - - if(chooseAction) - { - playedIdle = 0; - getRandomIdle(playedIdle); // NOTE: sets mPlayedIdle with a random selection - - if(!playedIdle && mDistance) - { - chooseAction = false; - moveNow = true; - } - else - { - // Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander: - MWWorld::TimeStamp currentTime = world->getTimeStamp(); - mStartTime = currentTime; - playIdle(actor, playedIdle); - chooseAction = false; - idleNow = true; - } - } - - // Play idle voiced dialogue entries randomly - int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); - if (hello > 0 && !MWBase::Environment::get().getWorld()->isSwimming(actor) - && MWBase::Environment::get().getSoundManager()->sayDone(actor)) - { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - - static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() - .get().find("fVoiceIdleOdds")->getFloat(); - - float roll = OEngine::Misc::Rng::rollProbability() * 10000.0f; - - // In vanilla MW the chance was FPS dependent, and did not allow proper changing of fVoiceIdleOdds - // due to the roll being an integer. - // Our implementation does not have these issues, so needs to be recalibrated. We chose to - // use the chance MW would have when run at 60 FPS with the default value of the GMST for calibration. - float x = fVoiceIdleOdds * 0.6f * (MWBase::Environment::get().getFrameDuration() / 0.1f); - - // Only say Idle voices when player is in LOS - // A bit counterintuitive, likely vanilla did this to reduce the appearance of - // voices going through walls? - if (roll < x && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) - < 3000*3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead - && MWBase::Environment::get().getWorld()->getLOS(player, actor)) - MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); - } + playIdleDialogueRandomly(actor); float& lastReaction = storage.mReaction; lastReaction += duration; - if(lastReaction < REACTION_INTERVAL) + if (AI_REACTION_TIME <= lastReaction) { - return false; + lastReaction = 0; + return reactionTimeActions(actor, storage, currentCell, cellChange, pos); } else - lastReaction = 0; + return false; + } - // NOTE: everything below get updated every REACTION_INTERVAL seconds - - if(mDuration) + bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, + const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos) + { + if (isPackageCompleted(actor, storage)) { - // End package if duration is complete or mid-night hits: - MWWorld::TimeStamp currentTime = world->getTimeStamp(); - if(currentTime.getHour() >= mStartTime.getHour() + mDuration) - { - if(!mRepeat) - { - stopWalking(actor, storage); - return true; - } - else - mStartTime = currentTime; - } - else if(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay()) - { - if(!mRepeat) - { - stopWalking(actor, storage); - return true; - } - else - mStartTime = currentTime; - } + return true; } // Initialization to discover & store allowed node points for this actor. - if(!mStoredAvailableNodes) + if (mPopulateAvailableNodes) { getAllowedNodes(actor, currentCell->getCell()); } @@ -400,159 +236,361 @@ namespace MWMechanics // For stationary NPCs, move back to the starting location if another AiPackage moved us elsewhere if (cellChange) mHasReturnPosition = false; - if (mDistance == 0 && mHasReturnPosition && Ogre::Vector3(pos.pos).squaredDistance(mReturnPosition) > 20*20) + if (mDistance == 0 && mHasReturnPosition + && (pos.asVec3() - mReturnPosition).length2() > (DESTINATION_TOLERANCE * DESTINATION_TOLERANCE)) { - chooseAction = false; - idleNow = false; - - if (!storage.mPathFinder.isPathConstructed()) - { - ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition)); - - // actor position is already in world co-ordinates - ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); - - // don't take shortcuts for wandering - storage.mPathFinder.buildPath(start, dest, actor.getCell()); - - if(storage.mPathFinder.isPathConstructed()) - { - moveNow = false; - walking = true; - } - } + returnToStartLocation(actor, storage, pos); } // Allow interrupting a walking actor to trigger a greeting - if(idleNow || walking) + WanderState& wanderState = storage.mState; + if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking)) { - // Play a random voice greeting if the player gets too close - int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); - float helloDistance = static_cast(hello); - static int iGreetDistanceMultiplier =MWBase::Environment::get().getWorld()->getStore() - .get().find("iGreetDistanceMultiplier")->getInt(); - - helloDistance *= iGreetDistanceMultiplier; - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - Ogre::Vector3 playerPos(player.getRefData().getPosition().pos); - Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); - float playerDistSqr = playerPos.squaredDistance(actorPos); - - int& greetingTimer = storage.mGreetingTimer; - if (greetingState == Greet_None) - { - if ((playerDistSqr <= helloDistance*helloDistance) && - !player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor) - && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor)) - greetingTimer++; - - if (greetingTimer >= GREETING_SHOULD_START) - { - greetingState = Greet_InProgress; - MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); - greetingTimer = 0; - } - } - - if(greetingState == Greet_InProgress) - { - greetingTimer++; - - if(walking) - { - stopWalking(actor, storage); - moveNow = false; - walking = false; - mObstacleCheck.clear(); - idleNow = true; - getRandomIdle(playedIdle); - } - - if(!rotate) - { - Ogre::Vector3 dir = playerPos - actorPos; - - Ogre::Radian faceAngle = Ogre::Math::ATan2(dir.x,dir.y); - Ogre::Radian actorAngle = actor.getRefData().getBaseNode()->getOrientation().getRoll(); - // an attempt at reducing the turning animation glitch - if( Ogre::Math::Abs( faceAngle - actorAngle ) >= Ogre::Degree(5) ) // TODO: is there a better way? - { - targetAngle = faceAngle; - rotate = true; - } - } - - if (greetingTimer >= GREETING_SHOULD_END) - { - greetingState = Greet_Done; - greetingTimer = 0; - } - } - - if (greetingState == MWMechanics::AiWander::Greet_Done) - { - float resetDist = 2*helloDistance; - if (playerDistSqr >= resetDist*resetDist) - greetingState = Greet_None; - } + playGreetingIfPlayerGetsTooClose(actor, storage); } - if(moveNow && mDistance) + if ((wanderState == Wander_MoveNow) && mDistance) { // Construct a new path if there isn't one if(!storage.mPathFinder.isPathConstructed()) { - assert(mAllowedNodes.size()); - unsigned int randNode = OEngine::Misc::Rng::rollDice(mAllowedNodes.size()); - // NOTE: initially constructed with local (i.e. cell) co-ordinates - Ogre::Vector3 destNodePos(PathFinder::MakeOgreVector3(mAllowedNodes[randNode])); - - // convert dest to use world co-ordinates - ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(destNodePos)); - if (currentCell->getCell()->isExterior()) + if (mAllowedNodes.size()) { - dest.mX += currentCell->getCell()->mData.mX * ESM::Land::REAL_SIZE; - dest.mY += currentCell->getCell()->mData.mY * ESM::Land::REAL_SIZE; + setPathToAnAllowedNode(actor, storage, pos); } - - // actor position is already in world co-ordinates - ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); - - // don't take shortcuts for wandering - storage.mPathFinder.buildPath(start, dest, actor.getCell()); - - if(storage.mPathFinder.isPathConstructed()) - { - // buildPath inserts dest in case it is not a pathgraph point - // index which is a duplicate for AiWander. However below code - // does not work since getPath() returns a copy of path not a - // reference - //if(storage.mPathFinder.getPathSize() > 1) - //storage.mPathFinder.getPath().pop_back(); - - // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): - ESM::Pathgrid::Point temp = mAllowedNodes[randNode]; - mAllowedNodes.erase(mAllowedNodes.begin() + randNode); - // check if mCurrentNode was taken out of mAllowedNodes - if(mTrimCurrentNode && mAllowedNodes.size() > 1) - mTrimCurrentNode = false; - else - mAllowedNodes.push_back(mCurrentNode); - mCurrentNode = temp; - - moveNow = false; - walking = true; - } - // Choose a different node and delete this one from possible nodes because it is uncreachable: - else - mAllowedNodes.erase(mAllowedNodes.begin() + randNode); } } return false; // AiWander package not yet completed } + bool AiWander::isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage) + { + if (mDuration) + { + // End package if duration is complete or mid-night hits: + MWWorld::TimeStamp currentTime = MWBase::Environment::get().getWorld()->getTimeStamp(); + if ((currentTime.getHour() >= mStartTime.getHour() + mDuration) || + (int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay())) + { + if (!mRepeat) + { + stopWalking(actor, storage); + return true; + } + else + { + mStartTime = currentTime; + } + } + } + // if get here, not yet completed + return false; + } + + void AiWander::returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos) + { + if (!storage.mPathFinder.isPathConstructed()) + { + ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition)); + + // actor position is already in world co-ordinates + ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); + + // don't take shortcuts for wandering + storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + + if (storage.mPathFinder.isPathConstructed()) + { + storage.mState = Wander_Walking; + } + } + } + + void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos) + { + switch (storage.mState) + { + case Wander_IdleNow: + onIdleStatePerFrameActions(actor, duration, storage); + break; + + case Wander_Walking: + onWalkingStatePerFrameActions(actor, duration, storage, pos); + break; + + case Wander_ChooseAction: + onChooseActionStatePerFrameActions(actor, storage); + break; + + case Wander_MoveNow: + break; // nothing to do + + default: + // should never get here + assert(false); + break; + } + } + + void AiWander::onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage) + { + // Check if an idle actor is too close to a door - if so start walking + mDoorCheckDuration += duration; + if (mDoorCheckDuration >= DOOR_CHECK_INTERVAL) + { + mDoorCheckDuration = 0; // restart timer + if (mDistance && // actor is not intended to be stationary + proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6f*1.6f)) // NOTE: checks interior cells only + { + storage.mState = Wander_MoveNow; + mTrimCurrentNode = false; // just in case + return; + } + } + + bool& rotate = storage.mTurnActorGivingGreetingToFacePlayer; + if (rotate) + { + // Reduce the turning animation glitch by using a *HUGE* value of + // epsilon... TODO: a proper fix might be in either the physics or the + // animation subsystem + if (zTurn(actor, storage.mTargetAngleRadians, osg::DegreesToRadians(5.f))) + rotate = false; + } + + // Check if idle animation finished + GreetingState& greetingState = storage.mSaidGreeting; + if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None)) + { + storage.mState = Wander_ChooseAction; + } + } + + void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, + float duration, AiWanderStorage& storage, ESM::Position& pos) + { + // Are we there yet? + if (storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) + { + stopWalking(actor, storage); + storage.mState = Wander_ChooseAction; + mHasReturnPosition = false; + } + else + { + // have not yet reached the destination + evadeObstacles(actor, storage, duration, pos); + } + } + + void AiWander::onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage) + { + + short unsigned& idleAnimation = storage.mIdleAnimation; + idleAnimation = getRandomIdle(); + + if (!idleAnimation && mDistance) + { + storage.mState = Wander_MoveNow; + return; + } + if(idleAnimation) + { + if(std::find(storage.mBadIdles.begin(), storage.mBadIdles.end(), idleAnimation)==storage.mBadIdles.end()) + { + if(!playIdle(actor, idleAnimation)) + { + storage.mBadIdles.push_back(idleAnimation); + storage.mState = Wander_ChooseAction; + return; + } + } + } + // Recreate vanilla (broken?) behavior of resetting start time of AIWander: + mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); + storage.mState = Wander_IdleNow; + } + + void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos) + { + // turn towards the next point in mPath + zTurn(actor, storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); + + MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); + if (mObstacleCheck.check(actor, duration)) + { + // first check if we're walking into a door + if (proximityToDoor(actor)) // NOTE: checks interior cells only + { + // remove allowed points then select another random destination + mTrimCurrentNode = true; + trimAllowedNodes(mAllowedNodes, storage.mPathFinder); + mObstacleCheck.clear(); + storage.mPathFinder.clearPath(); + storage.mState = Wander_MoveNow; + } + else // probably walking into another NPC + { + // TODO: diagonal should have same animation as walk forward + // but doesn't seem to do that? + mObstacleCheck.takeEvasiveAction(movement); + } + mStuckCount++; // TODO: maybe no longer needed + } + else + { + movement.mPosition[1] = 1; + } + + // if stuck for sufficiently long, act like current location was the destination + if (mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset + { + //std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl; + mObstacleCheck.clear(); + + stopWalking(actor, storage); + storage.mState = Wander_ChooseAction; + mStuckCount = 0; + } + } + + void AiWander::playIdleDialogueRandomly(const MWWorld::Ptr& actor) + { + int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); + if (hello > 0 && !MWBase::Environment::get().getWorld()->isSwimming(actor) + && MWBase::Environment::get().getSoundManager()->sayDone(actor)) + { + MWWorld::Ptr player = getPlayer(); + + static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() + .get().find("fVoiceIdleOdds")->getFloat(); + + float roll = Misc::Rng::rollProbability() * 10000.0f; + + // In vanilla MW the chance was FPS dependent, and did not allow proper changing of fVoiceIdleOdds + // due to the roll being an integer. + // Our implementation does not have these issues, so needs to be recalibrated. We chose to + // use the chance MW would have when run at 60 FPS with the default value of the GMST for calibration. + float x = fVoiceIdleOdds * 0.6f * (MWBase::Environment::get().getFrameDuration() / 0.1f); + + // Only say Idle voices when player is in LOS + // A bit counterintuitive, likely vanilla did this to reduce the appearance of + // voices going through walls? + const ESM::Position& pos = actor.getRefData().getPosition(); + if (roll < x && (player.getRefData().getPosition().asVec3() - pos.asVec3()).length2() + < 3000 * 3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead + && MWBase::Environment::get().getWorld()->getLOS(player, actor)) + MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); + } + } + + void AiWander::playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage) + { + // Play a random voice greeting if the player gets too close + int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); + float helloDistance = static_cast(hello); + static int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore() + .get().find("iGreetDistanceMultiplier")->getInt(); + + helloDistance *= iGreetDistanceMultiplier; + + MWWorld::Ptr player = getPlayer(); + osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); + osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); + float playerDistSqr = (playerPos - actorPos).length2(); + + int& greetingTimer = storage.mGreetingTimer; + GreetingState& greetingState = storage.mSaidGreeting; + if (greetingState == Greet_None) + { + if ((playerDistSqr <= helloDistance*helloDistance) && + !player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor) + && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor)) + greetingTimer++; + + if (greetingTimer >= GREETING_SHOULD_START) + { + greetingState = Greet_InProgress; + MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); + greetingTimer = 0; + } + } + + if (greetingState == Greet_InProgress) + { + greetingTimer++; + + if (storage.mState == Wander_Walking) + { + stopWalking(actor, storage); + mObstacleCheck.clear(); + storage.mState = Wander_IdleNow; + } + + turnActorToFacePlayer(actorPos, playerPos, storage); + + if (greetingTimer >= GREETING_SHOULD_END) + { + greetingState = Greet_Done; + greetingTimer = 0; + } + } + + if (greetingState == MWMechanics::AiWander::Greet_Done) + { + float resetDist = 2 * helloDistance; + if (playerDistSqr >= resetDist*resetDist) + greetingState = Greet_None; + } + } + + void AiWander::turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage) + { + osg::Vec3f dir = playerPosition - actorPosition; + + float faceAngleRadians = std::atan2(dir.x(), dir.y()); + storage.mTargetAngleRadians = faceAngleRadians; + storage.mTurnActorGivingGreetingToFacePlayer = true; + } + + void AiWander::setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos) + { + unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size()); + ESM::Pathgrid::Point dest(mAllowedNodes[randNode]); + ToWorldCoordinates(dest, storage.mCell->getCell()); + + // actor position is already in world co-ordinates + ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos)); + + // don't take shortcuts for wandering + storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + storage.mPathFinder.buildPath(start, dest, actor.getCell()); + + if (storage.mPathFinder.isPathConstructed()) + { + // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): + ESM::Pathgrid::Point temp = mAllowedNodes[randNode]; + mAllowedNodes.erase(mAllowedNodes.begin() + randNode); + // check if mCurrentNode was taken out of mAllowedNodes + if (mTrimCurrentNode && mAllowedNodes.size() > 1) + mTrimCurrentNode = false; + else + mAllowedNodes.push_back(mCurrentNode); + mCurrentNode = temp; + + storage.mState = Wander_Walking; + } + // Choose a different node and delete this one from possible nodes because it is uncreachable: + else + mAllowedNodes.erase(mAllowedNodes.begin() + randNode); + } + + void AiWander::ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell) + { + CoordinateConverter(cell).toWorld(point); + } + void AiWander::trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder) { @@ -590,12 +628,17 @@ namespace MWMechanics actor.getClass().getMovementSettings(actor).mPosition[1] = 0; } - void AiWander::playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect) + bool AiWander::playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect) { if ((GroupIndex_MinIdle <= idleSelect) && (idleSelect <= GroupIndex_MaxIdle)) { const std::string& groupName = sIdleSelectToGroupName[idleSelect - GroupIndex_MinIdle]; - MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, groupName, 0, 1); + return MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, groupName, 0, 1); + } + else + { + std::cerr<< "Attempted to play out of range idle animation \""<().find("fIdleChanceMultiplier")->getFloat(); unsigned short idleChance = static_cast(fIdleChanceMultiplier * mIdle[counter]); - unsigned short randSelect = (int)(OEngine::Misc::Rng::rollProbability() * int(100 / fIdleChanceMultiplier)); + unsigned short randSelect = (int)(Misc::Rng::rollProbability() * int(100 / fIdleChanceMultiplier)); if(randSelect < idleChance && randSelect > idleRoll) { - playedIdle = counter+2; + selectedAnimation = counter + GroupIndex_MinIdle; idleRoll = randSelect; } } + return selectedAnimation; } void AiWander::fastForward(const MWWorld::Ptr& actor, AiState &state) @@ -645,43 +690,39 @@ namespace MWMechanics if (mDistance == 0) return; - if (!mStoredAvailableNodes) + if (mPopulateAvailableNodes) getAllowedNodes(actor, actor.getCell()->getCell()); if (mAllowedNodes.empty()) return; - if (actor.getClass().isPureWaterCreature(actor)) - return; - state.moveIn(new AiWanderStorage()); - int index = OEngine::Misc::Rng::rollDice(mAllowedNodes.size()); + int index = Misc::Rng::rollDice(mAllowedNodes.size()); ESM::Pathgrid::Point dest = mAllowedNodes[index]; - // apply a slight offset to prevent overcrowding - dest.mX += static_cast(Ogre::Math::RangeRandom(-64, 64)); - dest.mY += static_cast(Ogre::Math::RangeRandom(-64, 64)); - - if (actor.getCell()->isExterior()) - { - dest.mX += actor.getCell()->getCell()->mData.mX * ESM::Land::REAL_SIZE; - dest.mY += actor.getCell()->getCell()->mData.mY * ESM::Land::REAL_SIZE; - } + dest.mX += OffsetToPreventOvercrowding(); + dest.mY += OffsetToPreventOvercrowding(); + ToWorldCoordinates(dest, actor.getCell()->getCell()); MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), static_cast(dest.mY), static_cast(dest.mZ)); actor.getClass().adjustPosition(actor, false); // may have changed cell - mStoredAvailableNodes = false; + mPopulateAvailableNodes = true; + } + + int AiWander::OffsetToPreventOvercrowding() + { + return static_cast(DESTINATION_TOLERANCE * (Misc::Rng::rollProbability() * 2.0f - 1.0f)); } void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell) { if (!mStoredInitialActorPosition) { - mInitialActorPosition = Ogre::Vector3(actor.getRefData().getPosition().pos); + mInitialActorPosition = actor.getRefData().getPosition().asVec3(); mStoredInitialActorPosition = true; } @@ -700,72 +741,85 @@ namespace MWMechanics // actor can wander from the spawn position. AiWander assumes that // pathgrid points are available, and uses them to randomly select wander // destinations within the allowed set of pathgrid points (nodes). - if(mDistance) + // ... pathgrids don't usually include water, so swimmers ignore them + if (mDistance && !actor.getClass().isPureWaterCreature(actor)) { - float cellXOffset = 0; - float cellYOffset = 0; - if(cell->isExterior()) - { - cellXOffset = static_cast(cell->mData.mX * ESM::Land::REAL_SIZE); - cellYOffset = static_cast(cell->mData.mY * ESM::Land::REAL_SIZE); - } - - // convert npcPos to local (i.e. cell) co-ordinates - Ogre::Vector3 npcPos(mInitialActorPosition); - npcPos[0] = npcPos[0] - cellXOffset; - npcPos[1] = npcPos[1] - cellYOffset; + // get NPC's position in local (i.e. cell) co-ordinates + osg::Vec3f npcPos(mInitialActorPosition); + CoordinateConverter(cell).toLocal(npcPos); // mAllowedNodes for this actor with pathgrid point indexes based on mDistance // NOTE: mPoints and mAllowedNodes are in local co-ordinates + int pointIndex = 0; for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) { - Ogre::Vector3 nodePos(PathFinder::MakeOgreVector3(pathgrid->mPoints[counter])); - if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance) + osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter])); + if((npcPos - nodePos).length2() <= mDistance * mDistance) + { mAllowedNodes.push_back(pathgrid->mPoints[counter]); + pointIndex = counter; + } + } + if (mAllowedNodes.size() == 1) + { + AddNonPathGridAllowedPoints(npcPos, pathgrid, pointIndex); } if(!mAllowedNodes.empty()) { - Ogre::Vector3 firstNodePos(PathFinder::MakeOgreVector3(mAllowedNodes[0])); - float closestNode = npcPos.squaredDistance(firstNodePos); - unsigned int index = 0; - for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++) - { - Ogre::Vector3 nodePos(PathFinder::MakeOgreVector3(mAllowedNodes[counterThree])); - float tempDist = npcPos.squaredDistance(nodePos); - if(tempDist < closestNode) - index = counterThree; - } - mCurrentNode = mAllowedNodes[index]; - mAllowedNodes.erase(mAllowedNodes.begin() + index); + SetCurrentNodeToClosestAllowedNode(npcPos); } - - // In vanilla Morrowind, sometimes distance is too small to include at least two points, - // in which case, we will take the two closest points regardless of the wander distance - // This is a backup option, as std::sort is potentially O(n^2) in time. - if (mAllowedNodes.empty()) + } + + mPopulateAvailableNodes = false; + } + + // When only one path grid point in wander distance, + // additional points for NPC to wander to are: + // 1. NPC's initial location + // 2. Partway along the path between the point and its connected points. + void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex) + { + mAllowedNodes.push_back(PathFinder::MakePathgridPoint(npcPos)); + for (std::vector::const_iterator it = pathGrid->mEdges.begin(); it != pathGrid->mEdges.end(); ++it) + { + if (it->mV0 == pointIndex) { - // Start with list of PathGrid nodes, sorted by distance from actor - std::vector nodeDistances; - for (unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) - { - float distance = npcPos.squaredDistance(PathFinder::MakeOgreVector3(pathgrid->mPoints[counter])); - nodeDistances.push_back(std::make_pair(distance, &pathgrid->mPoints.at(counter))); - } - std::sort(nodeDistances.begin(), nodeDistances.end(), sortByDistance); - - // make closest node the current node - mCurrentNode = *nodeDistances[0].second; - - // give Actor a 2nd node to walk to - mAllowedNodes.push_back(*nodeDistances[1].second); + AddPointBetweenPathGridPoints(pathGrid->mPoints[it->mV0], pathGrid->mPoints[it->mV1]); } - mStoredAvailableNodes = true; // set only if successful in finding allowed nodes } } - bool AiWander::sortByDistance(const PathDistance& left, const PathDistance& right) + void AiWander::AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end) { - return left.first < right.first; + osg::Vec3f vectorStart = PathFinder::MakeOsgVec3(start); + osg::Vec3f delta = PathFinder::MakeOsgVec3(end) - vectorStart; + float length = delta.length(); + delta.normalize(); + + int distance = std::max(mDistance / 2, MINIMUM_WANDER_DISTANCE); + + // must not travel longer than distance between waypoints or NPC goes past waypoint + distance = std::min(distance, static_cast(length)); + delta *= distance; + mAllowedNodes.push_back(PathFinder::MakePathgridPoint(vectorStart + delta)); + } + + void AiWander::SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos) + { + float distanceToClosestNode = std::numeric_limits::max(); + unsigned int index = 0; + for (unsigned int counterThree = 0; counterThree < mAllowedNodes.size(); counterThree++) + { + osg::Vec3f nodePos(PathFinder::MakeOsgVec3(mAllowedNodes[counterThree])); + float tempDist = (npcPos - nodePos).length2(); + if (tempDist < distanceToClosestNode) + { + index = counterThree; + distanceToClosestNode = tempDist; + } + } + mCurrentNode = mAllowedNodes[index]; + mAllowedNodes.erase(mAllowedNodes.begin() + index); } void AiWander::writeState(ESM::AiSequence::AiSequence &sequence) const diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 7f8fc5088a..b0fabfce3b 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -5,8 +5,6 @@ #include -#include - #include "pathfinding.hpp" #include "obstacle.hpp" @@ -48,13 +46,13 @@ namespace MWMechanics virtual AiPackage *clone() const; - virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration); + virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration); virtual int getTypeId() const; /// Set the position to return to for a stationary (non-wandering) actor /** In case another AI package moved the actor elsewhere **/ - void setReturnPosition (const Ogre::Vector3& position); + void setReturnPosition (const osg::Vec3f& position); virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; @@ -65,14 +63,37 @@ namespace MWMechanics Greet_InProgress, Greet_Done }; + + enum WanderState { + Wander_ChooseAction, + Wander_IdleNow, + Wander_MoveNow, + Wander_Walking + }; private: // NOTE: mDistance and mDuration must be set already void init(); void stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage); - void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); + + /// Have the given actor play an idle animation + /// @return Success or error + bool playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); - void getRandomIdle(unsigned short& playedIdle); + short unsigned getRandomIdle(); + void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos); + void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage); + void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos); + void playIdleDialogueRandomly(const MWWorld::Ptr& actor); + void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage); + void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos); + void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); + void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos); + void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage); + bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, + const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos); + bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); + void returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos); int mDistance; // how far the actor can wander from the spawn point int mDuration; @@ -83,15 +104,15 @@ namespace MWMechanics bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position, // if we had the actor in the AiWander constructor... - Ogre::Vector3 mReturnPosition; + osg::Vec3f mReturnPosition; - Ogre::Vector3 mInitialActorPosition; + osg::Vec3f mInitialActorPosition; bool mStoredInitialActorPosition; - // if false triggers calculating allowed nodes based on mDistance - bool mStoredAvailableNodes; + // do we need to calculate allowed nodes based on mDistance + bool mPopulateAvailableNodes; @@ -120,15 +141,19 @@ namespace MWMechanics GroupIndex_MaxIdle = 9 }; + /// convert point from local (i.e. cell) to world co-ordinates + void ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell); + + void SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos); + + void AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex); + + void AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end); + /// lookup table for converting idleSelect value to groupName static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; - /// record distances of pathgrid point nodes to actor - /// first value is distance between actor and node, second value is PathGrid node - typedef std::pair PathDistance; - - /// used to sort array of PathDistance objects into ascending order - static bool sortByDistance(const PathDistance& left, const PathDistance& right); + static int OffsetToPreventOvercrowding(); }; diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 58c42ddb81..d39c881506 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -1,14 +1,14 @@ - #include "alchemy.hpp" #include #include +#include #include #include #include -#include +#include #include #include @@ -44,6 +44,8 @@ std::set MWMechanics::Alchemy::listEffects() const { const MWWorld::LiveCellRef *ingredient = iter->get(); + std::set seenEffects; + for (int i=0; i<4; ++i) if (ingredient->mBase->mData.mEffectID[i]!=-1) { @@ -51,7 +53,8 @@ std::set MWMechanics::Alchemy::listEffects() const ingredient->mBase->mData.mEffectID[i], ingredient->mBase->mData.mSkills[i]!=-1 ? ingredient->mBase->mData.mSkills[i] : ingredient->mBase->mData.mAttributes[i]); - ++effects[key]; + if (seenEffects.insert(key).second) + ++effects[key]; } } } @@ -296,7 +299,7 @@ void MWMechanics::Alchemy::addPotion (const std::string& name) newRecord.mName = name; - int index = OEngine::Misc::Rng::rollDice(6); + int index = Misc::Rng::rollDice(6); assert (index>=0 && index<6); static const char *meshes[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" }; @@ -418,8 +421,8 @@ int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient) return -1; for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) - if (!iter->isEmpty() && Misc::StringUtils::ciEqual(ingredient.getClass().getId(ingredient), - iter->getClass().getId(*iter))) + if (!iter->isEmpty() && Misc::StringUtils::ciEqual(ingredient.getCellRef().getRefId(), + iter->getCellRef().getRefId())) return -1; mIngredients[slot] = ingredient; @@ -460,7 +463,10 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na return Result_NoName; if (listEffects().empty()) + { + removeIngredients(); return Result_NoEffects; + } if (beginEffects() == endEffects()) { @@ -469,7 +475,7 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na return Result_RandomFailure; } - if (getAlchemyFactor() < OEngine::Misc::Rng::roll0to99()) + if (getAlchemyFactor() < Misc::Rng::roll0to99()) { removeIngredients(); return Result_RandomFailure; diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index e4b1438260..b798ff5dce 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -1,6 +1,7 @@ #include "autocalcspell.hpp" #include +#include #include "../mwworld/esmstore.hpp" @@ -181,7 +182,7 @@ namespace MWMechanics void calcWeakestSchool (const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm) { - float minChance = FLT_MAX; + float minChance = std::numeric_limits::max(); const ESM::EffectList& effects = spell->mEffects; for (std::vector::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 399b51151b..db1b82ba6e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -19,18 +19,21 @@ #include "character.hpp" -#include -#include +#include + #include "movement.hpp" #include "npcstats.hpp" #include "creaturestats.hpp" #include "security.hpp" +#include "actorutil.hpp" -#include +#include #include +#include + #include "../mwrender/animation.hpp" #include "../mwbase/environment.hpp" @@ -42,17 +45,25 @@ #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" namespace { // Wraps a value to (-PI, PI] -void wrap(Ogre::Radian& rad) +void wrap(float& rad) { - if (rad.valueRadians()>0) - rad = Ogre::Radian(std::fmod(rad.valueRadians()+Ogre::Math::PI, 2.0f*Ogre::Math::PI)-Ogre::Math::PI); + if (rad>0) + rad = std::fmod(rad+osg::PI, 2.0f*osg::PI)-osg::PI; else - rad = Ogre::Radian(std::fmod(rad.valueRadians()-Ogre::Math::PI, 2.0f*Ogre::Math::PI)+Ogre::Math::PI); + rad = std::fmod(rad-osg::PI, 2.0f*osg::PI)+osg::PI; +} + +std::string toString(int num) +{ + std::ostringstream stream; + stream << num; + return stream.str(); } std::string getBestAttack (const ESM::Weapon* weapon) @@ -219,16 +230,16 @@ public: std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) { int numAnims=0; - while (mAnimation->hasAnimation(prefix + Ogre::StringConverter::toString(numAnims+1))) + while (mAnimation->hasAnimation(prefix + toString(numAnims+1))) ++numAnims; - int roll = OEngine::Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims] + int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims] if (num) *num = roll; - return prefix + Ogre::StringConverter::toString(roll); + return prefix + toString(roll); } -void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force) +void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force) { // hit recoils/knockdown animations handling if(mPtr.getClass().isActor()) @@ -238,31 +249,38 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); if(mHitState == CharState_None) { - if (mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 + if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0) + && mAnimation->hasAnimation("knockout")) { mHitState = CharState_KnockOut; mCurrentHit = "knockout"; - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, false, 1, "start", "stop", 0.0f, ~0ul); + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); } - else if(knockdown) + else if(knockdown && mAnimation->hasAnimation("knockdown")) { mHitState = CharState_KnockDown; mCurrentHit = "knockdown"; - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0); + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); } else if (recovery) { - mHitState = CharState_Hit; - mCurrentHit = chooseRandomGroup("hit"); - mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0); + std::string anim = chooseRandomGroup("hit"); + if (mAnimation->hasAnimation(anim)) + { + mHitState = CharState_Hit; + mCurrentHit = anim; + mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); + } } - else if (block) + else if (block && mAnimation->hasAnimation("shield")) { mHitState = CharState_Block; mCurrentHit = "shield"; - mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "block start", "block stop", 0.0f, 0); + MWRender::Animation::AnimPriority priorityBlock (Priority_Hit); + priorityBlock[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block; + mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0); } // Cancel upper body animations @@ -295,7 +313,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat { mHitState = CharState_KnockDown; mAnimation->disable(mCurrentHit); - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "loop stop", "stop", 0.0f, 0); + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); } } @@ -303,40 +321,41 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if (!mPtr.getClass().isBipedal(mPtr)) weap = sWeaponTypeListEnd; - if(force && mJumpState != JumpState_None) + if(force || jump != mJumpState) { - std::string jump; - MWRender::Animation::Group jumpgroup = MWRender::Animation::Group_All; + bool startAtLoop = (jump == mJumpState); + mJumpState = jump; + + std::string jumpAnimName; + MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; if(mJumpState != JumpState_None) { - jump = "jump"; + jumpAnimName = "jump"; if(weap != sWeaponTypeListEnd) { - jump += weap->shortgroup; - if(!mAnimation->hasAnimation(jump)) + jumpAnimName += weap->shortgroup; + if(!mAnimation->hasAnimation(jumpAnimName)) { - jumpgroup = MWRender::Animation::Group_LowerBody; - jump = "jump"; + jumpmask = MWRender::Animation::BlendMask_LowerBody; + jumpAnimName = "jump"; } } } if(mJumpState == JumpState_InAir) { - int mode = ((jump == mCurrentJump) ? 2 : 1); - mAnimation->disable(mCurrentJump); - mCurrentJump = jump; + mCurrentJump = jumpAnimName; if (mAnimation->hasAnimation("jump")) - mAnimation->play(mCurrentJump, Priority_Jump, jumpgroup, false, - 1.0f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); + mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false, + 1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul); } else { mAnimation->disable(mCurrentJump); mCurrentJump.clear(); if (mAnimation->hasAnimation("jump")) - mAnimation->play(jump, Priority_Jump, jumpgroup, true, + mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0); } } @@ -345,65 +364,72 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat { mMovementState = movement; - std::string movement; - MWRender::Animation::Group movegroup = MWRender::Animation::Group_All; + std::string movementAnimName; + MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState)); if(movestate != sMovementListEnd) { - movement = movestate->groupname; - if(weap != sWeaponTypeListEnd && movement.find("swim") == std::string::npos) + movementAnimName = movestate->groupname; + if(weap != sWeaponTypeListEnd && movementAnimName.find("swim") == std::string::npos) { - movement += weap->shortgroup; - if(!mAnimation->hasAnimation(movement)) + movementAnimName += weap->shortgroup; + if(!mAnimation->hasAnimation(movementAnimName)) { - movegroup = MWRender::Animation::Group_LowerBody; - movement = movestate->groupname; + movemask = MWRender::Animation::BlendMask_LowerBody; + movementAnimName = movestate->groupname; } } - if(!mAnimation->hasAnimation(movement)) + if(!mAnimation->hasAnimation(movementAnimName)) { - std::string::size_type swimpos = movement.find("swim"); + std::string::size_type swimpos = movementAnimName.find("swim"); if(swimpos == std::string::npos) { - std::string::size_type runpos = movement.find("run"); + std::string::size_type runpos = movementAnimName.find("run"); if (runpos != std::string::npos) { - movement.replace(runpos, runpos+3, "walk"); - if (!mAnimation->hasAnimation(movement)) - movement.clear(); + movementAnimName.replace(runpos, runpos+3, "walk"); + if (!mAnimation->hasAnimation(movementAnimName)) + movementAnimName.clear(); } else - movement.clear(); + movementAnimName.clear(); } else { - movegroup = MWRender::Animation::Group_LowerBody; - movement.erase(swimpos, 4); - if(!mAnimation->hasAnimation(movement)) - movement.clear(); + movementAnimName.erase(swimpos, 4); + if (weap != sWeaponTypeListEnd) + { + std::string weapMovementAnimName = movementAnimName + weap->shortgroup; + if(mAnimation->hasAnimation(weapMovementAnimName)) + movementAnimName = weapMovementAnimName; + else + movemask = MWRender::Animation::BlendMask_LowerBody; + } + + if (!mAnimation->hasAnimation(movementAnimName)) + movementAnimName.clear(); } } } /* If we're playing the same animation, restart from the loop start instead of the * beginning. */ - int mode = ((movement == mCurrentMovement) ? 2 : 1); + int mode = ((movementAnimName == mCurrentMovement) ? 2 : 1); mMovementAnimationControlled = true; mAnimation->disable(mCurrentMovement); - mCurrentMovement = movement; + mCurrentMovement = movementAnimName; if(!mCurrentMovement.empty()) { - float vel, speedmult = 1.0f; - bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !MWBase::Environment::get().getWorld()->isFlying(mPtr); // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. std::string anim = mCurrentMovement; + mAdjustMovementAnimSpeed = true; if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() && !(mPtr.get()->mBase->mFlags & ESM::Creature::Flies)) { @@ -411,36 +437,34 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat const StateInfo *stateinfo = std::find_if(sMovementList, sMovementListEnd, FindCharState(walkState)); anim = stateinfo->groupname; - if (mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f) - speedmult = mMovementSpeed / vel; - else + mMovementAnimSpeed = mAnimation->getVelocity(anim); + if (mMovementAnimSpeed <= 1.0f) + { // Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward), // then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist // we will play without any scaling. // Makes the speed attribute of most water creatures totally useless. // And again, this can not be fixed without patching game data. - speedmult = 1.f; + mAdjustMovementAnimSpeed = false; + mMovementAnimSpeed = 1.f; + } } else { - if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f) - { - speedmult = mMovementSpeed / vel; - } - else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) - speedmult = 1.f; // adjusted each frame - else if (mMovementSpeed > 0.0f) + mMovementAnimSpeed = mAnimation->getVelocity(anim); + + if (mMovementAnimSpeed <= 1.0f) { // The first person anims don't have any velocity to calculate a speed multiplier from. // We use the third person velocities instead. // FIXME: should be pulled from the actual animation, but it is not presently loaded. - speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f); + mMovementAnimSpeed = (isrunning ? 222.857f : 154.064f); mMovementAnimationControlled = false; } } - mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, - speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); + mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false, + 1.f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); } } @@ -448,7 +472,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), // the idle animation should be displayed if ((mUpperBodyState != UpperCharState_Nothing - || mMovementState != CharState_None + || (mMovementState != CharState_None && mMovementState != CharState_TurnLeft && mMovementState != CharState_TurnRight) || mHitState != CharState_None) && !mPtr.getClass().isBipedal(mPtr)) idle = CharState_None; @@ -458,12 +482,19 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat mIdleState = idle; std::string idle; + MWRender::Animation::AnimPriority idlePriority (Priority_Default); // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to // "idle"+weapon or "idle". if(mIdleState == CharState_IdleSwim && mAnimation->hasAnimation("idleswim")) + { idle = "idleswim"; + idlePriority = Priority_SwimIdle; + } else if(mIdleState == CharState_IdleSneak && mAnimation->hasAnimation("idlesneak")) + { idle = "idlesneak"; + idlePriority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody; + } else if(mIdleState != CharState_None) { idle = "idle"; @@ -478,11 +509,9 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat mAnimation->disable(mCurrentIdle); mCurrentIdle = idle; if(!mCurrentIdle.empty()) - mAnimation->play(mCurrentIdle, Priority_Default, MWRender::Animation::Group_All, false, + mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, ~0ul, true); } - - updateIdleStormState(); } @@ -491,6 +520,8 @@ void getWeaponGroup(WeaponType weaptype, std::string &group) const WeaponInfo *info = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(weaptype)); if(info != sWeaponTypeListEnd) group = info->longgroup; + else + group.clear(); } @@ -568,7 +599,7 @@ void CharacterController::playDeath(float startpoint, CharacterState death) mCurrentDeath = "deathknockout"; break; default: - mCurrentDeath = "death" + Ogre::StringConverter::toString(death - CharState_Death1 + 1); + mCurrentDeath = "death" + toString(death - CharState_Death1 + 1); } mDeathState = death; @@ -594,13 +625,13 @@ void CharacterController::playDeath(float startpoint, CharacterState death) mCurrentJump = ""; mMovementAnimationControlled = true; - mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, + mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startpoint, 0); } void CharacterController::playRandomDeath(float startpoint) { - if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mPtr == getPlayer()) { // The first-person animations do not include death, so we need to // force-switch to third person before playing the death animation. @@ -633,7 +664,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mAnimation(anim) , mIdleState(CharState_None) , mMovementState(CharState_None) - , mMovementSpeed(0.0f) + , mAdjustMovementAnimSpeed(false) , mHasMovedInXY(false) , mMovementAnimationControlled(true) , mDeathState(CharState_None) @@ -641,20 +672,24 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mUpperBodyState(UpperCharState_Nothing) , mJumpState(JumpState_None) , mWeaponType(WeapType_None) + , mAttackStrength(0.f) , mSkipAnim(false) , mSecondsOfSwimming(0) , mSecondsOfRunning(0) , mTurnAnimationThreshold(0) + , mAttackingOrSpell(false) { if(!mAnimation) return; + mAnimation->setTextKeyListener(this); + const MWWorld::Class &cls = mPtr.getClass(); if(cls.isActor()) { /* Accumulate along X/Y only for now, until we can figure out how we should * handle knockout and death which moves the character down. */ - mAnimation->setAccumulation(Ogre::Vector3(1.0f, 1.0f, 0.0f)); + mAnimation->setAccumulation(osg::Vec3f(1.0f, 1.0f, 0.0f)); if (cls.hasInventoryStore(mPtr)) { @@ -678,54 +713,194 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim mIdleState = CharState_Idle; else { - int deathindex = mPtr.getClass().getCreatureStats(mPtr).getDeathAnimation(); - playDeath(1.0f, CharacterState(CharState_Death1 + deathindex)); + // Set the death state, but don't play it yet + // We will play it in the first frame, but only if no script set the skipAnim flag + mDeathState = static_cast(CharState_Death1 + mPtr.getClass().getCreatureStats(mPtr).getDeathAnimation()); } } else { /* Don't accumulate with non-actors. */ - mAnimation->setAccumulation(Ogre::Vector3(0.0f)); + mAnimation->setAccumulation(osg::Vec3f(0.f, 0.f, 0.f)); mIdleState = CharState_Idle; } if(mDeathState == CharState_None) - refreshCurrentAnims(mIdleState, mMovementState, true); + refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); mAnimation->runAnimation(0.f); } CharacterController::~CharacterController() { + if (mAnimation) + mAnimation->setTextKeyListener(NULL); } +void split(const std::string &s, char delim, std::vector &elems) { + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } +} + +void CharacterController::handleTextKey(const std::string &groupname, const std::multimap::const_iterator &key, const std::multimap &map) +{ + const std::string &evt = key->second; + + if(evt.compare(0, 7, "sound: ") == 0) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f); + return; + } + if(evt.compare(0, 10, "soundgen: ") == 0) + { + std::string soundgen = evt.substr(10); + + // The event can optionally contain volume and pitch modifiers + float volume=1.f, pitch=1.f; + if (soundgen.find(" ") != std::string::npos) + { + std::vector tokens; + split(soundgen, ' ', tokens); + soundgen = tokens[0]; + if (tokens.size() >= 2) + { + std::stringstream stream; + stream << tokens[1]; + stream >> volume; + } + if (tokens.size() >= 3) + { + std::stringstream stream; + stream << tokens[2]; + stream >> pitch; + } + } + + std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen); + if(!sound.empty()) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(evt.compare(10, evt.size()-10, "left") == 0 || evt.compare(10, evt.size()-10, "right") == 0 || evt.compare(10, evt.size()-10, "land") == 0) + { + // Don't make foot sounds local for the player, it makes sense to keep them + // positioned on the ground. + sndMgr->playSound3D(mPtr, sound, volume, pitch, MWBase::SoundManager::Play_TypeFoot, + MWBase::SoundManager::Play_NoPlayerLocal); + } + else + sndMgr->playSound3D(mPtr, sound, volume, pitch); + } + return; + } + + if(evt.compare(0, groupname.size(), groupname) != 0 || + evt.compare(groupname.size(), 2, ": ") != 0) + { + // Not ours, skip it + return; + } + size_t off = groupname.size()+2; + size_t len = evt.size() - off; + + if(evt.compare(off, len, "equip attach") == 0) + mAnimation->showWeapons(true); + else if(evt.compare(off, len, "unequip detach") == 0) + mAnimation->showWeapons(false); + else if(evt.compare(off, len, "chop hit") == 0) + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); + else if(evt.compare(off, len, "slash hit") == 0) + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); + else if(evt.compare(off, len, "thrust hit") == 0) + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); + else if(evt.compare(off, len, "hit") == 0) + { + if (groupname == "attack1") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); + else if (groupname == "attack2") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); + else if (groupname == "attack3") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); + else + mPtr.getClass().hit(mPtr, mAttackStrength); + } + else if (!groupname.empty() && groupname.compare(0, groupname.size()-1, "attack") == 0 + && evt.compare(off, len, "start") == 0) + { + std::multimap::const_iterator hitKey = key; + + // Not all animations have a hit key defined. If there is none, the hit happens with the start key. + bool hasHitKey = false; + while (hitKey != map.end()) + { + if (hitKey->second == groupname + ": hit") + { + hasHitKey = true; + break; + } + if (hitKey->second == groupname + ": stop") + break; + ++hitKey; + } + if (!hasHitKey) + { + if (groupname == "attack1") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); + else if (groupname == "attack2") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); + else if (groupname == "attack3") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); + } + } + else if (evt.compare(off, len, "shoot attach") == 0) + mAnimation->attachArrow(); + else if (evt.compare(off, len, "shoot release") == 0) + mAnimation->releaseArrow(mAttackStrength); + else if (evt.compare(off, len, "shoot follow attach") == 0) + mAnimation->attachArrow(); + + else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release" + // Make sure this key is actually for the RangeType we are casting. The flame atronach has + // the same animation for all range types, so there are 3 "release" keys on the same time, one for each range type. + && evt.compare(off, len, mAttackType + " release") == 0) + { + MWBase::Environment::get().getWorld()->castSpell(mPtr); + } + + else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0) + mPtr.getClass().block(mPtr); +} void CharacterController::updatePtr(const MWWorld::Ptr &ptr) { mPtr = ptr; } -void CharacterController::updateIdleStormState() +void CharacterController::updateIdleStormState(bool inwater) { bool inStormDirection = false; if (MWBase::Environment::get().getWorld()->isInStorm()) { - Ogre::Vector3 stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); - Ogre::Vector3 characterDirection = mPtr.getRefData().getBaseNode()->getOrientation().yAxis(); - inStormDirection = stormDirection.angleBetween(characterDirection) > Ogre::Degree(120); + osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); + osg::Vec3f characterDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); + inStormDirection = std::acos(stormDirection * characterDirection / (stormDirection.length() * characterDirection.length())) + > osg::DegreesToRadians(120.f); } - if (inStormDirection && mUpperBodyState == UpperCharState_Nothing && mAnimation->hasAnimation("idlestorm")) + if (inStormDirection && !inwater && mUpperBodyState == UpperCharState_Nothing && mAnimation->hasAnimation("idlestorm")) { float complete = 0; mAnimation->getInfo("idlestorm", &complete); if (complete == 0) - mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, false, + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false, 1.0f, "start", "loop start", 0.0f, 0); else if (complete == 1) - mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, false, + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false, 1.0f, "loop start", "loop stop", 0.0f, ~0ul); } else @@ -736,7 +911,7 @@ void CharacterController::updateIdleStormState() { if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop")) { - mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, true, + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, true, 1.0f, "loop stop", "stop", 0.0f, 0); } } @@ -792,7 +967,7 @@ bool CharacterController::updateCreatureState() mAnimation->disable(mCurrentWeapon); } - if(stats.getAttackingOrSpell()) + if(mAttackingOrSpell) { if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None) { @@ -831,7 +1006,7 @@ bool CharacterController::updateCreatureState() } if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation { - int roll = OEngine::Misc::Rng::rollDice(3); // [0, 2] + int roll = Misc::Rng::rollDice(3); // [0, 2] if (roll == 0) mCurrentWeapon = "attack1"; else if (roll == 1) @@ -843,14 +1018,16 @@ bool CharacterController::updateCreatureState() if (!mCurrentWeapon.empty()) { mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_All, true, + MWRender::Animation::BlendMask_All, true, 1, startKey, stopKey, 0.0f, 0); mUpperBodyState = UpperCharState_StartToMinAttack; + + mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); } } - stats.setAttackingOrSpell(false); + mAttackingOrSpell = false; } bool animPlaying = mAnimation->getInfo(mCurrentWeapon); @@ -903,6 +1080,9 @@ bool CharacterController::updateWeaponState() } } + MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); + priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; + bool forcestateupdate = false; if(weaptype != mWeaponType && mHitState != CharState_KnockDown && mHitState != CharState_KnockOut && mHitState != CharState_Hit) @@ -914,11 +1094,14 @@ bool CharacterController::updateWeaponState() std::string weapgroup; if(weaptype == WeapType_None) { - getWeaponGroup(mWeaponType, weapgroup); - mAnimation->play(weapgroup, Priority_Weapon, - MWRender::Animation::Group_UpperBody, true, - 1.0f, "unequip start", "unequip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_UnEquipingWeap; + if ((!isWerewolf || mWeaponType != WeapType_Spell)) + { + getWeaponGroup(mWeaponType, weapgroup); + mAnimation->play(weapgroup, priorityWeapon, + MWRender::Animation::BlendMask_All, true, + 1.0f, "unequip start", "unequip stop", 0.0f, 0); + mUpperBodyState = UpperCharState_UnEquipingWeap; + } } else { @@ -926,8 +1109,8 @@ bool CharacterController::updateWeaponState() mAnimation->showWeapons(false); mAnimation->setWeaponGroup(weapgroup); - mAnimation->play(weapgroup, Priority_Weapon, - MWRender::Animation::Group_UpperBody, true, + mAnimation->play(weapgroup, priorityWeapon, + MWRender::Animation::BlendMask_All, true, 1.0f, "equip start", "equip stop", 0.0f, 0); mUpperBodyState = UpperCharState_EquipingWeap; @@ -995,9 +1178,9 @@ bool CharacterController::updateWeaponState() float complete; bool animPlaying; - if(stats.getAttackingOrSpell()) + if(mAttackingOrSpell) { - if(mUpperBodyState == UpperCharState_WeapEquiped && mHitState == CharState_None) + if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) { MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); mAttackType.clear(); @@ -1005,14 +1188,18 @@ bool CharacterController::updateWeaponState() { // Unset casting flag, otherwise pressing the mouse button down would // continue casting every frame if there is no animation - stats.setAttackingOrSpell(false); + mAttackingOrSpell = false; + if (mPtr == getPlayer()) + { + MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); + } const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); // For the player, set the spell we want to cast // This has to be done at the start of the casting animation, // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) - if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mPtr == getPlayer()) { std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); stats.getSpells().setSelectedSpell(selectedSpell); @@ -1030,14 +1217,10 @@ bool CharacterController::updateWeaponState() effect = store.get().find(effectentry.mEffectID); const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); - if (mAnimation->getNode("Left Hand")) - mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Left Hand", effect->mParticle); - else + if (mAnimation->getNode("Bip01 L Hand")) mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle); - if (mAnimation->getNode("Right Hand")) - mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Right Hand", effect->mParticle); - else + if (mAnimation->getNode("Bip01 R Hand")) mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle); switch(effectentry.mRange) @@ -1047,8 +1230,8 @@ bool CharacterController::updateWeaponState() case 2: mAttackType = "target"; break; } - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, true, + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, true, weapSpeed, mAttackType+" start", mAttackType+" stop", 0.0f, 0); mUpperBodyState = UpperCharState_CastingSpell; @@ -1079,8 +1262,8 @@ bool CharacterController::updateWeaponState() else if(item.getTypeName() == typeid(ESM::Probe).name()) Security(mPtr).probeTrap(target, item, resultMessage, resultSound); } - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, true, + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, true, 1.0f, "start", "stop", 0.0, 0); mUpperBodyState = UpperCharState_FollowStartToFollowStop; @@ -1096,7 +1279,7 @@ bool CharacterController::updateWeaponState() mAttackType = "shoot"; else { - if(isWeapon && mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr() && + if(isWeapon && mPtr == getPlayer() && Settings::Manager::getBool("best attack", "Game")) { MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); @@ -1106,8 +1289,8 @@ bool CharacterController::updateWeaponState() determineAttackType(); } - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, false, + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, weapSpeed, mAttackType+" start", mAttackType+" min attack", 0.0f, 0); mUpperBodyState = UpperCharState_StartToMinAttack; @@ -1115,6 +1298,8 @@ bool CharacterController::updateWeaponState() } animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); + if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && mHitState != CharState_KnockDown) + mAttackStrength = complete; } else { @@ -1127,7 +1312,7 @@ bool CharacterController::updateWeaponState() // most creatures don't actually have an attack wind-up animation, so use a uniform random value // (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings) // Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far. - attackStrength = std::min(1.f, 0.1f + OEngine::Misc::Rng::rollClosedProbability()); + attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); } if(mWeaponType != WeapType_Crossbow && mWeaponType != WeapType_BowAndArrow) @@ -1152,11 +1337,11 @@ bool CharacterController::updateWeaponState() sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack } } - stats.setAttackStrength(attackStrength); + mAttackStrength = attackStrength; mAnimation->disable(mCurrentWeapon); - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, false, + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, weapSpeed, mAttackType+" max attack", mAttackType+" min hit", 1.0f-complete, 0); @@ -1225,15 +1410,6 @@ bool CharacterController::updateWeaponState() mAnimation->attachArrow(); mUpperBodyState = UpperCharState_WeapEquiped; - //don't allow to continue playing hit animation on UpperBody after actor had attacked during it - if(mHitState == CharState_Hit) - { - mAnimation->changeGroups(mCurrentHit, MWRender::Animation::Group_LowerBody); - //commenting out following 2 lines will give a bit different combat dynamics(slower) - mHitState = CharState_None; - mCurrentHit.clear(); - mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false); - } } else if(mUpperBodyState == UpperCharState_UnEquipingWeap) mUpperBodyState = UpperCharState_Nothing; @@ -1251,8 +1427,8 @@ bool CharacterController::updateWeaponState() case UpperCharState_MinAttackToMaxAttack: //hack to avoid body pos desync when jumping/sneaking in 'max attack' state if(!mAnimation->isPlaying(mCurrentWeapon)) - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, false, + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0); break; case UpperCharState_MaxAttackToMinHit: @@ -1276,7 +1452,7 @@ bool CharacterController::updateWeaponState() } else { - float str = stats.getAttackStrength(); + float str = mAttackStrength; start = mAttackType+((str < 0.5f) ? " small follow start" : (str < 1.0f) ? " medium follow start" : " large follow start"); @@ -1294,33 +1470,16 @@ bool CharacterController::updateWeaponState() { mAnimation->disable(mCurrentWeapon); if (mUpperBodyState == UpperCharState_FollowStartToFollowStop) - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, true, + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, true, weapSpeed, start, stop, 0.0f, 0); else - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::Group_UpperBody, false, + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, weapSpeed, start, stop, 0.0f, 0); } } - //if playing combat animation and lowerbody is not busy switch to whole body animation - if((weaptype != WeapType_None || mUpperBodyState == UpperCharState_UnEquipingWeap) && animPlaying) - { - if( mMovementState != CharState_None || - mJumpState != JumpState_None || - mHitState != CharState_None || - MWBase::Environment::get().getWorld()->isSwimming(mPtr) || - cls.getCreatureStats(mPtr).getMovementFlag(CreatureStats::Flag_Sneak)) - { - mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_UpperBody); - } - else - { - mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_All); - } - } - if (mPtr.getClass().hasInventoryStore(mPtr)) { MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); @@ -1329,7 +1488,7 @@ bool CharacterController::updateWeaponState() && updateCarriedLeftVisible(mWeaponType)) { - mAnimation->play("torch", Priority_Torch, MWRender::Animation::Group_LeftArm, + mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true); } else if (mAnimation->isPlaying("torch")) @@ -1338,6 +1497,8 @@ bool CharacterController::updateWeaponState() } } + mAnimation->setAccurateAiming(mUpperBodyState > UpperCharState_WeapEquiped); + return forcestateupdate; } @@ -1345,7 +1506,8 @@ void CharacterController::update(float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Class &cls = mPtr.getClass(); - Ogre::Vector3 movement(0.0f); + osg::Vec3f movement(0.f, 0.f, 0.f); + float speed = 0.f; updateMagicEffects(); @@ -1359,7 +1521,7 @@ void CharacterController::update(float duration) mAnimQueue.pop_front(); mAnimation->play(mAnimQueue.front().first, Priority_Default, - MWRender::Animation::Group_All, false, + MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().second); } } @@ -1402,28 +1564,30 @@ void CharacterController::update(float duration) } } - Ogre::Vector3 vec(cls.getMovementSettings(mPtr).mPosition); - vec.normalise(); + osg::Vec3f vec(cls.getMovementSettings(mPtr).asVec3()); + vec.normalize(); if(mHitState != CharState_None && mJumpState == JumpState_None) - vec = Ogre::Vector3(0.0f); - Ogre::Vector3 rot = cls.getRotationVector(mPtr); + vec = osg::Vec3f(0.f, 0.f, 0.f); + osg::Vec3f rot = cls.getRotationVector(mPtr); - mMovementSpeed = cls.getSpeed(mPtr); + speed = cls.getSpeed(mPtr); - vec.x *= mMovementSpeed; - vec.y *= mMovementSpeed; + vec.x() *= speed; + vec.y() *= speed; CharacterState movestate = CharState_None; CharacterState idlestate = CharState_SpecialIdle; + JumpingState jumpstate = JumpState_None; + bool forcestateupdate = false; - mHasMovedInXY = std::abs(vec[0])+std::abs(vec[1]) > 0.0f; + mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f; isrunning = isrunning && mHasMovedInXY; // advance athletics - if(mHasMovedInXY && mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(mHasMovedInXY && mPtr == getPlayer()) { if(inwater) { @@ -1481,7 +1645,7 @@ void CharacterController::update(float duration) cls.getCreatureStats(mPtr).setFatigue(fatigue); if(sneak || inwater || flying) - vec.z = 0.0f; + vec.z() = 0.0f; if (inwater || flying) cls.getCreatureStats(mPtr).land(); @@ -1498,32 +1662,33 @@ void CharacterController::update(float duration) } forcestateupdate = (mJumpState != JumpState_InAir); - mJumpState = JumpState_InAir; + jumpstate = JumpState_InAir; static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat(); static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat(); float factor = fJumpMoveBase + fJumpMoveMult * mPtr.getClass().getSkill(mPtr, ESM::Skill::Acrobatics)/100.f; factor = std::min(1.f, factor); - vec.x *= factor; - vec.y *= factor; - vec.z = 0.0f; + vec.x() *= factor; + vec.y() *= factor; + vec.z() = 0.0f; } - else if(vec.z > 0.0f && mJumpState == JumpState_None) + else if(vec.z() > 0.0f && mJumpState == JumpState_None) { // Started a jump. float z = cls.getJump(mPtr); if (z > 0) { - if(vec.x == 0 && vec.y == 0) - vec = Ogre::Vector3(0.0f, 0.0f, z); + if(vec.x() == 0 && vec.y() == 0) + vec = osg::Vec3f(0.0f, 0.0f, z); else { - Ogre::Vector3 lat = Ogre::Vector3(vec.x, vec.y, 0.0f).normalisedCopy(); - vec = Ogre::Vector3(lat.x, lat.y, 1.0f) * z * 0.707f; + osg::Vec3f lat (vec.x(), vec.y(), 0.0f); + lat.normalize(); + vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f; } // advance acrobatics - if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mPtr == getPlayer()) cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); // decrease fatigue @@ -1542,8 +1707,8 @@ void CharacterController::update(float duration) else if(mJumpState == JumpState_InAir) { forcestateupdate = true; - mJumpState = JumpState_Landing; - vec.z = 0.0f; + jumpstate = JumpState_Landing; + vec.z() = 0.0f; float height = cls.getCreatureStats(mPtr).land(); float healthLost = getFallDamage(mPtr, height); @@ -1566,48 +1731,45 @@ void CharacterController::update(float duration) else { // report acrobatics progression - if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mPtr == getPlayer()) cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); } } } else { - mJumpState = JumpState_None; - vec.z = 0.0f; + jumpstate = JumpState_None; + vec.z() = 0.0f; inJump = false; - if(std::abs(vec.x/2.0f) > std::abs(vec.y)) + if(std::abs(vec.x()/2.0f) > std::abs(vec.y())) { - if(vec.x > 0.0f) + if(vec.x() > 0.0f) movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) : (sneak ? CharState_SneakRight : (isrunning ? CharState_RunRight : CharState_WalkRight))); - else if(vec.x < 0.0f) + else if(vec.x() < 0.0f) movestate = (inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft) : (sneak ? CharState_SneakLeft : (isrunning ? CharState_RunLeft : CharState_WalkLeft))); } - else if(vec.y != 0.0f) + else if(vec.y() != 0.0f) { - if(vec.y > 0.0f) + if(vec.y() > 0.0f) movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) : (sneak ? CharState_SneakForward : (isrunning ? CharState_RunForward : CharState_WalkForward))); - else if(vec.y < 0.0f) + else if(vec.y() < 0.0f) movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) : (sneak ? CharState_SneakBack : (isrunning ? CharState_RunBack : CharState_WalkBack))); } - // Don't play turning animations during attack. It would break positioning of the arrow bone when releasing a shot. - // Actually, in vanilla the turning animation is not even played when merely having equipped the weapon, - // but I don't think we need to go as far as that. - else if(rot.z != 0.0f && !inwater && !sneak && mUpperBodyState < UpperCharState_StartToMinAttack) + else if(rot.z() != 0.0f && !inwater && !sneak && !MWBase::Environment::get().getWorld()->isFirstPerson()) { - if(rot.z > 0.0f) + if(rot.z() > 0.0f) movestate = CharState_TurnRight; - else if(rot.z < 0.0f) + else if(rot.z() < 0.0f) movestate = CharState_TurnLeft; } } @@ -1627,9 +1789,9 @@ void CharacterController::update(float duration) if(movestate != CharState_None) clearAnimQueue(); - if(mAnimQueue.empty()) + if(mAnimQueue.empty() || inwater || sneak) { - idlestate = (inwater ? CharState_IdleSwim : (sneak ? CharState_IdleSneak : CharState_Idle)); + idlestate = (inwater ? CharState_IdleSwim : (sneak && !inJump ? CharState_IdleSneak : CharState_Idle)); } else if(mAnimQueue.size() > 1) { @@ -1639,43 +1801,52 @@ void CharacterController::update(float duration) mAnimQueue.pop_front(); mAnimation->play(mAnimQueue.front().first, Priority_Default, - MWRender::Animation::Group_All, false, + MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().second); } } - if(cls.isBipedal(mPtr)) - forcestateupdate = updateWeaponState() || forcestateupdate; - else - forcestateupdate = updateCreatureState() || forcestateupdate; - if (!mSkipAnim) - refreshCurrentAnims(idlestate, movestate, forcestateupdate); + { + // bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used. + if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr)) + forcestateupdate = updateWeaponState() || forcestateupdate; + else + forcestateupdate = updateCreatureState() || forcestateupdate; + + refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate); + updateIdleStormState(inwater); + } + if (inJump) mMovementAnimationControlled = false; if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) { if (duration > 0) - mAnimation->adjustSpeedMult(mCurrentMovement, std::min(1.5f, std::abs(rot.z) / duration / Ogre::Math::PI)); + mAnimation->adjustSpeedMult(mCurrentMovement, std::min(1.5f, std::abs(rot.z()) / duration / static_cast(osg::PI))); + } + else if (mMovementState != CharState_None && mAdjustMovementAnimSpeed) + { + float speedmult = speed / mMovementAnimSpeed; + mAnimation->adjustSpeedMult(mCurrentMovement, speedmult); } if (!mSkipAnim) { - rot *= Ogre::Math::RadiansToDegrees(1.0f); if(mHitState != CharState_KnockDown && mHitState != CharState_KnockOut) { - world->rotateObject(mPtr, rot.x, rot.y, rot.z, true); + world->rotateObject(mPtr, rot.x(), rot.y(), rot.z(), true); } else //avoid z-rotating for knockdown - world->rotateObject(mPtr, rot.x, rot.y, 0.0f, true); + world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true); if (!mMovementAnimationControlled) world->queueMovement(mPtr, vec); } else // We must always queue movement, even if there is none, to apply gravity. - world->queueMovement(mPtr, Ogre::Vector3(0.0f)); + world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); movement = vec; cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0; @@ -1687,29 +1858,36 @@ void CharacterController::update(float duration) } else if(cls.getCreatureStats(mPtr).isDead()) { - world->queueMovement(mPtr, Ogre::Vector3(0.0f)); + // initial start of death animation for actors that started the game as dead + // not done in constructor since we need to give scripts a chance to set the mSkipAnim flag + if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty()) + { + playDeath(1.f, mDeathState); + } + + world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); } - Ogre::Vector3 moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration); + osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration); if(duration > 0.0f) moved /= duration; else - moved = Ogre::Vector3(0.0f); + moved = osg::Vec3f(0.f, 0.f, 0.f); // Ensure we're moving in generally the right direction... - if(mMovementSpeed > 0.f) + if(speed > 0.f) { float l = moved.length(); - if((movement.x < 0.0f && movement.x < moved.x*2.0f) || - (movement.x > 0.0f && movement.x > moved.x*2.0f)) - moved.x = movement.x; - if((movement.y < 0.0f && movement.y < moved.y*2.0f) || - (movement.y > 0.0f && movement.y > moved.y*2.0f)) - moved.y = movement.y; - if((movement.z < 0.0f && movement.z < moved.z*2.0f) || - (movement.z > 0.0f && movement.z > moved.z*2.0f)) - moved.z = movement.z; + if((movement.x() < 0.0f && movement.x() < moved.x()*2.0f) || + (movement.x() > 0.0f && movement.x() > moved.x()*2.0f)) + moved.x() = movement.x(); + if((movement.y() < 0.0f && movement.y() < moved.y()*2.0f) || + (movement.y() > 0.0f && movement.y() > moved.y()*2.0f)) + moved.y() = movement.y(); + if((movement.z() < 0.0f && movement.z() < moved.z()*2.0f) || + (movement.z() > 0.0f && movement.z() > moved.z()*2.0f)) + moved.z() = movement.z(); // but keep the original speed float newLength = moved.length(); if (newLength > 0) @@ -1729,10 +1907,13 @@ void CharacterController::update(float duration) } -void CharacterController::playGroup(const std::string &groupname, int mode, int count) +bool CharacterController::playGroup(const std::string &groupname, int mode, int count) { if(!mAnimation || !mAnimation->hasAnimation(groupname)) - std::cerr<< "Animation "<play(groupname, Priority_Default, - MWRender::Animation::Group_All, false, 1.0f, + MWRender::Animation::BlendMask_All, false, 1.0f, ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1); } else if(mode == 0) @@ -1757,6 +1938,7 @@ void CharacterController::playGroup(const std::string &groupname, int mode, int mAnimQueue.push_back(std::make_pair(groupname, count-1)); } } + return true; } void CharacterController::skipAnim() @@ -1779,14 +1961,13 @@ void CharacterController::clearAnimQueue() mAnimQueue.clear(); } - void CharacterController::forceStateUpdate() { if(!mAnimation) return; clearAnimQueue(); - refreshCurrentAnims(mIdleState, mMovementState, true); + refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); if(mDeathState != CharState_None) { playRandomDeath(); @@ -1799,7 +1980,7 @@ bool CharacterController::kill() { if( isDead() ) { - if( mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr() && !isAnimPlaying(mCurrentDeath) ) + if( mPtr == getPlayer() && !isAnimPlaying(mCurrentDeath) ) { //player's death animation is over MWBase::Environment::get().getStateManager()->askLoadRecent(); @@ -1815,7 +1996,7 @@ bool CharacterController::kill() mCurrentIdle.clear(); // Play Death Music if it was the player dying - if(mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(mPtr == getPlayer()) MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3"); return true; @@ -1856,7 +2037,7 @@ void CharacterController::updateMagicEffects() float alpha = 1.f; if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude()) { - if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mPtr == getPlayer()) alpha = 0.4f; else alpha = 0.f; @@ -1897,55 +2078,110 @@ bool CharacterController::isKnockedOut() const return mHitState == CharState_KnockOut; } -void CharacterController::setHeadTrackTarget(const MWWorld::Ptr &target) +bool CharacterController::isSneaking() const +{ + return mIdleState == CharState_IdleSneak || + mMovementState == CharState_SneakForward || + mMovementState == CharState_SneakBack || + mMovementState == CharState_SneakLeft || + mMovementState == CharState_SneakRight; +} + +void CharacterController::setAttackingOrSpell(bool attackingOrSpell) +{ + mAttackingOrSpell = attackingOrSpell; +} + +bool CharacterController::readyToPrepareAttack() const +{ + return (mHitState == CharState_None || mHitState == CharState_Block) + && mUpperBodyState <= UpperCharState_WeapEquiped; +} + +bool CharacterController::readyToStartAttack() const +{ + if (mHitState != CharState_None && mHitState != CharState_Block) + return false; + + if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr)) + return mUpperBodyState == UpperCharState_WeapEquiped; + else + return mUpperBodyState == UpperCharState_Nothing; +} + +float CharacterController::getAttackStrength() const +{ + return mAttackStrength; +} + +void CharacterController::setActive(bool active) +{ + mAnimation->setActive(active); +} + +void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr &target) { mHeadTrackTarget = target; } void CharacterController::updateHeadTracking(float duration) { - Ogre::Node* head = mAnimation->getNode("Bip01 Head"); + const osg::Node* head = mAnimation->getNode("Bip01 Head"); if (!head) return; - Ogre::Radian zAngle (0.f); - Ogre::Radian xAngle (0.f); + + float zAngleRadians = 0.f; + float xAngleRadians = 0.f; + if (!mHeadTrackTarget.isEmpty()) { - Ogre::Vector3 headPos = mPtr.getRefData().getBaseNode()->convertLocalToWorldPosition(head->_getDerivedPosition()); - Ogre::Vector3 targetPos (mHeadTrackTarget.getRefData().getPosition().pos); - if (MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mHeadTrackTarget)) + osg::MatrixList mats = head->getWorldMatrices(); + if (mats.empty()) + return; + osg::Matrixf mat = mats[0]; + osg::Vec3f headPos = mat.getTrans(); + + osg::Vec3f direction; + if (const MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mHeadTrackTarget)) { - Ogre::Node* targetHead = anim->getNode("Head"); - if (!targetHead) - targetHead = anim->getNode("Bip01 Head"); - if (targetHead) - targetPos = mHeadTrackTarget.getRefData().getBaseNode()->convertLocalToWorldPosition( - targetHead->_getDerivedPosition()); + const osg::Node* node = anim->getNode("Head"); + if (node == NULL) + node = anim->getNode("Bip01 Head"); + if (node != NULL) + { + osg::MatrixList mats = node->getWorldMatrices(); + if (mats.size()) + direction = mats[0].getTrans() - headPos; + } + else + // no head node to look at, fall back to look at center of collision box + direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget); } + direction.normalize(); - Ogre::Vector3 direction = targetPos - headPos; - direction.normalise(); + if (!mPtr.getRefData().getBaseNode()) + return; + const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); - const Ogre::Vector3 actorDirection = mPtr.getRefData().getBaseNode()->getOrientation().yAxis(); + zAngleRadians = std::atan2(direction.x(), direction.y()) - std::atan2(actorDirection.x(), actorDirection.y()); + xAngleRadians = -std::asin(direction.z()); - zAngle = Ogre::Math::ATan2(direction.x,direction.y) - - Ogre::Math::ATan2(actorDirection.x, actorDirection.y); - xAngle = -Ogre::Math::ASin(direction.z); - wrap(zAngle); - wrap(xAngle); - xAngle = Ogre::Degree(std::min(xAngle.valueDegrees(), 40.f)); - xAngle = Ogre::Degree(std::max(xAngle.valueDegrees(), -40.f)); - zAngle = Ogre::Degree(std::min(zAngle.valueDegrees(), 30.f)); - zAngle = Ogre::Degree(std::max(zAngle.valueDegrees(), -30.f)); + wrap(zAngleRadians); + wrap(xAngleRadians); + xAngleRadians = std::min(xAngleRadians, osg::DegreesToRadians(40.f)); + xAngleRadians = std::max(xAngleRadians, osg::DegreesToRadians(-40.f)); + zAngleRadians = std::min(zAngleRadians, osg::DegreesToRadians(30.f)); + zAngleRadians = std::max(zAngleRadians, osg::DegreesToRadians(-30.f)); } + float factor = duration*5; factor = std::min(factor, 1.f); - xAngle = (1.f-factor) * mAnimation->getHeadPitch() + factor * (-xAngle); - zAngle = (1.f-factor) * mAnimation->getHeadYaw() + factor * (-zAngle); + xAngleRadians = (1.f-factor) * mAnimation->getHeadPitch() + factor * (-xAngleRadians); + zAngleRadians = (1.f-factor) * mAnimation->getHeadYaw() + factor * (-zAngleRadians); - mAnimation->setHeadPitch(xAngle); - mAnimation->setHeadYaw(zAngle); + mAnimation->setHeadPitch(xAngleRadians); + mAnimation->setHeadYaw(zAngleRadians); } } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index da74b2a33f..5e43bc2b73 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -1,12 +1,14 @@ #ifndef GAME_MWMECHANICS_CHARACTER_HPP #define GAME_MWMECHANICS_CHARACTER_HPP -#include +#include #include #include "../mwworld/ptr.hpp" +#include "../mwrender/animation.hpp" + namespace MWWorld { class ContainerStoreIterator; @@ -26,10 +28,14 @@ class CreatureStats; enum Priority { Priority_Default, + Priority_WeaponLowerBody, + Priority_SneakIdleLowerBody, + Priority_SwimIdle, Priority_Jump, Priority_Movement, Priority_Hit, Priority_Weapon, + Priority_Block, Priority_Knockdown, Priority_Torch, Priority_Storm, @@ -134,7 +140,7 @@ enum JumpingState { JumpState_Landing }; -class CharacterController +class CharacterController : public MWRender::Animation::TextKeyListener { MWWorld::Ptr mPtr; MWRender::Animation *mAnimation; @@ -147,7 +153,8 @@ class CharacterController CharacterState mMovementState; std::string mCurrentMovement; - float mMovementSpeed; + float mMovementAnimSpeed; + bool mAdjustMovementAnimSpeed; bool mHasMovedInXY; bool mMovementAnimationControlled; @@ -165,26 +172,31 @@ class CharacterController WeaponType mWeaponType; std::string mCurrentWeapon; + float mAttackStrength; + bool mSkipAnim; // counted for skill increase float mSecondsOfSwimming; float mSecondsOfRunning; - MWWorld::Ptr mHeadTrackTarget; + MWWorld::ConstPtr mHeadTrackTarget; float mTurnAnimationThreshold; // how long to continue playing turning animation after actor stopped turning std::string mAttackType; // slash, chop or thrust + + bool mAttackingOrSpell; + void determineAttackType(); - void refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force=false); + void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); void clearAnimQueue(); bool updateWeaponState(); bool updateCreatureState(); - void updateIdleStormState(); + void updateIdleStormState(bool inwater); void updateHeadTracking(float duration); @@ -205,6 +217,9 @@ public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); virtual ~CharacterController(); + virtual void handleTextKey(const std::string &groupname, const std::multimap::const_iterator &key, + const std::multimap& map); + // Be careful when to call this, see comment in Actors void updateContinuousVfx(); @@ -212,7 +227,7 @@ public: void update(float duration); - void playGroup(const std::string &groupname, int mode, int count); + bool playGroup(const std::string &groupname, int mode, int count); void skipAnim(); bool isAnimPlaying(const std::string &groupName); @@ -227,12 +242,22 @@ public: bool isReadyToBlock() const; bool isKnockedOut() const; + bool isSneaking() const; + + void setAttackingOrSpell(bool attackingOrSpell); + + bool readyToPrepareAttack() const; + bool readyToStartAttack() const; + + float getAttackStrength() const; + + /// @see Animation::setActive + void setActive(bool active); /// Make this character turn its head towards \a target. To turn off head tracking, pass an empty Ptr. - void setHeadTrackTarget(const MWWorld::Ptr& target); + void setHeadTrackTarget(const MWWorld::ConstPtr& target); }; - void getWeaponGroup(WeaponType weaptype, std::string &group); MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype); } diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 045f0108ca..a01dc7079c 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -1,8 +1,8 @@ #include "combat.hpp" -#include +#include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -19,19 +19,17 @@ #include "../mwworld/esmstore.hpp" #include "../mwbase/windowmanager.hpp" +#include "actorutil.hpp" namespace { -Ogre::Radian signedAngle(Ogre::Vector3 v1, Ogre::Vector3 v2, Ogre::Vector3 normal) +float signedAngleRadians (const osg::Vec3f& v1, const osg::Vec3f& v2, const osg::Vec3f& normal) { - return Ogre::Math::ATan2( - normal.dotProduct( v1.crossProduct(v2) ), - v1.dotProduct(v2) - ); + return std::atan2((normal * (v1 ^ v2)), (v1 * v2)); } -bool applyEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const Ogre::Vector3& hitPosition) +bool applyEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition) { std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : ""; if (!enchantmentName.empty()) @@ -54,7 +52,7 @@ bool applyEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, namespace MWMechanics { - bool blockMeleeAttack(const MWWorld::Ptr &attacker, const MWWorld::Ptr &blocker, const MWWorld::Ptr &weapon, float damage) + bool blockMeleeAttack(const MWWorld::Ptr &attacker, const MWWorld::Ptr &blocker, const MWWorld::Ptr &weapon, float damage, float attackStrength) { if (!blocker.getClass().hasInventoryStore(blocker)) return false; @@ -63,7 +61,7 @@ namespace MWMechanics if (blockerStats.getKnockedDown() // Used for both knockout or knockdown || blockerStats.getHitRecovery() - || blockerStats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + || blockerStats.isParalyzed()) return false; if (!MWBase::Environment::get().getMechanicsManager()->isReadyToBlock(blocker)) @@ -74,20 +72,26 @@ namespace MWMechanics if (shield == inv.end() || shield->getTypeName() != typeid(ESM::Armor).name()) return false; - Ogre::Degree angle = signedAngle (Ogre::Vector3(attacker.getRefData().getPosition().pos) - Ogre::Vector3(blocker.getRefData().getPosition().pos), - blocker.getRefData().getBaseNode()->getOrientation().yAxis(), Ogre::Vector3(0,0,1)); + if (!blocker.getRefData().getBaseNode()) + return false; // shouldn't happen + + float angleDegrees = osg::RadiansToDegrees( + signedAngleRadians ( + (attacker.getRefData().getPosition().asVec3() - blocker.getRefData().getPosition().asVec3()), + blocker.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0), + osg::Vec3f(0,0,1))); const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - if (angle.valueDegrees() < gmst.find("fCombatBlockLeftAngle")->getFloat()) + if (angleDegrees < gmst.find("fCombatBlockLeftAngle")->getFloat()) return false; - if (angle.valueDegrees() > gmst.find("fCombatBlockRightAngle")->getFloat()) + if (angleDegrees > gmst.find("fCombatBlockRightAngle")->getFloat()) return false; MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); float blockTerm = blocker.getClass().getSkill(blocker, ESM::Skill::Block) + 0.2f * blockerStats.getAttribute(ESM::Attribute::Agility).getModified() + 0.1f * blockerStats.getAttribute(ESM::Attribute::Luck).getModified(); - float enemySwing = attackerStats.getAttackStrength(); + float enemySwing = attackStrength; float swingTerm = enemySwing * gmst.find("fSwingBlockMult")->getFloat() + gmst.find("fSwingBlockBase")->getFloat(); float blockerTerm = blockTerm * swingTerm; @@ -109,7 +113,7 @@ namespace MWMechanics int iBlockMinChance = gmst.find("iBlockMinChance")->getInt(); x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x)); - if (OEngine::Misc::Rng::roll0to99() < x) + if (Misc::Rng::roll0to99() < x) { // Reduce shield durability by incoming damage int shieldhealth = shield->getClass().getItemHealth(*shield); @@ -128,13 +132,13 @@ namespace MWMechanics normalizedEncumbrance = std::min(1.f, normalizedEncumbrance); float fatigueLoss = fFatigueBlockBase + normalizedEncumbrance * fFatigueBlockMult; if (!weapon.isEmpty()) - fatigueLoss += weapon.getClass().getWeight(weapon) * attackerStats.getAttackStrength() * fWeaponFatigueBlockMult; + fatigueLoss += weapon.getClass().getWeight(weapon) * attackStrength * fWeaponFatigueBlockMult; fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); blockerStats.setFatigue(fatigue); blockerStats.setBlock(true); - if (blocker == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (blocker == getPlayer()) blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0); return true; @@ -144,9 +148,9 @@ namespace MWMechanics void resistNormalWeapon(const MWWorld::Ptr &actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr &weapon, float &damage) { - MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - float resistance = std::min(100.f, stats.getMagicEffects().get(ESM::MagicEffect::ResistNormalWeapons).getMagnitude() - - stats.getMagicEffects().get(ESM::MagicEffect::WeaknessToNormalWeapons).getMagnitude()); + const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects(); + float resistance = std::min(100.f, effects.get(ESM::MagicEffect::ResistNormalWeapons).getMagnitude() + - effects.get(ESM::MagicEffect::WeaknessToNormalWeapons).getMagnitude()); float multiplier = 1.f - resistance / 100.f; @@ -158,18 +162,16 @@ namespace MWMechanics && actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) damage *= MWBase::Environment::get().getWorld()->getStore().get().find("fWereWolfSilverWeaponDamageMult")->getFloat(); - if (damage == 0 && attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (damage == 0 && attacker == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}"); } void projectileHit(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, MWWorld::Ptr weapon, const MWWorld::Ptr &projectile, - const Ogre::Vector3& hitPosition) + const osg::Vec3f& hitPosition, float attackStrength) { MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &gmst = world->getStore().get(); - MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); - if(victim.isEmpty() || !victim.getClass().isActor() || victim.getClass().getCreatureStats(victim).isDead()) // Can't hit non-actors or dead actors { @@ -177,7 +179,7 @@ namespace MWMechanics return; } - if(attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(attacker == getPlayer()) MWBase::Environment::get().getWindowManager()->setEnemy(victim); int weapskill = ESM::Skill::Marksman; @@ -187,7 +189,7 @@ namespace MWMechanics int skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); - if (OEngine::Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue)) + if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue)) { victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, attacker); @@ -196,17 +198,17 @@ namespace MWMechanics const unsigned char* attack = weapon.get()->mBase->mData.mChop; - float damage = attack[0] + ((attack[1]-attack[0])*attackerStats.getAttackStrength()); // Bow/crossbow damage + float damage = attack[0] + ((attack[1]-attack[0])*attackStrength); // Bow/crossbow damage // Arrow/bolt damage // NB in case of thrown weapons, we are applying the damage twice since projectile == weapon attack = projectile.get()->mBase->mData.mChop; - damage += attack[0] + ((attack[1]-attack[0])*attackerStats.getAttackStrength()); + damage += attack[0] + ((attack[1]-attack[0])*attackStrength); adjustWeaponDamage(damage, weapon, attacker); reduceWeaponCondition(damage, true, weapon, attacker); - if(attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(attacker == getPlayer()) attacker.getClass().skillUsageSucceeded(attacker, weapskill, 0); if (victim.getClass().getCreatureStats(victim).getKnockedDown()) @@ -221,11 +223,11 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); // Non-enchanted arrows shot at enemies have a chance to turn up in their inventory - if (victim != MWBase::Environment::get().getWorld()->getPlayerPtr() + if (victim != getPlayer() && !appliedEnchantment) { float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); - if (OEngine::Misc::Rng::rollProbability() < fProjectileThrownStoreChance / 100.f) + if (Misc::Rng::rollProbability() < fProjectileThrownStoreChance / 100.f) victim.getClass().getContainerStore(victim).add(projectile, 1, victim); } @@ -241,15 +243,15 @@ namespace MWMechanics const MWWorld::Store &gmst = world->getStore().get(); float defenseTerm = 0; - if (victim.getClass().getCreatureStats(victim).getFatigue().getCurrent() >= 0) + MWMechanics::CreatureStats& victimStats = victim.getClass().getCreatureStats(victim); + if (victimStats.getFatigue().getCurrent() >= 0) { - MWMechanics::CreatureStats& victimStats = victim.getClass().getCreatureStats(victim); // Maybe we should keep an aware state for actors updated every so often instead of testing every time bool unaware = (!victimStats.getAiSequence().isInCombat()) - && (attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) + && (attacker == getPlayer()) && (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim)); if (!(victimStats.getKnockedDown() || - victimStats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0 + victimStats.isParalyzed() || unaware )) { defenseTerm = victimStats.getEvasion(); @@ -292,7 +294,7 @@ namespace MWMechanics saveTerm *= 1.25f * normalisedFatigue; - float x = std::max(0.f, saveTerm - OEngine::Misc::Rng::roll0to99()); + float x = std::max(0.f, saveTerm - Misc::Rng::roll0to99()); int element = ESM::MagicEffect::FireDamage; if (i == 1) @@ -362,7 +364,7 @@ namespace MWMechanics (attacker.getClass().getCreatureStats(attacker).getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult * 0.1f); } - void getHandToHandDamage(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, float &damage, bool &healthdmg) + void getHandToHandDamage(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, float &damage, bool &healthdmg, float attackStrength) { // Note: MCP contains an option to include Strength in hand-to-hand damage // calculations. Some mods recommend using it, so we may want to include an @@ -371,10 +373,10 @@ namespace MWMechanics float minstrike = store.get().find("fMinHandToHandMult")->getFloat(); float maxstrike = store.get().find("fMaxHandToHandMult")->getFloat(); damage = static_cast(attacker.getClass().getSkill(attacker, ESM::Skill::HandToHand)); - damage *= minstrike + ((maxstrike-minstrike)*attacker.getClass().getCreatureStats(attacker).getAttackStrength()); + damage *= minstrike + ((maxstrike-minstrike)*attackStrength); MWMechanics::CreatureStats& otherstats = victim.getClass().getCreatureStats(victim); - healthdmg = (otherstats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + healthdmg = otherstats.isParalyzed() || otherstats.getKnockedDown(); bool isWerewolf = (attacker.getClass().isNpc() && attacker.getClass().getNpcStats(attacker).isWerewolf()); if(isWerewolf) @@ -397,7 +399,7 @@ namespace MWMechanics sndMgr->playSound3D(victim, "Hand To Hand Hit", 1.0f, 1.0f); } - void applyFatigueLoss(const MWWorld::Ptr &attacker, const MWWorld::Ptr &weapon) + void applyFatigueLoss(const MWWorld::Ptr &attacker, const MWWorld::Ptr &weapon, float attackStrength) { // somewhat of a guess, but using the weapon weight makes sense const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); @@ -409,7 +411,7 @@ namespace MWMechanics const float normalizedEncumbrance = attacker.getClass().getNormalizedEncumbrance(attacker); float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; if (!weapon.isEmpty()) - fatigueLoss += weapon.getClass().getWeight(weapon) * stats.getAttackStrength() * fWeaponFatigueMult; + fatigueLoss += weapon.getClass().getWeight(weapon) * attackStrength * fWeaponFatigueMult; fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); stats.setFatigue(fatigue); } diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index a2fd8b0067..ca78d7956e 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -2,20 +2,19 @@ #define OPENMW_MECHANICS_COMBAT_H #include "../mwworld/ptr.hpp" -#include namespace MWMechanics { /// @return can we block the attack? -bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, float damage); +bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, float damage, float attackStrength); void resistNormalWeapon (const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage); /// @note for a thrown weapon, \a weapon == \a projectile, for bows/crossbows, \a projectile is the arrow/bolt /// @note \a victim may be empty (e.g. for a hit on terrain), a non-actor (environment objects) or an actor void projectileHit (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile, - const Ogre::Vector3& hitPosition); + const osg::Vec3f& hitPosition, float attackStrength); /// Get the chance (in percent) for \a attacker to successfully hit \a victim with a given weapon skill value float getHitChance (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, int skillValue); @@ -32,10 +31,10 @@ void reduceWeaponCondition (float damage, bool hit, MWWorld::Ptr& weapon, const /// Adjust weapon damage based on its condition. A used weapon will be less effective. void adjustWeaponDamage (float& damage, const MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker); -void getHandToHandDamage (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, float& damage, bool& healthdmg); +void getHandToHandDamage (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, float& damage, bool& healthdmg, float attackStrength); /// Apply the fatigue loss incurred by attacking with the given weapon (weapon may be empty = hand-to-hand) -void applyFatigueLoss(const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon); +void applyFatigueLoss(const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float attackStrength); /// Can attacker operate in victim's environment? /// e.g. If attacker is a fish, is victim in water? Or, if attacker can't swim, is victim on land? diff --git a/apps/openmw/mwmechanics/coordinateconverter.cpp b/apps/openmw/mwmechanics/coordinateconverter.cpp new file mode 100644 index 0000000000..0a2d99f924 --- /dev/null +++ b/apps/openmw/mwmechanics/coordinateconverter.cpp @@ -0,0 +1,43 @@ +#include "coordinateconverter.hpp" + +#include +#include + +namespace MWMechanics +{ + CoordinateConverter::CoordinateConverter(const ESM::Cell* cell) + : mCellX(0), mCellY(0) + { + if (cell->isExterior()) + { + mCellX = cell->mData.mX * ESM::Land::REAL_SIZE; + mCellY = cell->mData.mY * ESM::Land::REAL_SIZE; + } + } + + void CoordinateConverter::toWorld(ESM::Pathgrid::Point& point) + { + point.mX += mCellX; + point.mY += mCellY; + } + + void CoordinateConverter::toWorld(osg::Vec3f& point) + { + point.x() += static_cast(mCellX); + point.y() += static_cast(mCellY); + } + + void CoordinateConverter::toLocal(osg::Vec3f& point) + { + point.x() -= static_cast(mCellX); + point.y() -= static_cast(mCellY); + } + + osg::Vec3f CoordinateConverter::toLocalVec3(const ESM::Pathgrid::Point& point) + { + return osg::Vec3f( + static_cast(point.mX - mCellX), + static_cast(point.mY - mCellY), + static_cast(point.mZ)); + } +} diff --git a/apps/openmw/mwmechanics/coordinateconverter.hpp b/apps/openmw/mwmechanics/coordinateconverter.hpp new file mode 100644 index 0000000000..cd855e84a2 --- /dev/null +++ b/apps/openmw/mwmechanics/coordinateconverter.hpp @@ -0,0 +1,37 @@ +#ifndef GAME_MWMECHANICS_COORDINATECONVERTER_H +#define GAME_MWMECHANICS_COORDINATECONVERTER_H + +#include +#include + +namespace ESM +{ + struct Cell; +} + +namespace MWMechanics +{ + /// \brief convert coordinates between world and local cell + class CoordinateConverter + { + public: + CoordinateConverter(const ESM::Cell* cell); + + /// in-place conversion from local to world + void toWorld(ESM::Pathgrid::Point& point); + + /// in-place conversion from local to world + void toWorld(osg::Vec3f& point); + + /// in-place conversion from world to local + void toLocal(osg::Vec3f& point); + + osg::Vec3f toLocalVec3(const ESM::Pathgrid::Point& point); + + private: + int mCellX; + int mCellY; + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 308e720279..6933c40a3f 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include "../mwworld/esmstore.hpp" @@ -16,11 +18,11 @@ namespace MWMechanics CreatureStats::CreatureStats() : mDrawState (DrawState_Nothing), mDead (false), mDied (false), mMurdered(false), mFriendlyHits (0), - mTalkedTo (false), mAlarmed (false), mAttacked (false), mAttackingOrSpell(false), + mTalkedTo (false), mAlarmed (false), mAttacked (false), mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), - mHitRecovery(false), mBlock(false), mMovementFlags(0), mAttackStrength(0.f), + mHitRecovery(false), mBlock(false), mMovementFlags(0), mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), - mDeathAnimation(0), mIsWerewolf(false), mLevel (0) + mDeathAnimation(0), mLevel (0) { for (int i=0; i<4; ++i) mAiSettings[i] = 0; @@ -46,8 +48,10 @@ namespace MWMechanics const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - return gmst.find ("fFatigueBase")->getFloat() - - gmst.find ("fFatigueMult")->getFloat() * (1-normalised); + static const float fFatigueBase = gmst.find("fFatigueBase")->getFloat(); + static const float fFatigueMult = gmst.find("fFatigueMult")->getFloat(); + + return fFatigueBase - fFatigueMult * (1-normalised); } const AttributeValue &CreatureStats::getAttribute(int index) const @@ -55,7 +59,7 @@ namespace MWMechanics if (index < 0 || index > 7) { throw std::runtime_error("attribute index is out of range"); } - return (!mIsWerewolf ? mAttributes[index] : mWerewolfAttributes[index]); + return mAttributes[index]; } const DynamicStat &CreatureStats::getHealth() const @@ -88,11 +92,6 @@ namespace MWMechanics return mMagicEffects; } - bool CreatureStats::getAttackingOrSpell() const - { - return mAttackingOrSpell; - } - int CreatureStats::getLevel() const { return mLevel; @@ -139,14 +138,11 @@ namespace MWMechanics throw std::runtime_error("attribute index is out of range"); } - const AttributeValue& currentValue = !mIsWerewolf ? mAttributes[index] : mWerewolfAttributes[index]; + const AttributeValue& currentValue = mAttributes[index]; if (value != currentValue) { - if(!mIsWerewolf) - mAttributes[index] = value; - else - mWerewolfAttributes[index] = value; + mAttributes[index] = value; if (index == ESM::Attribute::Intelligence) mRecalcMagicka = true; @@ -215,11 +211,6 @@ namespace MWMechanics mMagicEffects.setModifiers(effects); } - void CreatureStats::setAttackingOrSpell(bool attackingOrSpell) - { - mAttackingOrSpell = attackingOrSpell; - } - void CreatureStats::setAiSetting (AiSetting index, Stat value) { mAiSettings[index] = value; @@ -232,6 +223,11 @@ namespace MWMechanics setAiSetting(index, stat); } + bool CreatureStats::isParalyzed() const + { + return mMagicEffects.get(ESM::MagicEffect::Paralyze).getMagnitude() > 0; + } + bool CreatureStats::isDead() const { return mDead; @@ -271,9 +267,11 @@ namespace MWMechanics { if (mDead) { + if (mDynamic[0].getModified() < 1) + mDynamic[0].setModified(1, 0); + mDynamic[0].setCurrent(mDynamic[0].getModified()); - if (mDynamic[0].getCurrent()>=1) - mDead = false; + mDead = false; } } @@ -469,16 +467,6 @@ namespace MWMechanics mDrawState = state; } - float CreatureStats::getAttackStrength() const - { - return mAttackStrength; - } - - void CreatureStats::setAttackStrength(float value) - { - mAttackStrength = value; - } - void CreatureStats::writeState (ESM::CreatureStats& state) const { for (int i=0; i mSummonGraveyard; protected: - // These two are only set by NpcStats, but they are declared in CreatureStats to prevent using virtual methods. - bool mIsWerewolf; - AttributeValue mWerewolfAttributes[8]; - int mLevel; public: @@ -89,10 +83,6 @@ namespace MWMechanics DrawState_ getDrawState() const; void setDrawState(DrawState_ state); - /// When attacking, stores how strong the attack should be (0 = weakest, 1 = strongest) - float getAttackStrength() const; - void setAttackStrength(float value); - bool needToRecalcDynamicStats(); void setNeedRecalcDynamicStats(bool val); @@ -165,6 +155,8 @@ namespace MWMechanics float getFatigueTerm() const; ///< Return effective fatigue + bool isParalyzed() const; + bool isDead() const; void notifyDied(); diff --git a/apps/openmw/mwmechanics/difficultyscaling.cpp b/apps/openmw/mwmechanics/difficultyscaling.cpp index 05ab12ccd2..950fe33cf4 100644 --- a/apps/openmw/mwmechanics/difficultyscaling.cpp +++ b/apps/openmw/mwmechanics/difficultyscaling.cpp @@ -6,9 +6,11 @@ #include +#include "actorutil.hpp" + float scaleDamage(float damage, const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim) { - const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr& player = MWMechanics::getPlayer(); // [-100, 100] int difficultySetting = Settings::Manager::getInt("difficulty", "Game"); diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index 0153be3dc8..25bde56ab8 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MECHANICS_DISEASE_H #define OPENMW_MECHANICS_DISEASE_H -#include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" @@ -9,8 +9,10 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/spells.hpp" -#include "../mwmechanics/creaturestats.hpp" +#include "spells.hpp" +#include "creaturestats.hpp" +#include "actorutil.hpp" + namespace MWMechanics { @@ -20,7 +22,7 @@ namespace MWMechanics /// @param carrier The disease carrier. inline void diseaseContact (MWWorld::Ptr actor, MWWorld::Ptr carrier) { - if (!carrier.getClass().isActor() || actor != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (!carrier.getClass().isActor() || actor != getPlayer()) return; float fDiseaseXferChance = @@ -32,8 +34,7 @@ namespace MWMechanics Spells& spells = carrier.getClass().getCreatureStats(carrier).getSpells(); for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); - + const ESM::Spell* spell = it->first; if (actor.getClass().getCreatureStats(actor).getSpells().hasSpell(spell->mId)) continue; @@ -51,7 +52,7 @@ namespace MWMechanics continue; int x = static_cast(fDiseaseXferChance * 100 * resist); - if (OEngine::Misc::Rng::rollDice(10000) < x) + if (Misc::Rng::rollDice(10000) < x) { // Contracted disease! actor.getClass().getCreatureStats(actor).getSpells().add(it->first); diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index bb02fb41d3..102e3787ad 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -1,6 +1,6 @@ #include "enchanting.hpp" -#include +#include #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" @@ -11,6 +11,7 @@ #include "creaturestats.hpp" #include "npcstats.hpp" #include "spellcasting.hpp" +#include "actorutil.hpp" namespace MWMechanics { @@ -54,7 +55,7 @@ namespace MWMechanics bool Enchanting::create() { - const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr& player = getPlayer(); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); ESM::Enchantment enchantment; enchantment.mData.mCharge = getGemCharge(); @@ -70,7 +71,7 @@ namespace MWMechanics if(mSelfEnchanting) { - if(getEnchantChance() <= (OEngine::Misc::Rng::roll0to99())) + if(getEnchantChance() <= (Misc::Rng::roll0to99())) return false; mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); @@ -217,7 +218,7 @@ namespace MWMechanics int Enchanting::getEffectiveCastCost() const { int baseCost = getBaseCastCost(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); return getEffectiveEnchantmentCastCost(static_cast(baseCost), player); } @@ -291,7 +292,7 @@ namespace MWMechanics void Enchanting::payForEnchantment() const { - const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr& player = getPlayer(); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); store.remove(MWWorld::ContainerStore::sGoldId, getEnchantPrice(), player); diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index 20b87a3a96..edfef53581 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -1,7 +1,9 @@ #ifndef OPENMW_MECHANICS_LEVELLEDLIST_H #define OPENMW_MECHANICS_LEVELLEDLIST_H -#include +#include + +#include #include "../mwworld/ptr.hpp" #include "../mwworld/esmstore.hpp" @@ -9,7 +11,8 @@ #include "../mwworld/class.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" -#include "../mwmechanics/creaturestats.hpp" +#include "creaturestats.hpp" +#include "actorutil.hpp" namespace MWMechanics { @@ -19,12 +22,12 @@ namespace MWMechanics { const std::vector& items = levItem->mList; - const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr& player = getPlayer(); int playerLevel = player.getClass().getCreatureStats(player).getLevel(); failChance += levItem->mChanceNone; - if (OEngine::Misc::Rng::roll0to99() < failChance) + if (Misc::Rng::roll0to99() < failChance) return std::string(); std::vector candidates; @@ -53,7 +56,7 @@ namespace MWMechanics } if (candidates.empty()) return std::string(); - std::string item = candidates[OEngine::Misc::Rng::rollDice(candidates.size())]; + std::string item = candidates[Misc::Rng::rollDice(candidates.size())]; // Vanilla doesn't fail on nonexistent items in levelled lists if (!MWBase::Environment::get().getWorld()->getStore().find(Misc::StringUtils::lowerCase(item))) diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 0b19df0a82..021691d6a9 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -1,4 +1,3 @@ - #include "magiceffects.hpp" #include diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index c829154e27..b98e5daa36 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1,11 +1,14 @@ - #include "mechanicsmanagerimp.hpp" -#include "npcstats.hpp" -#include +#include +#include + +#include #include +#include + #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" @@ -20,22 +23,20 @@ #include "../mwmechanics/aicombat.hpp" #include "../mwmechanics/aipursue.hpp" -#include - #include "spellcasting.hpp" #include "autocalcspell.hpp" - -#include +#include "npcstats.hpp" +#include "actorutil.hpp" namespace { float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2) { - Ogre::Vector3 pos1 (actor1.getRefData().getPosition().pos); - Ogre::Vector3 pos2 (actor2.getRefData().getPosition().pos); + osg::Vec3f pos1 (actor1.getRefData().getPosition().asVec3()); + osg::Vec3f pos2 (actor2.getRefData().getPosition().asVec3()); - float d = pos1.distance(pos2); + float d = (pos1 - pos2).length(); static const int iFightDistanceBase = MWBase::Environment::get().getWorld()->getStore().get().find( "iFightDistanceBase")->getInt(); @@ -83,7 +84,7 @@ namespace MWMechanics { void MechanicsManager::buildPlayer() { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr ptr = getPlayer(); MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats (ptr); @@ -361,7 +362,7 @@ namespace MWMechanics { // Uses ingame time, but scaled to real time duration /= MWBase::Environment::get().getWorld()->getTimeScaleFactor(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); player.getClass().getInventoryStore(player).rechargeItems(duration); } @@ -489,7 +490,7 @@ namespace MWMechanics // HACK? The player has been changed, so a new Animation object may // have been made for them. Make sure they're properly updated. - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr ptr = getPlayer(); mActors.removeActor(ptr); mActors.addActor(ptr, true); } @@ -586,7 +587,7 @@ namespace MWMechanics float x = static_cast(npcSkill.getBaseDisposition()); MWWorld::LiveCellRef* npc = ptr.get(); - MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr playerPtr = getPlayer(); MWWorld::LiveCellRef* player = playerPtr.get(); const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); @@ -603,7 +604,7 @@ namespace MWMechanics int rank = 0; std::string npcFaction = ptr.getClass().getPrimaryFaction(ptr); - Misc::StringUtils::toLower(npcFaction); + Misc::StringUtils::lowerCaseInPlace(npcFaction); if (playerStats.getFactionRanks().find(npcFaction) != playerStats.getFactionRanks().end()) { @@ -663,7 +664,7 @@ namespace MWMechanics const MWMechanics::NpcStats &sellerStats = ptr.getClass().getNpcStats(ptr); - MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr playerPtr = getPlayer(); const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); // I suppose the temporary disposition change _has_ to be considered here, @@ -707,7 +708,7 @@ namespace MWMechanics MWMechanics::NpcStats& npcStats = npc.getClass().getNpcStats(npc); - MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr playerPtr = getPlayer(); const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); float npcRating1, npcRating2, npcRating3; @@ -737,7 +738,7 @@ namespace MWMechanics float x = 0; float y = 0; - int roll = OEngine::Misc::Rng::roll0to99(); + int roll = Misc::Rng::roll0to99(); if (type == PT_Admire) { @@ -843,12 +844,12 @@ namespace MWMechanics mActors.forceStateUpdate(ptr); } - void MechanicsManager::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) + bool MechanicsManager::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) { if(ptr.getClass().isActor()) - mActors.playAnimationGroup(ptr, groupName, mode, number); + return mActors.playAnimationGroup(ptr, groupName, mode, number); else - mObjects.playAnimationGroup(ptr, groupName, mode, number); + return mObjects.playAnimationGroup(ptr, groupName, mode, number); } void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr) { @@ -926,7 +927,7 @@ namespace MWMechanics return true; } - if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { + if(MWMechanics::isPlayerInCombat()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage2}"); return true; } @@ -982,7 +983,7 @@ namespace MWMechanics MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - StolenItemsMap::iterator stolenIt = mStolenItems.find(Misc::StringUtils::lowerCase(it->getClass().getId(*it))); + StolenItemsMap::iterator stolenIt = mStolenItems.find(Misc::StringUtils::lowerCase(it->getCellRef().getRefId())); if (stolenIt == mStolenItems.end()) continue; OwnerMap& owners = stolenIt->second; @@ -1010,7 +1011,7 @@ namespace MWMechanics void MechanicsManager::itemTaken(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item, const MWWorld::Ptr& container, int count) { - if (ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr != getPlayer()) return; MWWorld::Ptr victim; @@ -1041,35 +1042,52 @@ namespace MWMechanics owner.first = ownerCellRef->getFaction(); owner.second = true; } - Misc::StringUtils::toLower(owner.first); + Misc::StringUtils::lowerCaseInPlace(owner.first); if (!Misc::StringUtils::ciEqual(item.getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) - mStolenItems[Misc::StringUtils::lowerCase(item.getClass().getId(item))][owner] += count; + mStolenItems[Misc::StringUtils::lowerCase(item.getCellRef().getRefId())][owner] += count; commitCrime(ptr, victim, OT_Theft, item.getClass().getValue(item) * count); } + + void getFollowers (const MWWorld::Ptr& actor, std::set& out) + { + std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(actor); + for(std::list::iterator it = followers.begin();it != followers.end();++it) + { + if (out.insert(*it).second) + { + getFollowers(*it, out); + } + } + } + bool MechanicsManager::commitCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, int arg, bool victimAware) { // NOTE: victim may be empty // Only player can commit crime - if (player != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (player != getPlayer()) return false; // Find all the actors within the alarm radius std::vector neighbors; - Ogre::Vector3 from = Ogre::Vector3(player.getRefData().getPosition().pos); + osg::Vec3f from (player.getRefData().getPosition().asVec3()); const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); float radius = esmStore.get().find("fAlarmRadius")->getFloat(); mActors.getObjectsInRange(from, radius, neighbors); // victim should be considered even beyond alarm radius - if (!victim.isEmpty() && from.squaredDistance(Ogre::Vector3(victim.getRefData().getPosition().pos)) > radius*radius) + if (!victim.isEmpty() && (from - victim.getRefData().getPosition().asVec3()).length2() > radius*radius) neighbors.push_back(victim); + // get the player's followers / allies (works recursively) that will not report crimes + std::set playerFollowers; + getFollowers(player, playerFollowers); + // Did anyone see it? bool crimeSeen = false; for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) @@ -1085,11 +1103,6 @@ namespace MWMechanics // TODO: Add mod support for stealth executions! || (type == OT_Murder && *it != victim)) { - if (type == OT_Theft || type == OT_Pickpocket) - MWBase::Environment::get().getDialogueManager()->say(*it, "thief"); - else if (type == OT_Trespassing) - MWBase::Environment::get().getDialogueManager()->say(*it, "intruder"); - // Crime reporting only applies to NPCs if (!it->getClass().isNpc()) continue; @@ -1097,6 +1110,14 @@ namespace MWMechanics if (it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(victim)) continue; + if (playerFollowers.find(*it) != playerFollowers.end()) + continue; + + if (type == OT_Theft || type == OT_Pickpocket) + MWBase::Environment::get().getDialogueManager()->say(*it, "thief"); + else if (type == OT_Trespassing) + MWBase::Environment::get().getDialogueManager()->say(*it, "intruder"); + crimeSeen = true; } } @@ -1150,13 +1171,13 @@ namespace MWMechanics const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - Ogre::Vector3 from = Ogre::Vector3(player.getRefData().getPosition().pos); + osg::Vec3f from (player.getRefData().getPosition().asVec3()); float radius = esmStore.get().find("fAlarmRadius")->getFloat(); mActors.getObjectsInRange(from, radius, neighbors); // victim should be considered even beyond alarm radius - if (!victim.isEmpty() && from.squaredDistance(Ogre::Vector3(victim.getRefData().getPosition().pos)) > radius*radius) + if (!victim.isEmpty() && (from - victim.getRefData().getPosition().asVec3()).length2() > radius*radius) neighbors.push_back(victim); int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId(); @@ -1286,15 +1307,16 @@ namespace MWMechanics bool MechanicsManager::actorAttacked(const MWWorld::Ptr &ptr, const MWWorld::Ptr &attacker) { - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == getPlayer()) return false; - std::list followers = getActorsFollowing(attacker); + std::list followers = getActorsSidingWith(attacker); + MWMechanics::CreatureStats& targetStats = ptr.getClass().getCreatureStats(ptr); if (std::find(followers.begin(), followers.end(), ptr) != followers.end()) { - ptr.getClass().getCreatureStats(ptr).friendlyHit(); + targetStats.friendlyHit(); - if (ptr.getClass().getCreatureStats(ptr).getFriendlyHits() < 4) + if (targetStats.getFriendlyHits() < 4) { MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); return false; @@ -1302,7 +1324,7 @@ namespace MWMechanics } // Attacking an NPC that is already in combat with any other NPC is not a crime - AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + AiSequence& seq = targetStats.getAiSequence(); bool isFightingNpc = false; for (std::list::const_iterator it = seq.begin(); it != seq.end(); ++it) { @@ -1314,13 +1336,13 @@ namespace MWMechanics } } - if (ptr.getClass().isNpc() && !attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker) + if (ptr.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) && !isAggressive(ptr, attacker) && !isFightingNpc) commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault); if (!attacker.isEmpty() && (attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(ptr) - || attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) - && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker)) + || attacker == getPlayer()) + && !seq.isInCombat(attacker)) { // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. // Note: accidental or collateral damage attacks are ignored. @@ -1330,6 +1352,27 @@ namespace MWMechanics return true; } + void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker) + { + if (attacker.isEmpty() || attacker != getPlayer()) + return; + + if (victim == attacker) + return; // known to happen + + if (!victim.getClass().isNpc()) + return; // TODO: implement animal rights + + const MWMechanics::NpcStats& victimStats = victim.getClass().getNpcStats(victim); + + // Simple check for who attacked first: if the player attacked first, a crimeId should be set + // Doesn't handle possible edge case where no one reported the assault, but in such a case, + // for bystanders it is not possible to tell who attacked first, anyway. + if (victimStats.getCrimeId() != -1) + commitCrime(attacker, victim, MWBase::MechanicsManager::OT_Murder); + + } + bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer) { if (observer.getClass().getCreatureStats(observer).isDead() || !observer.getRefData().isEnabled()) @@ -1367,9 +1410,9 @@ namespace MWMechanics static float fSneakDistBase = store.find("fSneakDistanceBase")->getFloat(); static float fSneakDistMult = store.find("fSneakDistanceMultiplier")->getFloat(); - Ogre::Vector3 pos1 (ptr.getRefData().getPosition().pos); - Ogre::Vector3 pos2 (observer.getRefData().getPosition().pos); - float distTerm = fSneakDistBase + fSneakDistMult * pos1.distance(pos2); + osg::Vec3f pos1 (ptr.getRefData().getPosition().asVec3()); + osg::Vec3f pos2 (observer.getRefData().getPosition().asVec3()); + float distTerm = fSneakDistBase + fSneakDistMult * (pos1 - pos2).length(); float chameleon = stats.getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); float x = sneakTerm * distTerm * stats.getFatigueTerm() + chameleon + invisibility; @@ -1386,16 +1429,21 @@ namespace MWMechanics static float fSneakNoViewMult = store.find("fSneakNoViewMult")->getFloat(); static float fSneakViewMult = store.find("fSneakViewMult")->getFloat(); float y = 0; - Ogre::Vector3 vec = pos1 - pos2; - Ogre::Radian angle = observer.getRefData().getBaseNode()->getOrientation().yAxis().angleBetween(vec); - if (angle < Ogre::Degree(90)) - y = obsTerm * observerStats.getFatigueTerm() * fSneakNoViewMult; - else - y = obsTerm * observerStats.getFatigueTerm() * fSneakViewMult; + osg::Vec3f vec = pos1 - pos2; + if (observer.getRefData().getBaseNode()) + { + osg::Vec3f observerDir = (observer.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0)); + + float angleRadians = std::acos(observerDir * vec / (observerDir.length() * vec.length())); + if (angleRadians < osg::DegreesToRadians(90.f)) + y = obsTerm * observerStats.getFatigueTerm() * fSneakNoViewMult; + else + y = obsTerm * observerStats.getFatigueTerm() * fSneakViewMult; + } float target = x - y; - return (OEngine::Misc::Rng::roll0to99() >= target); + return (Misc::Rng::roll0to99() >= target); } void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) @@ -1403,7 +1451,7 @@ namespace MWMechanics if (ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(target)) return; ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(MWMechanics::AiCombat(target), ptr); - if (target == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (target == getPlayer()) { // if guard starts combat with player, guards pursuing player should do the same if (ptr.getClass().isClass(ptr, "Guard")) @@ -1428,17 +1476,22 @@ namespace MWMechanics MWBase::Environment::get().getDialogueManager()->say(ptr, "attack"); } - void MechanicsManager::getObjectsInRange(const Ogre::Vector3 &position, float radius, std::vector &objects) + void MechanicsManager::getObjectsInRange(const osg::Vec3f &position, float radius, std::vector &objects) { mActors.getObjectsInRange(position, radius, objects); mObjects.getObjectsInRange(position, radius, objects); } - void MechanicsManager::getActorsInRange(const Ogre::Vector3 &position, float radius, std::vector &objects) + void MechanicsManager::getActorsInRange(const osg::Vec3f &position, float radius, std::vector &objects) { mActors.getObjectsInRange(position, radius, objects); } + std::list MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor) + { + return mActors.getActorsSidingWith(actor); + } + std::list MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor) { return mActors.getActorsFollowing(actor); @@ -1500,7 +1553,7 @@ namespace MWMechanics if (ptr.getClass().isNpc() && target.getClass().isNpc()) { if (target.getClass().getNpcStats(target).isWerewolf() || - (target == MWBase::Environment::get().getWorld()->getPlayerPtr() && + (target == getPlayer() && MWBase::Environment::get().getWorld()->getGlobalInt("pcknownwerewolf"))) { const ESM::GameSetting * iWerewolfFightMod = MWBase::Environment::get().getWorld()->getStore().get().search("iWerewolfFightMod"); @@ -1513,7 +1566,7 @@ namespace MWMechanics void MechanicsManager::keepPlayerAlive() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); CreatureStats& stats = player.getClass().getCreatureStats(player); if (stats.isDead()) { @@ -1532,4 +1585,112 @@ namespace MWMechanics { return mActors.isReadyToBlock(ptr); } + + void MechanicsManager::setWerewolf(const MWWorld::Ptr& actor, bool werewolf) + { + MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats(actor); + + // The actor does not have to change state + if (npcStats.isWerewolf() == werewolf) + return; + + MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer(); + + if (actor == player->getPlayer()) + { + if (werewolf) + { + player->saveSkillsAttributes(); + player->setWerewolfSkillsAttributes(); + } + else + player->restoreSkillsAttributes(); + } + + // Equipped items other than WerewolfRobe may reference bones that do not even + // exist with the werewolf object root, so make sure to unequip all items + // *before* we become a werewolf. + MWWorld::InventoryStore& invStore = actor.getClass().getInventoryStore(actor); + invStore.unequipAll(actor); + + // Werewolfs can not cast spells, so we need to unset the prepared spell if there is one. + if (npcStats.getDrawState() == MWMechanics::DrawState_Spell) + npcStats.setDrawState(MWMechanics::DrawState_Nothing); + + npcStats.setWerewolf(werewolf); + + if(werewolf) + { + MWWorld::InventoryStore &inv = actor.getClass().getInventoryStore(actor); + + inv.equip(MWWorld::InventoryStore::Slot_Robe, inv.ContainerStore::add("werewolfrobe", 1, actor), actor); + } + else + { + actor.getClass().getContainerStore(actor).remove("werewolfrobe", 1, actor); + } + + if(actor == player->getPlayer()) + { + MWBase::Environment::get().getWorld()->reattachPlayerCamera(); + + // Update the GUI only when called on the player + MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); + + if (werewolf) + { + windowManager->forceHide(MWGui::GW_Inventory); + windowManager->forceHide(MWGui::GW_Magic); + } + else + { + windowManager->unsetForceHide(MWGui::GW_Inventory); + windowManager->unsetForceHide(MWGui::GW_Magic); + } + + windowManager->setWerewolfOverlay(werewolf); + + // Witnesses of the player's transformation will make them a globally known werewolf + std::vector closeActors; + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + getActorsInRange(actor.getRefData().getPosition().asVec3(), gmst.find("fAlarmRadius")->getFloat(), closeActors); + + bool detected = false, reported = false; + for (std::vector::const_iterator it = closeActors.begin(); it != closeActors.end(); ++it) + { + if (*it == actor) + continue; + + if (!it->getClass().isNpc()) + continue; + + if (MWBase::Environment::get().getWorld()->getLOS(*it, actor) && awarenessCheck(actor, *it)) + detected = true; + if (it->getClass().getCreatureStats(*it).getAiSetting(MWMechanics::CreatureStats::AI_Alarm).getModified() > 0) + reported = true; + } + + if (detected) + { + windowManager->messageBox("#{sWerewolfAlarmMessage}"); + MWBase::Environment::get().getWorld()->setGlobalInt("pcknownwerewolf", 1); + + if (reported) + { + npcStats.setBounty(npcStats.getBounty()+ + gmst.find("iWereWolfBounty")->getInt()); + windowManager->messageBox("#{sCrimeMessage}"); + } + } + } + } + + void MechanicsManager::applyWerewolfAcrobatics(const MWWorld::Ptr &actor) + { + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + MWMechanics::NpcStats &stats = actor.getClass().getNpcStats(actor); + + stats.getSkill(ESM::Skill::Acrobatics).setBase(gmst.find("fWerewolfAcrobatics")->getInt()); + } + } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index d08334ae8c..fb29c07ea2 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -10,11 +10,6 @@ #include "objects.hpp" #include "actors.hpp" -namespace Ogre -{ - class Vector3; -} - namespace MWWorld { class CellStore; @@ -125,6 +120,11 @@ namespace MWMechanics OffenseType type, int arg=0, bool victimAware=false); /// @return false if the attack was considered a "friendly hit" and forgiven virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); + + /// Notify that actor was killed, add a murder bounty if applicable + /// @note No-op for non-player attackers + virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); + /// Utility to check if taking this item is illegal and calling commitCrime if so /// @param container The container the item is in; may be empty for an item in the world virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, @@ -137,7 +137,9 @@ namespace MWMechanics virtual void forceStateUpdate(const MWWorld::Ptr &ptr); - virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); + /// Attempt to play an animation group + /// @return Success or error + virtual bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); virtual void skipAnimation(const MWWorld::Ptr& ptr); virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName); @@ -145,9 +147,10 @@ namespace MWMechanics /// paused we may want to do it manually (after equipping permanent enchantment) virtual void updateMagicEffects (const MWWorld::Ptr& ptr); - virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& objects); - virtual void getActorsInRange(const Ogre::Vector3 &position, float radius, std::vector &objects); + virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector& objects); + virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector &objects); + virtual std::list getActorsSidingWith(const MWWorld::Ptr& actor); virtual std::list getActorsFollowing(const MWWorld::Ptr& actor); virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor); @@ -180,13 +183,18 @@ namespace MWMechanics /// Has the player stolen this item from the given owner? virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid); + + /// @return is \a ptr allowed to take/use \a cellref or is it a crime? + virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim); + + virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf); + virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor); private: void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0); - /// @return is \a ptr allowed to take/use \a cellref or is it a crime? - virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim); + }; } diff --git a/apps/openmw/mwmechanics/movement.hpp b/apps/openmw/mwmechanics/movement.hpp index 6c9a4b7589..c12b615388 100644 --- a/apps/openmw/mwmechanics/movement.hpp +++ b/apps/openmw/mwmechanics/movement.hpp @@ -1,6 +1,8 @@ #ifndef GAME_MWMECHANICS_MOVEMENT_H #define GAME_MWMECHANICS_MOVEMENT_H +#include + namespace MWMechanics { /// Desired movement for an actor @@ -14,6 +16,11 @@ namespace MWMechanics mPosition[0] = mPosition[1] = mPosition[2] = 0.0f; mRotation[0] = mRotation[1] = mRotation[2] = 0.0f; } + + osg::Vec3f asVec3() + { + return osg::Vec3f(mPosition[0], mPosition[1], mPosition[2]); + } }; } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 94819e6260..10d603ff14 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -1,4 +1,3 @@ - #include "npcstats.hpp" #include @@ -32,6 +31,7 @@ MWMechanics::NpcStats::NpcStats() , mWerewolfKills (0) , mLevelProgress(0) , mTimeToStartDrowning(20.0) + , mIsWerewolf(false) { mSkillIncreases.resize (ESM::Attribute::Length, 0); } @@ -51,7 +51,7 @@ const MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index) const if (index<0 || index>=ESM::Skill::Length) throw std::runtime_error ("skill index out of range"); - return (!mIsWerewolf ? mSkill[index] : mWerewolfSkill[index]); + return mSkill[index]; } MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index) @@ -59,7 +59,15 @@ MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index) if (index<0 || index>=ESM::Skill::Length) throw std::runtime_error ("skill index out of range"); - return (!mIsWerewolf ? mSkill[index] : mWerewolfSkill[index]); + return mSkill[index]; +} + +void MWMechanics::NpcStats::setSkill(int index, const MWMechanics::SkillValue &value) +{ + if (index<0 || index>=ESM::Skill::Length) + throw std::runtime_error ("skill index out of range"); + + mSkill[index] = value; } const std::map& MWMechanics::NpcStats::getFactionRanks() const @@ -188,10 +196,6 @@ float MWMechanics::NpcStats::getSkillProgressRequirement (int skillIndex, const void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_, int usageType, float extraFactor) { - // Don't increase skills as a werewolf - if(mIsWerewolf) - return; - const ESM::Skill *skill = MWBase::Environment::get().getWorld()->getStore().get().find (skillIndex); float skillGain = 1; @@ -403,34 +407,12 @@ bool MWMechanics::NpcStats::isWerewolf() const void MWMechanics::NpcStats::setWerewolf (bool set) { + if (mIsWerewolf == set) + return; + if(set != false) { - const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - mWerewolfKills = 0; - - for(size_t i = 0;i < ESM::Attribute::Length;i++) - { - mWerewolfAttributes[i] = getAttribute(i); - // Oh, Bethesda. It's "Intelligence". - std::string name = "fWerewolf"+((i==ESM::Attribute::Intelligence) ? std::string("Intellegence") : - ESM::Attribute::sAttributeNames[i]); - mWerewolfAttributes[i].setBase(int(gmst.find(name)->getFloat())); - } - - for(size_t i = 0;i < ESM::Skill::Length;i++) - { - mWerewolfSkill[i] = getSkill(i); - - // Acrobatics is set separately for some reason. - if(i == ESM::Skill::Acrobatics) - continue; - - // "Mercantile"! >_< - std::string name = "fWerewolf"+((i==ESM::Skill::Mercantile) ? std::string("Merchantile") : - ESM::Skill::sSkillNames[i]); - mWerewolfSkill[i].setBase(int(gmst.find(name)->getFloat())); - } } mIsWerewolf = set; } @@ -464,14 +446,8 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const state.mDisposition = mDisposition; for (int i=0; i& getFactionRanks() const; /// Increase the rank in this faction by 1, if such a rank exists. diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index ba35af777d..94b552a38f 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -1,6 +1,6 @@ #include "objects.hpp" -#include +#include #include "movement.hpp" @@ -79,11 +79,18 @@ void Objects::update(float duration, bool paused) } } -void Objects::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) +bool Objects::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) { PtrControllerMap::iterator iter = mObjects.find(ptr); if(iter != mObjects.end()) - iter->second->playGroup(groupName, mode, number); + { + return iter->second->playGroup(groupName, mode, number); + } + else + { + std::cerr<< "Error in Objects::playAnimationGroup: Unable to find " << ptr.getCellRef().getRefId() << std::endl; + return false; + } } void Objects::skipAnimation(const MWWorld::Ptr& ptr) { @@ -92,11 +99,11 @@ void Objects::skipAnimation(const MWWorld::Ptr& ptr) iter->second->skipAnim(); } -void Objects::getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector& out) +void Objects::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) { for (PtrControllerMap::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) { - if (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(position) <= radius*radius) + if ((position - iter->first.getRefData().getPosition().asVec3()).length2() <= radius*radius) out.push_back(iter->first); } } diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 373a2a1056..07e00675c2 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -38,10 +38,10 @@ namespace MWMechanics void update(float duration, bool paused); ///< Update object animations - void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); + bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); void skipAnimation(const MWWorld::Ptr& ptr); - void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& out); + void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector& out); }; } diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 7cfa6fcd5f..5815d8cbe3 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -1,32 +1,42 @@ #include "obstacle.hpp" -#include +#include #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" +#include "movement.hpp" + namespace MWMechanics { // NOTE: determined empirically but probably need further tweaking - static const float DIST_SAME_SPOT = 1.8f; - static const float DURATION_SAME_SPOT = 1.0f; + static const float DIST_SAME_SPOT = 0.5f; + static const float DURATION_SAME_SPOT = 1.5f; static const float DURATION_TO_EVADE = 0.4f; + const float ObstacleCheck::evadeDirections[NUM_EVADE_DIRECTIONS][2] = + { + { 1.0f, 0.0f }, // move to side + { 1.0f, -1.0f }, // move to side and backwards + { -1.0f, 0.0f }, // move to other side + { -1.0f, -1.0f } // move to side and backwards + }; + // Proximity check function for interior doors. Given that most interior cells // do not have many doors performance shouldn't be too much of an issue. // // Limitation: there can be false detections, and does not test whether the // actor is facing the door. - bool proximityToDoor(const MWWorld::Ptr& actor, float minSqr, bool closed) + bool proximityToDoor(const MWWorld::Ptr& actor, float minSqr) { - if(getNearbyDoor(actor, minSqr, closed)!=MWWorld::Ptr()) + if(getNearbyDoor(actor, minSqr)!=MWWorld::Ptr()) return true; else return false; } - MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minSqr, bool closed) + MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minSqr) { MWWorld::CellStore *cell = actor.getCell(); @@ -34,10 +44,10 @@ namespace MWMechanics return MWWorld::Ptr(); // check interior cells only // Check all the doors in this cell - MWWorld::CellRefList& doors = cell->get(); - MWWorld::CellRefList::List& refList = doors.mList; - MWWorld::CellRefList::List::iterator it = refList.begin(); - Ogre::Vector3 pos(actor.getRefData().getPosition().pos); + const MWWorld::CellRefList& doors = cell->getReadOnlyDoors(); + const MWWorld::CellRefList::List& refList = doors.mList; + MWWorld::CellRefList::List::const_iterator it = refList.begin(); + osg::Vec3f pos(actor.getRefData().getPosition().asVec3()); /// TODO: How to check whether the actor is facing a door? Below code is for /// the player, perhaps it can be adapted. @@ -49,13 +59,13 @@ namespace MWMechanics /// opposite of the code in World::activateDoor() ::confused:: for (; it != refList.end(); ++it) { - MWWorld::LiveCellRef& ref = *it; - if(pos.squaredDistance(Ogre::Vector3(ref.mData.getPosition().pos)) < minSqr) - if((closed && ref.mData.getLocalRotation().rot[2] == 0) || - (!closed && ref.mData.getLocalRotation().rot[2] >= 1)) - { - return MWWorld::Ptr(&ref, actor.getCell()); // found, stop searching - } + const MWWorld::LiveCellRef& ref = *it; + if((pos - ref.mData.getPosition().asVec3()).length2() < minSqr + && ref.mData.getPosition().rot[2] == ref.mRef.getPosition().rot[2]) + { + // FIXME cast + return MWWorld::Ptr(&const_cast &>(ref), actor.getCell()); // found, stop searching + } } return MWWorld::Ptr(); // none found } @@ -67,6 +77,7 @@ namespace MWMechanics , mStuckDuration(0) , mEvadeDuration(0) , mDistSameSpot(-1) // avoid calculating it each time + , mEvadeDirectionIndex(0) { } @@ -103,20 +114,23 @@ namespace MWMechanics * t = how long before considered stuck * u = how long to move sideways * - * DIST_SAME_SPOT is calibrated for movement speed of around 150. - * A rat has walking speed of around 30, so we need to adjust for - * that. */ bool ObstacleCheck::check(const MWWorld::Ptr& actor, float duration) { const MWWorld::Class& cls = actor.getClass(); ESM::Position pos = actor.getRefData().getPosition(); - if(mDistSameSpot == -1) - mDistSameSpot = DIST_SAME_SPOT * (cls.getSpeed(actor) / 150); + // actors can move at most 60 fps (the physics framerate). + // the max() can be removed if we implement physics interpolation. + float movementDuration = std::max(1/60.f, duration); + + if(mDistSameSpot == -1) + mDistSameSpot = DIST_SAME_SPOT * cls.getSpeed(actor); + + float distSameSpot = mDistSameSpot * movementDuration; + + bool samePosition = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2() < distSameSpot * distSameSpot; - bool samePosition = (std::abs(pos.pos[0] - mPrevX) < mDistSameSpot) && - (std::abs(pos.pos[1] - mPrevY) < mDistSameSpot); // update position mPrevX = pos.pos[0]; mPrevY = pos.pos[1]; @@ -149,6 +163,7 @@ namespace MWMechanics { mStuckDuration = 0; mWalkState = State_Evade; + chooseEvasionDirection(); } } } @@ -169,4 +184,21 @@ namespace MWMechanics } return false; // no obstacles to evade (yet) } + + void ObstacleCheck::takeEvasiveAction(MWMechanics::Movement& actorMovement) + { + actorMovement.mPosition[0] = evadeDirections[mEvadeDirectionIndex][0]; + actorMovement.mPosition[1] = evadeDirections[mEvadeDirectionIndex][1]; + } + + void ObstacleCheck::chooseEvasionDirection() + { + // change direction if attempt didn't work + ++mEvadeDirectionIndex; + if (mEvadeDirectionIndex == NUM_EVADE_DIRECTIONS) + { + mEvadeDirectionIndex = 0; + } + } + } diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index e0ae9203d7..98cc4e7a08 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -8,19 +8,21 @@ namespace MWWorld namespace MWMechanics { + struct Movement; + /// NOTE: determined empirically based on in-game behaviour static const float MIN_DIST_TO_DOOR_SQUARED = 128*128; + static const int NUM_EVADE_DIRECTIONS = 4; + /// tests actor's proximity to a closed door by default bool proximityToDoor(const MWWorld::Ptr& actor, - float minSqr = MIN_DIST_TO_DOOR_SQUARED, - bool closed = true); + float minSqr = MIN_DIST_TO_DOOR_SQUARED); /// Returns door pointer within range. No guarentee is given as too which one /** \return Pointer to the door, or NULL if none exists **/ MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, - float minSqr = MIN_DIST_TO_DOOR_SQUARED, - bool closed = true); + float minSqr = MIN_DIST_TO_DOOR_SQUARED); class ObstacleCheck { @@ -36,12 +38,18 @@ namespace MWMechanics // should be taken bool check(const MWWorld::Ptr& actor, float duration); + // change direction to try to fix "stuck" actor + void takeEvasiveAction(MWMechanics::Movement& actorMovement); + private: // for checking if we're stuck (ignoring Z axis) float mPrevX; float mPrevY; + // directions to try moving in when get stuck + static const float evadeDirections[NUM_EVADE_DIRECTIONS][2]; + enum WalkState { State_Norm, @@ -53,6 +61,9 @@ namespace MWMechanics float mStuckDuration; // accumulate time here while in same spot float mEvadeDuration; float mDistSameSpot; // take account of actor's speed + int mEvadeDirectionIndex; + + void chooseEvasionDirection(); }; } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index c32899dde2..48d306c52e 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -1,13 +1,12 @@ #include "pathfinding.hpp" - -#include "OgreMath.h" -#include "OgreVector3.h" +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "coordinateconverter.hpp" namespace { @@ -15,9 +14,9 @@ namespace // Caller needs to be careful for very short distances (i.e. less than 1) // or when accumuating the results i.e. (a + b)^2 != a^2 + b^2 // - float distanceSquared(ESM::Pathgrid::Point point, Ogre::Vector3 pos) + float distanceSquared(ESM::Pathgrid::Point point, const osg::Vec3f& pos) { - return MWMechanics::PathFinder::MakeOgreVector3(point).squaredDistance(pos); + return (MWMechanics::PathFinder::MakeOsgVec3(point) - pos).length2(); } // Return the closest pathgrid point index from the specified position co @@ -26,10 +25,9 @@ namespace // // NOTE: pos is expected to be in local co-ordinates, as is grid->mPoints // - int getClosestPoint(const ESM::Pathgrid* grid, Ogre::Vector3 pos) + int getClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos) { - if(!grid || grid->mPoints.empty()) - return -1; + assert(grid && !grid->mPoints.empty()); float distanceBetween = distanceSquared(grid->mPoints[0], pos); int closestIndex = 0; @@ -52,35 +50,41 @@ namespace // Chooses a reachable end pathgrid point. start is assumed reachable. std::pair getClosestReachablePoint(const ESM::Pathgrid* grid, const MWWorld::CellStore *cell, - Ogre::Vector3 pos, int start) + const osg::Vec3f pos, int start) { - if(!grid || grid->mPoints.empty()) - return std::pair (-1, false); + assert(grid && !grid->mPoints.empty()); - float distanceBetween = distanceSquared(grid->mPoints[0], pos); + float closestDistanceBetween = std::numeric_limits::max(); + float closestDistanceReachable = std::numeric_limits::max(); int closestIndex = 0; int closestReachableIndex = 0; // TODO: if this full scan causes performance problems mapping pathgrid // points to a quadtree may help - for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++) + for(unsigned int counter = 0; counter < grid->mPoints.size(); counter++) { float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos); - if(potentialDistBetween < distanceBetween) + if (potentialDistBetween < closestDistanceReachable) { // found a closer one - distanceBetween = potentialDistBetween; - closestIndex = counter; if (cell->isPointConnected(start, counter)) { + closestDistanceReachable = potentialDistBetween; closestReachableIndex = counter; } + if (potentialDistBetween < closestDistanceBetween) + { + closestDistanceBetween = potentialDistBetween; + closestIndex = counter; + } } } + + // post-condition: start and endpoint must be connected + assert(cell->isPointConnected(start, closestReachableIndex)); + // AiWander has logic that depends on whether a path was created, deleting // allowed nodes if not. Hence a path needs to be created even if the start // and the end points are the same. - //if(start == closestReachableIndex) - //closestReachableIndex = -1; // couldn't find anyting other than start return std::pair (closestReachableIndex, closestReachableIndex == closestIndex); @@ -90,14 +94,14 @@ namespace namespace MWMechanics { - float sqrDistanceIgnoreZ(ESM::Pathgrid::Point point, float x, float y) + float sqrDistanceIgnoreZ(const ESM::Pathgrid::Point& point, float x, float y) { x -= point.mX; y -= point.mY; return (x * x + y * y); } - float distance(ESM::Pathgrid::Point point, float x, float y, float z) + float distance(const ESM::Pathgrid::Point& point, float x, float y, float z) { x -= point.mX; y -= point.mY; @@ -105,7 +109,7 @@ namespace MWMechanics return sqrt(x * x + y * y + z * z); } - float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b) + float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b) { float x = static_cast(a.mX - b.mX); float y = static_cast(a.mY - b.mY); @@ -113,43 +117,42 @@ namespace MWMechanics return sqrt(x * x + y * y + z * z); } - float getZAngleToDir(const Ogre::Vector3& dir) + float getZAngleToDir(const osg::Vec3f& dir) { - return Ogre::Math::ATan2(dir.x,dir.y).valueDegrees(); + return std::atan2(dir.x(), dir.y()); } - float getXAngleToDir(const Ogre::Vector3& dir, float dirLen) + float getXAngleToDir(const osg::Vec3f& dir) { - float len = (dirLen > 0.0f)? dirLen : dir.length(); - return -Ogre::Math::ASin(dir.z / len).valueDegrees(); + return -std::asin(dir.z() / dir.length()); } float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest) { - Ogre::Vector3 dir = PathFinder::MakeOgreVector3(dest) - PathFinder::MakeOgreVector3(origin); + osg::Vec3f dir = PathFinder::MakeOsgVec3(dest) - PathFinder::MakeOsgVec3(origin); return getZAngleToDir(dir); } float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest) { - Ogre::Vector3 dir = PathFinder::MakeOgreVector3(dest) - PathFinder::MakeOgreVector3(origin); + osg::Vec3f dir = PathFinder::MakeOsgVec3(dest) - PathFinder::MakeOsgVec3(origin); return getXAngleToDir(dir); } - bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY) + bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY) { - if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z - to.z) <= PATHFIND_Z_REACH) + if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z() - to.z()) <= PATHFIND_Z_REACH) { - Ogre::Vector3 dir = to - from; - dir.z = 0; - dir.normalise(); + osg::Vec3f dir = to - from; + dir.z() = 0; + dir.normalize(); float verticalOffset = 200; // instead of '200' here we want the height of the actor - Ogre::Vector3 _from = from + dir*offsetXY + Ogre::Vector3::UNIT_Z * verticalOffset; + osg::Vec3f _from = from + dir*offsetXY + osg::Z_AXIS * verticalOffset; // cast up-down ray and find height in world space of hit - float h = _from.z - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -Ogre::Vector3::UNIT_Z, verticalOffset + PATHFIND_Z_REACH + 1); + float h = _from.z() - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -osg::Z_AXIS, verticalOffset + PATHFIND_Z_REACH + 1); - if(std::abs(from.z - h) <= PATHFIND_Z_REACH) + if(std::abs(from.z() - h) <= PATHFIND_Z_REACH) return true; } @@ -185,8 +188,6 @@ namespace MWMechanics * pathgrid point (e.g. wander) then it may be worth while to call * pop_back() to remove the redundant entry. * - * mPathConstructed is set true if successful, false if not - * * NOTE: co-ordinates must be converted prior to calling getClosestPoint() * * | @@ -228,62 +229,68 @@ namespace MWMechanics } // NOTE: getClosestPoint expects local co-ordinates - float xCell = 0; - float yCell = 0; - if (mCell->isExterior()) - { - xCell = static_cast(mCell->getCell()->mData.mX * ESM::Land::REAL_SIZE); - yCell = static_cast(mCell->getCell()->mData.mY * ESM::Land::REAL_SIZE); - } + CoordinateConverter converter(mCell->getCell()); // NOTE: It is possible that getClosestPoint returns a pathgrind point index // that is unreachable in some situations. e.g. actor is standing // outside an area enclosed by walls, but there is a pathgrid // point right behind the wall that is closer than any pathgrid // point outside the wall - int startNode = getClosestPoint(mPathgrid, - Ogre::Vector3(startPoint.mX - xCell, startPoint.mY - yCell, static_cast(startPoint.mZ))); - // Some cells don't have any pathgrids at all - if(startNode != -1) + osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint)); + int startNode = getClosestPoint(mPathgrid, startPointInLocalCoords); + + osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint)); + std::pair endNode = getClosestReachablePoint(mPathgrid, cell, + endPointInLocalCoords, + startNode); + + // if it's shorter for actor to travel from start to end, than to travel from either + // start or end to nearest pathgrid point, just travel from start to end. + float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2(); + float endTolastNodeLength2 = distanceSquared(mPathgrid->mPoints[endNode.first], endPointInLocalCoords); + float startTo1stNodeLength2 = distanceSquared(mPathgrid->mPoints[startNode], startPointInLocalCoords); + if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2)) { - std::pair endNode = getClosestReachablePoint(mPathgrid, cell, - Ogre::Vector3(endPoint.mX - xCell, endPoint.mY - yCell, static_cast(endPoint.mZ)), - startNode); + mPath.push_back(endPoint); + return; + } - // this shouldn't really happen, but just in case - if(endNode.first != -1) + // AiWander has logic that depends on whether a path was created, + // deleting allowed nodes if not. Hence a path needs to be created + // even if the start and the end points are the same. + // NOTE: aStarSearch will return an empty path if the start and end + // nodes are the same + if(startNode == endNode.first) + { + ESM::Pathgrid::Point temp(mPathgrid->mPoints[startNode]); + converter.toWorld(temp); + mPath.push_back(temp); + } + else + { + mPath = mCell->aStarSearch(startNode, endNode.first); + + // convert supplied path to world co-ordinates + for (std::list::iterator iter(mPath.begin()); iter != mPath.end(); ++iter) { - // AiWander has logic that depends on whether a path was created, - // deleting allowed nodes if not. Hence a path needs to be created - // even if the start and the end points are the same. - // NOTE: aStarSearch will return an empty path if the start and end - // nodes are the same - if(startNode == endNode.first) - { - mPath.push_back(endPoint); - return; - } - - mPath = mCell->aStarSearch(startNode, endNode.first); - - if(!mPath.empty()) - { - // Add the destination (which may be different to the closest - // pathgrid point). However only add if endNode was the closest - // point to endPoint. - // - // This logic can fail in the opposite situate, e.g. endPoint may - // have been reachable but happened to be very close to an - // unreachable pathgrid point. - // - // The AI routines will have to deal with such situations. - if(endNode.second) - mPath.push_back(endPoint); - } + converter.toWorld(*iter); } } - return; + // If endNode found is NOT the closest PathGrid point to the endPoint, + // assume endPoint is not reachable from endNode. In which case, + // path ends at endNode. + // + // So only add the destination (which may be different to the closest + // pathgrid point) when endNode was the closest point to endPoint. + // + // This logic can fail in the opposite situate, e.g. endPoint may + // have been reachable but happened to be very close to an + // unreachable pathgrid point. + // + // The AI routines will have to deal with such situations. + if(endNode.second) + mPath.push_back(endPoint); } float PathFinder::getZAngleToNext(float x, float y) const @@ -297,7 +304,7 @@ namespace MWMechanics float directionX = nextPoint.mX - x; float directionY = nextPoint.mY - y; - return Ogre::Math::ATan2(directionX,directionY).valueDegrees(); + return std::atan2(directionX, directionY); } float PathFinder::getXAngleToNext(float x, float y, float z) const @@ -308,9 +315,9 @@ namespace MWMechanics return 0.; const ESM::Pathgrid::Point &nextPoint = *mPath.begin(); - Ogre::Vector3 dir = MakeOgreVector3(nextPoint) - Ogre::Vector3(x,y,z); + osg::Vec3f dir = MakeOsgVec3(nextPoint) - osg::Vec3f(x,y,z); - return -Ogre::Math::ASin(dir.z / dir.length()).valueDegrees(); + return -std::asin(dir.z() / dir.length()); } bool PathFinder::checkPathCompleted(float x, float y, float tolerance) @@ -318,7 +325,7 @@ namespace MWMechanics if(mPath.empty()) return true; - ESM::Pathgrid::Point nextPoint = *mPath.begin(); + const ESM::Pathgrid::Point& nextPoint = *mPath.begin(); if (sqrDistanceIgnoreZ(nextPoint, x, y) < tolerance*tolerance) { mPath.pop_front(); diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index c978c9afab..6281bb072c 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -5,9 +5,6 @@ #include #include -#include -#include - namespace MWWorld { class CellStore; @@ -15,10 +12,10 @@ namespace MWWorld namespace MWMechanics { - float distance(ESM::Pathgrid::Point point, float x, float y, float z); - float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b); - float getZAngleToDir(const Ogre::Vector3& dir); - float getXAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f); + float distance(const ESM::Pathgrid::Point& point, float x, float y, float); + float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b); + float getZAngleToDir(const osg::Vec3f& dir); + float getXAngleToDir(const osg::Vec3f& dir); float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest); float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest); @@ -31,16 +28,18 @@ namespace MWMechanics // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH - bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY); + bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY); class PathFinder { public: PathFinder(); - static float sgn(Ogre::Radian a) + static const int PathTolerance = 32; + + static float sgn(float val) { - if(a.valueRadians() > 0) + if(val > 0) return 1.0; return -1.0; } @@ -57,9 +56,10 @@ namespace MWMechanics void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, const MWWorld::CellStore* cell); - bool checkPathCompleted(float x, float y, float tolerance=32.f); + bool checkPathCompleted(float x, float y, float tolerance = PathTolerance); ///< \Returns true if we are within \a tolerance units of the last path point. + /// In radians float getZAngleToNext(float x, float y) const; float getXAngleToNext(float x, float y, float z) const; @@ -94,8 +94,8 @@ namespace MWMechanics mPath.push_back(point); } - /// utility function to convert a Ogre::Vector3 to a Pathgrid::Point - static ESM::Pathgrid::Point MakePathgridPoint(const Ogre::Vector3& v) + /// utility function to convert a osg::Vec3f to a Pathgrid::Point + static ESM::Pathgrid::Point MakePathgridPoint(const osg::Vec3f& v) { return ESM::Pathgrid::Point(static_cast(v[0]), static_cast(v[1]), static_cast(v[2])); } @@ -106,14 +106,12 @@ namespace MWMechanics return ESM::Pathgrid::Point(static_cast(p.pos[0]), static_cast(p.pos[1]), static_cast(p.pos[2])); } - /// utility function to convert a Pathgrid::Point to a Ogre::Vector3 - static Ogre::Vector3 MakeOgreVector3(const ESM::Pathgrid::Point& p) + static osg::Vec3f MakeOsgVec3(const ESM::Pathgrid::Point& p) { - return Ogre::Vector3(static_cast(p.mX), static_cast(p.mY), static_cast(p.mZ)); + return osg::Vec3f(static_cast(p.mX), static_cast(p.mY), static_cast(p.mZ)); } private: - std::list mPath; const ESM::Pathgrid *mPathgrid; diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index 4e9bc89048..871baecdc3 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -312,29 +312,15 @@ namespace MWMechanics if(current != goal) return path; // for some reason couldn't build a path - // reconstruct path to return, using world co-ordinates - float xCell = 0; - float yCell = 0; - if (mIsExterior) - { - xCell = static_cast(mPathgrid->mData.mX * ESM::Land::REAL_SIZE); - yCell = static_cast(mPathgrid->mData.mY * ESM::Land::REAL_SIZE); - } - + // reconstruct path to return, using local co-ordinates while(graphParent[current] != -1) { - ESM::Pathgrid::Point pt = mPathgrid->mPoints[current]; - pt.mX += static_cast(xCell); - pt.mY += static_cast(yCell); - path.push_front(pt); + path.push_front(mPathgrid->mPoints[current]); current = graphParent[current]; } // add first node to path explicitly - ESM::Pathgrid::Point pt = mPathgrid->mPoints[start]; - pt.mX += static_cast(xCell); - pt.mY += static_cast(yCell); - path.push_front(pt); + path.push_front(mPathgrid->mPoints[start]); return path; } } diff --git a/apps/openmw/mwmechanics/pickpocket.cpp b/apps/openmw/mwmechanics/pickpocket.cpp index 561011df38..eca24606e6 100644 --- a/apps/openmw/mwmechanics/pickpocket.cpp +++ b/apps/openmw/mwmechanics/pickpocket.cpp @@ -1,6 +1,6 @@ #include "pickpocket.hpp" -#include +#include #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" @@ -41,7 +41,7 @@ namespace MWMechanics int iPickMaxChance = MWBase::Environment::get().getWorld()->getStore().get() .find("iPickMaxChance")->getInt(); - int roll = OEngine::Misc::Rng::roll0to99(); + int roll = Misc::Rng::roll0to99(); if (t < pcSneak / iPickMinChance) { return (roll > int(pcSneak / iPickMinChance)); diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index b5058fb88e..581ba3a841 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -14,15 +14,16 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/npcstats.hpp" +#include "creaturestats.hpp" +#include "npcstats.hpp" +#include "actorutil.hpp" namespace MWMechanics { void Repair::repair(const MWWorld::Ptr &itemToRepair) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); MWWorld::LiveCellRef *ref = mTool.get(); @@ -48,7 +49,7 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) float x = (0.1f * pcStrength + 0.1f * pcLuck + armorerSkill) * fatigueTerm; - int roll = OEngine::Misc::Rng::roll0to99(); + int roll = Misc::Rng::roll0to99(); if (roll <= x) { int y = static_cast(fRepairAmountMult * toolQuality * roll); @@ -82,7 +83,7 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) // tool used up? if (mTool.getCellRef().getCharge() == 0) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = getPlayer(); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); store.remove(mTool, 1, player); diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index 3f72f1b669..97878db874 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -1,6 +1,6 @@ #include "security.hpp" -#include +#include #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -31,7 +31,7 @@ namespace MWMechanics void Security::pickLock(const MWWorld::Ptr &lock, const MWWorld::Ptr &lockpick, std::string& resultMessage, std::string& resultSound) { - if (!(lock.getCellRef().getLockLevel() > 0)) //If it's unlocked back out immediately + if (!(lock.getCellRef().getLockLevel() > 0) || !lock.getClass().canLock(lock)) //If it's unlocked back out immediately return; int lockStrength = lock.getCellRef().getLockLevel(); @@ -50,7 +50,7 @@ namespace MWMechanics else { MWBase::Environment::get().getMechanicsManager()->objectOpened(mActor, lock); - if (OEngine::Misc::Rng::roll0to99() <= x) + if (Misc::Rng::roll0to99() <= x) { lock.getClass().unlock(lock); resultMessage = "#{sLockSuccess}"; @@ -91,7 +91,7 @@ namespace MWMechanics else { MWBase::Environment::get().getMechanicsManager()->objectOpened(mActor, trap); - if (OEngine::Misc::Rng::roll0to99() <= x) + if (Misc::Rng::roll0to99() <= x) { trap.getCellRef().setTrap(""); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 1f3a888279..ab8784beb1 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -1,10 +1,11 @@ #include "spellcasting.hpp" #include +#include #include -#include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -19,11 +20,14 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" + #include "../mwrender/animation.hpp" #include "magiceffects.hpp" #include "npcstats.hpp" #include "summoning.hpp" +#include "actorutil.hpp" namespace { @@ -65,57 +69,6 @@ namespace target.getClass().getCreatureStats(target).setDynamic(attribute, value); } - // TODO: refactor the effect tick functions in Actors so they can be reused here - void applyInstantEffectTick(MWMechanics::EffectKey effect, const MWWorld::Ptr& target, float magnitude) - { - int effectId = effect.mId; - if (effectId == ESM::MagicEffect::DamageHealth) - { - applyDynamicStatsEffect(0, target, magnitude * -1); - } - else if (effectId == ESM::MagicEffect::RestoreHealth) - { - applyDynamicStatsEffect(0, target, magnitude); - } - else if (effectId == ESM::MagicEffect::DamageFatigue) - { - applyDynamicStatsEffect(2, target, magnitude * -1); - } - else if (effectId == ESM::MagicEffect::RestoreFatigue) - { - applyDynamicStatsEffect(2, target, magnitude); - } - else if (effectId == ESM::MagicEffect::DamageMagicka) - { - applyDynamicStatsEffect(1, target, magnitude * -1); - } - else if (effectId == ESM::MagicEffect::RestoreMagicka) - { - applyDynamicStatsEffect(1, target, magnitude); - } - else if (effectId == ESM::MagicEffect::DamageAttribute || effectId == ESM::MagicEffect::RestoreAttribute) - { - int attribute = effect.mArg; - MWMechanics::AttributeValue value = target.getClass().getCreatureStats(target).getAttribute(attribute); - if (effectId == ESM::MagicEffect::DamageAttribute) - value.damage(magnitude); - else - value.restore(magnitude); - target.getClass().getCreatureStats(target).setAttribute(attribute, value); - } - else if (effectId == ESM::MagicEffect::DamageSkill || effectId == ESM::MagicEffect::RestoreSkill) - { - if (target.getTypeName() != typeid(ESM::NPC).name()) - return; - int skill = effect.mArg; - MWMechanics::SkillValue& value = target.getClass().getNpcStats(target).getSkill(skill); - if (effectId == ESM::MagicEffect::DamageSkill) - value.damage(magnitude); - else - value.restore(magnitude); - } - } - } namespace MWMechanics @@ -141,7 +94,7 @@ namespace MWMechanics if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude()) return 0; - float y = FLT_MAX; + float y = std::numeric_limits::max(); float lowestSkill = 0; for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) @@ -170,6 +123,9 @@ namespace MWMechanics } } + if (spell->mData.mType == ESM::Spell::ST_Power) + return stats.getSpells().canUsePower(spell) ? 100 : 0; + if (spell->mData.mType != ESM::Spell::ST_Spell) return 100; @@ -182,7 +138,7 @@ namespace MWMechanics int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); float castChance = (lowestSkill - spell->mData.mCost + castBonus + 0.2f * actorWillpower + 0.1f * actorLuck) * stats.getFatigueTerm(); - if (MWBase::Environment::get().getWorld()->getGodModeState() && actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (MWBase::Environment::get().getWorld()->getGodModeState() && actor == getPlayer()) castChance = 100; if (!cap) @@ -282,7 +238,7 @@ namespace MWMechanics if (castChance > 0) x *= 50 / castChance; - float roll = OEngine::Misc::Rng::rollClosedProbability() * 100; + float roll = Misc::Rng::rollClosedProbability() * 100; if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) roll -= resistance; @@ -381,14 +337,15 @@ namespace MWMechanics const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); if (spell && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) { - float x = (spell->mData.mType == ESM::Spell::ST_Disease) ? - target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).getMagnitude() - : target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).getMagnitude(); + int requiredResistance = (spell->mData.mType == ESM::Spell::ST_Disease) ? + ESM::MagicEffect::ResistCommonDisease + : ESM::MagicEffect::ResistBlightDisease; + float x = target.getClass().getCreatureStats(target).getMagicEffects().get(requiredResistance).getMagnitude(); - if (OEngine::Misc::Rng::roll0to99() <= x) + if (Misc::Rng::roll0to99() <= x) { // Fully resisted, show message - if (target == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); return; } @@ -406,27 +363,7 @@ namespace MWMechanics if (target.getClass().isActor()) targetEffects += target.getClass().getCreatureStats(target).getMagicEffects(); - bool castByPlayer = (!caster.isEmpty() && caster == MWBase::Environment::get().getWorld()->getPlayerPtr()); - - // Try absorbing if it's a spell - // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure - // if that is worth replicating. - bool absorbed = false; - if (spell && caster != target && target.getClass().isActor()) - { - float absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude(); - absorbed = (OEngine::Misc::Rng::roll0to99() < absorb); - if (absorbed) - { - const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); - MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( - "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, ""); - // Magicka is increased by cost of spell - DynamicStat magicka = target.getClass().getCreatureStats(target).getMagicka(); - magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); - target.getClass().getCreatureStats(target).setMagicka(magicka); - } - } + bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); for (std::vector::const_iterator effectIt (effects.mList.begin()); effectIt!=effects.mList.end(); ++effectIt) @@ -447,6 +384,26 @@ namespace MWMechanics && target.getClass().isActor()) MWBase::Environment::get().getWindowManager()->setEnemy(target); + // Try absorbing if it's a spell + // NOTE: Vanilla does this once per spell absorption effect source instead of adding the % from all sources together, not sure + // if that is worth replicating. + bool absorbed = false; + if (spell && caster != target && target.getClass().isActor()) + { + float absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude(); + absorbed = (Misc::Rng::roll0to99() < absorb); + if (absorbed) + { + const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); + MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( + "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, ""); + // Magicka is increased by cost of spell + DynamicStat magicka = target.getClass().getCreatureStats(target).getMagicka(); + magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); + target.getClass().getCreatureStats(target).setMagicka(magicka); + } + } + float magnitudeMult = 1; if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && target.getClass().isActor()) { @@ -463,7 +420,7 @@ namespace MWMechanics if (!reflected && magnitudeMult > 0 && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) { float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude(); - bool isReflected = (OEngine::Misc::Rng::roll0to99() < reflect); + bool isReflected = (Misc::Rng::roll0to99() < reflect); if (isReflected) { const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Reflect"); @@ -481,7 +438,7 @@ namespace MWMechanics if (magnitudeMult == 0) { // Fully resisted, show message - if (target == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (target == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); else if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); @@ -491,49 +448,62 @@ namespace MWMechanics if (magnitudeMult > 0 && !absorbed) { - float random = OEngine::Misc::Rng::rollClosedProbability(); + float random = Misc::Rng::rollClosedProbability(); float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; magnitude *= magnitudeMult; - bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); - if (target.getClass().isActor() && hasDuration && effectIt->mDuration > 0) + if (!target.getClass().isActor()) { - ActiveSpells::ActiveEffect effect; - effect.mEffectId = effectIt->mEffectID; - effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; - effect.mDuration = static_cast(effectIt->mDuration); - effect.mMagnitude = magnitude; - - targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude)); - - appliedLastingEffects.push_back(effect); - - // For absorb effects, also apply the effect to the caster - but with a negative - // magnitude, since we're transfering stats from the target to the caster - if (!caster.isEmpty() && caster.getClass().isActor()) + // non-actor objects have no list of active magic effects, so have to apply instantly + if (!applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude)) + continue; + } + else // target.getClass().isActor() == true + { + bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); + if (hasDuration && effectIt->mDuration == 0) { - for (int i=0; i<5; ++i) + // duration 0 means apply full magnitude instantly + bool wasDead = target.getClass().getCreatureStats(target).isDead(); + effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), magnitude); + bool isDead = target.getClass().getCreatureStats(target).isDead(); + + if (!wasDead && isDead) + MWBase::Environment::get().getMechanicsManager()->actorKilled(target, caster); + } + else + { + // add to list of active effects, to apply in next frame + ActiveSpells::ActiveEffect effect; + effect.mEffectId = effectIt->mEffectID; + effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; + effect.mDuration = static_cast(effectIt->mDuration); + effect.mMagnitude = magnitude; + + targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude)); + + appliedLastingEffects.push_back(effect); + + // For absorb effects, also apply the effect to the caster - but with a negative + // magnitude, since we're transfering stats from the target to the caster + if (!caster.isEmpty() && caster.getClass().isActor()) { - if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i) + for (int i=0; i<5; ++i) { - std::vector effects; - ActiveSpells::ActiveEffect effect_ = effect; - effect_.mMagnitude *= -1; - effects.push_back(effect_); - // Also make sure to set casterActorId = target, so that the effect on the caster gets purged when the target dies - caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, - effects, mSourceName, target.getClass().getCreatureStats(target).getActorId()); + if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i) + { + std::vector effects; + ActiveSpells::ActiveEffect effect_ = effect; + effect_.mMagnitude *= -1; + effects.push_back(effect_); + // Also make sure to set casterActorId = target, so that the effect on the caster gets purged when the target dies + caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, + effects, mSourceName, target.getClass().getCreatureStats(target).getActorId()); + } } } } } - else - { - if (hasDuration && target.getClass().isActor()) - applyInstantEffectTick(EffectKey(*effectIt), target, magnitude); - else - applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude); - } // Re-casting a summon effect will remove the creature from previous castings of that effect. if (isSummoningEffect(effectIt->mEffectID) && !target.isEmpty() && target.getClass().isActor()) @@ -601,19 +571,20 @@ namespace MWMechanics target.getClass().onHit(target, 0.f, true, MWWorld::Ptr(), caster, true); } - void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude) + bool CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude) { short effectId = effect.mId; - if (!target.getClass().isActor()) + if (target.getClass().canLock(target)) { if (effectId == ESM::MagicEffect::Lock) { if (target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude { - if (caster == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}"); - target.getCellRef().setLockLevel(static_cast(magnitude)); + target.getClass().lock(target, static_cast(magnitude)); } + return true; } else if (effectId == ESM::MagicEffect::Open) { @@ -625,50 +596,62 @@ namespace MWMechanics if (!caster.isEmpty() && caster.getClass().isActor()) MWBase::Environment::get().getMechanicsManager()->objectOpened(caster, target); - if (caster == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}"); } - target.getCellRef().setLockLevel(-abs(target.getCellRef().getLockLevel())); + target.getClass().unlock(target); } else MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); + return true; } } - else + else if (target.getClass().isActor()) { - if (effectId == ESM::MagicEffect::CurePoison) + switch (effectId) + { + case ESM::MagicEffect::CurePoison: target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison); - else if (effectId == ESM::MagicEffect::CureParalyzation) + return true; + case ESM::MagicEffect::CureParalyzation: target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze); - else if (effectId == ESM::MagicEffect::CureCommonDisease) + return true; + case ESM::MagicEffect::CureCommonDisease: target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease(); - else if (effectId == ESM::MagicEffect::CureBlightDisease) + return true; + case ESM::MagicEffect::CureBlightDisease: target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease(); - else if (effectId == ESM::MagicEffect::CureCorprusDisease) + return true; + case ESM::MagicEffect::CureCorprusDisease: target.getClass().getCreatureStats(target).getSpells().purgeCorprusDisease(); - else if (effectId == ESM::MagicEffect::Dispel) + return true; + case ESM::MagicEffect::Dispel: target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(magnitude); - else if (effectId == ESM::MagicEffect::RemoveCurse) + return true; + case ESM::MagicEffect::RemoveCurse: target.getClass().getCreatureStats(target).getSpells().purgeCurses(); + return true; + } - if (target != MWBase::Environment::get().getWorld()->getPlayerPtr()) - return; - if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled()) - return; + if (target != getPlayer()) + return false; if (effectId == ESM::MagicEffect::DivineIntervention) { MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "divinemarker"); + return true; } else if (effectId == ESM::MagicEffect::AlmsiviIntervention) { MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "templemarker"); + return true; } else if (effectId == ESM::MagicEffect::Mark) { MWBase::Environment::get().getWorld()->getPlayer().markPosition( target.getCell(), target.getRefData().getPosition()); + return true; } else if (effectId == ESM::MagicEffect::Recall) { @@ -682,8 +665,10 @@ namespace MWMechanics markedPosition, false); action.execute(target); } + return true; } } + return false; } @@ -727,7 +712,7 @@ namespace MWMechanics if (item.getCellRef().getEnchantmentCharge() < castCost) { - if (mCaster == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mCaster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); // Failure sound @@ -751,14 +736,14 @@ namespace MWMechanics if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) { - if (mCaster == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mCaster == getPlayer()) mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1); } if (enchantment->mData.mType == ESM::Enchantment::CastOnce) item.getContainerStore()->remove(item, 1, mCaster); else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes) { - if (mCaster == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mCaster == getPlayer()) { mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3); } @@ -779,7 +764,7 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, false, enchantment->mEffects, mCaster, mSourceName, // Not needed, enchantments can only be cast by actors - Ogre::Vector3(1,0,0)); + osg::Vec3f(1,0,0)); return true; } @@ -823,9 +808,9 @@ namespace MWMechanics // Check success float successChance = getSpellSuccessChance(spell, mCaster); - if (OEngine::Misc::Rng::roll0to99() >= successChance) + if (Misc::Rng::roll0to99() >= successChance) { - if (mCaster == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mCaster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); fail = true; } @@ -841,9 +826,13 @@ namespace MWMechanics sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f); return false; } + + // A power can be used once per 24h + if (spell->mData.mType == ESM::Spell::ST_Power) + stats.getSpells().usePower(spell); } - if (mCaster == MWBase::Environment::get().getWorld()->getPlayerPtr() && spellIncreasesSkill(spell)) + if (mCaster == getPlayer() && spellIncreasesSkill(spell)) mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0); @@ -861,13 +850,13 @@ namespace MWMechanics getProjectileInfo(spell->mEffects, projectileModel, sound, speed); if (!projectileModel.empty()) { - Ogre::Vector3 fallbackDirection (0,1,0); + osg::Vec3f fallbackDirection (0,1,0); // Fall back to a "caster to target" direction if we have no other means of determining it // (e.g. when cast by a non-actor) if (!mTarget.isEmpty()) fallbackDirection = - Ogre::Vector3(mTarget.getRefData().getPosition().pos)- - Ogre::Vector3(mCaster.getRefData().getPosition().pos); + osg::Vec3f(mTarget.getRefData().getPosition().asVec3())- + osg::Vec3f(mCaster.getRefData().getPosition().asVec3()); MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, false, spell->mEffects, mCaster, mSourceName, fallbackDirection); @@ -901,7 +890,7 @@ namespace MWMechanics + 0.1f * creatureStats.getAttribute (ESM::Attribute::Luck).getModified()) * creatureStats.getFatigueTerm(); - int roll = OEngine::Misc::Rng::roll0to99(); + int roll = Misc::Rng::roll0to99(); if (roll > x) { // "X has no effect on you" @@ -960,4 +949,171 @@ namespace MWMechanics || (effectId >= ESM::MagicEffect::SummonFabricant && effectId <= ESM::MagicEffect::SummonCreature05)); } + + bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate) + { + if (ptr.getClass().hasInventoryStore(ptr)) + { + MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); + MWWorld::ContainerStoreIterator item = + inv.getSlot(slot); + + if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon)) + { + if (!item->getClass().hasItemHealth(*item)) + return false; + int charge = item->getClass().getItemHealth(*item); + + if (charge == 0) + return false; + + // FIXME: charge should be a float, not int so that damage < 1 per frame can be applied. + // This was also a bug in the original engine. + charge -= + std::min(static_cast(disintegrate), + charge); + item->getCellRef().setCharge(charge); + + if (charge == 0) + { + // Will unequip the broken item and try to find a replacement + if (ptr != getPlayer()) + inv.autoEquip(ptr); + else + inv.unequipItem(*item, ptr); + } + + return true; + } + } + return false; + } + + void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude) + { + DynamicStat stat = creatureStats.getDynamic(index); + stat.setCurrent(stat.getCurrent() + magnitude, index == 2); + creatureStats.setDynamic(index, stat); + } + + void effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude) + { + if (magnitude == 0.f) + return; + + bool receivedMagicDamage = false; + + switch (effectKey.mId) + { + case ESM::MagicEffect::DamageAttribute: + { + AttributeValue attr = creatureStats.getAttribute(effectKey.mArg); + attr.damage(magnitude); + creatureStats.setAttribute(effectKey.mArg, attr); + break; + } + case ESM::MagicEffect::RestoreAttribute: + { + AttributeValue attr = creatureStats.getAttribute(effectKey.mArg); + attr.restore(magnitude); + creatureStats.setAttribute(effectKey.mArg, attr); + break; + } + case ESM::MagicEffect::RestoreHealth: + case ESM::MagicEffect::RestoreMagicka: + case ESM::MagicEffect::RestoreFatigue: + adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude); + break; + case ESM::MagicEffect::DamageHealth: + case ESM::MagicEffect::DamageMagicka: + case ESM::MagicEffect::DamageFatigue: + receivedMagicDamage = true; + adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); + break; + case ESM::MagicEffect::AbsorbHealth: + case ESM::MagicEffect::AbsorbMagicka: + case ESM::MagicEffect::AbsorbFatigue: + if (magnitude > 0.f) + receivedMagicDamage = true; + adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); + break; + + case ESM::MagicEffect::DisintegrateArmor: + { + // According to UESP + int priorities[] = { + MWWorld::InventoryStore::Slot_CarriedLeft, + MWWorld::InventoryStore::Slot_Cuirass, + MWWorld::InventoryStore::Slot_LeftPauldron, + MWWorld::InventoryStore::Slot_RightPauldron, + MWWorld::InventoryStore::Slot_LeftGauntlet, + MWWorld::InventoryStore::Slot_RightGauntlet, + MWWorld::InventoryStore::Slot_Helmet, + MWWorld::InventoryStore::Slot_Greaves, + MWWorld::InventoryStore::Slot_Boots + }; + + for (unsigned int i=0; iisExterior()) + break; + float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour(); + float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); + float damageScale = 1.f - timeDiff / 7.f; + // When cloudy, the sun damage effect is halved + static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get().find( + "fMagicSunBlockedMult")->getFloat(); + + int weather = MWBase::Environment::get().getWorld()->getCurrentWeather(); + if (weather > 1) + damageScale *= fMagicSunBlockedMult; + + adjustDynamicStat(creatureStats, 0, -magnitude * damageScale); + if (magnitude * damageScale > 0.f) + receivedMagicDamage = true; + break; + } + + case ESM::MagicEffect::FireDamage: + case ESM::MagicEffect::ShockDamage: + case ESM::MagicEffect::FrostDamage: + case ESM::MagicEffect::Poison: + { + adjustDynamicStat(creatureStats, 0, -magnitude); + receivedMagicDamage = true; + break; + } + + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::RestoreSkill: + { + if (!actor.getClass().isNpc()) + break; + NpcStats &npcStats = actor.getClass().getNpcStats(actor); + SkillValue& skill = npcStats.getSkill(effectKey.mArg); + if (effectKey.mId == ESM::MagicEffect::RestoreSkill) + skill.restore(magnitude); + else + skill.damage(magnitude); + break; + } + + } + + if (receivedMagicDamage && actor == getPlayer()) + MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); + } + } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index f50584edf2..5b48bd4a8d 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -3,8 +3,6 @@ #include "../mwworld/ptr.hpp" -#include - #include namespace ESM @@ -19,6 +17,7 @@ namespace MWMechanics { struct EffectKey; class MagicEffects; + class CreatureStats; ESM::Skill::SkillEnum spellSchoolToSkill(int school); @@ -62,6 +61,8 @@ namespace MWMechanics int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor); + void effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const MWMechanics::EffectKey& effectKey, float magnitude); + class CastSpell { private: @@ -71,7 +72,7 @@ namespace MWMechanics bool mStack; std::string mId; // ID of spell, potion, item etc std::string mSourceName; // Display name for spell, potion, etc - Ogre::Vector3 mHitPosition; // Used for spawning area orb + osg::Vec3f mHitPosition; // Used for spawning area orb bool mAlwaysSucceed; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false) public: @@ -96,7 +97,8 @@ namespace MWMechanics const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false); /// @note \a caster can be any type of object, or even an empty object. - void applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); + /// @return was the target suitable for the effect? + bool applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); }; } diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index b1829964be..a88b6b2633 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -1,10 +1,10 @@ - #include "spells.hpp" #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -25,9 +25,24 @@ namespace MWMechanics return mSpells.end(); } + const ESM::Spell* Spells::getSpell(const std::string& id) const + { + return MWBase::Environment::get().getWorld()->getStore().get().find(id); + } + + bool Spells::hasSpell(const std::string &spell) const + { + return hasSpell(getSpell(spell)); + } + + bool Spells::hasSpell(const ESM::Spell *spell) const + { + return mSpells.find(spell) != mSpells.end(); + } + void Spells::add (const ESM::Spell* spell) { - if (mSpells.find (spell->mId)==mSpells.end()) + if (mSpells.find (spell)==mSpells.end()) { std::map random; @@ -38,7 +53,7 @@ namespace MWMechanics for (unsigned int i=0; imEffects.mList.size();++i) { if (spell->mEffects.mList[i].mMagnMin != spell->mEffects.mList[i].mMagnMax) - random[i] = OEngine::Misc::Rng::rollClosedProbability(); + random[i] = Misc::Rng::rollClosedProbability(); } } @@ -48,32 +63,32 @@ namespace MWMechanics corprus.mWorsenings = 0; corprus.mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod; - mCorprusSpells[spell->mId] = corprus; + mCorprusSpells[spell] = corprus; } - mSpells.insert (std::make_pair (spell->mId, random)); + mSpells.insert (std::make_pair (spell, random)); } } void Spells::add (const std::string& spellId) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - add(spell); + add(getSpell(spellId)); } void Spells::remove (const std::string& spellId) { - std::string lower = Misc::StringUtils::lowerCase(spellId); - TContainer::iterator iter = mSpells.find (lower); - std::map::iterator corprusIt = mCorprusSpells.find(lower); + const ESM::Spell* spell = getSpell(spellId); + TContainer::iterator iter = mSpells.find (spell); + + std::map::iterator corprusIt = mCorprusSpells.find(spell); // if it's corprus, remove negative and keep positive effects if (corprusIt != mCorprusSpells.end()) { - worsenCorprus(lower); - if (mPermanentSpellEffects.find(lower) != mPermanentSpellEffects.end()) + worsenCorprus(spell); + if (mPermanentSpellEffects.find(spell) != mPermanentSpellEffects.end()) { - MagicEffects & effects = mPermanentSpellEffects[lower]; + MagicEffects & effects = mPermanentSpellEffects[spell]; for (MagicEffects::Collection::const_iterator effectIt = effects.begin(); effectIt != effects.end();) { const ESM::MagicEffect * magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->first.mId); @@ -101,8 +116,7 @@ namespace MWMechanics for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + const ESM::Spell *spell = iter->first; if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse) @@ -120,7 +134,7 @@ namespace MWMechanics } } - for (std::map::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) + for (std::map::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) { effects += it->second; } @@ -145,11 +159,10 @@ namespace MWMechanics bool Spells::isSpellActive(const std::string &id) const { - TContainer::const_iterator found = mSpells.find(id); + TContainer::const_iterator found = mSpells.find(getSpell(id)); if (found != mSpells.end()) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (id); + const ESM::Spell *spell = found->first; return (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse); @@ -161,9 +174,7 @@ namespace MWMechanics { for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - + const ESM::Spell *spell = iter->first; if (spell->mData.mType == ESM::Spell::ST_Disease) return true; } @@ -175,9 +186,7 @@ namespace MWMechanics { for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - + const ESM::Spell *spell = iter->first; if (spell->mData.mType == ESM::Spell::ST_Blight) return true; } @@ -189,9 +198,7 @@ namespace MWMechanics { for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - + const ESM::Spell *spell = iter->first; if (spell->mData.mType == ESM::Spell::ST_Disease) mSpells.erase(iter++); else @@ -203,9 +210,7 @@ namespace MWMechanics { for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - + const ESM::Spell *spell = iter->first; if (spell->mData.mType == ESM::Spell::ST_Blight && !hasCorprusEffect(spell)) mSpells.erase(iter++); else @@ -217,9 +222,7 @@ namespace MWMechanics { for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - + const ESM::Spell *spell = iter->first; if (hasCorprusEffect(spell)) mSpells.erase(iter++); else @@ -231,9 +234,7 @@ namespace MWMechanics { for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - + const ESM::Spell *spell = iter->first; if (spell->mData.mType == ESM::Spell::ST_Curse) mSpells.erase(iter++); else @@ -245,7 +246,7 @@ namespace MWMechanics { for (TIterator it = begin(); it != end(); ++it) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); + const ESM::Spell* spell = it->first; // these are the spell types that are permanently in effect if (!(spell->mData.mType == ESM::Spell::ST_Ability) @@ -268,14 +269,13 @@ namespace MWMechanics } } - void Spells::worsenCorprus(const std::string &corpSpellId) + void Spells::worsenCorprus(const ESM::Spell* spell) { - mCorprusSpells[corpSpellId].mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod; - mCorprusSpells[corpSpellId].mWorsenings++; + mCorprusSpells[spell].mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod; + mCorprusSpells[spell].mWorsenings++; // update worsened effects - mPermanentSpellEffects[corpSpellId] = MagicEffects(); - const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().find(corpSpellId); + mPermanentSpellEffects[spell] = MagicEffects(); int i=0; for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt, ++i) { @@ -283,12 +283,12 @@ namespace MWMechanics if ((effectIt->mEffectID != ESM::MagicEffect::Corprus) && (magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage)) // APPLIED_ONCE { float random = 1.f; - if (mSpells[corpSpellId].find(i) != mSpells[corpSpellId].end()) - random = mSpells[corpSpellId].at(i); + if (mSpells[spell].find(i) != mSpells[spell].end()) + random = mSpells[spell].at(i); float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; - magnitude *= std::max(1, mCorprusSpells[corpSpellId].mWorsenings); - mPermanentSpellEffects[corpSpellId].add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(magnitude)); + magnitude *= std::max(1, mCorprusSpells[spell].mWorsenings); + mPermanentSpellEffects[spell].add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(magnitude)); } } } @@ -305,43 +305,47 @@ namespace MWMechanics return false; } - const std::map &Spells::getCorprusSpells() const + const std::map &Spells::getCorprusSpells() const { return mCorprusSpells; } - bool Spells::canUsePower(const std::string &power) const + bool Spells::canUsePower(const ESM::Spell* spell) const { - std::map::const_iterator it = mUsedPowers.find(power); + std::map::const_iterator it = mUsedPowers.find(spell); if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp()) return true; else return false; } - void Spells::usePower(const std::string &power) + void Spells::usePower(const ESM::Spell* spell) { - mUsedPowers[power] = MWBase::Environment::get().getWorld()->getTimeStamp(); + mUsedPowers[spell] = MWBase::Environment::get().getWorld()->getTimeStamp(); } void Spells::readState(const ESM::SpellState &state) { - for (TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) + for (ESM::SpellState::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) { // Discard spells that are no longer available due to changed content files const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); if (spell) { - mSpells[it->first] = it->second; + mSpells[spell] = it->second; if (it->first == state.mSelectedSpell) mSelectedSpell = it->first; } } - // No need to discard spells here (doesn't really matter if non existent ids are kept) for (std::map::const_iterator it = state.mUsedPowers.begin(); it != state.mUsedPowers.end(); ++it) - mUsedPowers[it->first] = MWWorld::TimeStamp(it->second); + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + if (!spell) + continue; + mUsedPowers[spell] = MWWorld::TimeStamp(it->second); + } for (std::map >::const_iterator it = state.mPermanentSpellEffects.begin(); it != state.mPermanentSpellEffects.end(); ++it) @@ -350,33 +354,35 @@ namespace MWMechanics if (!spell) continue; - mPermanentSpellEffects[it->first] = MagicEffects(); + mPermanentSpellEffects[spell] = MagicEffects(); for (std::vector::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt) { - mPermanentSpellEffects[it->first].add(EffectKey(effectIt->mId, effectIt->mArg), effectIt->mMagnitude); + mPermanentSpellEffects[spell].add(EffectKey(effectIt->mId, effectIt->mArg), effectIt->mMagnitude); } } mCorprusSpells.clear(); for (std::map::const_iterator it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) { - if (mSpells.find(it->first) != mSpells.end()) // Discard unavailable corprus spells - { - mCorprusSpells[it->first].mWorsenings = state.mCorprusSpells.at(it->first).mWorsenings; - mCorprusSpells[it->first].mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); - } + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + if (!spell) // Discard unavailable corprus spells + continue; + mCorprusSpells[spell].mWorsenings = state.mCorprusSpells.at(it->first).mWorsenings; + mCorprusSpells[spell].mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); } } void Spells::writeState(ESM::SpellState &state) const { - state.mSpells = mSpells; + for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) + state.mSpells.insert(std::make_pair(it->first->mId, it->second)); + state.mSelectedSpell = mSelectedSpell; - for (std::map::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) - state.mUsedPowers[it->first] = it->second.toEsm(); + for (std::map::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) + state.mUsedPowers[it->first->mId] = it->second.toEsm(); - for (std::map::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) + for (std::map::const_iterator it = mPermanentSpellEffects.begin(); it != mPermanentSpellEffects.end(); ++it) { std::vector effectList; for (MagicEffects::Collection::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt) @@ -388,13 +394,13 @@ namespace MWMechanics effectList.push_back(info); } - state.mPermanentSpellEffects[it->first] = effectList; + state.mPermanentSpellEffects[it->first->mId] = effectList; } - for (std::map::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) + for (std::map::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) { - state.mCorprusSpells[it->first].mWorsenings = mCorprusSpells.at(it->first).mWorsenings; - state.mCorprusSpells[it->first].mNextWorsening = mCorprusSpells.at(it->first).mNextWorsening.toEsm(); + state.mCorprusSpells[it->first->mId].mWorsenings = mCorprusSpells.at(it->first).mWorsenings; + state.mCorprusSpells[it->first->mId].mNextWorsening = mCorprusSpells.at(it->first).mNextWorsening.toEsm(); } } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 6b41499395..1b1993d5ef 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -31,7 +31,9 @@ namespace MWMechanics { public: - typedef std::map > TContainer; // ID, + typedef const ESM::Spell* SpellKey; + + typedef std::map > TContainer; // ID, typedef TContainer::const_iterator TIterator; struct CorprusStats @@ -47,23 +49,26 @@ namespace MWMechanics TContainer mSpells; // spell-tied effects that will be applied even after removing the spell (currently used to keep positive effects when corprus is removed) - std::map mPermanentSpellEffects; + std::map mPermanentSpellEffects; // Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different) std::string mSelectedSpell; - std::map mUsedPowers; + std::map mUsedPowers; - std::map mCorprusSpells; + std::map mCorprusSpells; + + /// Get spell from ID, throws exception if not found + const ESM::Spell* getSpell(const std::string& id) const; public: - void worsenCorprus(const std::string &corpSpellId); + void worsenCorprus(const ESM::Spell* spell); static bool hasCorprusEffect(const ESM::Spell *spell); - const std::map & getCorprusSpells() const; + const std::map & getCorprusSpells() const; - bool canUsePower (const std::string& power) const; - void usePower (const std::string& power); + bool canUsePower (const ESM::Spell* spell) const; + void usePower (const ESM::Spell* spell); void purgeCommonDisease(); void purgeBlightDisease(); @@ -74,7 +79,8 @@ namespace MWMechanics TIterator end() const; - bool hasSpell(const std::string& spell) const { return mSpells.find(Misc::StringUtils::lowerCase(spell)) != mSpells.end(); } + bool hasSpell(const std::string& spell) const; + bool hasSpell(const ESM::Spell* spell) const; void add (const std::string& spell); ///< Adding a spell that is already listed in *this is a no-op. diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index 1b909d579c..6eb5e0246d 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -1,28 +1,276 @@ - #include "stat.hpp" -void MWMechanics::AttributeValue::writeState (ESM::StatState& state) const +#include + +namespace MWMechanics { - state.mBase = mBase; - state.mMod = mModifier; - state.mDamage = mDamage; + template + Stat::Stat() : mBase (0), mModified (0) {} + template + Stat::Stat(T base) : mBase (base), mModified (base) {} + template + Stat::Stat(T base, T modified) : mBase (base), mModified (modified) {} + + template + const T& Stat::getBase() const + { + return mBase; + } + + template + T Stat::getModified() const + { + return std::max(static_cast(0), mModified); + } + template + T Stat::getModifier() const + { + return mModified-mBase; + } + template + void Stat::set (const T& value) + { + mBase = mModified = value; + } + template + void Stat::modify(const T& diff) + { + mBase += diff; + if(mBase >= static_cast(0)) + mModified += diff; + else + { + mModified += diff - mBase; + mBase = static_cast(0); + } + } + template + void Stat::setBase (const T& value) + { + T diff = value - mBase; + mBase = value; + mModified += diff; + } + template + void Stat::setModified (T value, const T& min, const T& max) + { + T diff = value - mModified; + + if (mBase+diffmax) + { + value = max + (mModified - mBase); + diff = value - mModified; + } + + mModified = value; + mBase += diff; + } + template + void Stat::setModifier (const T& modifier) + { + mModified = mBase + modifier; + } + + template + void Stat::writeState (ESM::StatState& state) const + { + state.mBase = mBase; + state.mMod = mModified; + } + template + void Stat::readState (const ESM::StatState& state) + { + mBase = state.mBase; + mModified = state.mMod; + } + + + template + DynamicStat::DynamicStat() : mStatic (0), mCurrent (0) {} + template + DynamicStat::DynamicStat(T base) : mStatic (base), mCurrent (base) {} + template + DynamicStat::DynamicStat(T base, T modified, T current) : mStatic(base, modified), mCurrent (current) {} + template + DynamicStat::DynamicStat(const Stat &stat, T current) : mStatic(stat), mCurrent (current) {} + + + template + const T& DynamicStat::getBase() const + { + return mStatic.getBase(); + } + template + T DynamicStat::getModified() const + { + return mStatic.getModified(); + } + template + const T& DynamicStat::getCurrent() const + { + return mCurrent; + } + + template + void DynamicStat::set (const T& value) + { + mStatic.set (value); + mCurrent = value; + } + template + void DynamicStat::setBase (const T& value) + { + mStatic.setBase (value); + + if (mCurrent>getModified()) + mCurrent = getModified(); + } + template + void DynamicStat::setModified (T value, const T& min, const T& max) + { + mStatic.setModified (value, min, max); + + if (mCurrent>getModified()) + mCurrent = getModified(); + } + template + void DynamicStat::modify (const T& diff, bool allowCurrentDecreaseBelowZero) + { + mStatic.modify (diff); + setCurrent (getCurrent()+diff, allowCurrentDecreaseBelowZero); + } + template + void DynamicStat::setCurrent (const T& value, bool allowDecreaseBelowZero) + { + if (value > mCurrent) + { + // increase + mCurrent = value; + + if (mCurrent > getModified()) + mCurrent = getModified(); + } + else if (value > 0 || allowDecreaseBelowZero) + { + // allowed decrease + mCurrent = value; + } + else if (mCurrent > 0) + { + // capped decrease + mCurrent = 0; + } + } + template + void DynamicStat::setModifier (const T& modifier, bool allowCurrentDecreaseBelowZero) + { + T diff = modifier - mStatic.getModifier(); + mStatic.setModifier (modifier); + setCurrent (getCurrent()+diff, allowCurrentDecreaseBelowZero); + } + + template + void DynamicStat::writeState (ESM::StatState& state) const + { + mStatic.writeState (state); + state.mCurrent = mCurrent; + } + template + void DynamicStat::readState (const ESM::StatState& state) + { + mStatic.readState (state); + mCurrent = state.mCurrent; + } + + AttributeValue::AttributeValue() : + mBase(0), mModifier(0), mDamage(0) + { + } + + int AttributeValue::getModified() const + { + return std::max(0, mBase - (int) mDamage + mModifier); + } + int AttributeValue::getBase() const + { + return mBase; + } + int AttributeValue::getModifier() const + { + return mModifier; + } + + void AttributeValue::setBase(int base) + { + mBase = std::max(0, base); + } + + void AttributeValue::setModifier(int mod) + { + mModifier = mod; + } + + void AttributeValue::damage(float damage) + { + mDamage += std::min(damage, (float)getModified()); + } + void AttributeValue::restore(float amount) + { + mDamage -= std::min(mDamage, amount); + } + + float AttributeValue::getDamage() const + { + return mDamage; + } + + void AttributeValue::writeState (ESM::StatState& state) const + { + state.mBase = mBase; + state.mMod = mModifier; + state.mDamage = mDamage; + } + + void AttributeValue::readState (const ESM::StatState& state) + { + mBase = state.mBase; + mModifier = state.mMod; + mDamage = state.mDamage; + } + + SkillValue::SkillValue() : + mProgress(0) + { + } + + float SkillValue::getProgress() const + { + return mProgress; + } + void SkillValue::setProgress(float progress) + { + mProgress = progress; + } + + void SkillValue::writeState (ESM::StatState& state) const + { + AttributeValue::writeState (state); + state.mProgress = mProgress; + } + + void SkillValue::readState (const ESM::StatState& state) + { + AttributeValue::readState (state); + mProgress = state.mProgress; + } } -void MWMechanics::AttributeValue::readState (const ESM::StatState& state) -{ - mBase = state.mBase; - mModifier = state.mMod; - mDamage = state.mDamage; -} - -void MWMechanics::SkillValue::writeState (ESM::StatState& state) const -{ - AttributeValue::writeState (state); - state.mProgress = mProgress; -} - -void MWMechanics::SkillValue::readState (const ESM::StatState& state) -{ - AttributeValue::readState (state); - mProgress = state.mProgress; -} +template class MWMechanics::Stat; +template class MWMechanics::Stat; +template class MWMechanics::DynamicStat; +template class MWMechanics::DynamicStat; diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index ffbc19e151..c075f5fa7b 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -1,12 +1,14 @@ #ifndef GAME_MWMECHANICS_STAT_H #define GAME_MWMECHANICS_STAT_H -#undef min -#undef max - +#include #include -#include +namespace ESM +{ + template + struct StatState; +} namespace MWMechanics { @@ -19,87 +21,28 @@ namespace MWMechanics public: typedef T Type; - Stat() : mBase (0), mModified (0) {} - Stat(T base) : mBase (base), mModified (base) {} - Stat(T base, T modified) : mBase (base), mModified (modified) {} + Stat(); + Stat(T base); + Stat(T base, T modified); - const T& getBase() const - { - return mBase; - } + const T& getBase() const; - T getModified() const - { - return std::max(static_cast(0), mModified); - } - - T getModifier() const - { - return mModified-mBase; - } + T getModified() const; + T getModifier() const; /// Set base and modified to \a value. - void set (const T& value) - { - mBase = mModified = value; - } - - void modify(const T& diff) - { - mBase += diff; - if(mBase >= static_cast(0)) - mModified += diff; - else - { - mModified += diff - mBase; - mBase = static_cast(0); - } - } + void set (const T& value); + void modify(const T& diff); /// Set base and adjust modified accordingly. - void setBase (const T& value) - { - T diff = value - mBase; - mBase = value; - mModified += diff; - } + void setBase (const T& value); /// Set modified value an adjust base accordingly. - void setModified (T value, const T& min, const T& max = std::numeric_limits::max()) - { - T diff = value - mModified; + void setModified (T value, const T& min, const T& max = std::numeric_limits::max()); + void setModifier (const T& modifier); - if (mBase+diffmax) - { - value = max + (mModified - mBase); - diff = value - mModified; - } - - mModified = value; - mBase += diff; - } - - void setModifier (const T& modifier) - { - mModified = mBase + modifier; - } - - void writeState (ESM::StatState& state) const - { - state.mBase = mBase; - state.mMod = mModified; - } - - void readState (const ESM::StatState& state) - { - mBase = state.mBase; - mModified = state.mMod; - } + void writeState (ESM::StatState& state) const; + void readState (const ESM::StatState& state); }; template @@ -124,98 +67,32 @@ namespace MWMechanics public: typedef T Type; - DynamicStat() : mStatic (0), mCurrent (0) {} - DynamicStat(T base) : mStatic (base), mCurrent (base) {} - DynamicStat(T base, T modified, T current) : mStatic(base, modified), mCurrent (current) {} - DynamicStat(const Stat &stat, T current) : mStatic(stat), mCurrent (current) {} + DynamicStat(); + DynamicStat(T base); + DynamicStat(T base, T modified, T current); + DynamicStat(const Stat &stat, T current); - const T& getBase() const - { - return mStatic.getBase(); - } - - T getModified() const - { - return mStatic.getModified(); - } - - const T& getCurrent() const - { - return mCurrent; - } + const T& getBase() const; + T getModified() const; + const T& getCurrent() const; /// Set base, modified and current to \a value. - void set (const T& value) - { - mStatic.set (value); - mCurrent = value; - } + void set (const T& value); /// Set base and adjust modified accordingly. - void setBase (const T& value) - { - mStatic.setBase (value); - - if (mCurrent>getModified()) - mCurrent = getModified(); - } + void setBase (const T& value); /// Set modified value an adjust base accordingly. - void setModified (T value, const T& min, const T& max = std::numeric_limits::max()) - { - mStatic.setModified (value, min, max); - - if (mCurrent>getModified()) - mCurrent = getModified(); - } + void setModified (T value, const T& min, const T& max = std::numeric_limits::max()); /// Change modified relatively. - void modify (const T& diff, bool allowCurrentDecreaseBelowZero=false) - { - mStatic.modify (diff); - setCurrent (getCurrent()+diff, allowCurrentDecreaseBelowZero); - } + void modify (const T& diff, bool allowCurrentDecreaseBelowZero=false); - void setCurrent (const T& value, bool allowDecreaseBelowZero = false) - { - if (value > mCurrent) - { - // increase - mCurrent = value; + void setCurrent (const T& value, bool allowDecreaseBelowZero = false); + void setModifier (const T& modifier, bool allowCurrentDecreaseBelowZero=false); - if (mCurrent > getModified()) - mCurrent = getModified(); - } - else if (value > 0 || allowDecreaseBelowZero) - { - // allowed decrease - mCurrent = value; - } - else if (mCurrent > 0) - { - // capped decrease - mCurrent = 0; - } - } - - void setModifier (const T& modifier, bool allowCurrentDecreaseBelowZero=false) - { - T diff = modifier - mStatic.getModifier(); - mStatic.setModifier (modifier); - setCurrent (getCurrent()+diff, allowCurrentDecreaseBelowZero); - } - - void writeState (ESM::StatState& state) const - { - mStatic.writeState (state); - state.mCurrent = mCurrent; - } - - void readState (const ESM::StatState& state) - { - mStatic.readState (state); - mCurrent = state.mCurrent; - } + void writeState (ESM::StatState& state) const; + void readState (const ESM::StatState& state); }; template @@ -239,26 +116,25 @@ namespace MWMechanics float mDamage; // needs to be float to allow continuous damage public: - AttributeValue() : mBase(0), mModifier(0), mDamage(0) {} + AttributeValue(); - int getModified() const { return std::max(0, mBase - (int) mDamage + mModifier); } - int getBase() const { return mBase; } - int getModifier() const { return mModifier; } + int getModified() const; + int getBase() const; + int getModifier() const; - void setBase(int base) { mBase = std::max(0, base); } + void setBase(int base); - void setModifier(int mod) { mModifier = mod; } + void setModifier(int mod); // Maximum attribute damage is limited to the modified value. // Note: I think MW applies damage directly to mModified, since you can also // "restore" drained attributes. We need to rewrite the magic effect system to support this. - void damage(float damage) { mDamage += std::min(damage, (float)getModified()); } - void restore(float amount) { mDamage -= std::min(mDamage, amount); } + void damage(float damage); + void restore(float amount); - float getDamage() const { return mDamage; } + float getDamage() const; void writeState (ESM::StatState& state) const; - void readState (const ESM::StatState& state); }; @@ -266,12 +142,11 @@ namespace MWMechanics { float mProgress; public: - SkillValue() : mProgress(0) {} - float getProgress() const { return mProgress; } - void setProgress(float progress) { mProgress = progress; } + SkillValue(); + float getProgress() const; + void setProgress(float progress); void writeState (ESM::StatState& state) const; - void readState (const ESM::StatState& state); }; diff --git a/apps/openmw/mwmechanics/steering.cpp b/apps/openmw/mwmechanics/steering.cpp index 9f887f5ca8..1ef46e1aef 100644 --- a/apps/openmw/mwmechanics/steering.cpp +++ b/apps/openmw/mwmechanics/steering.cpp @@ -10,37 +10,39 @@ namespace MWMechanics { -bool smoothTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, int axis, Ogre::Degree epsilon) +bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, float epsilonRadians) { - Ogre::Radian currentAngle (actor.getRefData().getPosition().rot[axis]); - Ogre::Radian diff (targetAngle - currentAngle); - if (diff >= Ogre::Degree(180)) + float currentAngle (actor.getRefData().getPosition().rot[axis]); + float diff (targetAngleRadians - currentAngle); + if (std::abs(diff) >= osg::DegreesToRadians(180.f)) { - // Turning the other way would be a better idea - diff = diff-Ogre::Degree(360); + if (diff >= 0) + { + diff = diff - osg::DegreesToRadians(360.f); + } + else + { + diff = osg::DegreesToRadians(360.f) + diff; + } } - else if (diff <= Ogre::Degree(-180)) - { - diff = Ogre::Degree(360)-diff; - } - Ogre::Radian absDiff = Ogre::Math::Abs(diff); + float absDiff = std::abs(diff); // The turning animation actually moves you slightly, so the angle will be wrong again. // Use epsilon to prevent jerkiness. - if (absDiff < epsilon) + if (absDiff < epsilonRadians) return true; - Ogre::Radian limit = MAX_VEL_ANGULAR * MWBase::Environment::get().getFrameDuration(); + float limit = MAX_VEL_ANGULAR_RADIANS * MWBase::Environment::get().getFrameDuration(); if (absDiff > limit) - diff = Ogre::Math::Sign(diff) * limit; + diff = osg::sign(diff) * limit; - actor.getClass().getMovementSettings(actor).mRotation[axis] = diff.valueRadians(); + actor.getClass().getMovementSettings(actor).mRotation[axis] = diff; return false; } -bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, Ogre::Degree epsilon) +bool zTurn(const MWWorld::Ptr& actor, float targetAngleRadians, float epsilonRadians) { - return smoothTurn(actor, targetAngle, 2, epsilon); + return smoothTurn(actor, targetAngleRadians, 2, epsilonRadians); } } diff --git a/apps/openmw/mwmechanics/steering.hpp b/apps/openmw/mwmechanics/steering.hpp index 91df49f0dd..632ab36115 100644 --- a/apps/openmw/mwmechanics/steering.hpp +++ b/apps/openmw/mwmechanics/steering.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_MECHANICS_STEERING_H +#define OPENMW_MECHANICS_STEERING_H -#include +#include namespace MWWorld { @@ -11,15 +12,15 @@ namespace MWMechanics { // Max rotating speed, radian/sec -const Ogre::Radian MAX_VEL_ANGULAR(10); +const float MAX_VEL_ANGULAR_RADIANS(10); /// configure rotation settings for an actor to reach this target angle (eventually) /// @return have we reached the target angle? -bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, - Ogre::Degree epsilon = Ogre::Degree(0.5)); +bool zTurn(const MWWorld::Ptr& actor, float targetAngleRadians, + float epsilonRadians = osg::DegreesToRadians(0.5)); -bool smoothTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, int axis, - Ogre::Degree epsilon = Ogre::Degree(0.5)); +bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, + float epsilonRadians = osg::DegreesToRadians(0.5)); } diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 668031141c..636e199074 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -1,6 +1,6 @@ #include "summoning.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -34,9 +34,9 @@ namespace MWMechanics .search("VFX_Summon_End"); if (fx) MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, - "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); + "", ptr.getRefData().getPosition().asVec3()); } - else + else if (creatureActorId != -1) { // We didn't find the creature. It's probably in an inactive cell. // Add to graveyard so we can delete it when the cell becomes active. @@ -115,13 +115,14 @@ namespace MWMechanics if (!found) { ESM::Position ipos = mActor.getRefData().getPosition(); - Ogre::Vector3 pos(ipos.pos); - Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z); + osg::Vec3f pos(ipos.asVec3()); + + osg::Quat rot (-ipos.rot[2], osg::Vec3f(0,0,1)); const float distance = 50; - pos = pos + distance*rot.yAxis(); - ipos.pos[0] = pos.x; - ipos.pos[1] = pos.y; - ipos.pos[2] = pos.z; + pos = pos + (rot * osg::Vec3f(0,1,0)) * distance; + ipos.pos[0] = pos.x(); + ipos.pos[1] = pos.y(); + ipos.pos[2] = pos.z(); ipos.rot[0] = 0; ipos.rot[1] = 0; ipos.rot[2] = 0; @@ -133,25 +134,34 @@ namespace MWMechanics if (!creatureID.empty()) { MWWorld::CellStore* store = mActor.getCell(); - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); - ref.getPtr().getCellRef().setPosition(ipos); - - MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); - - // Make the summoned creature follow its master and help in fights - AiFollow package(mActor.getCellRef().getRefId()); - summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); - int creatureActorId = summonedCreatureStats.getActorId(); - - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); - - MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); - if (anim) + int creatureActorId = -1; + try { - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_Start"); - if (fx) - anim->addEffect("meshes\\" + fx->mModel, -1, false); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); + ref.getPtr().getCellRef().setPosition(ipos); + + MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); + + // Make the summoned creature follow its master and help in fights + AiFollow package(mActor.getCellRef().getRefId()); + summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); + creatureActorId = summonedCreatureStats.getActorId(); + + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); + if (anim) + { + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_Start"); + if (fx) + anim->addEffect("meshes\\" + fx->mModel, -1, false); + } + } + catch (std::exception& e) + { + std::cerr << "Failed to spawn summoned creature: " << e.what() << std::endl; + // still insert into creatureMap so we don't try to spawn again every frame, that would spam the warning log } creatureMap.insert(std::make_pair(*it, creatureActorId)); @@ -188,7 +198,7 @@ namespace MWMechanics .search("VFX_Summon_End"); if (fx) MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, - "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); + "", ptr.getRefData().getPosition().asVec3()); MWBase::Environment::get().getWorld()->deleteObject(ptr); } diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp new file mode 100644 index 0000000000..cc46897d13 --- /dev/null +++ b/apps/openmw/mwphysics/actor.cpp @@ -0,0 +1,167 @@ +#include "actor.hpp" + +#include +#include +#include + +#include +#include + +#include "../mwworld/class.hpp" + +#include "convert.hpp" +#include "collisiontype.hpp" + +namespace MWPhysics +{ + + +Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world) + : mCanWaterWalk(false), mWalkingOnWater(false) + , mCollisionObject(0), mForce(0.f, 0.f, 0.f), mOnGround(false) + , mInternalCollisionMode(true) + , mExternalCollisionMode(true) + , mCollisionWorld(world) +{ + mPtr = ptr; + + mHalfExtents = shape->mCollisionBoxHalfExtents; + mMeshTranslation = shape->mCollisionBoxTranslate; + + // Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it) + if (std::abs(mHalfExtents.x()-mHalfExtents.y())= mHalfExtents.x()) + { + // Could also be btCapsuleShapeZ, but the movement solver seems to have issues with it (jumping on slopes doesn't work) + mShape.reset(new btCylinderShapeZ(toBullet(mHalfExtents))); + } + else + mShape.reset(new btBoxShape(toBullet(mHalfExtents))); + + mCollisionObject.reset(new btCollisionObject); + mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); + mCollisionObject->setActivationState(DISABLE_DEACTIVATION); + mCollisionObject->setCollisionShape(mShape.get()); + mCollisionObject->setUserPointer(static_cast(this)); + + updateRotation(); + updateScale(); + // already called by updateScale() + //updatePosition(); + + updateCollisionMask(); +} + +Actor::~Actor() +{ + if (mCollisionObject.get()) + mCollisionWorld->removeCollisionObject(mCollisionObject.get()); +} + +void Actor::enableCollisionMode(bool collision) +{ + mInternalCollisionMode = collision; +} + +void Actor::enableCollisionBody(bool collision) +{ + if (mExternalCollisionMode != collision) + { + mExternalCollisionMode = collision; + updateCollisionMask(); + } +} + +void Actor::updateCollisionMask() +{ + mCollisionWorld->removeCollisionObject(mCollisionObject.get()); + int collisionMask = CollisionType_World | CollisionType_HeightMap; + if (mExternalCollisionMode) + collisionMask |= CollisionType_Actor | CollisionType_Projectile | CollisionType_Door; + if (mCanWaterWalk) + collisionMask |= CollisionType_Water; + mCollisionWorld->addCollisionObject(mCollisionObject.get(), CollisionType_Actor, collisionMask); +} + +void Actor::updatePosition() +{ + osg::Vec3f position = mPtr.getRefData().getPosition().asVec3(); + + btTransform tr = mCollisionObject->getWorldTransform(); + osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale); + osg::Vec3f newPosition = scaledTranslation + position; + + tr.setOrigin(toBullet(newPosition)); + mCollisionObject->setWorldTransform(tr); +} + +osg::Vec3f Actor::getPosition() const +{ + return toOsg(mCollisionObject->getWorldTransform().getOrigin()); +} + +void Actor::updateRotation () +{ + btTransform tr = mCollisionObject->getWorldTransform(); + mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); + tr.setRotation(toBullet(mRotation)); + mCollisionObject->setWorldTransform(tr); + + updatePosition(); +} + +void Actor::updateScale() +{ + float scale = mPtr.getCellRef().getScale(); + osg::Vec3f scaleVec(scale,scale,scale); + + mPtr.getClass().adjustScale(mPtr, scaleVec, false); + mScale = scaleVec; + mShape->setLocalScaling(toBullet(mScale)); + + scaleVec = osg::Vec3f(scale,scale,scale); + mPtr.getClass().adjustScale(mPtr, scaleVec, true); + mRenderingScale = scaleVec; + + updatePosition(); +} + +osg::Vec3f Actor::getHalfExtents() const +{ + return osg::componentMultiply(mHalfExtents, mScale); +} + +osg::Vec3f Actor::getRenderingHalfExtents() const +{ + return osg::componentMultiply(mHalfExtents, mRenderingScale); +} + +void Actor::setInertialForce(const osg::Vec3f &force) +{ + mForce = force; +} + +void Actor::setOnGround(bool grounded) +{ + mOnGround = grounded; +} + +bool Actor::isWalkingOnWater() const +{ + return mWalkingOnWater; +} + +void Actor::setWalkingOnWater(bool walkingOnWater) +{ + mWalkingOnWater = walkingOnWater; +} + +void Actor::setCanWaterWalk(bool waterWalk) +{ + if (waterWalk != mCanWaterWalk) + { + mCanWaterWalk = waterWalk; + updateCollisionMask(); + } +} + +} diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp new file mode 100644 index 0000000000..03193c675e --- /dev/null +++ b/apps/openmw/mwphysics/actor.hpp @@ -0,0 +1,156 @@ +#ifndef OPENMW_MWPHYSICS_ACTOR_H +#define OPENMW_MWPHYSICS_ACTOR_H + +#include + +#include "../mwworld/ptr.hpp" + +#include +#include +#include + +class btCollisionWorld; +class btCollisionShape; +class btCollisionObject; + +namespace Resource +{ + class BulletShapeInstance; +} + +namespace MWPhysics +{ + + class PtrHolder + { + public: + virtual ~PtrHolder() {} + + void updatePtr(const MWWorld::Ptr& updated) + { + mPtr = updated; + } + + MWWorld::Ptr getPtr() + { + return mPtr; + } + + MWWorld::ConstPtr getPtr() const + { + return mPtr; + } + + protected: + MWWorld::Ptr mPtr; + }; + + class Actor : public PtrHolder + { + public: + Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world); + ~Actor(); + + /** + * Sets the collisionMode for this actor. If disabled, the actor can fly and clip geometry. + */ + void enableCollisionMode(bool collision); + + bool getCollisionMode() const + { + return mInternalCollisionMode; + } + + /** + * Enables or disables the *external* collision body. If disabled, other actors will not collide with this actor. + */ + void enableCollisionBody(bool collision); + + void updateScale(); + void updateRotation(); + void updatePosition(); + + /** + * Returns the half extents of the collision body (scaled according to collision scale) + */ + osg::Vec3f getHalfExtents() const; + + /** + * Returns the position of the collision body + * @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. + */ + osg::Vec3f getPosition() const; + + /** + * Returns the half extents of the collision body (scaled according to rendering scale) + * @note The reason we need this extra method is because of an inconsistency in MW - NPC race scales aren't applied to the collision shape, + * most likely to make environment collision testing easier. However in some cases (swimming level) we want the actual scale. + */ + osg::Vec3f getRenderingHalfExtents() const; + + /** + * Sets the current amount of inertial force (incl. gravity) affecting this physic actor + */ + void setInertialForce(const osg::Vec3f &force); + + /** + * Gets the current amount of inertial force (incl. gravity) affecting this physic actor + */ + const osg::Vec3f &getInertialForce() const + { + return mForce; + } + + void setOnGround(bool grounded); + + bool getOnGround() const + { + return mInternalCollisionMode && mOnGround; + } + + btCollisionObject* getCollisionObject() const + { + return mCollisionObject.get(); + } + + /// Sets whether this actor should be able to collide with the water surface + void setCanWaterWalk(bool waterWalk); + + /// Sets whether this actor has been walking on the water surface in the last frame + void setWalkingOnWater(bool walkingOnWater); + bool isWalkingOnWater() const; + + private: + /// Removes then re-adds the collision object to the dynamics world + void updateCollisionMask(); + + bool mCanWaterWalk; + bool mWalkingOnWater; + + std::auto_ptr mShape; + + std::auto_ptr mCollisionObject; + + osg::Vec3f mMeshTranslation; + osg::Vec3f mHalfExtents; + osg::Quat mRotation; + + osg::Vec3f mScale; + osg::Vec3f mRenderingScale; + osg::Vec3f mPosition; + + osg::Vec3f mForce; + bool mOnGround; + bool mInternalCollisionMode; + bool mExternalCollisionMode; + + btCollisionWorld* mCollisionWorld; + + Actor(const Actor&); + Actor& operator=(const Actor&); + }; + +} + + +#endif diff --git a/apps/openmw/mwphysics/collisiontype.hpp b/apps/openmw/mwphysics/collisiontype.hpp new file mode 100644 index 0000000000..0d6a32fc09 --- /dev/null +++ b/apps/openmw/mwphysics/collisiontype.hpp @@ -0,0 +1,18 @@ +#ifndef OPENMW_MWPHYSICS_COLLISIONTYPE_H +#define OPENMW_MWPHYSICS_COLLISIONTYPE_H + +namespace MWPhysics +{ + +enum CollisionType { + CollisionType_World = 1<<0, + CollisionType_Door = 1<<1, + CollisionType_Actor = 1<<2, + CollisionType_HeightMap = 1<<3, + CollisionType_Projectile = 1<<4, + CollisionType_Water = 1<<5 +}; + +} + +#endif diff --git a/apps/openmw/mwphysics/convert.hpp b/apps/openmw/mwphysics/convert.hpp new file mode 100644 index 0000000000..c5075a2c33 --- /dev/null +++ b/apps/openmw/mwphysics/convert.hpp @@ -0,0 +1,35 @@ +#ifndef OPENMW_MWPHYSICS_CONVERT_H +#define OPENMW_MWPHYSICS_CONVERT_H + +#include +#include + +#include +#include + +namespace MWPhysics +{ + + inline btVector3 toBullet(const osg::Vec3f& vec) + { + return btVector3(vec.x(), vec.y(), vec.z()); + } + + inline btQuaternion toBullet(const osg::Quat& quat) + { + return btQuaternion(quat.x(), quat.y(), quat.z(), quat.w()); + } + + inline osg::Vec3f toOsg(const btVector3& vec) + { + return osg::Vec3f(vec.x(), vec.y(), vec.z()); + } + + inline osg::Quat toOsg(const btQuaternion& quat) + { + return osg::Quat(quat.x(), quat.y(), quat.z(), quat.w()); + } + +} + +#endif diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp new file mode 100644 index 0000000000..f6883ae35d --- /dev/null +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -0,0 +1,1415 @@ +#include "physicssystem.hpp" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include // FindRecIndexVisitor + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/movement.hpp" +#include "../mwmechanics/actorutil.hpp" + +#include "../mwworld/esmstore.hpp" +#include "../mwworld/cellstore.hpp" + +#include "../mwrender/bulletdebugdraw.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwworld/class.hpp" + +#include "collisiontype.hpp" +#include "actor.hpp" +#include "convert.hpp" +#include "trace.h" + +namespace MWPhysics +{ + + static const float sMaxSlope = 49.0f; + static const float sStepSizeUp = 34.0f; + static const float sStepSizeDown = 62.0f; + + // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. + static const int sMaxIterations = 8; + + // FIXME: move to a separate file + class MovementSolver + { + private: + static float getSlope(osg::Vec3f normal) + { + normal.normalize(); + return osg::RadiansToDegrees(std::acos(normal * osg::Vec3f(0.f, 0.f, 1.f))); + } + + static bool stepMove(btCollisionObject *colobj, osg::Vec3f &position, + const osg::Vec3f &toMove, float &remainingTime, btCollisionWorld* collisionWorld) + { + /* + * Slide up an incline or set of stairs. Should be called only after a + * collision detection otherwise unnecessary tracing will be performed. + * + * NOTE: with a small change this method can be used to step over an obstacle + * of height sStepSize. + * + * If successful return 'true' and update 'position' to the new possible + * location and adjust 'remainingTime'. + * + * If not successful return 'false'. May fail for these reasons: + * - can't move directly up from current position + * - having moved up by between epsilon() and sStepSize, can't move forward + * - having moved forward by between epsilon() and toMove, + * = moved down between 0 and just under sStepSize but slope was too steep, or + * = moved the full sStepSize down (FIXME: this could be a bug) + * + * + * + * Starting position. Obstacle or stairs with height upto sStepSize in front. + * + * +--+ +--+ |XX + * | | -------> toMove | | +--+XX + * | | | | |XXXXX + * | | +--+ | | +--+XXXXX + * | | |XX| | | |XXXXXXXX + * +--+ +--+ +--+ +-------- + * ============================================== + */ + + /* + * Try moving up sStepSize using stepper. + * FIXME: does not work in case there is no front obstacle but there is one above + * + * +--+ +--+ + * | | | | + * | | | | |XX + * | | | | +--+XX + * | | | | |XXXXX + * +--+ +--+ +--+ +--+XXXXX + * |XX| |XXXXXXXX + * +--+ +-------- + * ============================================== + */ + ActorTracer tracer, stepper; + + stepper.doTrace(colobj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), collisionWorld); + if(stepper.mFraction < std::numeric_limits::epsilon()) + return false; // didn't even move the smallest representable amount + // (TODO: shouldn't this be larger? Why bother with such a small amount?) + + /* + * Try moving from the elevated position using tracer. + * + * +--+ +--+ + * | | |YY| FIXME: collision with object YY + * | | +--+ + * | | + * <------------------->| | + * +--+ +--+ + * |XX| the moved amount is toMove*tracer.mFraction + * +--+ + * ============================================== + */ + tracer.doTrace(colobj, stepper.mEndPos, stepper.mEndPos + toMove, collisionWorld); + if(tracer.mFraction < std::numeric_limits::epsilon()) + return false; // didn't even move the smallest representable amount + + /* + * Try moving back down sStepSizeDown using stepper. + * NOTE: if there is an obstacle below (e.g. stairs), we'll be "stepping up". + * Below diagram is the case where we "stepped over" an obstacle in front. + * + * +--+ + * |YY| + * +--+ +--+ + * | | + * | | + * +--+ | | + * |XX| | | + * +--+ +--+ + * ============================================== + */ + stepper.doTrace(colobj, tracer.mEndPos, tracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), collisionWorld); + if(stepper.mFraction < 1.0f && getSlope(stepper.mPlaneNormal) <= sMaxSlope) + { + // don't allow stepping up other actors + if (stepper.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor) + return false; + // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall. + // TODO: stepper.mPlaneNormal does not appear to be reliable - needs more testing + // NOTE: caller's variables 'position' & 'remainingTime' are modified here + position = stepper.mEndPos; + remainingTime *= (1.0f-tracer.mFraction); // remaining time is proportional to remaining distance + return true; + } + + // moved between 0 and just under sStepSize distance but slope was too great, + // or moved full sStepSize distance (FIXME: is this a bug?) + return false; + } + + + ///Project a vector u on another vector v + static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v) + { + return v * (u * v); + // ^ dot product + } + + ///Helper for computing the character sliding + static inline osg::Vec3f slide(const osg::Vec3f& direction, const osg::Vec3f &planeNormal) + { + return direction - project(direction, planeNormal); + } + + static inline osg::Vec3f reflect(const osg::Vec3& velocity, const osg::Vec3f& normal) + { + return velocity - (normal * (normal * velocity)) * 2; + // ^ dot product + } + + + public: + static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight) + { + osg::Vec3f position(ptr.getRefData().getPosition().asVec3()); + + ActorTracer tracer; + tracer.findGround(actor, position, position-osg::Vec3f(0,0,maxHeight), collisionWorld); + if(tracer.mFraction >= 1.0f) + { + actor->setOnGround(false); + return position; + } + else + { + // Check if we actually found a valid spawn point (use an infinitely thin ray this time). + // Required for some broken door destinations in Morrowind.esm, where the spawn point + // intersects with other geometry if the actor's base is taken into account + btVector3 from = toBullet(position); + btVector3 to = from - btVector3(0,0,maxHeight); + + btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); + resultCallback1.m_collisionFilterGroup = 0xff; + resultCallback1.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap; + + collisionWorld->rayTest(from, to, resultCallback1); + + if (resultCallback1.hasHit() && + ( (toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos).length() > 30 + || getSlope(tracer.mPlaneNormal) > sMaxSlope)) + { + actor->setOnGround(getSlope(toOsg(resultCallback1.m_hitNormalWorld)) <= sMaxSlope); + return toOsg(resultCallback1.m_hitPointWorld) + osg::Vec3f(0.f, 0.f, 1.f); + } + + actor->setOnGround(getSlope(tracer.mPlaneNormal) <= sMaxSlope); + + return tracer.mEndPos; + } + } + + static osg::Vec3f move(const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time, + bool isFlying, float waterlevel, float slowFall, btCollisionWorld* collisionWorld, + std::map& standingCollisionTracker) + { + const ESM::Position& refpos = ptr.getRefData().getPosition(); + osg::Vec3f position(refpos.asVec3()); + + // Early-out for totally static creatures + // (Not sure if gravity should still apply?) + if (!ptr.getClass().isMobile(ptr)) + return position; + + // Reset per-frame data + physicActor->setWalkingOnWater(false); + // Anything to collide with? + if(!physicActor->getCollisionMode()) + { + return position + (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * + osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1)) + ) * movement * time; + } + + btCollisionObject *colobj = physicActor->getCollisionObject(); + osg::Vec3f halfExtents = physicActor->getHalfExtents(); + + // NOTE: here we don't account for the collision box translation (i.e. physicActor->getPosition() - refpos.pos). + // That means the collision shape used for moving this actor is in a different spot than the collision shape + // other actors are using to collide against this actor. + // While this is strictly speaking wrong, it's needed for MW compatibility. + position.z() += halfExtents.z(); + + static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get() + .find("fSwimHeightScale")->getFloat(); + float swimlevel = waterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); + + ActorTracer tracer; + osg::Vec3f inertia = physicActor->getInertialForce(); + osg::Vec3f velocity; + + if(position.z() < swimlevel || isFlying) + { + velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * + osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement; + } + else + { + velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement; + + if (velocity.z() > 0.f) + inertia = velocity; + if(!physicActor->getOnGround()) + { + velocity = velocity + physicActor->getInertialForce(); + } + } + + // dead actors underwater will float to the surface + if (ptr.getClass().getCreatureStats(ptr).isDead() && position.z() < swimlevel) + velocity = osg::Vec3f(0,0,1) * 25; + + ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; + + // Now that we have the effective movement vector, apply wind forces to it + if (MWBase::Environment::get().getWorld()->isInStorm()) + { + osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); + float angleDegrees = osg::RadiansToDegrees(std::acos(stormDirection * velocity / (stormDirection.length() * velocity.length()))); + static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get() + .find("fStromWalkMult")->getFloat(); + velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f)); + } + + osg::Vec3f origVelocity = velocity; + + osg::Vec3f newPosition = position; + /* + * A loop to find newPosition using tracer, if successful different from the starting position. + * nextpos is the local variable used to find potential newPosition, using velocity and remainingTime + * The initial velocity was set earlier (see above). + */ + float remainingTime = time; + for(int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations) + { + osg::Vec3f nextpos = newPosition + velocity * remainingTime; + + // If not able to fly, don't allow to swim up into the air + if(newPosition.z() < swimlevel && + !isFlying && // can't fly + nextpos.z() > swimlevel && // but about to go above water + newPosition.z() <= swimlevel) + { + const osg::Vec3f down(0,0,-1); + float movelen = velocity.normalize(); + osg::Vec3f reflectdir = reflect(velocity, down); + reflectdir.normalize(); + velocity = slide(reflectdir, down)*movelen; + // NOTE: remainingTime is unchanged before the loop continues + continue; // velocity updated, calculate nextpos again + } + + if((newPosition - nextpos).length2() > 0.0001) + { + // trace to where character would go if there were no obstructions + tracer.doTrace(colobj, newPosition, nextpos, collisionWorld); + + // check for obstructions + if(tracer.mFraction >= 1.0f) + { + newPosition = tracer.mEndPos; // ok to move, so set newPosition + break; + } + } + else + { + // The current position and next position are nearly the same, so just exit. + // Note: Bullet can trigger an assert in debug modes if the positions + // are the same, since that causes it to attempt to normalize a zero + // length vector (which can also happen with nearly identical vectors, since + // precision can be lost due to any math Bullet does internally). Since we + // aren't performing any collision detection, we want to reject the next + // position, so that we don't slowly move inside another object. + break; + } + + + osg::Vec3f oldPosition = newPosition; + // We hit something. Try to step up onto it. (NOTE: stepMove does not allow stepping over) + // NOTE: stepMove modifies newPosition if successful + bool result = stepMove(colobj, newPosition, velocity*remainingTime, remainingTime, collisionWorld); + if (!result) // to make sure the maximum stepping distance isn't framerate-dependent or movement-speed dependent + { + osg::Vec3f normalizedVelocity = velocity; + normalizedVelocity.normalize(); + result = stepMove(colobj, newPosition, normalizedVelocity*10.f, remainingTime, collisionWorld); + } + if(result) + { + // don't let pure water creatures move out of water after stepMove + if (ptr.getClass().isPureWaterCreature(ptr) + && newPosition.z() + halfExtents.z() > waterlevel) + newPosition = oldPosition; + } + else + { + // Can't move this way, try to find another spot along the plane + osg::Vec3f direction = velocity; + float movelen = direction.normalize(); + osg::Vec3f reflectdir = reflect(velocity, tracer.mPlaneNormal); + reflectdir.normalize(); + + osg::Vec3f newVelocity = slide(reflectdir, tracer.mPlaneNormal)*movelen; + if ((newVelocity-velocity).length2() < 0.01) + break; + if ((velocity * origVelocity) <= 0.f) + break; // ^ dot product + + velocity = newVelocity; + + // Do not allow sliding upward if there is gravity. Stepping will have taken + // care of that. + if(!(newPosition.z() < swimlevel || isFlying)) + velocity.z() = std::min(velocity.z(), 0.0f); + } + } + + bool isOnGround = false; + if (!(inertia.z() > 0.f) && !(newPosition.z() < swimlevel)) + { + osg::Vec3f from = newPosition; + osg::Vec3f to = newPosition - (physicActor->getOnGround() ? + osg::Vec3f(0,0,sStepSizeDown+2.f) : osg::Vec3f(0,0,2.f)); + tracer.doTrace(colobj, from, to, collisionWorld); + if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope + && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != CollisionType_Actor) + { + const btCollisionObject* standingOn = tracer.mHitObject; + PtrHolder* ptrHolder = static_cast(standingOn->getUserPointer()); + if (ptrHolder) + standingCollisionTracker[ptr] = ptrHolder->getPtr(); + + if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) + physicActor->setWalkingOnWater(true); + if (!isFlying) + newPosition.z() = tracer.mEndPos.z() + 1.0f; + + isOnGround = true; + } + else + { + // standing on actors is not allowed (see above). + // in addition to that, apply a sliding effect away from the center of the actor, + // so that we do not stay suspended in air indefinitely. + if (tracer.mFraction < 1.0f && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor) + { + if (osg::Vec3f(velocity.x(), velocity.y(), 0).length2() < 100.f*100.f) + { + btVector3 aabbMin, aabbMax; + tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax); + btVector3 center = (aabbMin + aabbMax) / 2.f; + inertia = osg::Vec3f(position.x() - center.x(), position.y() - center.y(), 0); + inertia.normalize(); + inertia *= 100; + } + } + + isOnGround = false; + } + } + + if(isOnGround || newPosition.z() < swimlevel || isFlying) + physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f)); + else + { + inertia.z() += time * -627.2f; + if (inertia.z() < 0) + inertia.z() *= slowFall; + physicActor->setInertialForce(inertia); + } + physicActor->setOnGround(isOnGround); + + newPosition.z() -= halfExtents.z(); // remove what was added at the beginning + return newPosition; + } + }; + + + // --------------------------------------------------------------- + + class HeightField + { + public: + HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts) + { + // find the minimum and maximum heights (needed for bullet) + float minh = heights[0]; + float maxh = heights[0]; + for(int i = 1;i < sqrtVerts*sqrtVerts;++i) + { + float h = heights[i]; + if(h > maxh) maxh = h; + if(h < minh) minh = h; + } + + mShape = new btHeightfieldTerrainShape( + sqrtVerts, sqrtVerts, heights, 1, + minh, maxh, 2, + PHY_FLOAT, true + ); + mShape->setUseDiamondSubdivision(true); + mShape->setLocalScaling(btVector3(triSize, triSize, 1)); + + btTransform transform(btQuaternion::getIdentity(), + btVector3((x+0.5f) * triSize * (sqrtVerts-1), + (y+0.5f) * triSize * (sqrtVerts-1), + (maxh+minh)*0.5f)); + + mCollisionObject = new btCollisionObject; + mCollisionObject->setCollisionShape(mShape); + mCollisionObject->setWorldTransform(transform); + } + ~HeightField() + { + delete mCollisionObject; + delete mShape; + } + btCollisionObject* getCollisionObject() + { + return mCollisionObject; + } + + private: + btHeightfieldTerrainShape* mShape; + btCollisionObject* mCollisionObject; + }; + + // -------------------------------------------------------------- + + class Object : public PtrHolder + { + public: + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance) + : mShapeInstance(shapeInstance) + , mSolid(true) + { + mPtr = ptr; + + mCollisionObject.reset(new btCollisionObject); + mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape()); + + mCollisionObject->setUserPointer(static_cast(this)); + + setScale(ptr.getCellRef().getScale()); + setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude())); + const float* pos = ptr.getRefData().getPosition().pos; + setOrigin(btVector3(pos[0], pos[1], pos[2])); + } + + void setScale(float scale) + { + mShapeInstance->getCollisionShape()->setLocalScaling(btVector3(scale,scale,scale)); + } + + void setRotation(const btQuaternion& quat) + { + mCollisionObject->getWorldTransform().setRotation(quat); + } + + void setOrigin(const btVector3& vec) + { + mCollisionObject->getWorldTransform().setOrigin(vec); + } + + btCollisionObject* getCollisionObject() + { + return mCollisionObject.get(); + } + + /// Return solid flag. Not used by the object itself, true by default. + bool isSolid() const + { + return mSolid; + } + + void setSolid(bool solid) + { + mSolid = solid; + } + + bool isAnimated() const + { + return !mShapeInstance->mAnimatedShapes.empty(); + } + + void animateCollisionShapes(btCollisionWorld* collisionWorld) + { + if (mShapeInstance->mAnimatedShapes.empty()) + return; + + assert (mShapeInstance->getCollisionShape()->isCompound()); + + btCompoundShape* compound = dynamic_cast(mShapeInstance->getCollisionShape()); + + for (std::map::iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end();) + { + int recIndex = it->first; + int shapeIndex = it->second; + + NifOsg::FindGroupByRecIndex visitor(recIndex); + mPtr.getRefData().getBaseNode()->accept(visitor); + if (!visitor.mFound) + { + std::cerr << "animateCollisionShapes: Can't find node " << recIndex << std::endl; + return; + } + + osg::NodePath path = visitor.mFoundPath; + path.erase(path.begin()); + + // Attempt to remove "animated" shapes that are not actually animated + // We may get these because the BulletNifLoader does not know if a .kf file with additional controllers will be attached later on. + // On the first animateCollisionShapes call, we'll consider the graph completely loaded (with extra controllers and what not), + // so now we can better decide if the shape is really animated. + bool animated = false; + for (osg::NodePath::iterator nodePathIt = path.begin(); nodePathIt != path.end(); ++nodePathIt) + { + osg::Node* node = *nodePathIt; + if (node->getUpdateCallback()) + animated = true; + } + if (!animated) + { + mShapeInstance->mAnimatedShapes.erase(it++); + break; + } + + osg::Matrixf matrix = osg::computeLocalToWorld(path); + osg::Vec3f scale = matrix.getScale(); + matrix.orthoNormalize(matrix); + + btTransform transform; + transform.setOrigin(toBullet(matrix.getTrans()) * compound->getLocalScaling()); + for (int i=0; i<3; ++i) + for (int j=0; j<3; ++j) + transform.getBasis()[i][j] = matrix(j,i); // NB column/row major difference + + compound->getChildShape(shapeIndex)->setLocalScaling(compound->getLocalScaling() * toBullet(scale)); + compound->updateChildTransform(shapeIndex, transform); + + ++it; + } + + collisionWorld->updateSingleAabb(mCollisionObject.get()); + } + + private: + std::auto_ptr mCollisionObject; + osg::ref_ptr mShapeInstance; + bool mSolid; + }; + + // --------------------------------------------------------------- + + PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode) + : mShapeManager(new Resource::BulletShapeManager(resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager())) + , mDebugDrawEnabled(false) + , mTimeAccum(0.0f) + , mWaterHeight(0) + , mWaterEnabled(false) + , mParentNode(parentNode) + { + mCollisionConfiguration = new btDefaultCollisionConfiguration(); + mDispatcher = new btCollisionDispatcher(mCollisionConfiguration); + mBroadphase = new btDbvtBroadphase(); + + mCollisionWorld = new btCollisionWorld(mDispatcher, mBroadphase, mCollisionConfiguration); + + // Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this. + // Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb. + mCollisionWorld->setForceUpdateAllAabbs(false); + } + + PhysicsSystem::~PhysicsSystem() + { + if (mWaterCollisionObject.get()) + mCollisionWorld->removeCollisionObject(mWaterCollisionObject.get()); + + for (HeightFieldMap::iterator it = mHeightFields.begin(); it != mHeightFields.end(); ++it) + { + mCollisionWorld->removeCollisionObject(it->second->getCollisionObject()); + delete it->second; + } + + for (ObjectMap::iterator it = mObjects.begin(); it != mObjects.end(); ++it) + { + mCollisionWorld->removeCollisionObject(it->second->getCollisionObject()); + delete it->second; + } + + for (ActorMap::iterator it = mActors.begin(); it != mActors.end(); ++it) + { + delete it->second; + } + + delete mCollisionWorld; + delete mCollisionConfiguration; + delete mDispatcher; + delete mBroadphase; + } + + bool PhysicsSystem::toggleDebugRendering() + { + mDebugDrawEnabled = !mDebugDrawEnabled; + + if (mDebugDrawEnabled && !mDebugDrawer.get()) + { + mDebugDrawer.reset(new MWRender::DebugDrawer(mParentNode, mCollisionWorld)); + mCollisionWorld->setDebugDrawer(mDebugDrawer.get()); + mDebugDrawer->setDebugMode(mDebugDrawEnabled); + } + else if (mDebugDrawer.get()) + mDebugDrawer->setDebugMode(mDebugDrawEnabled); + return mDebugDrawEnabled; + } + + void PhysicsSystem::markAsNonSolid(const MWWorld::ConstPtr &ptr) + { + ObjectMap::iterator found = mObjects.find(ptr); + if (found == mObjects.end()) + return; + + found->second->setSolid(false); + } + + bool PhysicsSystem::isOnSolidGround (const MWWorld::Ptr& actor) const + { + const Actor* physactor = getActor(actor); + if (!physactor || !physactor->getOnGround()) + return false; + + CollisionMap::const_iterator found = mStandingCollisions.find(actor); + if (found == mStandingCollisions.end()) + return true; // assume standing on terrain (which is a non-object, so not collision tracked) + + ObjectMap::const_iterator foundObj = mObjects.find(found->second); + if (foundObj == mObjects.end()) + return false; + + if (!foundObj->second->isSolid()) + return false; + + return true; + } + + class DeepestNotMeContactTestResultCallback : public btCollisionWorld::ContactResultCallback + { + const btCollisionObject* mMe; + + // Store the real origin, since the shape's origin is its center + btVector3 mOrigin; + + public: + const btCollisionObject *mObject; + btVector3 mContactPoint; + btScalar mLeastDistSqr; + + DeepestNotMeContactTestResultCallback(const btCollisionObject* me, const btVector3 &origin) + : mMe(me), mOrigin(origin), mObject(NULL), mContactPoint(0,0,0), + mLeastDistSqr(std::numeric_limits::max()) + { } + +#if BT_BULLET_VERSION >= 281 + virtual btScalar addSingleResult(btManifoldPoint& cp, + const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, + const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) + { + const btCollisionObject* collisionObject = col1Wrap->m_collisionObject; +#else + virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObject* col0, int partId0, int index0, + const btCollisionObject* col1, int partId1, int index1) + { + const btCollisionObject* collisionObject = col1; +#endif + if (collisionObject != mMe) + { + btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); + if(!mObject || distsqr < mLeastDistSqr) + { + mObject = collisionObject; + mLeastDistSqr = distsqr; + mContactPoint = cp.getPositionWorldOnA(); + } + } + + return 0.f; + } + }; + + std::pair PhysicsSystem::getHitContact(const MWWorld::ConstPtr& actor, + const osg::Vec3f &origin, + const osg::Quat &orient, + float queryDistance) + { + const MWWorld::Store &store = MWBase::Environment::get().getWorld()->getStore().get(); + + btConeShape shape (osg::DegreesToRadians(store.find("fCombatAngleXY")->getFloat()/2.0f), queryDistance); + shape.setLocalScaling(btVector3(1, 1, osg::DegreesToRadians(store.find("fCombatAngleZ")->getFloat()/2.0f) / + shape.getRadius())); + + // The shape origin is its center, so we have to move it forward by half the length. The + // real origin will be provided to getFilteredContact to find the closest. + osg::Vec3f center = origin + (orient * osg::Vec3f(0.0f, queryDistance*0.5f, 0.0f)); + + btCollisionObject object; + object.setCollisionShape(&shape); + object.setWorldTransform(btTransform(toBullet(orient), toBullet(center))); + + const btCollisionObject* me = NULL; + const Actor* physactor = getActor(actor); + if (physactor) + me = physactor->getCollisionObject(); + + DeepestNotMeContactTestResultCallback resultCallback(me, toBullet(origin)); + resultCallback.m_collisionFilterGroup = CollisionType_Actor; + resultCallback.m_collisionFilterMask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; + mCollisionWorld->contactTest(&object, resultCallback); + + if (resultCallback.mObject) + { + PtrHolder* holder = static_cast(resultCallback.mObject->getUserPointer()); + if (holder) + return std::make_pair(holder->getPtr(), toOsg(resultCallback.mContactPoint)); + } + return std::make_pair(MWWorld::Ptr(), osg::Vec3f()); + } + + float PhysicsSystem::getHitDistance(const osg::Vec3f &point, const MWWorld::ConstPtr &target) const + { + btCollisionObject* targetCollisionObj = NULL; + const Actor* actor = getActor(target); + if (actor) + targetCollisionObj = actor->getCollisionObject(); + if (!targetCollisionObj) + return 0.f; + + btTransform rayFrom; + rayFrom.setIdentity(); + rayFrom.setOrigin(toBullet(point)); + + // target the collision object's world origin, this should be the center of the collision object + btTransform rayTo; + rayTo.setIdentity(); + rayTo.setOrigin(targetCollisionObj->getWorldTransform().getOrigin()); + + btCollisionWorld::ClosestRayResultCallback cb(rayFrom.getOrigin(), rayTo.getOrigin()); + + btCollisionWorld::rayTestSingle(rayFrom, rayTo, targetCollisionObj, targetCollisionObj->getCollisionShape(), targetCollisionObj->getWorldTransform(), cb); + if (!cb.hasHit()) + { + // didn't hit the target. this could happen if point is already inside the collision box + return 0.f; + } + else + return (point - toOsg(cb.m_hitPointWorld)).length(); + } + + class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback + { + public: + ClosestNotMeRayResultCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to) + : btCollisionWorld::ClosestRayResultCallback(from, to) + , mMe(me) + { + } + + virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) + { + if (rayResult.m_collisionObject == mMe) + return 1.f; + return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); + } + private: + const btCollisionObject* mMe; + }; + + PhysicsSystem::RayResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, MWWorld::ConstPtr ignore, int mask, int group) const + { + btVector3 btFrom = toBullet(from); + btVector3 btTo = toBullet(to); + + const btCollisionObject* me = NULL; + if (!ignore.isEmpty()) + { + const Actor* actor = getActor(ignore); + if (actor) + me = actor->getCollisionObject(); + } + + ClosestNotMeRayResultCallback resultCallback(me, btFrom, btTo); + resultCallback.m_collisionFilterGroup = group; + resultCallback.m_collisionFilterMask = mask; + + mCollisionWorld->rayTest(btFrom, btTo, resultCallback); + + RayResult result; + result.mHit = resultCallback.hasHit(); + if (resultCallback.hasHit()) + { + result.mHitPos = toOsg(resultCallback.m_hitPointWorld); + result.mHitNormal = toOsg(resultCallback.m_hitNormalWorld); + if (PtrHolder* ptrHolder = static_cast(resultCallback.m_collisionObject->getUserPointer())) + result.mHitObject = ptrHolder->getPtr(); + } + return result; + } + + PhysicsSystem::RayResult PhysicsSystem::castSphere(const osg::Vec3f &from, const osg::Vec3f &to, float radius) + { + btCollisionWorld::ClosestConvexResultCallback callback(toBullet(from), toBullet(to)); + callback.m_collisionFilterGroup = 0xff; + callback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door; + + btSphereShape shape(radius); + const btQuaternion btrot = btQuaternion::getIdentity(); + + btTransform from_ (btrot, toBullet(from)); + btTransform to_ (btrot, toBullet(to)); + + mCollisionWorld->convexSweepTest(&shape, from_, to_, callback); + + RayResult result; + result.mHit = callback.hasHit(); + if (result.mHit) + { + result.mHitPos = toOsg(callback.m_hitPointWorld); + result.mHitNormal = toOsg(callback.m_hitNormalWorld); + } + return result; + } + + bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const + { + const Actor* physactor1 = getActor(actor1); + const Actor* physactor2 = getActor(actor2); + + if (!physactor1 || !physactor2) + return false; + + osg::Vec3f pos1 (physactor1->getPosition() + osg::Vec3f(0,0,physactor1->getHalfExtents().z() * 0.8)); // eye level + osg::Vec3f pos2 (physactor2->getPosition() + osg::Vec3f(0,0,physactor2->getHalfExtents().z() * 0.8)); + + RayResult result = castRay(pos1, pos2, MWWorld::Ptr(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door); + + return !result.mHit; + } + + // physactor->getOnGround() is not a reliable indicator of whether the actor + // is on the ground (defaults to false, which means code blocks such as + // CharacterController::update() may falsely detect "falling"). + // + // Also, collisions can move z position slightly off zero, giving a false + // indication. In order to reduce false detection of jumping, small distance + // below the actor is detected and ignored. A value of 1.5 is used here, but + // something larger may be more suitable. This change should resolve Bug#1271. + // + // TODO: There might be better places to update PhysicActor::mOnGround. + bool PhysicsSystem::isOnGround(const MWWorld::Ptr &actor) + { + Actor* physactor = getActor(actor); + if(!physactor) + return false; + if(physactor->getOnGround()) + return true; + else + { + osg::Vec3f pos(actor.getRefData().getPosition().asVec3()); + + ActorTracer tracer; + // a small distance above collision object is considered "on ground" + tracer.findGround(physactor, + pos, + pos - osg::Vec3f(0, 0, 1.5f), // trace a small amount down + mCollisionWorld); + if(tracer.mFraction < 1.0f) // collision, must be close to something below + { + physactor->setOnGround(true); + return true; + } + else + return false; + } + } + + osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const + { + const Actor* physactor = getActor(actor); + if (physactor) + return physactor->getHalfExtents(); + else + return osg::Vec3f(); + } + + osg::Vec3f PhysicsSystem::getRenderingHalfExtents(const MWWorld::ConstPtr &actor) const + { + const Actor* physactor = getActor(actor); + if (physactor) + return physactor->getRenderingHalfExtents(); + else + return osg::Vec3f(); + } + + osg::Vec3f PhysicsSystem::getPosition(const MWWorld::ConstPtr &actor) const + { + const Actor* physactor = getActor(actor); + if (physactor) + return physactor->getPosition(); + else + return osg::Vec3f(); + } + + class ContactTestResultCallback : public btCollisionWorld::ContactResultCallback + { + public: + ContactTestResultCallback(const btCollisionObject* testedAgainst) + : mTestedAgainst(testedAgainst) + { + } + + const btCollisionObject* mTestedAgainst; + + std::vector mResult; + +#if BT_BULLET_VERSION >= 281 + virtual btScalar addSingleResult(btManifoldPoint& cp, + const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, + const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) + { + const btCollisionObject* collisionObject = col0Wrap->m_collisionObject; + if (collisionObject == mTestedAgainst) + collisionObject = col1Wrap->m_collisionObject; +#else + virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObject* col0, int partId0, int index0, + const btCollisionObject* col1, int partId1, int index1) + { + const btCollisionObject* collisionObject = col0; + if (collisionObject == mTestedAgainst) + collisionObject = col1; +#endif + PtrHolder* holder = static_cast(collisionObject->getUserPointer()); + if (holder) + mResult.push_back(holder->getPtr()); + return 0.f; + } + }; + + std::vector PhysicsSystem::getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const + { + btCollisionObject* me = NULL; + + ObjectMap::const_iterator found = mObjects.find(ptr); + if (found != mObjects.end()) + me = found->second->getCollisionObject(); + else + return std::vector(); + + ContactTestResultCallback resultCallback (me); + resultCallback.m_collisionFilterGroup = collisionGroup; + resultCallback.m_collisionFilterMask = collisionMask; + mCollisionWorld->contactTest(me, resultCallback); + return resultCallback.mResult; + } + + osg::Vec3f PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, float maxHeight) + { + ActorMap::iterator found = mActors.find(ptr); + if (found == mActors.end()) + return ptr.getRefData().getPosition().asVec3(); + else + return MovementSolver::traceDown(ptr, found->second, mCollisionWorld, maxHeight); + } + + void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts) + { + HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts); + mHeightFields[std::make_pair(x,y)] = heightfield; + + mCollisionWorld->addCollisionObject(heightfield->getCollisionObject(), CollisionType_HeightMap, + CollisionType_Actor|CollisionType_Projectile); + } + + void PhysicsSystem::removeHeightField (int x, int y) + { + HeightFieldMap::iterator heightfield = mHeightFields.find(std::make_pair(x,y)); + if(heightfield != mHeightFields.end()) + { + mCollisionWorld->removeCollisionObject(heightfield->second->getCollisionObject()); + delete heightfield->second; + mHeightFields.erase(heightfield); + } + } + + void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) + { + osg::ref_ptr shapeInstance = mShapeManager->createInstance(mesh); + if (!shapeInstance || !shapeInstance->getCollisionShape()) + return; + + Object *obj = new Object(ptr, shapeInstance); + mObjects.insert(std::make_pair(ptr, obj)); + + if (obj->isAnimated()) + mAnimatedObjects.insert(obj); + + mCollisionWorld->addCollisionObject(obj->getCollisionObject(), collisionType, + CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); + } + + void PhysicsSystem::remove(const MWWorld::Ptr &ptr) + { + ObjectMap::iterator found = mObjects.find(ptr); + if (found != mObjects.end()) + { + mCollisionWorld->removeCollisionObject(found->second->getCollisionObject()); + + mAnimatedObjects.erase(found->second); + + delete found->second; + mObjects.erase(found); + } + + ActorMap::iterator foundActor = mActors.find(ptr); + if (foundActor != mActors.end()) + { + delete foundActor->second; + mActors.erase(foundActor); + } + } + + void PhysicsSystem::updateCollisionMapPtr(CollisionMap& map, const MWWorld::Ptr &old, const MWWorld::Ptr &updated) + { + CollisionMap::iterator found = map.find(old); + if (found != map.end()) + { + map[updated] = found->second; + map.erase(found); + } + + for (CollisionMap::iterator it = map.begin(); it != map.end(); ++it) + { + if (it->second == old) + it->second = updated; + } + } + + void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) + { + ObjectMap::iterator found = mObjects.find(old); + if (found != mObjects.end()) + { + Object* obj = found->second; + obj->updatePtr(updated); + mObjects.erase(found); + mObjects.insert(std::make_pair(updated, obj)); + } + + ActorMap::iterator foundActor = mActors.find(old); + if (foundActor != mActors.end()) + { + Actor* actor = foundActor->second; + actor->updatePtr(updated); + mActors.erase(foundActor); + mActors.insert(std::make_pair(updated, actor)); + } + + updateCollisionMapPtr(mStandingCollisions, old, updated); + } + + Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr) + { + ActorMap::iterator found = mActors.find(ptr); + if (found != mActors.end()) + return found->second; + return NULL; + } + + const Actor *PhysicsSystem::getActor(const MWWorld::ConstPtr &ptr) const + { + ActorMap::const_iterator found = mActors.find(ptr); + if (found != mActors.end()) + return found->second; + return NULL; + } + + void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr) + { + ObjectMap::iterator found = mObjects.find(ptr); + if (found != mObjects.end()) + { + float scale = ptr.getCellRef().getScale(); + found->second->setScale(scale); + mCollisionWorld->updateSingleAabb(found->second->getCollisionObject()); + return; + } + ActorMap::iterator foundActor = mActors.find(ptr); + if (foundActor != mActors.end()) + { + foundActor->second->updateScale(); + mCollisionWorld->updateSingleAabb(foundActor->second->getCollisionObject()); + return; + } + } + + void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) + { + ObjectMap::iterator found = mObjects.find(ptr); + if (found != mObjects.end()) + { + found->second->setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude())); + mCollisionWorld->updateSingleAabb(found->second->getCollisionObject()); + return; + } + ActorMap::iterator foundActor = mActors.find(ptr); + if (foundActor != mActors.end()) + { + foundActor->second->updateRotation(); + mCollisionWorld->updateSingleAabb(foundActor->second->getCollisionObject()); + return; + } + } + + void PhysicsSystem::updatePosition(const MWWorld::Ptr &ptr) + { + ObjectMap::iterator found = mObjects.find(ptr); + if (found != mObjects.end()) + { + found->second->setOrigin(toBullet(ptr.getRefData().getPosition().asVec3())); + mCollisionWorld->updateSingleAabb(found->second->getCollisionObject()); + return; + } + ActorMap::iterator foundActor = mActors.find(ptr); + if (foundActor != mActors.end()) + { + foundActor->second->updatePosition(); + mCollisionWorld->updateSingleAabb(foundActor->second->getCollisionObject()); + return; + } + } + + void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) { + osg::ref_ptr shapeInstance = mShapeManager->createInstance(mesh); + if (!shapeInstance) + return; + + Actor* actor = new Actor(ptr, shapeInstance, mCollisionWorld); + mActors.insert(std::make_pair(ptr, actor)); + } + + bool PhysicsSystem::toggleCollisionMode() + { + ActorMap::iterator found = mActors.find(MWMechanics::getPlayer()); + if (found != mActors.end()) + { + bool cmode = found->second->getCollisionMode(); + cmode = !cmode; + found->second->enableCollisionMode(cmode); + return cmode; + } + + return false; + } + + void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &movement) + { + PtrVelocityList::iterator iter = mMovementQueue.begin(); + for(;iter != mMovementQueue.end();++iter) + { + if(iter->first == ptr) + { + iter->second = movement; + return; + } + } + + mMovementQueue.push_back(std::make_pair(ptr, movement)); + } + + void PhysicsSystem::clearQueuedMovement() + { + mMovementQueue.clear(); + mStandingCollisions.clear(); + } + + const PtrVelocityList& PhysicsSystem::applyQueuedMovement(float dt) + { + mMovementResults.clear(); + + mTimeAccum += dt; + if(mTimeAccum >= 1.0f/60.0f) + { + // Collision events should be available on every frame + mStandingCollisions.clear(); + + const MWBase::World *world = MWBase::Environment::get().getWorld(); + PtrVelocityList::iterator iter = mMovementQueue.begin(); + for(;iter != mMovementQueue.end();++iter) + { + float waterlevel = -std::numeric_limits::max(); + const MWWorld::CellStore *cell = iter->first.getCell(); + if(cell->getCell()->hasWater()) + waterlevel = cell->getWaterLevel(); + + float oldHeight = iter->first.getRefData().getPosition().pos[2]; + + const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects(); + + bool waterCollision = false; + if (effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() + && cell->getCell()->hasWater() + && !world->isUnderwater(iter->first.getCell(), + osg::Vec3f(iter->first.getRefData().getPosition().asVec3()))) + waterCollision = true; + + ActorMap::iterator foundActor = mActors.find(iter->first); + if (foundActor == mActors.end()) // actor was already removed from the scene + continue; + Actor* physicActor = foundActor->second; + physicActor->setCanWaterWalk(waterCollision); + + // Slow fall reduces fall speed by a factor of (effect magnitude / 200) + float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); + + osg::Vec3f newpos = MovementSolver::move(physicActor->getPtr(), physicActor, iter->second, mTimeAccum, + world->isFlying(iter->first), + waterlevel, slowFall, mCollisionWorld, mStandingCollisions); + + float heightDiff = newpos.z() - oldHeight; + + if (heightDiff < 0) + iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff); + + mMovementResults.push_back(std::make_pair(iter->first, newpos)); + } + + mTimeAccum = 0.0f; + } + mMovementQueue.clear(); + + return mMovementResults; + } + + void PhysicsSystem::stepSimulation(float dt) + { + for (std::set::iterator it = mAnimatedObjects.begin(); it != mAnimatedObjects.end(); ++it) + (*it)->animateCollisionShapes(mCollisionWorld); + + CProfileManager::Reset(); + CProfileManager::Increment_Frame_Counter(); + } + + void PhysicsSystem::debugDraw() + { + if (mDebugDrawer.get()) + mDebugDrawer->step(); + } + + bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const + { + for (CollisionMap::const_iterator it = mStandingCollisions.begin(); it != mStandingCollisions.end(); ++it) + { + if (it->first == actor && it->second == object) + return true; + } + return false; + } + + void PhysicsSystem::getActorsStandingOn(const MWWorld::ConstPtr &object, std::vector &out) const + { + for (CollisionMap::const_iterator it = mStandingCollisions.begin(); it != mStandingCollisions.end(); ++it) + { + if (it->second == object) + out.push_back(it->first); + } + } + + bool PhysicsSystem::isActorCollidingWith(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const + { + std::vector collisions = getCollisions(object, CollisionType_World, CollisionType_Actor); + return (std::find(collisions.begin(), collisions.end(), actor) != collisions.end()); + } + + void PhysicsSystem::getActorsCollidingWith(const MWWorld::ConstPtr &object, std::vector &out) const + { + std::vector collisions = getCollisions(object, CollisionType_World, CollisionType_Actor); + out.insert(out.end(), collisions.begin(), collisions.end()); + } + + void PhysicsSystem::disableWater() + { + if (mWaterEnabled) + { + mWaterEnabled = false; + updateWater(); + } + } + + void PhysicsSystem::enableWater(float height) + { + if (!mWaterEnabled || mWaterHeight != height) + { + mWaterEnabled = true; + mWaterHeight = height; + updateWater(); + } + } + + void PhysicsSystem::setWaterHeight(float height) + { + if (mWaterHeight != height) + { + mWaterHeight = height; + updateWater(); + } + } + + void PhysicsSystem::updateWater() + { + if (mWaterCollisionObject.get()) + { + mCollisionWorld->removeCollisionObject(mWaterCollisionObject.get()); + } + + if (!mWaterEnabled) + return; + + mWaterCollisionObject.reset(new btCollisionObject()); + mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight)); + mWaterCollisionObject->setCollisionShape(mWaterCollisionShape.get()); + mCollisionWorld->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water, + CollisionType_Actor); + } +} diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp new file mode 100644 index 0000000000..f53d7e3d9b --- /dev/null +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -0,0 +1,214 @@ +#ifndef OPENMW_MWPHYSICS_PHYSICSSYSTEM_H +#define OPENMW_MWPHYSICS_PHYSICSSYSTEM_H + +#include +#include +#include + +#include +#include + +#include "../mwworld/ptr.hpp" + +#include "collisiontype.hpp" + +namespace osg +{ + class Group; +} + +namespace MWRender +{ + class DebugDrawer; +} + +namespace Resource +{ + class BulletShapeManager; +} + +namespace Resource +{ + class ResourceSystem; +} + +class btCollisionWorld; +class btBroadphaseInterface; +class btDefaultCollisionConfiguration; +class btCollisionDispatcher; +class btCollisionObject; +class btCollisionShape; + +namespace MWPhysics +{ + typedef std::vector > PtrVelocityList; + + class HeightField; + class Object; + class Actor; + + class PhysicsSystem + { + public: + PhysicsSystem (Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode); + ~PhysicsSystem (); + + void enableWater(float height); + void setWaterHeight(float height); + void disableWater(); + + void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); + void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); + + void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); + + Actor* getActor(const MWWorld::Ptr& ptr); + const Actor* getActor(const MWWorld::ConstPtr& ptr) const; + + // Object or Actor + void remove (const MWWorld::Ptr& ptr); + + void updateScale (const MWWorld::Ptr& ptr); + void updateRotation (const MWWorld::Ptr& ptr); + void updatePosition (const MWWorld::Ptr& ptr); + + + void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts); + + void removeHeightField (int x, int y); + + bool toggleCollisionMode(); + + void stepSimulation(float dt); + void debugDraw(); + + std::vector getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const; ///< get handles this object collides with + osg::Vec3f traceDown(const MWWorld::Ptr &ptr, float maxHeight); + + std::pair getHitContact(const MWWorld::ConstPtr& actor, + const osg::Vec3f &origin, + const osg::Quat &orientation, + float queryDistance); + + + /// Get distance from \a point to the collision shape of \a target. Uses a raycast to find where the + /// target vector hits the collision shape and then calculates distance from the intersection point. + /// This can be used to find out how much nearer we need to move to the target for a "getHitContact" to be successful. + /// \note Only Actor targets are supported at the moment. + float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const; + + struct RayResult + { + bool mHit; + osg::Vec3f mHitPos; + osg::Vec3f mHitNormal; + MWWorld::Ptr mHitObject; + }; + + /// @param me Optional, a Ptr to ignore in the list of results + RayResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, MWWorld::ConstPtr ignore = MWWorld::ConstPtr(), int mask = + CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const; + + RayResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius); + + /// Return true if actor1 can see actor2. + bool getLineOfSight(const MWWorld::ConstPtr& actor1, const MWWorld::ConstPtr& actor2) const; + + bool isOnGround (const MWWorld::Ptr& actor); + + /// Get physical half extents (scaled) of the given actor. + osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor) const; + + /// @see MWPhysics::Actor::getRenderingHalfExtents + osg::Vec3f getRenderingHalfExtents(const MWWorld::ConstPtr& actor) const; + + /// Get the position of the collision shape for the actor. Use together with getHalfExtents() to get the collision bounds in world space. + /// @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. + osg::Vec3f getPosition(const MWWorld::ConstPtr& actor) const; + + /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will + /// be overwritten. Valid until the next call to applyQueuedMovement. + void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity); + + /// Apply all queued movements, then clear the list. + const PtrVelocityList& applyQueuedMovement(float dt); + + /// Clear the queued movements list without applying. + void clearQueuedMovement(); + + /// Return true if \a actor has been standing on \a object in this frame + /// This will trigger whenever the object is directly below the actor. + /// It doesn't matter if the actor is stationary or moving. + bool isActorStandingOn(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const; + + /// Get the handle of all actors standing on \a object in this frame. + void getActorsStandingOn(const MWWorld::ConstPtr& object, std::vector& out) const; + + /// Return true if \a actor has collided with \a object in this frame. + /// This will detect running into objects, but will not detect climbing stairs, stepping up a small object, etc. + bool isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const; + + /// Get the handle of all actors colliding with \a object in this frame. + void getActorsCollidingWith(const MWWorld::ConstPtr& object, std::vector& out) const; + + bool toggleDebugRendering(); + + /// Mark the given object as a 'non-solid' object. A non-solid object means that + /// \a isOnSolidGround will return false for actors standing on that object. + void markAsNonSolid (const MWWorld::ConstPtr& ptr); + + bool isOnSolidGround (const MWWorld::Ptr& actor) const; + + private: + + void updateWater(); + + btBroadphaseInterface* mBroadphase; + btDefaultCollisionConfiguration* mCollisionConfiguration; + btCollisionDispatcher* mDispatcher; + btCollisionWorld* mCollisionWorld; + + std::auto_ptr mShapeManager; + + typedef std::map ObjectMap; + ObjectMap mObjects; + + std::set mAnimatedObjects; // stores pointers to elements in mObjects + + typedef std::map ActorMap; + ActorMap mActors; + + typedef std::map, HeightField*> HeightFieldMap; + HeightFieldMap mHeightFields; + + bool mDebugDrawEnabled; + + // Tracks standing collisions happening during a single frame. + // This will detect standing on an object, but won't detect running e.g. against a wall. + typedef std::map CollisionMap; + CollisionMap mStandingCollisions; + + // replaces all occurences of 'old' in the map by 'updated', no matter if its a key or value + void updateCollisionMapPtr(CollisionMap& map, const MWWorld::Ptr &old, const MWWorld::Ptr &updated); + + PtrVelocityList mMovementQueue; + PtrVelocityList mMovementResults; + + float mTimeAccum; + + float mWaterHeight; + float mWaterEnabled; + + std::auto_ptr mWaterCollisionObject; + std::auto_ptr mWaterCollisionShape; + + std::auto_ptr mDebugDrawer; + + osg::ref_ptr mParentNode; + + PhysicsSystem (const PhysicsSystem&); + PhysicsSystem& operator= (const PhysicsSystem&); + }; +} + +#endif diff --git a/libs/openengine/bullet/trace.cpp b/apps/openmw/mwphysics/trace.cpp similarity index 67% rename from libs/openengine/bullet/trace.cpp rename to apps/openmw/mwphysics/trace.cpp index c0f653dae7..94434b856a 100644 --- a/libs/openengine/bullet/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -1,17 +1,16 @@ - #include "trace.h" #include -#include -#include +#include +#include +#include -#include "physic.hpp" +#include "collisiontype.hpp" +#include "actor.hpp" +#include "convert.hpp" - -namespace OEngine -{ -namespace Physic +namespace MWPhysics { class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback @@ -37,8 +36,6 @@ public: hitNormalWorld = convexResult.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 = mUp.dot(hitNormalWorld); if(dotUp < mMinSlopeDot) return btScalar(1); @@ -53,10 +50,10 @@ protected: }; -void ActorTracer::doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, const PhysicEngine *enginePass) +void ActorTracer::doTrace(btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, btCollisionWorld* world) { - const btVector3 btstart(start.x, start.y, start.z); - const btVector3 btend(end.x, end.y, end.z); + const btVector3 btstart = toBullet(start); + const btVector3 btend = toBullet(end); const btTransform &trans = actor->getWorldTransform(); btTransform from(trans); @@ -71,7 +68,7 @@ void ActorTracer::doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, btCollisionShape *shape = actor->getCollisionShape(); assert(shape->isConvex()); - enginePass->mDynamicsWorld->convexSweepTest(static_cast(shape), + world->convexSweepTest(static_cast(shape), from, to, newTraceCallback); // Copy the hit data over to our trace results struct: @@ -79,55 +76,54 @@ void ActorTracer::doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, { const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld; mFraction = newTraceCallback.m_closestHitFraction; - mPlaneNormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); + mPlaneNormal = osg::Vec3f(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); mEndPos = (end-start)*mFraction + start; mHitObject = newTraceCallback.m_hitCollisionObject; } else { mEndPos = end; - mPlaneNormal = Ogre::Vector3(0.0f, 0.0f, 1.0f); + mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f); mFraction = 1.0f; mHitObject = NULL; } } -void ActorTracer::findGround(const OEngine::Physic::PhysicActor* actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, const PhysicEngine *enginePass) +void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, btCollisionWorld* world) { - const btVector3 btstart(start.x, start.y, start.z+1.0f); - const btVector3 btend(end.x, end.y, end.z+1.0f); + const btVector3 btstart(start.x(), start.y(), start.z()+1.0f); + const btVector3 btend(end.x(), end.y(), end.z()+1.0f); - const btTransform &trans = actor->getCollisionBody()->getWorldTransform(); + const btTransform &trans = actor->getCollisionObject()->getWorldTransform(); btTransform from(trans.getBasis(), btstart); btTransform to(trans.getBasis(), btend); - ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionBody(), btstart-btend, btScalar(0.0)); + ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionObject(), btstart-btend, btScalar(0.0)); // Inherit the actor's collision group and mask - newTraceCallback.m_collisionFilterGroup = actor->getCollisionBody()->getBroadphaseHandle()->m_collisionFilterGroup; - newTraceCallback.m_collisionFilterMask = actor->getCollisionBody()->getBroadphaseHandle()->m_collisionFilterMask; + newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup; + newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask; newTraceCallback.m_collisionFilterMask &= ~CollisionType_Actor; - btVector3 halfExtents(actor->getHalfExtents().x, actor->getHalfExtents().y, actor->getHalfExtents().z); + btVector3 halfExtents = toBullet(actor->getHalfExtents()); halfExtents[2] = 1.0f; btCylinderShapeZ base(halfExtents); - enginePass->mDynamicsWorld->convexSweepTest(&base, from, to, newTraceCallback); + world->convexSweepTest(&base, from, to, newTraceCallback); if(newTraceCallback.hasHit()) { const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld; mFraction = newTraceCallback.m_closestHitFraction; - mPlaneNormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); + mPlaneNormal = osg::Vec3f(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); mEndPos = (end-start)*mFraction + start; mEndPos[2] += 1.0f; } else { mEndPos = end; - mPlaneNormal = Ogre::Vector3(0.0f, 0.0f, 1.0f); + mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f); mFraction = 1.0f; } } } -} diff --git a/apps/openmw/mwphysics/trace.h b/apps/openmw/mwphysics/trace.h new file mode 100644 index 0000000000..ef1a24d44a --- /dev/null +++ b/apps/openmw/mwphysics/trace.h @@ -0,0 +1,27 @@ +#ifndef OENGINE_BULLET_TRACE_H +#define OENGINE_BULLET_TRACE_H + +#include + +class btCollisionObject; +class btCollisionWorld; + + +namespace MWPhysics +{ + class Actor; + + struct ActorTracer + { + osg::Vec3f mEndPos; + osg::Vec3f mPlaneNormal; + const btCollisionObject* mHitObject; + + float mFraction; + + void doTrace(btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, btCollisionWorld* world); + void findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, btCollisionWorld* world); + }; +} + +#endif diff --git a/apps/openmw/mwrender/activatoranimation.cpp b/apps/openmw/mwrender/activatoranimation.cpp deleted file mode 100644 index 1ef68f619c..0000000000 --- a/apps/openmw/mwrender/activatoranimation.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "activatoranimation.hpp" - -#include -#include - -#include - -#include "renderconst.hpp" - -namespace MWRender -{ - -ActivatorAnimation::~ActivatorAnimation() -{ -} - -ActivatorAnimation::ActivatorAnimation(const MWWorld::Ptr &ptr, const std::string& model) - : Animation(ptr, ptr.getRefData().getBaseNode()) -{ - if(!model.empty()) - { - setObjectRoot(model, false); - setRenderProperties(mObjectRoot, RV_Misc, RQG_Main, RQG_Alpha); - - addAnimSource(model); - } - else - { - // No model given. Create an object root anyway, so that lights can be added to it if needed. - mObjectRoot = NifOgre::ObjectScenePtr (new NifOgre::ObjectScene(mInsert->getCreator())); - } -} - -void ActivatorAnimation::addLight(const ESM::Light *light) -{ - addExtraLight(mInsert->getCreator(), mObjectRoot, light); -} - -void ActivatorAnimation::removeParticles() -{ - for (unsigned int i=0; imParticles.size(); ++i) - { - // Don't destroyParticleSystem, the ParticleSystemController is still holding a pointer to it. - // Don't setVisible, this could conflict with a VisController. - // The following will remove all spawned particles, then set the speed factor to zero so that no new ones will be spawned. - mObjectRoot->mParticles[i]->setSpeedFactor(0.f); - mObjectRoot->mParticles[i]->clear(); - } -} - -} diff --git a/apps/openmw/mwrender/activatoranimation.hpp b/apps/openmw/mwrender/activatoranimation.hpp deleted file mode 100644 index a234defe7a..0000000000 --- a/apps/openmw/mwrender/activatoranimation.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef GAME_RENDER_ACTIVATORANIMATION_H -#define GAME_RENDER_ACTIVATORANIMATION_H - -#include "animation.hpp" - -namespace MWWorld -{ - class Ptr; -} - -namespace MWRender -{ - class ActivatorAnimation : public Animation - { - public: - ActivatorAnimation(const MWWorld::Ptr& ptr, const std::string &model); - virtual ~ActivatorAnimation(); - - void addLight(const ESM::Light *light); - void removeParticles(); - }; -} - -#endif diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp deleted file mode 100644 index 3da6c40c83..0000000000 --- a/apps/openmw/mwrender/actors.cpp +++ /dev/null @@ -1,215 +0,0 @@ -#include "actors.hpp" - -#include -#include - -#include "../mwworld/ptr.hpp" -#include "../mwworld/class.hpp" - -#include "../mwrender/renderingmanager.hpp" - -#include "animation.hpp" -#include "activatoranimation.hpp" -#include "creatureanimation.hpp" -#include "npcanimation.hpp" - -#include "renderconst.hpp" - - -namespace MWRender -{ -using namespace Ogre; - -Actors::~Actors() -{ - PtrAnimationMap::iterator it = mAllActors.begin(); - for(;it != mAllActors.end();++it) - { - delete it->second; - it->second = NULL; - } -} - -void Actors::setRootNode(Ogre::SceneNode* root) -{ mRootNode = root; } - -void Actors::insertBegin(const MWWorld::Ptr &ptr) -{ - Ogre::SceneNode* cellnode; - CellSceneNodeMap::const_iterator celliter = mCellSceneNodes.find(ptr.getCell()); - if(celliter != mCellSceneNodes.end()) - cellnode = celliter->second; - else - { - //Create the scenenode and put it in the map - cellnode = mRootNode->createChildSceneNode(); - mCellSceneNodes[ptr.getCell()] = cellnode; - } - - Ogre::SceneNode* insert = cellnode->createChildSceneNode(); - const float *f = ptr.getRefData().getPosition().pos; - insert->setPosition(f[0], f[1], f[2]); - insert->setScale(ptr.getCellRef().getScale(), ptr.getCellRef().getScale(), ptr.getCellRef().getScale()); - - // Convert MW rotation to a quaternion: - f = ptr.getCellRef().getPosition().rot; - - // For rendering purposes, actors should only rotate around the Z axis. - // X rotation is used for camera rotation (for the player) and for - // ranged magic / ranged weapon aiming. - Ogre::Quaternion zr(Ogre::Radian(-f[2]), Ogre::Vector3::UNIT_Z); - - insert->setOrientation(zr); - ptr.getRefData().setBaseNode(insert); -} - -void Actors::insertNPC(const MWWorld::Ptr& ptr) -{ - insertBegin(ptr); - NpcAnimation* anim = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors); - delete mAllActors[ptr]; - mAllActors[ptr] = anim; - mRendering->addWaterRippleEmitter (ptr); -} -void Actors::insertCreature (const MWWorld::Ptr& ptr, const std::string &model, bool weaponsShields) -{ - insertBegin(ptr); - Animation* anim = NULL; - if (weaponsShields) - anim = new CreatureWeaponAnimation(ptr, model); - else - anim = new CreatureAnimation(ptr, model); - delete mAllActors[ptr]; - mAllActors[ptr] = anim; - mRendering->addWaterRippleEmitter (ptr); -} -void Actors::insertActivator (const MWWorld::Ptr& ptr, const std::string &model, bool addLight) -{ - insertBegin(ptr); - ActivatorAnimation* anim = new ActivatorAnimation(ptr, model); - - if(ptr.getTypeName() == typeid(ESM::Light).name()) - { - if (addLight) - anim->addLight(ptr.get()->mBase); - else - anim->removeParticles(); - } - - delete mAllActors[ptr]; - mAllActors[ptr] = anim; -} - -bool Actors::deleteObject (const MWWorld::Ptr& ptr) -{ - if (mAllActors.find(ptr) == mAllActors.end()) - return false; - - mRendering->removeWaterRippleEmitter (ptr); - - delete mAllActors[ptr]; - mAllActors.erase(ptr); - - if(Ogre::SceneNode *base=ptr.getRefData().getBaseNode()) - { - Ogre::SceneNode *parent = base->getParentSceneNode(); - CellSceneNodeMap::const_iterator iter(mCellSceneNodes.begin()); - for(;iter != mCellSceneNodes.end();++iter) - { - if(iter->second == parent) - { - base->removeAndDestroyAllChildren(); - mRend.getScene()->destroySceneNode (base); - ptr.getRefData().setBaseNode (0); - return true; - } - } - - return false; - } - - return true; -} - -void Actors::removeCell(MWWorld::CellStore* store) -{ - for(PtrAnimationMap::iterator iter = mAllActors.begin();iter != mAllActors.end();) - { - if(iter->first.getCell() == store) - { - mRendering->removeWaterRippleEmitter (iter->first); - delete iter->second; - mAllActors.erase(iter++); - } - else - ++iter; - } - CellSceneNodeMap::iterator celliter = mCellSceneNodes.find(store); - if(celliter != mCellSceneNodes.end()) - { - Ogre::SceneNode *base = celliter->second; - base->removeAndDestroyAllChildren(); - mRend.getScene()->destroySceneNode(base); - - mCellSceneNodes.erase(celliter); - } -} - -void Actors::update (Ogre::Camera* camera) -{ - for(PtrAnimationMap::iterator iter = mAllActors.begin();iter != mAllActors.end(); ++iter) - { - iter->second->preRender(camera); - } -} - -Animation* Actors::getAnimation(const MWWorld::Ptr &ptr) -{ - PtrAnimationMap::const_iterator iter = mAllActors.find(ptr); - if(iter != mAllActors.end()) - return iter->second; - return NULL; -} - -void Actors::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) -{ - Ogre::SceneNode *node; - MWWorld::CellStore *newCell = cur.getCell(); - - CellSceneNodeMap::const_iterator celliter = mCellSceneNodes.find(newCell); - if(celliter != mCellSceneNodes.end()) - node = celliter->second; - else - { - node = mRootNode->createChildSceneNode(); - mCellSceneNodes[newCell] = node; - } - node->addChild(cur.getRefData().getBaseNode()); - - PtrAnimationMap::iterator iter = mAllActors.find(old); - if(iter != mAllActors.end()) - { - Animation *anim = iter->second; - mAllActors.erase(iter); - anim->updatePtr(cur); - mAllActors[cur] = anim; - } - - mRendering->updateWaterRippleEmitterPtr (old, cur); -} - -void Actors::enableLights() -{ - PtrAnimationMap::const_iterator it = mAllActors.begin(); - for(;it != mAllActors.end();++it) - it->second->enableLights(true); -} - -void Actors::disableLights() -{ - PtrAnimationMap::const_iterator it = mAllActors.begin(); - for(;it != mAllActors.end();++it) - it->second->enableLights(false); -} - -} diff --git a/apps/openmw/mwrender/actors.hpp b/apps/openmw/mwrender/actors.hpp deleted file mode 100644 index 4f6c1bec28..0000000000 --- a/apps/openmw/mwrender/actors.hpp +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef GAME_RENDER_ACTORS_H -#define GAME_RENDER_ACTORS_H - -#include - -namespace MWWorld -{ - class Ptr; - class CellStore; - class InventoryStore; -} - -namespace MWRender -{ - class Animation; - class RenderingManager; - - class Actors - { - typedef std::map CellSceneNodeMap; - typedef std::map PtrAnimationMap; - - OEngine::Render::OgreRenderer &mRend; - MWRender::RenderingManager* mRendering; - Ogre::SceneNode* mRootNode; - - CellSceneNodeMap mCellSceneNodes; - PtrAnimationMap mAllActors; - - void insertBegin(const MWWorld::Ptr &ptr); - - public: - Actors(OEngine::Render::OgreRenderer& _rend, MWRender::RenderingManager* rendering) - : mRend(_rend) - , mRendering(rendering) - , mRootNode(NULL) - {} - ~Actors(); - - void setRootNode(Ogre::SceneNode* root); - - void insertNPC(const MWWorld::Ptr& ptr); - void insertCreature (const MWWorld::Ptr& ptr, const std::string& model, bool weaponsShields); - void insertActivator (const MWWorld::Ptr& ptr, const std::string& model, bool addLight=false); - bool deleteObject (const MWWorld::Ptr& ptr); - ///< \return found? - - void enableLights(); - void disableLights(); - - void removeCell(MWWorld::CellStore* store); - - void update (Ogre::Camera* camera); - - /// Updates containing cell for object rendering data - void updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur); - - Animation* getAnimation(const MWWorld::Ptr &ptr); - }; -} -#endif diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6dcd92b154..68a073731d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1,684 +1,680 @@ #include "animation.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include +#include -#include +#include + +#include +#include +#include +#include + +#include // KeyframeHolder +#include + +#include + +#include +#include +#include +#include +#include +#include +#include #include "../mwbase/environment.hpp" -#include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" - -#include "../mwmechanics/character.hpp" -#include "../mwmechanics/creaturestats.hpp" - +#include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/fallback.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/esmstore.hpp" -#include "renderconst.hpp" +#include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority +#include "vismask.hpp" +#include "util.hpp" +#include "rotatecontroller.hpp" + +namespace +{ + + class GlowUpdater : public SceneUtil::StateSetUpdater + { + public: + GlowUpdater(osg::Vec4f color, const std::vector >& textures) + : mTexUnit(1) // FIXME: might not always be 1 + , mColor(color) + , mTextures(textures) + { + } + + virtual void setDefaults(osg::StateSet *stateset) + { + stateset->setTextureMode(mTexUnit, GL_TEXTURE_2D, osg::StateAttribute::ON); + + osg::TexGen* texGen = new osg::TexGen; + texGen->setMode(osg::TexGen::SPHERE_MAP); + + stateset->setTextureAttributeAndModes(mTexUnit, texGen, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; + texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); + texEnv->setConstantColor(mColor); + texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); + texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE); + texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_COLOR); + + stateset->setTextureAttributeAndModes(mTexUnit, texEnv, osg::StateAttribute::ON); + } + + virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) + { + float time = nv->getFrameStamp()->getSimulationTime(); + int index = (int)(time*16) % mTextures.size(); + stateset->setTextureAttribute(mTexUnit, mTextures[index], osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + + private: + int mTexUnit; + osg::Vec4f mColor; + std::vector > mTextures; + }; + + class NodeMapVisitor : public osg::NodeVisitor + { + public: + NodeMapVisitor() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) {} + + void apply(osg::MatrixTransform& trans) + { + mMap[Misc::StringUtils::lowerCase(trans.getName())] = &trans; + traverse(trans); + } + + typedef std::map > NodeMap; + + const NodeMap& getNodeMap() const + { + return mMap; + } + + private: + NodeMap mMap; + }; + + NifOsg::TextKeyMap::const_iterator findGroupStart(const NifOsg::TextKeyMap &keys, const std::string &groupname) + { + NifOsg::TextKeyMap::const_iterator iter(keys.begin()); + for(;iter != keys.end();++iter) + { + if(iter->second.compare(0, groupname.size(), groupname) == 0 && + iter->second.compare(groupname.size(), 2, ": ") == 0) + break; + } + return iter; + } + + float calcAnimVelocity(const std::multimap& keys, + NifOsg::KeyframeController *nonaccumctrl, const osg::Vec3f& accum, const std::string &groupname) + { + const std::string start = groupname+": start"; + const std::string loopstart = groupname+": loop start"; + const std::string loopstop = groupname+": loop stop"; + const std::string stop = groupname+": stop"; + float starttime = std::numeric_limits::max(); + float stoptime = 0.0f; + + // Pick the last Loop Stop key and the last Loop Start key. + // This is required because of broken text keys in AshVampire.nif. + // It has *two* WalkForward: Loop Stop keys at different times, the first one is used for stopping playback + // but the animation velocity calculation uses the second one. + // As result the animation velocity calculation is not correct, and this incorrect velocity must be replicated, + // because otherwise the Creature's Speed (dagoth uthol) would not be sufficient to move fast enough. + NifOsg::TextKeyMap::const_reverse_iterator keyiter(keys.rbegin()); + while(keyiter != keys.rend()) + { + if(keyiter->second == start || keyiter->second == loopstart) + { + starttime = keyiter->first; + break; + } + ++keyiter; + } + keyiter = keys.rbegin(); + while(keyiter != keys.rend()) + { + if (keyiter->second == stop) + stoptime = keyiter->first; + else if (keyiter->second == loopstop) + { + stoptime = keyiter->first; + break; + } + ++keyiter; + } + + if(stoptime > starttime) + { + osg::Vec3f startpos = osg::componentMultiply(nonaccumctrl->getTranslation(starttime), accum); + osg::Vec3f endpos = osg::componentMultiply(nonaccumctrl->getTranslation(stoptime), accum); + + return (startpos-endpos).length() / (stoptime - starttime); + } + + return 0.0f; + } + + /// @brief Base class for visitors that remove nodes from a scene graph. + /// Subclasses need to fill the mToRemove vector. + /// To use, node->accept(removeVisitor); removeVisitor.remove(); + class RemoveVisitor : public osg::NodeVisitor + { + public: + RemoveVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + void remove() + { + for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) + it->second->removeChild(it->first); + } + + protected: + // + typedef std::vector > RemoveVec; + std::vector > mToRemove; + }; + + // Removes all drawables from a graph. + class RemoveDrawableVisitor : public RemoveVisitor + { + public: + virtual void apply(osg::Geode &geode) + { + applyImpl(geode); + } + +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) + virtual void apply(osg::Drawable& drw) + { + applyImpl(drw); + } +#endif + + void applyImpl(osg::Node& node) + { + osg::NodePath::iterator parent = getNodePath().end()-2; + // We know that the parent is a Group because only Groups can have children. + osg::Group* parentGroup = static_cast(*parent); + + // Try to prune nodes that would be empty after the removal + if (parent != getNodePath().begin()) + { + // This could be extended to remove the parent's parent, and so on if they are empty as well. + // But for NIF files, there won't be a benefit since only TriShapes can be set to STATIC dataVariance. + osg::Group* parentParent = static_cast(*(parent - 1)); + if (parentGroup->getNumChildren() == 1 && parentGroup->getDataVariance() == osg::Object::STATIC) + { + mToRemove.push_back(std::make_pair(parentGroup, parentParent)); + return; + } + } + + mToRemove.push_back(std::make_pair(&node, parentGroup)); + } + }; + + class RemoveTriBipVisitor : public RemoveVisitor + { + public: + virtual void apply(osg::Geode &node) + { + applyImpl(node); + } + +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) + virtual void apply(osg::Drawable& drw) + { + applyImpl(drw); + } +#endif + + void applyImpl(osg::Node& node) + { + const std::string toFind = "tri bip"; + if (Misc::StringUtils::ciCompareLen(node.getName(), toFind, toFind.size()) == 0) + { + osg::Group* parent = static_cast(*(getNodePath().end()-2)); + // Not safe to remove in apply(), since the visitor is still iterating the child list + mToRemove.push_back(std::make_pair(&node, parent)); + } + } + }; + +} namespace MWRender { -Ogre::Real Animation::AnimationTime::getValue() const -{ - AnimStateMap::const_iterator iter = mAnimation->mStates.find(mAnimationName); - if(iter != mAnimation->mStates.end()) - return iter->second.mTime; - return 0.0f; -} - -void Animation::AnimationTime::setValue(Ogre::Real) -{ -} - -Ogre::Real Animation::EffectAnimationTime::getValue() const -{ - return mTime; -} - -void Animation::EffectAnimationTime::setValue(Ogre::Real) -{ -} - -Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) - : mPtr(ptr) - , mGlowLight(NULL) - , mInsert(node) - , mSkelBase(NULL) - , mAccumRoot(NULL) - , mNonAccumRoot(NULL) - , mNonAccumCtrl(NULL) - , mAccumulate(0.0f) - , mNullAnimationTimePtr(OGRE_NEW NullAnimationTime) -{ - for(size_t i = 0;i < sNumGroups;i++) - mAnimationTimePtr[i].bind(OGRE_NEW AnimationTime(this)); -} - -Animation::~Animation() -{ - setLightEffect(0); - - mEffects.clear(); - - mAnimSources.clear(); -} - -std::string Animation::getObjectRootName() const -{ - if (mSkelBase) - return mSkelBase->getMesh()->getName(); - return std::string(); -} - -void Animation::setObjectRoot(const std::string &model, bool baseonly) -{ - OgreAssert(mAnimSources.empty(), "Setting object root while animation sources are set!"); - - mSkelBase = NULL; - mObjectRoot.setNull(); - - if(model.empty()) - return; - - mObjectRoot = (!baseonly ? NifOgre::Loader::createObjects(mInsert, model) : - NifOgre::Loader::createObjectBase(mInsert, model)); - - if(mObjectRoot->mSkelBase) + struct Animation::AnimSource { - mSkelBase = mObjectRoot->mSkelBase; + osg::ref_ptr mKeyframes; - Ogre::AnimationStateSet *aset = mObjectRoot->mSkelBase->getAllAnimationStates(); - Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator(); - while(asiter.hasMoreElements()) - { - Ogre::AnimationState *state = asiter.getNext(); - state->setEnabled(false); - state->setLoop(false); - } + typedef std::map > ControllerMap; - // Set the bones as manually controlled since we're applying the - // transformations manually - Ogre::SkeletonInstance *skelinst = mObjectRoot->mSkelBase->getSkeleton(); - Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator(); - while(boneiter.hasMoreElements()) - boneiter.getNext()->setManuallyControlled(true); + ControllerMap mControllerMap[Animation::sNumBlendMasks]; - // Reattach any objects that have been attached to this one - ObjectAttachMap::iterator iter = mAttachedObjects.begin(); - while(iter != mAttachedObjects.end()) - { - if(!skelinst->hasBone(iter->second)) - mAttachedObjects.erase(iter++); - else - { - mSkelBase->attachObjectToBone(iter->second, iter->first); - ++iter; - } - } - } - else - mAttachedObjects.clear(); -} - -struct AddGlow -{ - Ogre::Vector3* mColor; - NifOgre::MaterialControllerManager* mMaterialControllerMgr; - AddGlow(Ogre::Vector3* col, NifOgre::MaterialControllerManager* materialControllerMgr) - : mColor(col) - , mMaterialControllerMgr(materialControllerMgr) - {} - - void operator()(Ogre::Entity* entity) const - { - if (!entity->getNumSubEntities()) - return; - Ogre::MaterialPtr writableMaterial = mMaterialControllerMgr->getWritableMaterial(entity); - sh::MaterialInstance* instance = sh::Factory::getInstance().getMaterialInstance(writableMaterial->getName()); - - instance->setProperty("env_map", sh::makeProperty(new sh::BooleanValue(true))); - instance->setProperty("env_map_color", sh::makeProperty(new sh::Vector3(mColor->x, mColor->y, mColor->z))); - // Workaround for crash in Ogre (https://bitbucket.org/sinbad/ogre/pull-request/447/fix-shadows-crash-for-textureunitstates/diff) - // Remove when the fix is merged - instance->getMaterial()->setShadowCasterMaterial("openmw_shadowcaster_noalpha"); - } -}; - -class VisQueueSet -{ - Ogre::uint32 mVisFlags; - Ogre::uint8 mSolidQueue, mTransQueue; - Ogre::Real mDist; - -public: - VisQueueSet(Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist) - : mVisFlags(visflags), mSolidQueue(solidqueue), mTransQueue(transqueue), mDist(dist) - { } - - void operator()(Ogre::Entity *entity) const - { - if(mVisFlags != 0) - entity->setVisibilityFlags(mVisFlags); - entity->setRenderingDistance(mDist); - - unsigned int numsubs = entity->getNumSubEntities(); - for(unsigned int i = 0;i < numsubs;++i) - { - Ogre::SubEntity* subEnt = entity->getSubEntity(i); - sh::Factory::getInstance()._ensureMaterial(subEnt->getMaterial()->getName(), "Default"); - subEnt->setRenderQueueGroup(subEnt->getMaterial()->isTransparent() ? mTransQueue : mSolidQueue); - } - } - - void operator()(Ogre::ParticleSystem *psys) const - { - if(mVisFlags != 0) - psys->setVisibilityFlags(mVisFlags); - psys->setRenderingDistance(mDist); - // TODO: Check particle material for actual transparency - psys->setRenderQueueGroup(mTransQueue); - } -}; - -void Animation::setRenderProperties(NifOgre::ObjectScenePtr objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist, bool enchantedGlow, Ogre::Vector3* glowColor) -{ - std::for_each(objlist->mEntities.begin(), objlist->mEntities.end(), - VisQueueSet(visflags, solidqueue, transqueue, dist)); - std::for_each(objlist->mParticles.begin(), objlist->mParticles.end(), - VisQueueSet(visflags, solidqueue, transqueue, dist)); - - if (enchantedGlow) - std::for_each(objlist->mEntities.begin(), objlist->mEntities.end(), - AddGlow(glowColor, &objlist->mMaterialControllerMgr)); -} - - -size_t Animation::detectAnimGroup(const Ogre::Node *node) -{ - static const char sGroupRoots[sNumGroups][32] = { - "", /* Lower body / character root */ - "Bip01 Spine1", /* Torso */ - "Bip01 L Clavicle", /* Left arm */ - "Bip01 R Clavicle", /* Right arm */ + const std::multimap& getTextKeys(); }; - while(node) + class ResetAccumRootCallback : public osg::NodeCallback { - const Ogre::String &name = node->getName(); - for(size_t i = 1;i < sNumGroups;i++) + public: + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { - if(name == sGroupRoots[i]) - return i; + osg::MatrixTransform* transform = static_cast(node); + + osg::Matrix mat = transform->getMatrix(); + osg::Vec3f position = mat.getTrans(); + position = osg::componentMultiply(mResetAxes, position); + mat.setTrans(position); + transform->setMatrix(mat); + + traverse(node, nv); } - node = node->getParent(); + void setAccumulate(const osg::Vec3f& accumulate) + { + // anything that accumulates (1.f) should be reset in the callback to (0.f) + mResetAxes.x() = accumulate.x() != 0.f ? 0.f : 1.f; + mResetAxes.y() = accumulate.y() != 0.f ? 0.f : 1.f; + mResetAxes.z() = accumulate.z() != 0.f ? 0.f : 1.f; + } + + private: + osg::Vec3f mResetAxes; + }; + + Animation::Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) + : mInsert(parentNode) + , mSkeleton(NULL) + , mPtr(ptr) + , mResourceSystem(resourceSystem) + , mAccumulate(1.f, 1.f, 0.f) + , mTextKeyListener(NULL) + , mHeadYawRadians(0.f) + , mHeadPitchRadians(0.f) + , mAlpha(1.f) + { + for(size_t i = 0;i < sNumBlendMasks;i++) + mAnimationTimePtr[i].reset(new AnimationTime); } - return 0; -} - - -void Animation::addAnimSource(const std::string &model) -{ - OgreAssert(mInsert, "Object is missing a root!"); - if(!mSkelBase) - return; - - std::string kfname = model; - Misc::StringUtils::toLower(kfname); - - if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) - kfname.replace(kfname.size()-4, 4, ".kf"); - - if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(kfname)) - return; - - std::vector > ctrls; - Ogre::SharedPtr animsrc(OGRE_NEW AnimSource); - NifOgre::Loader::createKfControllers(mSkelBase, kfname, animsrc->mTextKeys, ctrls); - if(animsrc->mTextKeys.empty() || ctrls.empty()) - return; - - mAnimSources.push_back(animsrc); - - std::vector > *grpctrls = animsrc->mControllers; - for(size_t i = 0;i < ctrls.size();i++) + Animation::~Animation() { - NifOgre::NodeTargetValue *dstval; - dstval = static_cast*>(ctrls[i].getDestination().getPointer()); + setLightEffect(0.f); - size_t grp = detectAnimGroup(dstval->getNode()); + if (mObjectRoot) + mInsert->removeChild(mObjectRoot); + } - if(!mAccumRoot && grp == 0) + MWWorld::Ptr Animation::getPtr() + { + return mPtr; + } + + void Animation::setActive(bool active) + { + if (mSkeleton) + mSkeleton->setActive(active); + } + + void Animation::updatePtr(const MWWorld::Ptr &ptr) + { + mPtr = ptr; + } + + void Animation::setAccumulation(const osg::Vec3f& accum) + { + mAccumulate = accum; + + if (mResetAccumRootCallback) + mResetAccumRootCallback->setAccumulate(mAccumulate); + } + + size_t Animation::detectBlendMask(osg::Node* node) + { + static const char sBlendMaskRoots[sNumBlendMasks][32] = { + "", /* Lower body / character root */ + "Bip01 Spine1", /* Torso */ + "Bip01 L Clavicle", /* Left arm */ + "Bip01 R Clavicle", /* Right arm */ + }; + + while(node != mObjectRoot) { - mNonAccumRoot = dstval->getNode(); - mAccumRoot = mNonAccumRoot->getParent(); - if(!mAccumRoot) + const std::string &name = node->getName(); + for(size_t i = 1;i < sNumBlendMasks;i++) { - std::cerr<< "Non-Accum root for "<getNumParents() > 0); + + node = node->getParent(0); + } + + return 0; + } + + const std::multimap &Animation::AnimSource::getTextKeys() + { + return mKeyframes->mTextKeys; + } + + void Animation::addAnimSource(const std::string &model) + { + std::string kfname = model; + Misc::StringUtils::lowerCaseInPlace(kfname); + + if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) + kfname.replace(kfname.size()-4, 4, ".kf"); + else + return; + + if(!mResourceSystem->getVFS()->exists(kfname)) + return; + + boost::shared_ptr animsrc; + animsrc.reset(new AnimSource); + animsrc->mKeyframes = mResourceSystem->getKeyframeManager()->get(kfname); + + if (!animsrc->mKeyframes || animsrc->mKeyframes->mTextKeys.empty() || animsrc->mKeyframes->mKeyframeControllers.empty()) + return; + + for (NifOsg::KeyframeHolder::KeyframeControllerMap::const_iterator it = animsrc->mKeyframes->mKeyframeControllers.begin(); + it != animsrc->mKeyframes->mKeyframeControllers.end(); ++it) + { + std::string bonename = Misc::StringUtils::lowerCase(it->first); + NodeMap::const_iterator found = mNodeMap.find(bonename); + if (found == mNodeMap.end()) + { + std::cerr << "addAnimSource: can't find bone '" + bonename << "' in " << model << " (referenced by " << kfname << ")" << std::endl; + continue; + } + + osg::Node* node = found->second; + + size_t blendMask = detectBlendMask(node); + + // clone the controller, because each Animation needs its own ControllerSource + osg::ref_ptr cloned = osg::clone(it->second.get(), osg::CopyOp::DEEP_COPY_ALL); + cloned->setSource(mAnimationTimePtr[blendMask]); + + animsrc->mControllerMap[blendMask].insert(std::make_pair(bonename, cloned)); + } + + mAnimSources.push_back(animsrc); + + SceneUtil::AssignControllerSourcesVisitor assignVisitor(mAnimationTimePtr[0]); + mObjectRoot->accept(assignVisitor); + + if (!mAccumRoot) + { + NodeMap::const_iterator found = mNodeMap.find("root bone"); + if (found == mNodeMap.end()) + found = mNodeMap.find("bip01"); + + if (found != mNodeMap.end()) + mAccumRoot = found->second; + } + } + + void Animation::clearAnimSources() + { + mStates.clear(); + + for(size_t i = 0;i < sNumBlendMasks;i++) + mAnimationTimePtr[i]->setTimePtr(boost::shared_ptr()); + + mAccumCtrl = NULL; + + mAnimSources.clear(); + } + + bool Animation::hasAnimation(const std::string &anim) + { + AnimSourceList::const_iterator iter(mAnimSources.begin()); + for(;iter != mAnimSources.end();++iter) + { + const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys(); + if(findGroupStart(keys, anim) != keys.end()) + return true; + } + + return false; + } + + float Animation::getStartTime(const std::string &groupname) const + { + for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) + { + const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys(); + + NifOsg::TextKeyMap::const_iterator found = findGroupStart(keys, groupname); + if(found != keys.end()) + return found->first; + } + return -1.f; + } + + float Animation::getTextKeyTime(const std::string &textKey) const + { + for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) + { + const NifOsg::TextKeyMap &keys = (*iter)->getTextKeys(); + + for(NifOsg::TextKeyMap::const_iterator iterKey(keys.begin()); iterKey != keys.end(); ++iterKey) + { + if(iterKey->second.compare(0, textKey.size(), textKey) == 0) + return iterKey->first; } } - if (grp == 0 && (dstval->getNode()->getName() == "Bip01" || dstval->getNode()->getName() == "Root Bone")) + return -1.f; + } + + void Animation::handleTextKey(AnimState &state, const std::string &groupname, const std::multimap::const_iterator &key, + const std::multimap& map) + { + const std::string &evt = key->second; + + size_t off = groupname.size()+2; + size_t len = evt.size() - off; + + if(evt.compare(0, groupname.size(), groupname) == 0 && + evt.compare(groupname.size(), 2, ": ") == 0) { - mNonAccumRoot = dstval->getNode(); - mAccumRoot = mNonAccumRoot->getParent(); - if(!mAccumRoot) + if(evt.compare(off, len, "loop start") == 0) + state.mLoopStartTime = key->first; + else if(evt.compare(off, len, "loop stop") == 0) + state.mLoopStopTime = key->first; + } + + if (mTextKeyListener) + { + try { - std::cerr<< "Non-Accum root for "<handleTextKey(groupname, key, map); + } + catch (std::exception& e) + { + std::cerr << "Error handling text key " << evt << ": " << e.what() << std::endl; } } - - ctrls[i].setSource(mAnimationTimePtr[grp]); - grpctrls[grp].push_back(ctrls[i]); } - for (unsigned int i = 0; i < mObjectRoot->mControllers.size(); ++i) + void Animation::play(const std::string &groupname, const AnimPriority& priority, int blendMask, bool autodisable, float speedmult, + const std::string &start, const std::string &stop, float startpoint, size_t loops, bool loopfallback) { - if (mObjectRoot->mControllers[i].getSource().isNull()) - mObjectRoot->mControllers[i].setSource(mAnimationTimePtr[0]); - } -} + if(!mObjectRoot || mAnimSources.empty()) + return; -void Animation::clearAnimSources() -{ - mStates.clear(); - - for(size_t i = 0;i < sNumGroups;i++) - mAnimationTimePtr[i]->setAnimName(std::string()); - - mNonAccumCtrl = NULL; - - mAccumRoot = NULL; - mNonAccumRoot = NULL; - - mAnimSources.clear(); -} - - -void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScenePtr objlist, const ESM::Light *light) -{ - const MWWorld::Fallback *fallback = MWBase::Environment::get().getWorld()->getFallback(); - - const unsigned int clr = light->mData.mColor; - Ogre::ColourValue color(((clr >> 0) & 0xFF) / 255.0f, - ((clr >> 8) & 0xFF) / 255.0f, - ((clr >> 16) & 0xFF) / 255.0f); - const float radius = float(light->mData.mRadius); - - if((light->mData.mFlags&ESM::Light::Negative)) - color *= -1; - - objlist->mLights.push_back(sceneMgr->createLight()); - Ogre::Light *olight = objlist->mLights.back(); - olight->setDiffuseColour(color); - - Ogre::ControllerValueRealPtr src(Ogre::ControllerManager::getSingleton().getFrameTimeSource()); - Ogre::ControllerValueRealPtr dest(OGRE_NEW OEngine::Render::LightValue(olight, color)); - Ogre::ControllerFunctionRealPtr func(OGRE_NEW OEngine::Render::LightFunction( - (light->mData.mFlags&ESM::Light::Flicker) ? OEngine::Render::LT_Flicker : - (light->mData.mFlags&ESM::Light::FlickerSlow) ? OEngine::Render::LT_FlickerSlow : - (light->mData.mFlags&ESM::Light::Pulse) ? OEngine::Render::LT_Pulse : - (light->mData.mFlags&ESM::Light::PulseSlow) ? OEngine::Render::LT_PulseSlow : - OEngine::Render::LT_Normal - )); - objlist->mControllers.push_back(Ogre::Controller(src, dest, func)); - - bool interior = !(mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior()); - - static bool outQuadInLin = fallback->getFallbackBool("LightAttenuation_OutQuadInLin"); - static bool useQuadratic = fallback->getFallbackBool("LightAttenuation_UseQuadratic"); - static float quadraticValue = fallback->getFallbackFloat("LightAttenuation_QuadraticValue"); - static float quadraticRadiusMult = fallback->getFallbackFloat("LightAttenuation_QuadraticRadiusMult"); - static bool useLinear = fallback->getFallbackBool("LightAttenuation_UseLinear"); - static float linearRadiusMult = fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult"); - static float linearValue = fallback->getFallbackFloat("LightAttenuation_LinearValue"); - - bool quadratic = useQuadratic && (!outQuadInLin || !interior); - - - // with the standard 1 / (c + d*l + d*d*q) equation the attenuation factor never becomes zero, - // so we ignore lights if their attenuation falls below this factor. - const float threshold = 0.03f; - - float quadraticAttenuation = 0; - float linearAttenuation = 0; - float activationRange = 0; - if (quadratic) - { - float r = radius * quadraticRadiusMult; - quadraticAttenuation = quadraticValue / std::pow(r, 2); - activationRange = std::sqrt(1.0f / (threshold * quadraticAttenuation)); - } - if (useLinear) - { - float r = radius * linearRadiusMult; - linearAttenuation = linearValue / r; - activationRange = std::max(activationRange, 1.0f / (threshold * linearAttenuation)); - } - - olight->setAttenuation(activationRange, 0, linearAttenuation, quadraticAttenuation); - - // If there's an AttachLight bone, attach the light to that, otherwise put it in the center, - if(objlist->mSkelBase && objlist->mSkelBase->getSkeleton()->hasBone("AttachLight")) - objlist->mSkelBase->attachObjectToBone("AttachLight", olight); - else - { - Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; - for(size_t i = 0;i < objlist->mEntities.size();i++) + if(groupname.empty()) { - Ogre::Entity *ent = objlist->mEntities[i]; - bounds.merge(ent->getBoundingBox()); + resetActiveGroups(); + return; } - Ogre::SceneNode *node = bounds.isFinite() ? mInsert->createChildSceneNode(bounds.getCenter()) - : mInsert->createChildSceneNode(); - node->attachObject(olight); - } -} - - -Ogre::Node* Animation::getNode(const std::string &name) -{ - if(mSkelBase) - { - Ogre::SkeletonInstance *skel = mSkelBase->getSkeleton(); - if(skel->hasBone(name)) - return skel->getBone(name); - } - return NULL; -} - -Ogre::Node* Animation::getNode(int handle) -{ - if (mSkelBase) - { - Ogre::SkeletonInstance *skel = mSkelBase->getSkeleton(); - return skel->getBone(handle); - } - return NULL; -} - -NifOgre::TextKeyMap::const_iterator Animation::findGroupStart(const NifOgre::TextKeyMap &keys, const std::string &groupname) -{ - NifOgre::TextKeyMap::const_iterator iter(keys.begin()); - for(;iter != keys.end();++iter) - { - if(iter->second.compare(0, groupname.size(), groupname) == 0 && - iter->second.compare(groupname.size(), 2, ": ") == 0) - break; - } - return iter; -} - - -bool Animation::hasAnimation(const std::string &anim) -{ - AnimSourceList::const_iterator iter(mAnimSources.begin()); - for(;iter != mAnimSources.end();++iter) - { - const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; - if(findGroupStart(keys, anim) != keys.end()) - return true; - } - - return false; -} - - -void Animation::setAccumulation(const Ogre::Vector3 &accum) -{ - mAccumulate = accum; -} - - -void Animation::updatePtr(const MWWorld::Ptr &ptr) -{ - mPtr = ptr; -} - - -float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::NodeTargetValue *nonaccumctrl, const Ogre::Vector3 &accum, const std::string &groupname) -{ - const std::string start = groupname+": start"; - const std::string loopstart = groupname+": loop start"; - const std::string loopstop = groupname+": loop stop"; - const std::string stop = groupname+": stop"; - float starttime = std::numeric_limits::max(); - float stoptime = 0.0f; - - // Pick the last Loop Stop key and the last Loop Start key. - // This is required because of broken text keys in AshVampire.nif. - // It has *two* WalkForward: Loop Stop keys at different times, the first one is used for stopping playback - // but the animation velocity calculation uses the second one. - // As result the animation velocity calculation is not correct, and this incorrect velocity must be replicated, - // because otherwise the Creature's Speed (dagoth uthol) would not be sufficient to move fast enough. - NifOgre::TextKeyMap::const_reverse_iterator keyiter(keys.rbegin()); - while(keyiter != keys.rend()) - { - if(keyiter->second == start || keyiter->second == loopstart) + AnimStateMap::iterator stateiter = mStates.begin(); + while(stateiter != mStates.end()) { - starttime = keyiter->first; - break; + if(stateiter->second.mPriority == priority) + mStates.erase(stateiter++); + else + ++stateiter; } - ++keyiter; - } - keyiter = keys.rbegin(); - while(keyiter != keys.rend()) - { - if (keyiter->second == stop) - stoptime = keyiter->first; - else if (keyiter->second == loopstop) + + stateiter = mStates.find(groupname); + if(stateiter != mStates.end()) { - stoptime = keyiter->first; - break; + stateiter->second.mPriority = priority; + resetActiveGroups(); + return; } - ++keyiter; - } - if(stoptime > starttime) - { - Ogre::Vector3 startpos = nonaccumctrl->getTranslation(starttime) * accum; - Ogre::Vector3 endpos = nonaccumctrl->getTranslation(stoptime) * accum; - - return startpos.distance(endpos) / (stoptime - starttime); - } - - return 0.0f; -} - -float Animation::getVelocity(const std::string &groupname) const -{ - /* Look in reverse; last-inserted source has priority. */ - AnimSourceList::const_reverse_iterator animsrc(mAnimSources.rbegin()); - for(;animsrc != mAnimSources.rend();++animsrc) - { - const NifOgre::TextKeyMap &keys = (*animsrc)->mTextKeys; - if(findGroupStart(keys, groupname) != keys.end()) - break; - } - if(animsrc == mAnimSources.rend()) - return 0.0f; - - float velocity = 0.0f; - const NifOgre::TextKeyMap &keys = (*animsrc)->mTextKeys; - const std::vector >&ctrls = (*animsrc)->mControllers[0]; - for(size_t i = 0;i < ctrls.size();i++) - { - NifOgre::NodeTargetValue *dstval; - dstval = static_cast*>(ctrls[i].getDestination().getPointer()); - if(dstval->getNode() == mNonAccumRoot) + /* Look in reverse; last-inserted source has priority. */ + AnimState state; + AnimSourceList::reverse_iterator iter(mAnimSources.rbegin()); + for(;iter != mAnimSources.rend();++iter) { - velocity = calcAnimVelocity(keys, dstval, mAccumulate, groupname); - break; - } - } - - // If there's no velocity, keep looking - if(!(velocity > 1.0f)) - { - AnimSourceList::const_reverse_iterator animiter = mAnimSources.rbegin(); - while(*animiter != *animsrc) - ++animiter; - - while(!(velocity > 1.0f) && ++animiter != mAnimSources.rend()) - { - const NifOgre::TextKeyMap &keys = (*animiter)->mTextKeys; - const std::vector >&ctrls = (*animiter)->mControllers[0]; - for(size_t i = 0;i < ctrls.size();i++) + const NifOsg::TextKeyMap &textkeys = (*iter)->getTextKeys(); + if(reset(state, textkeys, groupname, start, stop, startpoint, loopfallback)) { - NifOgre::NodeTargetValue *dstval; - dstval = static_cast*>(ctrls[i].getDestination().getPointer()); - if(dstval->getNode() == mNonAccumRoot) + state.mSource = *iter; + state.mSpeedMult = speedmult; + state.mLoopCount = loops; + state.mPlaying = (state.getTime() < state.mStopTime); + state.mPriority = priority; + state.mBlendMask = blendMask; + state.mAutoDisable = autodisable; + mStates[groupname] = state; + + NifOsg::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.getTime())); + if (state.mPlaying) { - velocity = calcAnimVelocity(keys, dstval, mAccumulate, groupname); - break; + while(textkey != textkeys.end() && textkey->first <= state.getTime()) + { + handleTextKey(state, groupname, textkey, textkeys); + ++textkey; + } } + + if(state.getTime() >= state.mLoopStopTime && state.mLoopCount > 0) + { + state.mLoopCount--; + state.setTime(state.mLoopStartTime); + state.mPlaying = true; + if(state.getTime() >= state.mLoopStopTime) + break; + + NifOsg::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.getTime())); + while(textkey != textkeys.end() && textkey->first <= state.getTime()) + { + handleTextKey(state, groupname, textkey, textkeys); + ++textkey; + } + } + + break; } } + if(iter == mAnimSources.rend()) + std::cerr<< "Failed to find animation "<getName() != " " // really should be != "", but see workaround in skeleton.cpp for empty node names - && skelsrc->hasBone(bone->getName())) + bool Animation::reset(AnimState &state, const NifOsg::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint, bool loopfallback) { - Ogre::Bone *srcbone = skelsrc->getBone(bone->getName()); - if(!srcbone->getParent() || !bone->getParent()) + // Look for text keys in reverse. This normally wouldn't matter, but for some reason undeadwolf_2.nif has two + // separate walkforward keys, and the last one is supposed to be used. + NifOsg::TextKeyMap::const_reverse_iterator groupend(keys.rbegin()); + for(;groupend != keys.rend();++groupend) { - bone->setOrientation(srcbone->getOrientation()); - bone->setPosition(srcbone->getPosition()); - bone->setScale(srcbone->getScale()); + if(groupend->second.compare(0, groupname.size(), groupname) == 0 && + groupend->second.compare(groupname.size(), 2, ": ") == 0) + break; + } + + std::string starttag = groupname+": "+start; + NifOsg::TextKeyMap::const_reverse_iterator startkey(groupend); + while(startkey != keys.rend() && startkey->second != starttag) + ++startkey; + if(startkey == keys.rend() && start == "loop start") + { + starttag = groupname+": start"; + startkey = groupend; + while(startkey != keys.rend() && startkey->second != starttag) + ++startkey; + } + if(startkey == keys.rend()) + return false; + + const std::string stoptag = groupname+": "+stop; + NifOsg::TextKeyMap::const_reverse_iterator stopkey(groupend); + while(stopkey != keys.rend() + // We have to ignore extra garbage at the end. + // The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop". + // Why, just why? :( + && (stopkey->second.size() < stoptag.size() || stopkey->second.substr(0,stoptag.size()) != stoptag)) + ++stopkey; + if(stopkey == keys.rend()) + return false; + + if(startkey->first > stopkey->first) + return false; + + state.mStartTime = startkey->first; + if (loopfallback) + { + state.mLoopStartTime = startkey->first; + state.mLoopStopTime = stopkey->first; } else { - bone->_setDerivedOrientation(srcbone->_getDerivedOrientation()); - bone->_setDerivedPosition(srcbone->_getDerivedPosition()); - bone->setScale(Ogre::Vector3::UNIT_SCALE); + state.mLoopStartTime = startkey->first; + state.mLoopStopTime = std::numeric_limits::max(); } - } + state.mStopTime = stopkey->first; - Ogre::Node::ChildNodeIterator boneiter = bone->getChildIterator(); - while(boneiter.hasMoreElements()) - updateBoneTree(skelsrc, static_cast(boneiter.getNext())); -} + state.setTime(state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint)); -void Animation::updateSkeletonInstance(const Ogre::SkeletonInstance *skelsrc, Ogre::SkeletonInstance *skel) -{ - Ogre::Skeleton::BoneIterator boneiter = skel->getRootBoneIterator(); - while(boneiter.hasMoreElements()) - updateBoneTree(skelsrc, boneiter.getNext()); -} - - -void Animation::updatePosition(float oldtime, float newtime, Ogre::Vector3 &position) -{ - /* Get the non-accumulation root's difference from the last update, and move the position - * accordingly. - */ - Ogre::Vector3 off = mNonAccumCtrl->getTranslation(newtime)*mAccumulate; - position += off - mNonAccumCtrl->getTranslation(oldtime)*mAccumulate; - - /* Translate the accumulation root back to compensate for the move. */ - mAccumRoot->setPosition(-off); -} - -bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint, bool loopfallback) -{ - // Look for text keys in reverse. This normally wouldn't matter, but for some reason undeadwolf_2.nif has two - // separate walkforward keys, and the last one is supposed to be used. - NifOgre::TextKeyMap::const_reverse_iterator groupend(keys.rbegin()); - for(;groupend != keys.rend();++groupend) - { - if(groupend->second.compare(0, groupname.size(), groupname) == 0 && - groupend->second.compare(groupname.size(), 2, ": ") == 0) - break; - } - - std::string starttag = groupname+": "+start; - NifOgre::TextKeyMap::const_reverse_iterator startkey(groupend); - while(startkey != keys.rend() && startkey->second != starttag) - ++startkey; - if(startkey == keys.rend() && start == "loop start") - { - starttag = groupname+": start"; - startkey = groupend; - while(startkey != keys.rend() && startkey->second != starttag) - ++startkey; - } - if(startkey == keys.rend()) - return false; - - const std::string stoptag = groupname+": "+stop; - NifOgre::TextKeyMap::const_reverse_iterator stopkey(groupend); - while(stopkey != keys.rend() - // We have to ignore extra garbage at the end. - // The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop". - // Why, just why? :( - && (stopkey->second.size() < stoptag.size() || stopkey->second.substr(0,stoptag.size()) != stoptag)) - ++stopkey; - if(stopkey == keys.rend()) - return false; - - if(startkey->first > stopkey->first) - return false; - - state.mStartTime = startkey->first; - if (loopfallback) - { - state.mLoopStartTime = startkey->first; - state.mLoopStopTime = stopkey->first; - } - else - { - state.mLoopStartTime = startkey->first; - state.mLoopStopTime = std::numeric_limits::max(); - } - state.mStopTime = stopkey->first; - - state.mTime = state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint); - - // mLoopStartTime and mLoopStopTime normally get assigned when encountering these keys while playing the animation - // (see handleTextKey). But if startpoint is already past these keys, we need to assign them now. - if(state.mTime > state.mStartTime) - { + // mLoopStartTime and mLoopStopTime normally get assigned when encountering these keys while playing the animation + // (see handleTextKey). But if startpoint is already past these keys, or start time is == stop time, we need to assign them now. const std::string loopstarttag = groupname+": loop start"; const std::string loopstoptag = groupname+": loop stop"; - NifOgre::TextKeyMap::const_reverse_iterator key(groupend); + NifOsg::TextKeyMap::const_reverse_iterator key(groupend); for (; key != startkey && key != keys.rend(); ++key) { - if (key->first > state.mTime) + if (key->first > state.getTime()) continue; if (key->second == loopstarttag) @@ -686,866 +682,790 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s else if (key->second == loopstoptag) state.mLoopStopTime = key->first; } + + return true; } - return true; -} - -void split(const std::string &s, char delim, std::vector &elems) { - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) { - elems.push_back(item); - } -} - -void Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key, - const NifOgre::TextKeyMap& textkeys) -{ - //float time = key->first; - const std::string &evt = key->second; - - if(evt.compare(0, 7, "sound: ") == 0) + void Animation::setTextKeyListener(Animation::TextKeyListener *listener) { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f); - return; + mTextKeyListener = listener; } - if(evt.compare(0, 10, "soundgen: ") == 0) - { - std::string soundgen = evt.substr(10); - // The event can optionally contain volume and pitch modifiers - float volume=1.f, pitch=1.f; - if (soundgen.find(" ") != std::string::npos) + void Animation::resetActiveGroups() + { + // remove all previous external controllers from the scene graph + for (ControllerMap::iterator it = mActiveControllers.begin(); it != mActiveControllers.end(); ++it) { - std::vector tokens; - split(soundgen, ' ', tokens); - soundgen = tokens[0]; - if (tokens.size() >= 2) - volume = Ogre::StringConverter::parseReal(tokens[1]); - if (tokens.size() >= 3) - pitch = Ogre::StringConverter::parseReal(tokens[2]); + osg::Node* node = it->first; + node->removeUpdateCallback(it->second); + + // Should be no longer needed with OSG 3.4 + it->second->setNestedCallback(NULL); } - std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen); - if(!sound.empty()) + mActiveControllers.clear(); + + mAccumCtrl = NULL; + + for(size_t blendMask = 0;blendMask < sNumBlendMasks;blendMask++) { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - MWBase::SoundManager::PlayType type = MWBase::SoundManager::Play_TypeSfx; - if(evt.compare(10, evt.size()-10, "left") == 0 || evt.compare(10, evt.size()-10, "right") == 0 || evt.compare(10, evt.size()-10, "land") == 0) - type = MWBase::SoundManager::Play_TypeFoot; - sndMgr->playSound3D(mPtr, sound, volume, pitch, type); - } - return; - } + AnimStateMap::const_iterator active = mStates.end(); - if(evt.compare(0, groupname.size(), groupname) != 0 || - evt.compare(groupname.size(), 2, ": ") != 0) - { - // Not ours, skip it - return; - } - size_t off = groupname.size()+2; - size_t len = evt.size() - off; - - if(evt.compare(off, len, "loop start") == 0) - state.mLoopStartTime = key->first; - else if(evt.compare(off, len, "loop stop") == 0) - state.mLoopStopTime = key->first; - else if(evt.compare(off, len, "equip attach") == 0) - showWeapons(true); - else if(evt.compare(off, len, "unequip detach") == 0) - showWeapons(false); - else if(evt.compare(off, len, "chop hit") == 0) - mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Chop); - else if(evt.compare(off, len, "slash hit") == 0) - mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Slash); - else if(evt.compare(off, len, "thrust hit") == 0) - mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Thrust); - else if(evt.compare(off, len, "hit") == 0) - { - if (groupname == "attack1") - mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Chop); - else if (groupname == "attack2") - mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Slash); - else if (groupname == "attack3") - mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Thrust); - else - mPtr.getClass().hit(mPtr); - } - else if (!groupname.empty() && groupname.compare(0, groupname.size()-1, "attack") == 0 - && evt.compare(off, len, "start") == 0) - { - NifOgre::TextKeyMap::const_iterator hitKey = key; - - // Not all animations have a hit key defined. If there is none, the hit happens with the start key. - bool hasHitKey = false; - while (hitKey != textkeys.end()) - { - if (hitKey->second == groupname + ": hit") + AnimStateMap::const_iterator state = mStates.begin(); + for(;state != mStates.end();++state) { - hasHitKey = true; - break; + if(!(state->second.mBlendMask&(1<second.mPriority[(BoneGroup)blendMask] < state->second.mPriority[(BoneGroup)blendMask]) + active = state; } - if (hitKey->second == groupname + ": stop") - break; - ++hitKey; - } - if (!hasHitKey) - { - if (groupname == "attack1") - mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Chop); - else if (groupname == "attack2") - mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Slash); - else if (groupname == "attack3") - mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Thrust); - } - } - else if (evt.compare(off, len, "shoot attach") == 0) - attachArrow(); - else if (evt.compare(off, len, "shoot release") == 0) - releaseArrow(); - else if (evt.compare(off, len, "shoot follow attach") == 0) - attachArrow(); - else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release") - { - // Make sure this key is actually for the RangeType we are casting. The flame atronach has - // the same animation for all range types, so there are 3 "release" keys on the same time, one for each range type. - // FIXME: This logic should really be in the CharacterController - const std::string& spellid = mPtr.getClass().getCreatureStats(mPtr).getSpells().getSelectedSpell(); - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellid); - const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); - int range = 0; - if (evt.compare(off, len, "self release") == 0) - range = 0; - else if (evt.compare(off, len, "touch release") == 0) - range = 1; - else if (evt.compare(off, len, "target release") == 0) - range = 2; - if (effectentry.mRange == range) - { - MWBase::Environment::get().getWorld()->castSpell(mPtr); - } - } + mAnimationTimePtr[blendMask]->setTimePtr(active == mStates.end() ? boost::shared_ptr() : active->second.mTime); - else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0) - mPtr.getClass().block(mPtr); -} - -void Animation::changeGroups(const std::string &groupname, int groups) -{ - AnimStateMap::iterator stateiter = mStates.find(groupname); - if(stateiter != mStates.end()) - { - if(stateiter->second.mGroups != groups) - { - stateiter->second.mGroups = groups; - resetActiveGroups(); - } - return; - } -} - -void Animation::stopLooping(const std::string& groupname) -{ - AnimStateMap::iterator stateiter = mStates.find(groupname); - if(stateiter != mStates.end()) - { - stateiter->second.mLoopCount = 0; - return; - } -} - -void Animation::play(const std::string &groupname, int priority, int groups, bool autodisable, float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops, bool loopfallback) -{ - if(!mSkelBase || mAnimSources.empty()) - return; - - if(groupname.empty()) - { - resetActiveGroups(); - return; - } - - priority = std::max(0, priority); - - AnimStateMap::iterator stateiter = mStates.begin(); - while(stateiter != mStates.end()) - { - if(stateiter->second.mPriority == priority) - mStates.erase(stateiter++); - else - ++stateiter; - } - - stateiter = mStates.find(groupname); - if(stateiter != mStates.end()) - { - stateiter->second.mPriority = priority; - resetActiveGroups(); - return; - } - - /* Look in reverse; last-inserted source has priority. */ - AnimState state; - AnimSourceList::reverse_iterator iter(mAnimSources.rbegin()); - for(;iter != mAnimSources.rend();++iter) - { - const NifOgre::TextKeyMap &textkeys = (*iter)->mTextKeys; - if(reset(state, textkeys, groupname, start, stop, startpoint, loopfallback)) - { - state.mSource = *iter; - state.mSpeedMult = speedmult; - state.mLoopCount = loops; - state.mPlaying = (state.mTime < state.mStopTime); - state.mPriority = priority; - state.mGroups = groups; - state.mAutoDisable = autodisable; - mStates[groupname] = state; - - NifOgre::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.mTime)); - if (state.mPlaying) + // add external controllers for the AnimSource active in this blend mask + if (active != mStates.end()) { - while(textkey != textkeys.end() && textkey->first <= state.mTime) + boost::shared_ptr animsrc = active->second.mSource; + + for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); it != animsrc->mControllerMap[blendMask].end(); ++it) { - handleTextKey(state, groupname, textkey, textkeys); - ++textkey; + osg::ref_ptr node = mNodeMap.at(it->first); // this should not throw, we already checked for the node existing in addAnimSource + + node->addUpdateCallback(it->second); + mActiveControllers.insert(std::make_pair(node, it->second)); + + if (blendMask == 0 && node == mAccumRoot) + { + mAccumCtrl = it->second; + + // make sure reset is last in the chain of callbacks + if (!mResetAccumRootCallback) + { + mResetAccumRootCallback = new ResetAccumRootCallback; + mResetAccumRootCallback->setAccumulate(mAccumulate); + } + mAccumRoot->addUpdateCallback(mResetAccumRootCallback); + mActiveControllers.insert(std::make_pair(mAccumRoot, mResetAccumRootCallback)); + } } } - - if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0) - { - state.mLoopCount--; - state.mTime = state.mLoopStartTime; - state.mPlaying = true; - if(state.mTime >= state.mLoopStopTime) - break; - - NifOgre::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.mTime)); - while(textkey != textkeys.end() && textkey->first <= state.mTime) - { - handleTextKey(state, groupname, textkey, textkeys); - ++textkey; - } - } - - break; } + addControllers(); } - if(iter == mAnimSources.rend()) - std::cerr<< "Failed to find animation "<setPosition(-mNonAccumCtrl->getTranslation(state.mTime)*mAccumulate); - } -} - -void Animation::adjustSpeedMult(const std::string &groupname, float speedmult) -{ - AnimStateMap::iterator state(mStates.find(groupname)); - if(state != mStates.end()) - state->second.mSpeedMult = speedmult; -} - -bool Animation::isPlaying(const std::string &groupname) const -{ - AnimStateMap::const_iterator state(mStates.find(groupname)); - if(state != mStates.end()) - return state->second.mPlaying; - return false; -} - -void Animation::resetActiveGroups() -{ - for(size_t grp = 0;grp < sNumGroups;grp++) - { - AnimStateMap::const_iterator active = mStates.end(); - - AnimStateMap::const_iterator state = mStates.begin(); - for(;state != mStates.end();++state) + AnimStateMap::iterator stateiter = mStates.find(groupname); + if(stateiter != mStates.end()) { - if(!(state->second.mGroups&(1<second.mPriority < state->second.mPriority) - active = state; - } - - mAnimationTimePtr[grp]->setAnimName((active == mStates.end()) ? - std::string() : active->first); - } - mNonAccumCtrl = NULL; - - if(!mNonAccumRoot || mAccumulate == Ogre::Vector3(0.0f)) - return; - - AnimStateMap::const_iterator state = mStates.find(mAnimationTimePtr[0]->getAnimName()); - if(state == mStates.end()) - { - if (mAccumRoot && mNonAccumRoot) - mAccumRoot->setPosition(-mNonAccumRoot->getPosition()*mAccumulate); - return; - } - - const Ogre::SharedPtr &animsrc = state->second.mSource; - const std::vector >&ctrls = animsrc->mControllers[0]; - for(size_t i = 0;i < ctrls.size();i++) - { - NifOgre::NodeTargetValue *dstval; - dstval = static_cast*>(ctrls[i].getDestination().getPointer()); - if(dstval->getNode() == mNonAccumRoot) - { - mNonAccumCtrl = dstval; - break; + stateiter->second.mLoopCount = 0; + return; } } - if (mAccumRoot && mNonAccumCtrl) - mAccumRoot->setPosition(-mNonAccumCtrl->getTranslation(state->second.mTime)*mAccumulate); -} - - -bool Animation::getInfo(const std::string &groupname, float *complete, float *speedmult) const -{ - AnimStateMap::const_iterator iter = mStates.find(groupname); - if(iter == mStates.end()) + void Animation::adjustSpeedMult(const std::string &groupname, float speedmult) { - if(complete) *complete = 0.0f; - if(speedmult) *speedmult = 0.0f; + AnimStateMap::iterator state(mStates.find(groupname)); + if(state != mStates.end()) + state->second.mSpeedMult = speedmult; + } + + bool Animation::isPlaying(const std::string &groupname) const + { + AnimStateMap::const_iterator state(mStates.find(groupname)); + if(state != mStates.end()) + return state->second.mPlaying; return false; } - if(complete) + bool Animation::getInfo(const std::string &groupname, float *complete, float *speedmult) const { - if(iter->second.mStopTime > iter->second.mStartTime) - *complete = (iter->second.mTime - iter->second.mStartTime) / - (iter->second.mStopTime - iter->second.mStartTime); - else - *complete = (iter->second.mPlaying ? 0.0f : 1.0f); - } - if(speedmult) *speedmult = iter->second.mSpeedMult; - return true; -} - -float Animation::getStartTime(const std::string &groupname) const -{ - for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) - { - const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; - - NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname); - if(found != keys.end()) - return found->first; - } - return -1.f; -} - -float Animation::getTextKeyTime(const std::string &textKey) const -{ - for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) - { - const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; - - for(NifOgre::TextKeyMap::const_iterator iterKey(keys.begin()); iterKey != keys.end(); ++iterKey) + AnimStateMap::const_iterator iter = mStates.find(groupname); + if(iter == mStates.end()) { - if(iterKey->second.compare(0, textKey.size(), textKey) == 0) - return iterKey->first; + if(complete) *complete = 0.0f; + if(speedmult) *speedmult = 0.0f; + return false; } + + if(complete) + { + if(iter->second.mStopTime > iter->second.mStartTime) + *complete = (iter->second.getTime() - iter->second.mStartTime) / + (iter->second.mStopTime - iter->second.mStartTime); + else + *complete = (iter->second.mPlaying ? 0.0f : 1.0f); + } + if(speedmult) *speedmult = iter->second.mSpeedMult; + return true; } - return -1.f; -} - -float Animation::getCurrentTime(const std::string &groupname) const -{ - AnimStateMap::const_iterator iter = mStates.find(groupname); - if(iter == mStates.end()) - return -1.f; - - return iter->second.mTime; -} - -void Animation::disable(const std::string &groupname) -{ - AnimStateMap::iterator iter = mStates.find(groupname); - if(iter != mStates.end()) - mStates.erase(iter); - resetActiveGroups(); -} - - -Ogre::Vector3 Animation::runAnimation(float duration) -{ - Ogre::Vector3 movement(0.0f); - AnimStateMap::iterator stateiter = mStates.begin(); - while(stateiter != mStates.end()) + float Animation::getCurrentTime(const std::string &groupname) const { - AnimState &state = stateiter->second; - const NifOgre::TextKeyMap &textkeys = state.mSource->mTextKeys; - NifOgre::TextKeyMap::const_iterator textkey(textkeys.upper_bound(state.mTime)); + AnimStateMap::const_iterator iter = mStates.find(groupname); + if(iter == mStates.end()) + return -1.f; - float timepassed = duration * state.mSpeedMult; - while(state.mPlaying) + return iter->second.getTime(); + } + + void Animation::disable(const std::string &groupname) + { + AnimStateMap::iterator iter = mStates.find(groupname); + if(iter != mStates.end()) + mStates.erase(iter); + resetActiveGroups(); + } + + float Animation::getVelocity(const std::string &groupname) const + { + if (!mAccumRoot) + return 0.0f; + + // Look in reverse; last-inserted source has priority. + AnimSourceList::const_reverse_iterator animsrc(mAnimSources.rbegin()); + for(;animsrc != mAnimSources.rend();++animsrc) { - float targetTime; + const NifOsg::TextKeyMap &keys = (*animsrc)->getTextKeys(); + if(findGroupStart(keys, groupname) != keys.end()) + break; + } + if(animsrc == mAnimSources.rend()) + return 0.0f; - if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0) - goto handle_loop; + float velocity = 0.0f; + const NifOsg::TextKeyMap &keys = (*animsrc)->getTextKeys(); - targetTime = state.mTime + timepassed; - if(textkey == textkeys.end() || textkey->first > targetTime) + const AnimSource::ControllerMap& ctrls = (*animsrc)->mControllerMap[0]; + for (AnimSource::ControllerMap::const_iterator it = ctrls.begin(); it != ctrls.end(); ++it) + { + if (Misc::StringUtils::ciEqual(it->first, mAccumRoot->getName())) { - if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName()) - updatePosition(state.mTime, targetTime, movement); - state.mTime = std::min(targetTime, state.mStopTime); + velocity = calcAnimVelocity(keys, it->second, mAccumulate, groupname); + break; } - else + } + + // If there's no velocity, keep looking + if(!(velocity > 1.0f)) + { + AnimSourceList::const_reverse_iterator animiter = mAnimSources.rbegin(); + while(*animiter != *animsrc) + ++animiter; + + while(!(velocity > 1.0f) && ++animiter != mAnimSources.rend()) { - if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName()) - updatePosition(state.mTime, textkey->first, movement); - state.mTime = textkey->first; + const NifOsg::TextKeyMap &keys = (*animiter)->getTextKeys(); + + const AnimSource::ControllerMap& ctrls = (*animiter)->mControllerMap[0]; + for (AnimSource::ControllerMap::const_iterator it = ctrls.begin(); it != ctrls.end(); ++it) + { + if (Misc::StringUtils::ciEqual(it->first, mAccumRoot->getName())) + { + velocity = calcAnimVelocity(keys, it->second, mAccumulate, groupname); + break; + } + } } + } - state.mPlaying = (state.mTime < state.mStopTime); - timepassed = targetTime - state.mTime; + return velocity; + } - while(textkey != textkeys.end() && textkey->first <= state.mTime) + void Animation::updatePosition(float oldtime, float newtime, osg::Vec3f& position) + { + // Get the difference from the last update, and move the position + osg::Vec3f off = osg::componentMultiply(mAccumCtrl->getTranslation(newtime), mAccumulate); + position += off - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), mAccumulate); + } + + osg::Vec3f Animation::runAnimation(float duration) + { + osg::Vec3f movement(0.f, 0.f, 0.f); + AnimStateMap::iterator stateiter = mStates.begin(); + while(stateiter != mStates.end()) + { + AnimState &state = stateiter->second; + const NifOsg::TextKeyMap &textkeys = state.mSource->getTextKeys(); + NifOsg::TextKeyMap::const_iterator textkey(textkeys.upper_bound(state.getTime())); + + float timepassed = duration * state.mSpeedMult; + while(state.mPlaying) { - handleTextKey(state, stateiter->first, textkey, textkeys); - ++textkey; - } + float targetTime; - if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0) - { - handle_loop: - state.mLoopCount--; - state.mTime = state.mLoopStartTime; - state.mPlaying = true; + if(state.getTime() >= state.mLoopStopTime && state.mLoopCount > 0) + goto handle_loop; - textkey = textkeys.lower_bound(state.mTime); - while(textkey != textkeys.end() && textkey->first <= state.mTime) + targetTime = state.getTime() + timepassed; + if(textkey == textkeys.end() || textkey->first > targetTime) + { + if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + updatePosition(state.getTime(), targetTime, movement); + state.setTime(std::min(targetTime, state.mStopTime)); + } + else + { + if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + updatePosition(state.getTime(), textkey->first, movement); + state.setTime(textkey->first); + } + + state.mPlaying = (state.getTime() < state.mStopTime); + timepassed = targetTime - state.getTime(); + + while(textkey != textkeys.end() && textkey->first <= state.getTime()) { handleTextKey(state, stateiter->first, textkey, textkeys); ++textkey; } - if(state.mTime >= state.mLoopStopTime) + if(state.getTime() >= state.mLoopStopTime && state.mLoopCount > 0) + { + handle_loop: + state.mLoopCount--; + state.setTime(state.mLoopStartTime); + state.mPlaying = true; + + textkey = textkeys.lower_bound(state.getTime()); + while(textkey != textkeys.end() && textkey->first <= state.getTime()) + { + handleTextKey(state, stateiter->first, textkey, textkeys); + ++textkey; + } + + if(state.getTime() >= state.mLoopStopTime) + break; + } + + if(timepassed <= 0.0f) break; } - if(timepassed <= 0.0f) - break; - } - - if(!state.mPlaying && state.mAutoDisable) - { - mStates.erase(stateiter++); - - resetActiveGroups(); - } - else - ++stateiter; - } - - for(size_t i = 0;i < mObjectRoot->mControllers.size();i++) - { - if(!mObjectRoot->mControllers[i].getSource().isNull()) - mObjectRoot->mControllers[i].update(); - } - - // Apply group controllers - for(size_t grp = 0;grp < sNumGroups;grp++) - { - const std::string &name = mAnimationTimePtr[grp]->getAnimName(); - if(!name.empty() && (stateiter=mStates.find(name)) != mStates.end()) - { - const Ogre::SharedPtr &src = stateiter->second.mSource; - for(size_t i = 0;i < src->mControllers[grp].size();i++) - src->mControllers[grp][i].update(); - } - } - - if(mSkelBase) - { - // HACK: Dirty the animation state set so that Ogre will apply the - // transformations to entities this skeleton instance is shared with. - mSkelBase->getAllAnimationStates()->_notifyDirty(); - } - - updateEffects(duration); - - return movement; -} - -void Animation::showWeapons(bool showWeapon) -{ -} - - -class ToggleLight { - bool mEnable; - -public: - ToggleLight(bool enable) : mEnable(enable) { } - - void operator()(Ogre::Light *light) const - { light->setVisible(mEnable); } -}; - -void Animation::enableLights(bool enable) -{ - std::for_each(mObjectRoot->mLights.begin(), mObjectRoot->mLights.end(), ToggleLight(enable)); -} - - -class MergeBounds { - Ogre::AxisAlignedBox *mBounds; - -public: - MergeBounds(Ogre::AxisAlignedBox *bounds) : mBounds(bounds) { } - - void operator()(Ogre::MovableObject *obj) - { - mBounds->merge(obj->getWorldBoundingBox(true)); - } -}; - -Ogre::AxisAlignedBox Animation::getWorldBounds() -{ - Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; - std::for_each(mObjectRoot->mEntities.begin(), mObjectRoot->mEntities.end(), MergeBounds(&bounds)); - return bounds; -} - - -Ogre::TagPoint *Animation::attachObjectToBone(const Ogre::String &bonename, Ogre::MovableObject *obj) -{ - Ogre::TagPoint *tag = NULL; - Ogre::SkeletonInstance *skel = (mSkelBase ? mSkelBase->getSkeleton() : NULL); - if(skel && skel->hasBone(bonename)) - { - tag = mSkelBase->attachObjectToBone(bonename, obj); - mAttachedObjects[obj] = bonename; - } - return tag; -} - -void Animation::detachObjectFromBone(Ogre::MovableObject *obj) -{ - ObjectAttachMap::iterator iter = mAttachedObjects.find(obj); - if(iter != mAttachedObjects.end()) - mAttachedObjects.erase(iter); - mSkelBase->detachObjectFromBone(obj); -} - -bool Animation::upperBodyReady() const -{ - for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter) - { - if((stateiter->second.mPriority > MWMechanics::Priority_Movement - && stateiter->second.mPriority < MWMechanics::Priority_Torch) - || stateiter->second.mPriority == MWMechanics::Priority_Death) - return false; - } - return true; -} - -void Animation::addEffect(const std::string &model, int effectId, bool loop, const std::string &bonename, std::string texture) -{ - // Early out if we already have this effect - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) - if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) - return; - - std::string correctedTexture = Misc::ResourceHelpers::correctTexturePath(texture); - - EffectParams params; - params.mModelName = model; - if (bonename.empty()) - params.mObjects = NifOgre::Loader::createObjects(mInsert, model); - else - { - if (!mSkelBase) - return; - params.mObjects = NifOgre::Loader::createObjects(mSkelBase, bonename, "", mInsert, model); - } - - setRenderProperties(params.mObjects, RV_Effects, - RQG_Main, RQG_Alpha, 0.f, false, NULL); - - params.mLoop = loop; - params.mEffectId = effectId; - params.mBoneName = bonename; - - for(size_t i = 0;i < params.mObjects->mControllers.size();i++) - { - if(params.mObjects->mControllers[i].getSource().isNull()) - params.mObjects->mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationTime())); - } - - - // Do some manual adjustments on the created entities/particle systems - - // It looks like vanilla MW totally ignores lighting settings for effects attached to characters. - // If we don't do this, some effects will look way too dark depending on the environment - // (e.g. magic_cast_dst.nif). They were clearly meant to use emissive lighting. - // We used to have this hack in the NIF material loader, but for effects not attached to characters - // (e.g. ash storms) the lighting settings do seem to be in use. Is there maybe a flag we have missed? - Ogre::ColourValue ambient = Ogre::ColourValue(0.f, 0.f, 0.f); - Ogre::ColourValue diffuse = Ogre::ColourValue(0.f, 0.f, 0.f); - Ogre::ColourValue specular = Ogre::ColourValue(0.f, 0.f, 0.f); - Ogre::ColourValue emissive = Ogre::ColourValue(1.f, 1.f, 1.f); - for(size_t i = 0;i < params.mObjects->mParticles.size(); ++i) - { - Ogre::ParticleSystem* partSys = params.mObjects->mParticles[i]; - - Ogre::MaterialPtr mat = params.mObjects->mMaterialControllerMgr.getWritableMaterial(partSys); - - for (int t=0; tgetNumTechniques(); ++t) - { - Ogre::Technique* tech = mat->getTechnique(t); - for (int p=0; pgetNumPasses(); ++p) + if(!state.mPlaying && state.mAutoDisable) { - Ogre::Pass* pass = tech->getPass(p); + mStates.erase(stateiter++); - pass->setAmbient(ambient); - pass->setDiffuse(diffuse); - pass->setSpecular(specular); - pass->setEmissive(emissive); - - if (!texture.empty()) - { - for (int tex=0; texgetNumTextureUnitStates(); ++tex) - { - Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); - tus->setTextureName(correctedTexture); - } - } - } - } - } - for(size_t i = 0;i < params.mObjects->mEntities.size(); ++i) - { - Ogre::Entity* ent = params.mObjects->mEntities[i]; - if (ent == params.mObjects->mSkelBase) - continue; - Ogre::MaterialPtr mat = params.mObjects->mMaterialControllerMgr.getWritableMaterial(ent); - - for (int t=0; tgetNumTechniques(); ++t) - { - Ogre::Technique* tech = mat->getTechnique(t); - for (int p=0; pgetNumPasses(); ++p) - { - Ogre::Pass* pass = tech->getPass(p); - - pass->setAmbient(ambient); - pass->setDiffuse(diffuse); - pass->setSpecular(specular); - pass->setEmissive(emissive); - - if (!texture.empty()) - { - for (int tex=0; texgetNumTextureUnitStates(); ++tex) - { - Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); - tus->setTextureName(correctedTexture); - } - } - } - } - } - - mEffects.push_back(params); -} - -void Animation::removeEffect(int effectId) -{ - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) - { - if (it->mEffectId == effectId) - { - mEffects.erase(it); - return; - } - } -} - -void Animation::getLoopingEffects(std::vector &out) -{ - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) - { - if (it->mLoop) - out.push_back(it->mEffectId); - } -} - -void Animation::updateEffects(float duration) -{ - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) - { - NifOgre::ObjectScenePtr objects = it->mObjects; - for(size_t i = 0; i < objects->mControllers.size() ;i++) - { - EffectAnimationTime* value = dynamic_cast(objects->mControllers[i].getSource().get()); - if (value) - value->addTime(duration); - - objects->mControllers[i].update(); - } - - if (objects->mControllers[0].getSource()->getValue() >= objects->mMaxControllerLength) - { - if (it->mLoop) - { - // Start from the beginning again; carry over the remainder - float remainder = objects->mControllers[0].getSource()->getValue() - objects->mMaxControllerLength; - for(size_t i = 0; i < objects->mControllers.size() ;i++) - { - EffectAnimationTime* value = dynamic_cast(objects->mControllers[i].getSource().get()); - if (value) - value->resetTime(remainder); - } + resetActiveGroups(); } else + ++stateiter; + } + + updateEffects(duration); + + if (mHeadController) + { + const float epsilon = 0.001f; + bool enable = (std::abs(mHeadPitchRadians) > epsilon || std::abs(mHeadYawRadians) > epsilon); + mHeadController->setEnabled(enable); + if (enable) + mHeadController->setRotate(osg::Quat(mHeadPitchRadians, osg::Vec3f(1,0,0)) * osg::Quat(mHeadYawRadians, osg::Vec3f(0,0,1))); + } + + return movement; + } + + void Animation::setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature) + { + osg::ref_ptr previousStateset; + if (mObjectRoot) + { + previousStateset = mObjectRoot->getStateSet(); + mObjectRoot->getParent(0)->removeChild(mObjectRoot); + } + mObjectRoot = NULL; + mSkeleton = NULL; + + mNodeMap.clear(); + mActiveControllers.clear(); + mAccumRoot = NULL; + mAccumCtrl = NULL; + + if (!forceskeleton) + { + osg::ref_ptr created = mResourceSystem->getSceneManager()->createInstance(model, mInsert); + mObjectRoot = created->asGroup(); + if (!mObjectRoot) { - it = mEffects.erase(it); - continue; + mInsert->removeChild(created); + mObjectRoot = new osg::Group; + mObjectRoot->addChild(created); + mInsert->addChild(mObjectRoot); } } - ++it; - } -} + else + { + osg::ref_ptr created = mResourceSystem->getSceneManager()->createInstance(model); + osg::ref_ptr skel = dynamic_cast(created.get()); + if (!skel) + { + skel = new SceneUtil::Skeleton; + skel->addChild(created); + } + mSkeleton = skel.get(); + mObjectRoot = skel; + mInsert->addChild(mObjectRoot); + } -void Animation::preRender(Ogre::Camera *camera) -{ - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + if (previousStateset) + mObjectRoot->setStateSet(previousStateset); + + if (baseonly) + { + RemoveDrawableVisitor removeDrawableVisitor; + mObjectRoot->accept(removeDrawableVisitor); + removeDrawableVisitor.remove(); + } + + if (isCreature) + { + RemoveTriBipVisitor removeTriBipVisitor; + mObjectRoot->accept(removeTriBipVisitor); + removeTriBipVisitor.remove(); + } + + NodeMapVisitor visitor; + mObjectRoot->accept(visitor); + mNodeMap = visitor.getNodeMap(); + + mObjectRoot->addCullCallback(new SceneUtil::LightListCallback); + } + + osg::Group* Animation::getObjectRoot() { - NifOgre::ObjectScenePtr objects = it->mObjects; - objects->rotateBillboardNodes(camera); + return mObjectRoot.get(); } - mObjectRoot->rotateBillboardNodes(camera); -} -// TODO: Should not be here -Ogre::Vector3 Animation::getEnchantmentColor(MWWorld::Ptr item) -{ - Ogre::Vector3 result(1,1,1); - std::string enchantmentName = item.getClass().getEnchantment(item); - if (enchantmentName.empty()) + osg::Group* Animation::getOrCreateObjectRoot() + { + if (mObjectRoot) + return mObjectRoot.get(); + + mObjectRoot = new osg::Group; + mInsert->addChild(mObjectRoot); + return mObjectRoot.get(); + } + + void Animation::addGlow(osg::ref_ptr node, osg::Vec4f glowColor) + { + std::vector > textures; + for (int i=0; i<32; ++i) + { + std::stringstream stream; + stream << "textures/magicitem/caust"; + stream << std::setw(2); + stream << std::setfill('0'); + stream << i; + stream << ".dds"; + + textures.push_back(mResourceSystem->getTextureManager()->getTexture2D(stream.str(), osg::Texture2D::REPEAT, osg::Texture2D::REPEAT)); + } + + osg::ref_ptr glowupdater (new GlowUpdater(glowColor, textures)); + node->addUpdateCallback(glowupdater); + } + + // TODO: Should not be here + osg::Vec4f Animation::getEnchantmentColor(MWWorld::Ptr item) + { + osg::Vec4f result(1,1,1,1); + std::string enchantmentName = item.getClass().getEnchantment(item); + if (enchantmentName.empty()) + return result; + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); + assert (enchantment->mEffects.mList.size()); + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find( + enchantment->mEffects.mList.front().mEffectID); + result.x() = magicEffect->mData.mRed / 255.f; + result.y() = magicEffect->mData.mGreen / 255.f; + result.z() = magicEffect->mData.mBlue / 255.f; return result; - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); - assert (enchantment->mEffects.mList.size()); - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find( - enchantment->mEffects.mList.front().mEffectID); - result.x = magicEffect->mData.mRed / 255.f; - result.y = magicEffect->mData.mGreen / 255.f; - result.z = magicEffect->mData.mBlue / 255.f; - return result; -} - -void Animation::setLightEffect(float effect) -{ - if (effect == 0) - { - if (mGlowLight) - { - mInsert->getCreator()->destroySceneNode(mGlowLight->getParentSceneNode()); - mInsert->getCreator()->destroyLight(mGlowLight); - mGlowLight = NULL; - } } - else - { - if (!mGlowLight) - { - mGlowLight = mInsert->getCreator()->createLight(); - Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; - for(size_t i = 0;i < mObjectRoot->mEntities.size();i++) + void Animation::addExtraLight(osg::ref_ptr parent, const ESM::Light *esmLight) + { + SceneUtil::FindByNameVisitor visitor("AttachLight"); + parent->accept(visitor); + + osg::Group* attachTo = NULL; + if (visitor.mFoundNode) + { + attachTo = visitor.mFoundNode; + } + else + { + osg::ComputeBoundsVisitor computeBound; + computeBound.setTraversalMask(~Mask_ParticleSystem); + parent->accept(computeBound); + + // PositionAttitudeTransform seems to be slightly faster than MatrixTransform + osg::ref_ptr trans(new osg::PositionAttitudeTransform); + trans->setPosition(computeBound.getBoundingBox().center()); + + parent->addChild(trans); + + attachTo = trans; + } + + osg::ref_ptr lightSource = new SceneUtil::LightSource; + osg::ref_ptr light (new osg::Light); + lightSource->setNodeMask(Mask_Lighting); + + const MWWorld::Fallback* fallback = MWBase::Environment::get().getWorld()->getFallback(); + + float radius = esmLight->mData.mRadius; + lightSource->setRadius(radius); + + static bool outQuadInLin = fallback->getFallbackBool("LightAttenuation_OutQuadInLin"); + static bool useQuadratic = fallback->getFallbackBool("LightAttenuation_UseQuadratic"); + static float quadraticValue = fallback->getFallbackFloat("LightAttenuation_QuadraticValue"); + static float quadraticRadiusMult = fallback->getFallbackFloat("LightAttenuation_QuadraticRadiusMult"); + static bool useLinear = fallback->getFallbackBool("LightAttenuation_UseLinear"); + static float linearRadiusMult = fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult"); + static float linearValue = fallback->getFallbackFloat("LightAttenuation_LinearValue"); + + bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); + + SceneUtil::configureLight(light, radius, exterior, outQuadInLin, useQuadratic, quadraticValue, + quadraticRadiusMult, useLinear, linearRadiusMult, linearValue); + + osg::Vec4f diffuse = SceneUtil::colourFromRGB(esmLight->mData.mColor); + if (esmLight->mData.mFlags & ESM::Light::Negative) + { + diffuse *= -1; + diffuse.a() = 1; + } + light->setDiffuse(diffuse); + light->setAmbient(osg::Vec4f(0,0,0,1)); + light->setSpecular(osg::Vec4f(0,0,0,0)); + + lightSource->setLight(light); + + osg::ref_ptr ctrl (new SceneUtil::LightController); + ctrl->setDiffuse(light->getDiffuse()); + if (esmLight->mData.mFlags & ESM::Light::Flicker) + ctrl->setType(SceneUtil::LightController::LT_Flicker); + if (esmLight->mData.mFlags & ESM::Light::FlickerSlow) + ctrl->setType(SceneUtil::LightController::LT_FlickerSlow); + if (esmLight->mData.mFlags & ESM::Light::Pulse) + ctrl->setType(SceneUtil::LightController::LT_Pulse); + if (esmLight->mData.mFlags & ESM::Light::PulseSlow) + ctrl->setType(SceneUtil::LightController::LT_PulseSlow); + + lightSource->addUpdateCallback(ctrl); + + attachTo->addChild(lightSource); + } + + void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, std::string texture) + { + if (!mObjectRoot.get()) + return; + + // Early out if we already have this effect + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) + return; + + EffectParams params; + params.mModelName = model; + osg::ref_ptr parentNode; + if (bonename.empty()) + parentNode = mInsert; + else + { + NodeMap::iterator found = mNodeMap.find(Misc::StringUtils::lowerCase(bonename)); + if (found == mNodeMap.end()) + throw std::runtime_error("Can't find bone " + bonename); + + parentNode = found->second; + } + osg::ref_ptr node = mResourceSystem->getSceneManager()->createInstance(model, parentNode); + + node->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + + params.mObjects = PartHolderPtr(new PartHolder(node)); + + SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; + node->accept(findMaxLengthVisitor); + + // FreezeOnCull doesn't work so well with effect particles, that tend to have moving emitters + SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; + node->accept(disableFreezeOnCullVisitor); + + params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); + + node->setNodeMask(Mask_Effect); + + params.mLoop = loop; + params.mEffectId = effectId; + params.mBoneName = bonename; + + params.mAnimTime = boost::shared_ptr(new EffectAnimationTime); + + SceneUtil::AssignControllerSourcesVisitor assignVisitor(boost::shared_ptr(params.mAnimTime)); + node->accept(assignVisitor); + + overrideTexture(texture, mResourceSystem, node); + + // TODO: in vanilla morrowind the effect is scaled based on the host object's bounding box. + + mEffects.push_back(params); + } + + void Animation::removeEffect(int effectId) + { + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + { + if (it->mEffectId == effectId) { - Ogre::Entity *ent = mObjectRoot->mEntities[i]; - bounds.merge(ent->getBoundingBox()); + mEffects.erase(it); + return; } - mInsert->createChildSceneNode(bounds.getCenter())->attachObject(mGlowLight); } - mGlowLight->setType(Ogre::Light::LT_POINT); - effect += 3; - mGlowLight->setAttenuation(1.0f / (0.03f * (0.5f/effect)), 0, 0.5f/effect, 0); } -} - -ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model) - : Animation(ptr, ptr.getRefData().getBaseNode()) -{ - if (!model.empty()) + void Animation::getLoopingEffects(std::vector &out) { - setObjectRoot(model, false); - - Ogre::Vector3 extents = getWorldBounds().getSize(); - float size = std::max(std::max(extents.x, extents.y), extents.z); - - bool small = (size < Settings::Manager::getInt("small object size", "Viewing distance")) && - Settings::Manager::getBool("limit small object distance", "Viewing distance"); - // do not fade out doors. that will cause holes and look stupid - if(ptr.getTypeName().find("Door") != std::string::npos) - small = false; - - float dist = small ? Settings::Manager::getInt("small object distance", "Viewing distance") : 0.0f; - Ogre::Vector3 col = getEnchantmentColor(ptr); - setRenderProperties(mObjectRoot, (mPtr.getTypeName() == typeid(ESM::Static).name()) ? - (small ? RV_StaticsSmall : RV_Statics) : RV_Misc, - RQG_Main, RQG_Alpha, dist, !ptr.getClass().getEnchantment(ptr).empty(), &col); - } - else - { - // No model given. Create an object root anyway, so that lights can be added to it if needed. - mObjectRoot = NifOgre::ObjectScenePtr (new NifOgre::ObjectScene(mInsert->getCreator())); - } -} - - -class FindEntityTransparency { -public: - bool operator()(Ogre::Entity *ent) const - { - unsigned int numsubs = ent->getNumSubEntities(); - for(unsigned int i = 0;i < numsubs;++i) + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) { - sh::Factory::getInstance()._ensureMaterial(ent->getSubEntity(i)->getMaterial()->getName(), "Default"); - if(ent->getSubEntity(i)->getMaterial()->isTransparent()) - return true; + if (it->mLoop) + out.push_back(it->mEffectId); } - return false; } -}; -bool ObjectAnimation::canBatch() const -{ - if(!mObjectRoot->mParticles.empty() || !mObjectRoot->mLights.empty() || !mObjectRoot->mControllers.empty()) - return false; - if (!mObjectRoot->mBillboardNodes.empty()) - return false; - return std::find_if(mObjectRoot->mEntities.begin(), mObjectRoot->mEntities.end(), - FindEntityTransparency()) == mObjectRoot->mEntities.end(); -} - -void ObjectAnimation::fillBatch(Ogre::StaticGeometry *sg) -{ - std::vector::reverse_iterator iter = mObjectRoot->mEntities.rbegin(); - for(;iter != mObjectRoot->mEntities.rend();++iter) + void Animation::updateEffects(float duration) { - Ogre::Node *node = (*iter)->getParentNode(); - if ((*iter)->isVisible()) - sg->addEntity(*iter, node->_getDerivedPosition(), node->_getDerivedOrientation(), node->_getDerivedScale()); + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) + { + it->mAnimTime->addTime(duration); + + if (it->mAnimTime->getTime() >= it->mMaxControllerLength) + { + if (it->mLoop) + { + // Start from the beginning again; carry over the remainder + // Not sure if this is actually needed, the controller function might already handle loops + float remainder = it->mAnimTime->getTime() - it->mMaxControllerLength; + it->mAnimTime->resetTime(remainder); + } + else + { + it = mEffects.erase(it); + continue; + } + } + ++it; + } + } + + bool Animation::upperBodyReady() const + { + for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter) + { + if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Hit)) + || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Weapon)) + || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Knockdown)) + || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Death))) + return false; + } + return true; + } + + const osg::Node* Animation::getNode(const std::string &name) const + { + std::string lowerName = Misc::StringUtils::lowerCase(name); + NodeMap::const_iterator found = mNodeMap.find(lowerName); + if (found == mNodeMap.end()) + return NULL; + else + return found->second; + } + + void Animation::setAlpha(float alpha) + { + if (alpha == mAlpha) + return; + mAlpha = alpha; + + if (alpha != 1.f) + { + osg::StateSet* stateset (new osg::StateSet); + + osg::BlendFunc* blendfunc (new osg::BlendFunc); + stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + // FIXME: overriding diffuse/ambient/emissive colors + osg::Material* material (new osg::Material); + material->setColorMode(osg::Material::OFF); + material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha)); + material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + mObjectRoot->setStateSet(stateset); + } + else + { + mObjectRoot->setStateSet(NULL); + } + + setRenderBin(); + } + + void Animation::setRenderBin() + { + if (mAlpha != 1.f) + { + osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet(); + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + } + else if (osg::StateSet* stateset = mObjectRoot->getStateSet()) + stateset->setRenderBinToInherit(); + } + + void Animation::setLightEffect(float effect) + { + if (effect == 0) + { + if (mGlowLight) + { + mInsert->removeChild(mGlowLight); + mGlowLight = NULL; + } + } + else + { + effect += 3; + float radius = effect * 66.f; + float linearAttenuation = 0.5f / effect; + + if (!mGlowLight || linearAttenuation != mGlowLight->getLight(0)->getLinearAttenuation()) + { + if (mGlowLight) + { + mInsert->removeChild(mGlowLight); + mGlowLight = NULL; + } + + osg::ref_ptr light (new osg::Light); + light->setDiffuse(osg::Vec4f(0,0,0,0)); + light->setSpecular(osg::Vec4f(0,0,0,0)); + light->setAmbient(osg::Vec4f(1.5f,1.5f,1.5f,1.f)); + light->setLinearAttenuation(linearAttenuation); + + mGlowLight = new SceneUtil::LightSource; + mGlowLight->setNodeMask(Mask_Lighting); + mInsert->addChild(mGlowLight); + mGlowLight->setLight(light); + } + + mGlowLight->setRadius(radius); + } + } + + void Animation::addControllers() + { + mHeadController = NULL; + + if (mPtr.getClass().isBipedal(mPtr)) + { + NodeMap::iterator found = mNodeMap.find("bip01 head"); + if (found != mNodeMap.end() && dynamic_cast(found->second.get())) + { + osg::Node* node = found->second; + mHeadController = new RotateController(mObjectRoot.get()); + node->addUpdateCallback(mHeadController); + mActiveControllers.insert(std::make_pair(node, mHeadController)); + } + } + } + + void Animation::setHeadPitch(float pitchRadians) + { + mHeadPitchRadians = pitchRadians; + } + + void Animation::setHeadYaw(float yawRadians) + { + mHeadYawRadians = yawRadians; + } + + float Animation::getHeadPitch() const + { + return mHeadPitchRadians; + } + + float Animation::getHeadYaw() const + { + return mHeadYawRadians; + } + + // ------------------------------------------------------ + + float Animation::AnimationTime::getValue(osg::NodeVisitor*) + { + if (mTimePtr) + return *mTimePtr; + return 0.f; + } + + float EffectAnimationTime::getValue(osg::NodeVisitor*) + { + return mTime; + } + + void EffectAnimationTime::addTime(float duration) + { + mTime += duration; + } + + void EffectAnimationTime::resetTime(float time) + { + mTime = time; + } + + float EffectAnimationTime::getTime() const + { + return mTime; + } + + // -------------------------------------------------------------------------------- + + ObjectAnimation::ObjectAnimation(const MWWorld::Ptr &ptr, const std::string &model, Resource::ResourceSystem* resourceSystem, bool animated, bool allowLight) + : Animation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), resourceSystem) + { + if (!model.empty()) + { + setObjectRoot(model, false, false, false); + if (animated) + addAnimSource(model); + + if (!ptr.getClass().getEnchantment(ptr).empty()) + addGlow(mObjectRoot, getEnchantmentColor(ptr)); + } + if (ptr.getTypeName() == typeid(ESM::Light).name() && allowLight) + addExtraLight(getOrCreateObjectRoot(), ptr.get()->mBase); + } + + Animation::AnimState::~AnimState() + { + + } + + // ------------------------------ + + PartHolder::PartHolder(osg::ref_ptr node) + : mNode(node) + { + } + + PartHolder::~PartHolder() + { + if (mNode->getNumParents()) + mNode->getParent(0)->removeChild(mNode); } -} } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index dab8cfebb7..213e33f673 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -1,122 +1,248 @@ #ifndef GAME_RENDER_ANIMATION_H #define GAME_RENDER_ANIMATION_H -#include -#include - -#include - #include "../mwworld/ptr.hpp" +#include + namespace ESM { struct Light; } +namespace Resource +{ + class ResourceSystem; +} + +namespace NifOsg +{ + class KeyframeHolder; + class KeyframeController; +} + +namespace SceneUtil +{ + class LightSource; + class Skeleton; +} + namespace MWRender { -class Camera; + +class ResetAccumRootCallback; +class RotateController; + +class EffectAnimationTime : public SceneUtil::ControllerSource +{ +private: + float mTime; +public: + virtual float getValue(osg::NodeVisitor* nv); + + void addTime(float duration); + void resetTime(float time); + float getTime() const; + + EffectAnimationTime() : mTime(0) { } +}; + +/// @brief Detaches the node from its parent when the object goes out of scope. +class PartHolder +{ +public: + PartHolder(osg::ref_ptr node); + + ~PartHolder(); + + osg::ref_ptr getNode() + { + return mNode; + } + +private: + osg::ref_ptr mNode; + + void operator= (const PartHolder&); + PartHolder(const PartHolder&); +}; +typedef boost::shared_ptr PartHolderPtr; class Animation { public: - enum Group { - Group_LowerBody = 1<<0, - - Group_Torso = 1<<1, - Group_LeftArm = 1<<2, - Group_RightArm = 1<<3, - - Group_UpperBody = Group_Torso | Group_LeftArm | Group_RightArm, - - Group_All = Group_LowerBody | Group_UpperBody + enum BoneGroup { + BoneGroup_LowerBody = 0, + BoneGroup_Torso, + BoneGroup_LeftArm, + BoneGroup_RightArm }; + enum BlendMask { + BlendMask_LowerBody = 1<<0, + BlendMask_Torso = 1<<1, + BlendMask_LeftArm = 1<<2, + BlendMask_RightArm = 1<<3, + + BlendMask_UpperBody = BlendMask_Torso | BlendMask_LeftArm | BlendMask_RightArm, + + BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody + }; + /* This is the number of *discrete* blend masks. */ + static const size_t sNumBlendMasks = 4; + + /// Holds an animation priority value for each BoneGroup. + struct AnimPriority + { + /// Convenience constructor, initialises all priorities to the same value. + AnimPriority(int priority) + { + for (unsigned int i=0; i::const_iterator &key, + const std::multimap& map) = 0; + }; + + void setTextKeyListener(TextKeyListener* listener); + protected: - /* This is the number of *discrete* groups. */ - static const size_t sNumGroups = 4; - - class AnimationTime : public Ogre::ControllerValue + class AnimationTime : public SceneUtil::ControllerSource { private: - Animation *mAnimation; - std::string mAnimationName; + boost::shared_ptr mTimePtr; public: - AnimationTime(Animation *anim) - : mAnimation(anim) - { } - void setAnimName(const std::string &name) - { mAnimationName = name; } - const std::string &getAnimName() const - { return mAnimationName; } + void setTimePtr(boost::shared_ptr time) + { mTimePtr = time; } + boost::shared_ptr getTimePtr() const + { return mTimePtr; } - virtual Ogre::Real getValue() const; - virtual void setValue(Ogre::Real value); + virtual float getValue(osg::NodeVisitor* nv); }; - class EffectAnimationTime : public Ogre::ControllerValue - { - private: - float mTime; - public: - EffectAnimationTime() : mTime(0) { } - void addTime(float time) { mTime += time; } - void resetTime(float value) { mTime = value; } - - virtual Ogre::Real getValue() const; - virtual void setValue(Ogre::Real value); - }; - - - - class NullAnimationTime : public Ogre::ControllerValue + class NullAnimationTime : public SceneUtil::ControllerSource { public: - virtual Ogre::Real getValue() const - { return 0.0f; } - virtual void setValue(Ogre::Real value) - { } + virtual float getValue(osg::NodeVisitor *nv) + { + return 0.f; + } }; - - struct AnimSource : public Ogre::AnimationAlloc { - NifOgre::TextKeyMap mTextKeys; - std::vector > mControllers[sNumGroups]; - }; - typedef std::vector< Ogre::SharedPtr > AnimSourceList; + struct AnimSource; struct AnimState { - Ogre::SharedPtr mSource; + boost::shared_ptr mSource; float mStartTime; float mLoopStartTime; float mLoopStopTime; float mStopTime; - float mTime; + typedef boost::shared_ptr TimePtr; + TimePtr mTime; float mSpeedMult; bool mPlaying; size_t mLoopCount; - int mPriority; - int mGroups; + AnimPriority mPriority; + int mBlendMask; bool mAutoDisable; AnimState() : mStartTime(0.0f), mLoopStartTime(0.0f), mLoopStopTime(0.0f), mStopTime(0.0f), - mTime(0.0f), mSpeedMult(1.0f), mPlaying(false), mLoopCount(0), - mPriority(0), mGroups(0), mAutoDisable(true) - { } + mTime(new float), mSpeedMult(1.0f), mPlaying(false), mLoopCount(0), + mPriority(0), mBlendMask(0), mAutoDisable(true) + { + } + ~AnimState(); + + float getTime() const + { + return *mTime; + } + void setTime(float time) + { + *mTime = time; + } }; typedef std::map AnimStateMap; + AnimStateMap mStates; - typedef std::map ObjectAttachMap; + typedef std::vector > AnimSourceList; + AnimSourceList mAnimSources; + + osg::ref_ptr mInsert; + + osg::ref_ptr mObjectRoot; + SceneUtil::Skeleton* mSkeleton; + + // The node expected to accumulate movement during movement animations. + osg::ref_ptr mAccumRoot; + + // The controller animating that node. + osg::ref_ptr mAccumCtrl; + + // Used to reset the position of the accumulation root every frame - the movement should be applied to the physics system + osg::ref_ptr mResetAccumRootCallback; + + // Keep track of controllers that we added to our scene graph. + // We may need to rebuild these controllers when the active animation groups / sources change. + typedef std::multimap, osg::ref_ptr > ControllerMap; + ControllerMap mActiveControllers; + + boost::shared_ptr mAnimationTimePtr[sNumBlendMasks]; + + // Stored in all lowercase for a case-insensitive lookup + typedef std::map > NodeMap; + NodeMap mNodeMap; + + MWWorld::Ptr mPtr; + + Resource::ResourceSystem* mResourceSystem; + + osg::Vec3f mAccumulate; struct EffectParams { std::string mModelName; // Just here so we don't add the same effect twice - NifOgre::ObjectScenePtr mObjects; + PartHolderPtr mObjects; + boost::shared_ptr mAnimTime; + float mMaxControllerLength; int mEffectId; bool mLoop; std::string mBoneName; @@ -124,95 +250,86 @@ protected: std::vector mEffects; - MWWorld::Ptr mPtr; + TextKeyListener* mTextKeyListener; - Ogre::Light* mGlowLight; + osg::ref_ptr mHeadController; + float mHeadYawRadians; + float mHeadPitchRadians; - Ogre::SceneNode *mInsert; - Ogre::Entity *mSkelBase; - NifOgre::ObjectScenePtr mObjectRoot; - AnimSourceList mAnimSources; - Ogre::Node *mAccumRoot; - Ogre::Node *mNonAccumRoot; - NifOgre::NodeTargetValue *mNonAccumCtrl; - Ogre::Vector3 mAccumulate; - - AnimStateMap mStates; - - Ogre::SharedPtr mAnimationTimePtr[sNumGroups]; - Ogre::SharedPtr mNullAnimationTimePtr; - - ObjectAttachMap mAttachedObjects; + osg::ref_ptr mGlowLight; + float mAlpha; /* Sets the appropriate animations on the bone groups based on priority. */ void resetActiveGroups(); - static size_t detectAnimGroup(const Ogre::Node *node); - - static float calcAnimVelocity(const NifOgre::TextKeyMap &keys, - NifOgre::NodeTargetValue *nonaccumctrl, - const Ogre::Vector3 &accum, - const std::string &groupname); - - /* Updates a skeleton instance so that all bones matching the source skeleton (based on - * bone names) are positioned identically. */ - void updateSkeletonInstance(const Ogre::SkeletonInstance *skelsrc, Ogre::SkeletonInstance *skel); + size_t detectBlendMask(osg::Node* node); /* Updates the position of the accum root node for the given time, and * returns the wanted movement vector from the previous time. */ - void updatePosition(float oldtime, float newtime, Ogre::Vector3 &position); - - static NifOgre::TextKeyMap::const_iterator findGroupStart(const NifOgre::TextKeyMap &keys, const std::string &groupname); + void updatePosition(float oldtime, float newtime, osg::Vec3f& position); /* Resets the animation to the time of the specified start marker, without * moving anything, and set the end time to the specified stop marker. If * the marker is not found, or if the markers are the same, it returns * false. */ - bool reset(AnimState &state, const NifOgre::TextKeyMap &keys, + bool reset(AnimState &state, const std::multimap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint, bool loopfallback); - void handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key, - const NifOgre::TextKeyMap& map); + void handleTextKey(AnimState &state, const std::string &groupname, const std::multimap::const_iterator &key, + const std::multimap& map); - /* Sets the root model of the object. If 'baseonly' is true, then any meshes or particle - * systems in the model are ignored (useful for NPCs, where only the skeleton is needed for - * the root). + /** Sets the root model of the object. * * Note that you must make sure all animation sources are cleared before reseting the object * root. All nodes previously retrieved with getNode will also become invalidated. + * @param forceskeleton Wrap the object root in a Skeleton, even if it contains no skinned parts. Use this if you intend to add skinned parts manually. + * @param baseonly If true, then any meshes or particle systems in the model are ignored + * (useful for NPCs, where only the skeleton is needed for the root, and the actual NPC parts are then assembled from separate files). */ - void setObjectRoot(const std::string &model, bool baseonly); + void setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature); /* Adds the keyframe controllers in the specified model as a new animation source. Note that * the filename portion of the provided model name will be prepended with 'x', and the .nif * extension will be replaced with .kf. */ void addAnimSource(const std::string &model); - /** Adds an additional light to the given object list using the specified ESM record. */ - void addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScenePtr objlist, const ESM::Light *light); + /** Adds an additional light to the given node using the specified ESM record. */ + void addExtraLight(osg::ref_ptr parent, const ESM::Light *light); void clearAnimSources(); - // TODO: Should not be here - Ogre::Vector3 getEnchantmentColor(MWWorld::Ptr item); + /** + * Provided to allow derived classes adding their own controllers. Note, the controllers must be added to mActiveControllers + * so they get cleaned up properly on the next controller rebuild. A controller rebuild may be necessary to ensure correct ordering. + */ + virtual void addControllers(); + + osg::Vec4f getEnchantmentColor(MWWorld::Ptr item); + + void addGlow(osg::ref_ptr node, osg::Vec4f glowColor); + + /// Set the render bin for this animation's object root. May be customized by subclasses. + virtual void setRenderBin(); public: - // FIXME: Move outside of this class - static void setRenderProperties(NifOgre::ObjectScenePtr objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, - Ogre::uint8 transqueue, Ogre::Real dist=0.0f, - bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); - /// Returns the name of the .nif file that makes up this animation's base skeleton. - /// If there is no skeleton, returns "". - std::string getObjectRootName() const; - - Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node); + Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); virtual ~Animation(); + MWWorld::Ptr getPtr(); + + /// Set active flag on the object skeleton, if one exists. + /// @see SceneUtil::Skeleton::setActive + void setActive(bool active); + + osg::Group* getOrCreateObjectRoot(); + + osg::Group* getObjectRoot(); + /** * @brief Add an effect mesh attached to a bone or the insert scene node * @param model @@ -220,35 +337,28 @@ public: * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true, * you need to remove it manually using removeEffect when the effect should end. * @param bonename Bone to attach to, or empty string to use the scene node instead - * @param texture override the texture specified in the model's materials + * @param texture override the texture specified in the model's materials - if empty, do not override * @note Will not add an effect twice. */ void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", std::string texture = ""); void removeEffect (int effectId); void getLoopingEffects (std::vector& out); - /// Prepare this animation for being rendered with \a camera (rotates billboard nodes) - virtual void preRender (Ogre::Camera* camera); - - virtual void setAlpha(float alpha) {} - virtual void setVampire(bool vampire) {} - -public: - void updatePtr(const MWWorld::Ptr &ptr); + virtual void updatePtr(const MWWorld::Ptr &ptr); bool hasAnimation(const std::string &anim); // Specifies the axis' to accumulate on. Non-accumulated axis will just // move visually, but not affect the actual movement. Each x/y/z value // should be on the scale of 0 to 1. - void setAccumulation(const Ogre::Vector3 &accum); + void setAccumulation(const osg::Vec3f& accum); /** Plays an animation. * \param groupname Name of the animation group to play. * \param priority Priority of the animation. The animation will play on * bone groups that don't have another animation set of a * higher priority. - * \param groups Bone groups to play the animation on. + * \param blendMask Bone groups to play the animation on. * \param autodisable Automatically disable the animation when it stops * playing. * \param speedmult Speed multiplier for the animation. @@ -263,7 +373,7 @@ public: * \param loopFallback Allow looping an animation that has no loop keys, i.e. fall back to use * the "start" and "stop" keys for looping? */ - void play(const std::string &groupname, int priority, int groups, bool autodisable, + void play(const std::string &groupname, const AnimPriority& priority, int blendMask, bool autodisable, float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops, bool loopfallback=false); @@ -302,56 +412,48 @@ public: * \param groupname Animation group to disable. */ void disable(const std::string &groupname); - void changeGroups(const std::string &groupname, int group); - - virtual void setWeaponGroup(const std::string& group) {} /** Retrieves the velocity (in units per second) that the animation will move. */ float getVelocity(const std::string &groupname) const; - /// A relative factor (0-1) that decides if and how much the skeleton should be pitched - /// to indicate the facing orientation of the character. - virtual void setPitchFactor(float factor) {} - virtual void setHeadPitch(Ogre::Radian factor) {} - virtual void setHeadYaw(Ogre::Radian factor) {} - virtual Ogre::Radian getHeadPitch() const { return Ogre::Radian(0.f); } - virtual Ogre::Radian getHeadYaw() const { return Ogre::Radian(0.f); } - - virtual Ogre::Vector3 runAnimation(float duration); + virtual osg::Vec3f runAnimation(float duration); /// This is typically called as part of runAnimation, but may be called manually if needed. void updateEffects(float duration); + /// Return a node with the specified name, or NULL if not existing. + /// @note The matching is case-insensitive. + const osg::Node* getNode(const std::string& name) const; + + virtual void showWeapons(bool showWeapon) {} + virtual void showCarriedLeft(bool show) {} + virtual void setWeaponGroup(const std::string& group) {} + virtual void setVampire(bool vampire) {} + /// A value < 1 makes the animation translucent, 1.f = fully opaque + void setAlpha(float alpha); + virtual void setPitchFactor(float factor) {} + virtual void attachArrow() {} + virtual void releaseArrow(float attackStrength) {} + virtual void enableHeadAnimation(bool enable) {} // TODO: move outside of this class /// Makes this object glow, by placing a Light in its center. /// @param effect Controls the radius and intensity of the light. - void setLightEffect(float effect); + virtual void setLightEffect(float effect); - virtual void showWeapons(bool showWeapon); - virtual void showCarriedLeft(bool show) {} - virtual void attachArrow() {} - virtual void releaseArrow() {} - void enableLights(bool enable); - virtual void enableHeadAnimation(bool enable) {} + virtual void setHeadPitch(float pitchRadians); + virtual void setHeadYaw(float yawRadians); + virtual float getHeadPitch() const; + virtual float getHeadYaw() const; + virtual void setAccurateAiming(bool enabled) {} - Ogre::AxisAlignedBox getWorldBounds(); - - Ogre::Node *getNode(const std::string &name); - Ogre::Node *getNode(int handle); - - // Attaches the given object to a bone on this object's base skeleton. If the bone doesn't - // exist, the object isn't attached and NULL is returned. The returned TagPoint is only - // valid until the next setObjectRoot call. - Ogre::TagPoint *attachObjectToBone(const Ogre::String &bonename, Ogre::MovableObject *obj); - void detachObjectFromBone(Ogre::MovableObject *obj); +private: + Animation(const Animation&); + void operator=(Animation&); }; class ObjectAnimation : public Animation { public: - ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model); - - bool canBatch() const; - void fillBatch(Ogre::StaticGeometry *sg); + ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model, Resource::ResourceSystem* resourceSystem, bool animated, bool allowLight); }; } diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp new file mode 100644 index 0000000000..eaf36cf857 --- /dev/null +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -0,0 +1,119 @@ +#include "bulletdebugdraw.hpp" + +#include + +#include + +#include +#include +#include + +#include "vismask.hpp" + +namespace +{ + osg::Vec3f toOsg(const btVector3& vec) + { + return osg::Vec3f(vec.x(), vec.y(), vec.z()); + } +} + +namespace MWRender +{ + +DebugDrawer::DebugDrawer(osg::ref_ptr parentNode, btCollisionWorld *world) + : mParentNode(parentNode), + mWorld(world), + mDebugOn(true) +{ + mGeode = new osg::Geode; + mParentNode->addChild(mGeode); + mGeode->setNodeMask(Mask_Debug); + + createGeometry(); + + mParentNode->addChild(mGeode); +} + +void DebugDrawer::createGeometry() +{ + if (!mGeometry) + { + mGeometry = new osg::Geometry; + + mVertices = new osg::Vec3Array; + + mDrawArrays = new osg::DrawArrays(osg::PrimitiveSet::LINES); + + mGeometry->setUseDisplayList(false); + mGeometry->setVertexArray(mVertices); + mGeometry->setDataVariance(osg::Object::DYNAMIC); + mGeometry->addPrimitiveSet(mDrawArrays); + + mGeode->addDrawable(mGeometry); + } +} + +void DebugDrawer::destroyGeometry() +{ + if (mGeometry) + { + mGeode->removeDrawable(mGeometry); + mGeometry = NULL; + mVertices = NULL; + mDrawArrays = NULL; + } +} + +DebugDrawer::~DebugDrawer() +{ + mParentNode->removeChild(mGeode); +} + +void DebugDrawer::step() +{ + if (mDebugOn) + { + mVertices->clear(); + mWorld->debugDrawWorld(); + mDrawArrays->setCount(mVertices->size()); + mVertices->dirty(); + mGeometry->dirtyBound(); + } +} + +void DebugDrawer::drawLine(const btVector3 &from, const btVector3 &to, const btVector3 &color) +{ + mVertices->push_back(toOsg(from)); + mVertices->push_back(toOsg(to)); +} + +void DebugDrawer::drawContactPoint(const btVector3 &PointOnB, const btVector3 &normalOnB, btScalar distance, int lifeTime, const btVector3 &color) +{ + mVertices->push_back(toOsg(PointOnB)); + mVertices->push_back(toOsg(PointOnB) + (toOsg(normalOnB) * distance * 20)); +} + +void DebugDrawer::reportErrorWarning(const char *warningString) +{ + std::cerr << warningString << std::endl; +} + +void DebugDrawer::setDebugMode(int isOn) +{ + mDebugOn = (isOn == 0) ? false : true; + + if (!mDebugOn) + destroyGeometry(); + else + createGeometry(); +} + +int DebugDrawer::getDebugMode() const +{ + return mDebugOn; +} + + + +} diff --git a/apps/openmw/mwrender/bulletdebugdraw.hpp b/apps/openmw/mwrender/bulletdebugdraw.hpp new file mode 100644 index 0000000000..1bccc20bd0 --- /dev/null +++ b/apps/openmw/mwrender/bulletdebugdraw.hpp @@ -0,0 +1,63 @@ +#ifndef OPENMW_MWRENDER_BULLETDEBUGDRAW_H +#define OPENMW_MWRENDER_BULLETDEBUGDRAW_H + +#include +#include +#include + +#include + +class btCollisionWorld; + +namespace osg +{ + class Group; + class Geode; + class Geometry; +} + +namespace MWRender +{ + +class DebugDrawer : public btIDebugDraw +{ +protected: + osg::ref_ptr mParentNode; + btCollisionWorld *mWorld; + osg::ref_ptr mGeode; + osg::ref_ptr mGeometry; + osg::ref_ptr mVertices; + osg::ref_ptr mDrawArrays; + + bool mDebugOn; + + void createGeometry(); + void destroyGeometry(); + +public: + + DebugDrawer(osg::ref_ptr parentNode, btCollisionWorld *world); + ~DebugDrawer(); + + void step(); + + void drawLine(const btVector3& from,const btVector3& to,const btVector3& color); + + void drawContactPoint(const btVector3& PointOnB,const btVector3& normalOnB,btScalar distance,int lifeTime,const btVector3& color); + + void reportErrorWarning(const char* warningString); + + void draw3dText(const btVector3& location,const char* textString) {} + + //0 for off, anything else for on. + void setDebugMode(int isOn); + + //0 for off, anything else for on. + int getDebugMode() const; + +}; + + +} + +#endif diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index c7a27dfe8f..88934414f6 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -1,9 +1,8 @@ #include "camera.hpp" -#include -#include -#include -#include +#include + +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -13,12 +12,39 @@ #include "npcanimation.hpp" +namespace +{ + +class UpdateRenderCameraCallback : public osg::NodeCallback +{ +public: + UpdateRenderCameraCallback(MWRender::Camera* cam) + : mCamera(cam) + { + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osg::Camera* cam = static_cast(node); + + // traverse first to update animations, in case the camera is attached to an animated node + traverse(node, nv); + + mCamera->updateCamera(cam); + } + +private: + MWRender::Camera* mCamera; +}; + +} + namespace MWRender { - Camera::Camera (Ogre::Camera *camera) - : mCamera(camera), - mCameraNode(NULL), - mCameraPosNode(NULL), + + Camera::Camera (osg::Camera* camera) + : mHeightScale(1.f), + mCamera(camera), mAnimation(NULL), mFirstPersonView(true), mPreviewMode(false), @@ -27,10 +53,11 @@ namespace MWRender mFurthest(800.f), mIsNearest(false), mHeight(124.f), - mCameraDistance(192.f), + mMaxCameraDistance(192.f), mDistanceAdjusted(false), mVanityToggleQueued(false), - mViewModeToggleQueued(false) + mViewModeToggleQueued(false), + mCameraDistance(0.f) { mVanity.enabled = false; mVanity.allowed = true; @@ -41,10 +68,53 @@ namespace MWRender mMainCam.pitch = 0.f; mMainCam.yaw = 0.f; mMainCam.offset = 400.f; + + mUpdateCallback = new UpdateRenderCameraCallback(this); + mCamera->addUpdateCallback(mUpdateCallback); } Camera::~Camera() { + mCamera->removeUpdateCallback(mUpdateCallback); + } + + MWWorld::Ptr Camera::getTrackingPtr() const + { + return mTrackingPtr; + } + + osg::Vec3d Camera::getFocalPoint() + { + const osg::Node* trackNode = mTrackingNode; + if (!trackNode) + return osg::Vec3d(); + osg::MatrixList mats = trackNode->getWorldMatrices(); + if (!mats.size()) + return osg::Vec3d(); + const osg::Matrix& worldMat = mats[0]; + + osg::Vec3d position = worldMat.getTrans(); + if (!isFirstPerson()) + position.z() += mHeight * mHeightScale; + return position; + } + + void Camera::updateCamera(osg::Camera *cam) + { + if (mTrackingPtr.isEmpty()) + return; + + osg::Vec3d position = getFocalPoint(); + + osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1)); + + osg::Vec3d offset = orient * osg::Vec3d(0, -mCameraDistance, 0); + position += offset; + + osg::Vec3d forward = orient * osg::Vec3d(0,1,0); + osg::Vec3d up = orient * osg::Vec3d(0,0,1); + + cam->setViewMatrixAsLookAt(position, position + forward, up); } void Camera::reset() @@ -55,68 +125,20 @@ namespace MWRender toggleViewMode(); } - void Camera::rotateCamera(const Ogre::Vector3 &rot, bool adjust) + void Camera::rotateCamera(float pitch, float yaw, bool adjust) { - if (adjust) { - setYaw(getYaw() + rot.z); - setPitch(getPitch() + rot.x); - } else { - setYaw(rot.z); - setPitch(rot.x); + if (adjust) + { + pitch += getPitch(); + yaw += getYaw(); } - - Ogre::Quaternion xr(Ogre::Radian(getPitch() + Ogre::Math::HALF_PI), Ogre::Vector3::UNIT_X); - Ogre::Quaternion orient = xr; - if (mVanity.enabled || mPreviewMode) { - Ogre::Quaternion zr(Ogre::Radian(getYaw()), Ogre::Vector3::UNIT_Z); - orient = zr * xr; - } - - if (isFirstPerson()) - mCamera->getParentNode()->setOrientation(orient); - else - mCameraNode->setOrientation(orient); + setYaw(yaw); + setPitch(pitch); } - const std::string &Camera::getHandle() const - { - return mTrackingPtr.getRefData().getHandle(); - } - - Ogre::SceneNode* Camera::attachTo(const MWWorld::Ptr &ptr) + void Camera::attachTo(const MWWorld::Ptr &ptr) { mTrackingPtr = ptr; - Ogre::SceneNode *node = mTrackingPtr.getRefData().getBaseNode()->createChildSceneNode(Ogre::Vector3(0.0f, 0.0f, mHeight)); - node->setInheritScale(false); - Ogre::SceneNode *posNode = node->createChildSceneNode(); - posNode->setInheritScale(false); - if(mCameraNode) - { - node->setOrientation(mCameraNode->getOrientation()); - posNode->setPosition(mCameraPosNode->getPosition()); - mCameraNode->getCreator()->destroySceneNode(mCameraNode); - mCameraNode->getCreator()->destroySceneNode(mCameraPosNode); - } - mCameraNode = node; - mCameraPosNode = posNode; - - if (!isFirstPerson()) - { - mCamera->detachFromParent(); - mCameraPosNode->attachObject(mCamera); - } - - return mCameraPosNode; - } - - void Camera::setPosition(const Ogre::Vector3& position) - { - mCameraPosNode->setPosition(position); - } - - void Camera::setPosition(float x, float y, float z) - { - setPosition(Ogre::Vector3(x,y,z)); } void Camera::update(float duration, bool paused) @@ -147,9 +169,7 @@ namespace MWRender if(mVanity.enabled) { - Ogre::Vector3 rot(0.f, 0.f, 0.f); - rot.z = Ogre::Degree(3.f * duration).valueRadians(); - rotateCamera(rot, true); + rotateCamera(0.f, osg::DegreesToRadians(3.f * duration), true); } } @@ -169,9 +189,9 @@ namespace MWRender processViewChange(); if (mFirstPersonView) { - setPosition(0.f, 0.f, 0.f); + mCameraDistance = 0.f; } else { - setPosition(0.f, 0.f, mCameraDistance); + mCameraDistance = mMaxCameraDistance; } } @@ -202,18 +222,15 @@ namespace MWRender processViewChange(); float offset = mPreviewCam.offset; - Ogre::Vector3 rot(0.f, 0.f, 0.f); + if (mVanity.enabled) { - rot.x = Ogre::Degree(-30.f).valueRadians(); - mMainCam.offset = mCameraPosNode->getPosition().z; + setPitch(osg::DegreesToRadians(-30.f)); + mMainCam.offset = mCameraDistance; } else { - rot.x = getPitch(); offset = mMainCam.offset; } - rot.z = getYaw(); - setPosition(0.f, 0.f, offset); - rotateCamera(rot, false); + mCameraDistance = offset; return true; } @@ -229,7 +246,7 @@ namespace MWRender mPreviewMode = enable; processViewChange(); - float offset = mCameraPosNode->getPosition().z; + float offset = mCameraDistance; if (mPreviewMode) { mMainCam.offset = offset; offset = mPreviewCam.offset; @@ -238,13 +255,12 @@ namespace MWRender offset = mMainCam.offset; } - setPosition(0.f, 0.f, offset); + mCameraDistance = offset; } void Camera::setSneakOffset(float offset) { - if(mAnimation) - mAnimation->addFirstPersonOffset(Ogre::Vector3(0.f, 0.f, -offset)); + mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset)); } float Camera::getYaw() @@ -256,10 +272,10 @@ namespace MWRender void Camera::setYaw(float angle) { - if (angle > Ogre::Math::PI) { - angle -= Ogre::Math::TWO_PI; - } else if (angle < -Ogre::Math::PI) { - angle += Ogre::Math::TWO_PI; + if (angle > osg::PI) { + angle -= osg::PI*2; + } else if (angle < -osg::PI) { + angle += osg::PI*2; } if (mVanity.enabled || mPreviewMode) { mPreviewCam.yaw = angle; @@ -279,7 +295,7 @@ namespace MWRender void Camera::setPitch(float angle) { const float epsilon = 0.000001f; - float limit = Ogre::Math::HALF_PI - epsilon; + float limit = osg::PI_2 - epsilon; if(mPreviewMode) limit /= 2; @@ -297,7 +313,7 @@ namespace MWRender float Camera::getCameraDistance() const { - return mCameraPosNode->getPosition().z; + return mCameraDistance; } void Camera::setCameraDistance(float dist, bool adjust, bool override) @@ -307,25 +323,24 @@ namespace MWRender mIsNearest = false; - Ogre::Vector3 v(0.f, 0.f, dist); - if (adjust) { - v += mCameraPosNode->getPosition(); - } - if (v.z >= mFurthest) { - v.z = mFurthest; - } else if (!override && v.z < 10.f) { - v.z = 10.f; - } else if (override && v.z <= mNearest) { - v.z = mNearest; + if (adjust) + dist += mCameraDistance; + + if (dist >= mFurthest) { + dist = mFurthest; + } else if (!override && dist < 10.f) { + dist = 10.f; + } else if (override && dist <= mNearest) { + dist = mNearest; mIsNearest = true; } - setPosition(v); + mCameraDistance = dist; if (override) { if (mVanity.enabled || mPreviewMode) { - mPreviewCam.offset = v.z; + mPreviewCam.offset = mCameraDistance; } else if (!mFirstPersonView) { - mCameraDistance = v.z; + mMaxCameraDistance = mCameraDistance; } } else { mDistanceAdjusted = true; @@ -336,9 +351,9 @@ namespace MWRender { if (mDistanceAdjusted) { if (mVanity.enabled || mPreviewMode) { - setPosition(0, 0, mPreviewCam.offset); + mCameraDistance = mPreviewCam.offset; } else if (!mFirstPersonView) { - setPosition(0, 0, mCameraDistance); + mCameraDistance = mMaxCameraDistance; } } mDistanceAdjusted = false; @@ -346,13 +361,6 @@ namespace MWRender void Camera::setAnimation(NpcAnimation *anim) { - // If we're switching to a new NpcAnimation, ensure the old one is - // using a normal view mode - if(mAnimation && mAnimation != anim) - { - mAnimation->setViewMode(NpcAnimation::VM_Normal); - mAnimation->detachObjectFromBone(mCamera); - } mAnimation = anim; processViewChange(); @@ -360,29 +368,35 @@ namespace MWRender void Camera::processViewChange() { - mAnimation->detachObjectFromBone(mCamera); - mCamera->detachFromParent(); - if(isFirstPerson()) { mAnimation->setViewMode(NpcAnimation::VM_FirstPerson); - Ogre::TagPoint *tag = mAnimation->attachObjectToBone("Head", mCamera); - tag->setInheritOrientation(false); + mTrackingNode = mAnimation->getNode("Camera"); + if (!mTrackingNode) + mTrackingNode = mAnimation->getNode("Head"); + mHeightScale = 1.f; } else { mAnimation->setViewMode(NpcAnimation::VM_Normal); - mCameraPosNode->attachObject(mCamera); + SceneUtil::PositionAttitudeTransform* transform = mTrackingPtr.getRefData().getBaseNode(); + mTrackingNode = transform; + if (transform) + mHeightScale = transform->getScale().z(); + else + mHeightScale = 1.f; } - rotateCamera(Ogre::Vector3(getPitch(), 0.f, getYaw()), false); + rotateCamera(getPitch(), getYaw(), false); } - void Camera::getPosition(Ogre::Vector3 &focal, Ogre::Vector3 &camera) + void Camera::getPosition(osg::Vec3f &focal, osg::Vec3f &camera) { - mCamera->getParentSceneNode()->needUpdate(true); + focal = getFocalPoint(); - camera = mCamera->getRealPosition(); - focal = mCameraNode->_getDerivedPosition(); + osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1)); + + osg::Vec3d offset = orient * osg::Vec3d(0, -mCameraDistance, 0); + camera = focal + offset; } void Camera::togglePlayerLooking(bool enable) diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index 691a80862b..fab63cd3f2 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -3,13 +3,17 @@ #include +#include +#include +#include + #include "../mwworld/ptr.hpp" -namespace Ogre +namespace osg { - class Vector3; class Camera; - class SceneNode; + class NodeCallback; + class Node; } namespace MWRender @@ -24,10 +28,10 @@ namespace MWRender }; MWWorld::Ptr mTrackingPtr; + osg::ref_ptr mTrackingNode; + float mHeightScale; - Ogre::Camera *mCamera; - Ogre::SceneNode *mCameraNode; - Ogre::SceneNode *mCameraPosNode; + osg::ref_ptr mCamera; NpcAnimation *mAnimation; @@ -42,7 +46,7 @@ namespace MWRender bool enabled, allowed; } mVanity; - float mHeight, mCameraDistance; + float mHeight, mMaxCameraDistance; CamData mMainCam, mPreviewCam; bool mDistanceAdjusted; @@ -50,19 +54,25 @@ namespace MWRender bool mVanityToggleQueued; bool mViewModeToggleQueued; - void setPosition(const Ogre::Vector3& position); - void setPosition(float x, float y, float z); + float mCameraDistance; + + osg::ref_ptr mUpdateCallback; public: - Camera(Ogre::Camera *camera); + Camera(osg::Camera* camera); ~Camera(); + MWWorld::Ptr getTrackingPtr() const; + + /// Update the view matrix of \a cam + void updateCamera(osg::Camera* cam); + /// Reset to defaults void reset(); /// Set where the camera is looking at. Uses Morrowind (euler) angles /// \param rot Rotation angles in radians - void rotateCamera(const Ogre::Vector3 &rot, bool adjust); + void rotateCamera(float pitch, float yaw, bool adjust); float getYaw(); void setYaw(float angle); @@ -70,10 +80,8 @@ namespace MWRender float getPitch(); void setPitch(float angle); - const std::string &getHandle() const; - /// Attach camera to object - Ogre::SceneNode* attachTo(const MWWorld::Ptr &); + void attachTo(const MWWorld::Ptr &); /// @param Force view mode switch, even if currently not allowed by the animation. void toggleViewMode(bool force=false); @@ -85,9 +93,6 @@ namespace MWRender void togglePreviewMode(bool enable); /// \brief Lowers the camera for sneak. - /// As animation is tied to the camera, this needs - /// to be set each frame after the animation is - /// applied. void setSneakOffset(float offset); bool isFirstPerson() const @@ -110,8 +115,10 @@ namespace MWRender void setAnimation(NpcAnimation *anim); + osg::Vec3d getFocalPoint(); + /// Stores focal and camera world positions in passed arguments - void getPosition(Ogre::Vector3 &focal, Ogre::Vector3 &camera); + void getPosition(osg::Vec3f &focal, osg::Vec3f &camera); void togglePlayerLooking(bool enable); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 7d61e3b6c1..a6f68b5d4c 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -1,196 +1,210 @@ #include "characterpreview.hpp" -#include -#include -#include -#include -#include -#include -#include -#include +#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" -#include "renderconst.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "npcanimation.hpp" +#include "vismask.hpp" namespace MWRender { - CharacterPreview::CharacterPreview(MWWorld::Ptr character, int sizeX, int sizeY, const std::string& name, - Ogre::Vector3 position, Ogre::Vector3 lookAt) - : mRecover(false) - , mRenderTarget(NULL) - , mViewport(NULL) - , mCamera(NULL) - , mSceneMgr (0) - , mNode(NULL) + class DrawOnceCallback : public osg::NodeCallback + { + public: + DrawOnceCallback () + : mRendered(false) + , mLastRenderedFrame(0) + { + } + + virtual void operator () (osg::Node* node, osg::NodeVisitor* nv) + { + if (!mRendered) + { + mRendered = true; + + mLastRenderedFrame = nv->getTraversalNumber(); + traverse(node, nv); + } + else + { + node->setNodeMask(0); + } + } + + void redrawNextFrame() + { + mRendered = false; + } + + unsigned int getLastRenderedFrame() const + { + return mLastRenderedFrame; + } + + private: + bool mRendered; + unsigned int mLastRenderedFrame; + }; + + CharacterPreview::CharacterPreview(osgViewer::Viewer* viewer, Resource::ResourceSystem* resourceSystem, + MWWorld::Ptr character, int sizeX, int sizeY, const osg::Vec3f& position, const osg::Vec3f& lookAt) + : mViewer(viewer) + , mResourceSystem(resourceSystem) , mPosition(position) , mLookAt(lookAt) , mCharacter(character) , mAnimation(NULL) - , mName(name) , mSizeX(sizeX) , mSizeY(sizeY) { + mTexture = new osg::Texture2D; + mTexture->setTextureSize(sizeX, sizeY); + mTexture->setInternalFormat(GL_RGBA); + mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + + mCamera = new osg::Camera; + // hints that the camera is not relative to the master camera + mCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + mCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); + mCamera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); + mCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + const float fovYDegrees = 12.3f; + mCamera->setProjectionMatrixAsPerspective(fovYDegrees, sizeX/static_cast(sizeY), 0.1f, 10000.f); // zNear and zFar are autocomputed + mCamera->setViewport(0, 0, sizeX, sizeY); + mCamera->setRenderOrder(osg::Camera::PRE_RENDER); + mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture); + mCamera->setGraphicsContext(mViewer->getCamera()->getGraphicsContext()); + + mCamera->setNodeMask(Mask_RenderToTexture); + + osg::ref_ptr lightManager = new SceneUtil::LightManager; + lightManager->setStartLight(1); + osg::ref_ptr stateset = new osg::StateSet; + stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); + stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); + + osg::ref_ptr lightmodel = new osg::LightModel; + lightmodel->setAmbientIntensity(osg::Vec4(0.25, 0.25, 0.25, 1.0)); + stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON); + + /// \todo Read the fallback values from INIImporter (Inventory:Directional*) ? + osg::ref_ptr light = new osg::Light; + light->setPosition(osg::Vec4(-0.3,0.3,0.7, 0.0)); + light->setDiffuse(osg::Vec4(1,1,1,1)); + light->setAmbient(osg::Vec4(0,0,0,1)); + light->setSpecular(osg::Vec4(0,0,0,0)); + light->setLightNum(0); + light->setConstantAttenuation(1.f); + light->setLinearAttenuation(0.f); + light->setQuadraticAttenuation(0.f); + + osg::ref_ptr lightSource = new osg::LightSource; + lightSource->setLight(light); + + lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON); + + lightManager->setStateSet(stateset); + lightManager->addChild(lightSource); + + mCamera->addChild(lightManager); + + mNode = new osg::PositionAttitudeTransform; + lightManager->addChild(mNode); + + mDrawOnceCallback = new DrawOnceCallback; + mCamera->addUpdateCallback(mDrawOnceCallback); + + mViewer->getSceneData()->asGroup()->addChild(mCamera); + mCharacter.mCell = NULL; } - void CharacterPreview::onSetup() - { - - } - - void CharacterPreview::onFrame() - { - if (mRecover) - { - setupRenderTarget(); - mRenderTarget->update(); - mRecover = false; - } - } - - void CharacterPreview::setup () - { - mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); - - // This is a dummy light to turn off shadows without having to use a separate set of shaders - Ogre::Light* l = mSceneMgr->createLight(); - l->setType (Ogre::Light::LT_DIRECTIONAL); - l->setDiffuseColour (Ogre::ColourValue(0,0,0)); - - /// \todo Read the fallback values from INIImporter (Inventory:Directional*) - l = mSceneMgr->createLight(); - l->setType (Ogre::Light::LT_DIRECTIONAL); - l->setDirection (Ogre::Vector3(0.3f, -0.7f, 0.3f)); - l->setDiffuseColour (Ogre::ColourValue(1,1,1)); - - mSceneMgr->setAmbientLight (Ogre::ColourValue(0.25, 0.25, 0.25)); - - mCamera = mSceneMgr->createCamera (mName); - mCamera->setFOVy(Ogre::Degree(12.3f)); - mCamera->setAspectRatio (float(mSizeX) / float(mSizeY)); - - Ogre::SceneNode* renderRoot = mSceneMgr->getRootSceneNode()->createChildSceneNode("renderRoot"); - - // leftover of old coordinate system. TODO: remove this and adjust positions/orientations to match - renderRoot->pitch(Ogre::Degree(-90)); - - mNode = renderRoot->createChildSceneNode(); - - mAnimation = new NpcAnimation(mCharacter, mNode, - 0, true, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); - - Ogre::Vector3 scale = mNode->getScale(); - mCamera->setPosition(mPosition * scale); - mCamera->lookAt(mLookAt * scale); - - mCamera->setNearClipDistance (1); - mCamera->setFarClipDistance (1000); - - mTexture = Ogre::TextureManager::getSingleton().createManual(mName, - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mSizeX, mSizeY, 0, Ogre::PF_A8R8G8B8, Ogre::TU_RENDERTARGET, this); - - setupRenderTarget(); - - onSetup (); - } - CharacterPreview::~CharacterPreview () { - if (mSceneMgr) - { - mSceneMgr->destroyAllCameras(); - delete mAnimation; - Ogre::Root::getSingleton().destroySceneManager(mSceneMgr); - Ogre::TextureManager::getSingleton().remove(mName); - } + mViewer->getSceneData()->asGroup()->removeChild(mCamera); + } + + int CharacterPreview::getTextureWidth() const + { + return mSizeX; + } + + int CharacterPreview::getTextureHeight() const + { + return mSizeY; + } + + void CharacterPreview::onSetup() + { + } + + osg::ref_ptr CharacterPreview::getTexture() + { + return mTexture; } void CharacterPreview::rebuild() { - delete mAnimation; - mAnimation = NULL; - mAnimation = new NpcAnimation(mCharacter, mNode, - 0, true, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); + mAnimation.reset(NULL); - float scale=1.f; - mCharacter.getClass().adjustScale(mCharacter, scale); - mNode->setScale(Ogre::Vector3(scale)); - - mCamera->setPosition(mPosition * mNode->getScale()); - mCamera->lookAt(mLookAt * mNode->getScale()); + mAnimation.reset(new NpcAnimation(mCharacter, mNode, mResourceSystem, true, true, + (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal))); onSetup(); + + redraw(); } - void CharacterPreview::loadResource(Ogre::Resource *resource) + void CharacterPreview::redraw() { - Ogre::Texture* tex = dynamic_cast(resource); - if (!tex) - return; - - tex->createInternalResources(); - - mRenderTarget = NULL; - mViewport = NULL; - mRecover = true; - } - - void CharacterPreview::setupRenderTarget() - { - mRenderTarget = mTexture->getBuffer()->getRenderTarget(); - mRenderTarget->removeAllViewports (); - mViewport = mRenderTarget->addViewport(mCamera); - mViewport->setOverlaysEnabled(false); - mViewport->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0)); - mViewport->setShadowsEnabled(false); - mRenderTarget->setActive(true); - mRenderTarget->setAutoUpdated (false); + mCamera->setNodeMask(~0); + mDrawOnceCallback->redrawNextFrame(); } // -------------------------------------------------------------------------------------------------- - InventoryPreview::InventoryPreview(MWWorld::Ptr character) - : CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 71, -700), Ogre::Vector3(0,71,0)) - , mSizeX(0) - , mSizeY(0) - , mSelectionBuffer(NULL) + InventoryPreview::InventoryPreview(osgViewer::Viewer* viewer, Resource::ResourceSystem* resourceSystem, MWWorld::Ptr character) + : CharacterPreview(viewer, resourceSystem, character, 512, 1024, osg::Vec3f(0, 700, 71), osg::Vec3f(0,0,71)) { } - InventoryPreview::~InventoryPreview() + void InventoryPreview::setViewport(int sizeX, int sizeY) { - delete mSelectionBuffer; - } + sizeX = std::max(sizeX, 0); + sizeY = std::max(sizeY, 0); - void InventoryPreview::resize(int sizeX, int sizeY) - { - mSizeX = sizeX; - mSizeY = sizeY; + mCamera->setViewport(0, 0, std::min(mSizeX, sizeX), std::min(mSizeY, sizeY)); - mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024))); - mTexture->load(); - - if (!mRenderTarget) - setupRenderTarget(); - - mRenderTarget->update(); + redraw(); } void InventoryPreview::update() { - if (!mAnimation) + if (!mAnimation.get()) return; + mAnimation->showWeapons(true); mAnimation->updateParts(); MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter); @@ -236,13 +250,13 @@ namespace MWRender mAnimation->showCarriedLeft(showCarriedLeft); mCurrentAnimGroup = groupname; - mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); + mAnimation->play(mCurrentAnimGroup, 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0); MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() && showCarriedLeft) { if(!mAnimation->getInfo("torch")) - mAnimation->play("torch", 2, MWRender::Animation::Group_LeftArm, false, + mAnimation->play("torch", 2, Animation::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, ~0ul, true); } else if(mAnimation->getInfo("torch")) @@ -250,70 +264,74 @@ namespace MWRender mAnimation->runAnimation(0.0f); - mNode->setOrientation (Ogre::Quaternion::IDENTITY); - - mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024))); - mTexture->load(); - - if (!mRenderTarget) - setupRenderTarget(); - - mRenderTarget->update(); - - mSelectionBuffer->update(); - } - - void InventoryPreview::setupRenderTarget() - { - CharacterPreview::setupRenderTarget(); - mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024))); + redraw(); } int InventoryPreview::getSlotSelected (int posX, int posY) { - return mSelectionBuffer->getSelected (posX, posY); + float projX = (posX / mCamera->getViewport()->width()) * 2 - 1.f; + float projY = (posY / mCamera->getViewport()->height()) * 2 - 1.f; + // With Intersector::WINDOW, the intersection ratios are slightly inaccurate. Seems to be a + // precision issue - compiling with OSG_USE_FLOAT_MATRIX=0, Intersector::WINDOW works ok. + // Using Intersector::PROJECTION results in better precision because the start/end points and the model matrices + // don't go through as many transformations. + osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::Intersector::PROJECTION, projX, projY)); + + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); + osgUtil::IntersectionVisitor visitor(intersector); + visitor.setTraversalMode(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN); + // Set the traversal number from the last draw, so that the frame switch used for RigGeometry double buffering works correctly + visitor.setTraversalNumber(mDrawOnceCallback->getLastRenderedFrame()); + + osg::Node::NodeMask nodeMask = mCamera->getNodeMask(); + mCamera->setNodeMask(~0); + mCamera->accept(visitor); + mCamera->setNodeMask(nodeMask); + + if (intersector->containsIntersections()) + { + osgUtil::LineSegmentIntersector::Intersection intersection = intersector->getFirstIntersection(); + return mAnimation->getSlot(intersection.nodePath); + } + return -1; } - void InventoryPreview::onSetup () + void InventoryPreview::updatePtr(const MWWorld::Ptr &ptr) { - delete mSelectionBuffer; - mSelectionBuffer = new OEngine::Render::SelectionBuffer(mCamera, 512, 1024, 0); + mCharacter = MWWorld::Ptr(ptr.getBase(), NULL); + } - mAnimation->showWeapons(true); + void InventoryPreview::onSetup() + { + osg::Vec3f scale (1.f, 1.f, 1.f); + mCharacter.getClass().adjustScale(mCharacter, scale, true); - mCurrentAnimGroup = "inventoryhandtohand"; - mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); + mNode->setScale(scale); + + mCamera->setViewMatrixAsLookAt(mPosition * scale.z(), mLookAt * scale.z(), osg::Vec3f(0,0,1)); } // -------------------------------------------------------------------------------------------------- - RaceSelectionPreview::RaceSelectionPreview() - : CharacterPreview(MWBase::Environment::get().getWorld()->getPlayerPtr(), - 512, 512, "CharacterHeadPreview", Ogre::Vector3(0, 8, -125), Ogre::Vector3(0,127,0)) + RaceSelectionPreview::RaceSelectionPreview(osgViewer::Viewer* viewer, Resource::ResourceSystem* resourceSystem) + : CharacterPreview(viewer, resourceSystem, MWMechanics::getPlayer(), + 512, 512, osg::Vec3f(0, 125, 8), osg::Vec3f(0,0,8)) , mBase (*mCharacter.get()->mBase) , mRef(&mBase) - , mPitch(Ogre::Degree(6)) + , mPitchRadians(osg::DegreesToRadians(6.f)) { mCharacter = MWWorld::Ptr(&mRef, NULL); } - void RaceSelectionPreview::update(float angle) + RaceSelectionPreview::~RaceSelectionPreview() { - mAnimation->runAnimation(0.0f); - - mNode->setOrientation(Ogre::Quaternion(Ogre::Radian(angle), Ogre::Vector3::UNIT_Z) - * Ogre::Quaternion(mPitch, Ogre::Vector3::UNIT_X)); - - updateCamera(); } - void RaceSelectionPreview::render() + void RaceSelectionPreview::setAngle(float angleRadians) { - mTexture->load(); - - if (!mRenderTarget) - setupRenderTarget(); - mRenderTarget->update(); + mNode->setAttitude(osg::Quat(mPitchRadians, osg::Vec3(1,0,0)) + * osg::Quat(angleRadians, osg::Vec3(0,0,1))); + redraw(); } void RaceSelectionPreview::setPrototype(const ESM::NPC &proto) @@ -321,27 +339,58 @@ namespace MWRender mBase = proto; mBase.mId = "player"; rebuild(); - mAnimation->runAnimation(0.0f); - updateCamera(); } + class UpdateCameraCallback : public osg::NodeCallback + { + public: + UpdateCameraCallback(osg::ref_ptr nodeToFollow, const osg::Vec3& posOffset, const osg::Vec3& lookAtOffset) + : mNodeToFollow(nodeToFollow) + , mPosOffset(posOffset) + , mLookAtOffset(lookAtOffset) + { + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osg::Camera* cam = static_cast(node); + + // Update keyframe controllers in the scene graph first... + traverse(node, nv); + + // Now update camera utilizing the updated head position + osg::MatrixList mats = mNodeToFollow->getWorldMatrices(); + if (!mats.size()) + return; + osg::Matrix worldMat = mats[0]; + osg::Vec3 headOffset = worldMat.getTrans(); + + cam->setViewMatrixAsLookAt(headOffset + mPosOffset, headOffset + mLookAtOffset, osg::Vec3(0,0,1)); + } + + private: + osg::ref_ptr mNodeToFollow; + osg::Vec3 mPosOffset; + osg::Vec3 mLookAtOffset; + }; + void RaceSelectionPreview::onSetup () { - mAnimation->play("idle", 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); + mAnimation->play("idle", 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0); + mAnimation->runAnimation(0.f); - updateCamera(); + // attach camera to follow the head node + if (mUpdateCameraCallback) + mCamera->removeUpdateCallback(mUpdateCameraCallback); + + const osg::Node* head = mAnimation->getNode("Bip01 Head"); + if (head) + { + mUpdateCameraCallback = new UpdateCameraCallback(head, mPosition, mLookAt); + mCamera->addUpdateCallback(mUpdateCameraCallback); + } + else + std::cerr << "Error: Bip01 Head node not found" << std::endl; } - void RaceSelectionPreview::updateCamera() - { - Ogre::Vector3 scale = mNode->getScale(); - Ogre::Node* headNode = mAnimation->getNode("Bip01 Head"); - if (!headNode) - return; - Ogre::Vector3 headOffset = headNode->_getDerivedPosition(); - headOffset = mNode->convertLocalToWorldPosition(headOffset); - - mCamera->setPosition(headOffset + mPosition * scale); - mCamera->lookAt(headOffset + mPosition*Ogre::Vector3(0,1,0) * scale); - } } diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index 80dbe18b4a..2b7984b009 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -1,45 +1,49 @@ #ifndef MWRENDER_CHARACTERPREVIEW_H #define MWRENDER_CHARACTERPREVIEW_H -#include -#include -#include +#include +#include + +#include #include +#include + #include "../mwworld/ptr.hpp" -namespace OEngine +namespace osg { -namespace Render -{ -class SelectionBuffer; + class Texture2D; + class Camera; } + +namespace osgViewer +{ + class Viewer; } namespace MWRender { class NpcAnimation; + class DrawOnceCallback; - class CharacterPreview : public Ogre::ManualResourceLoader + class CharacterPreview { public: - CharacterPreview(MWWorld::Ptr character, int sizeX, int sizeY, const std::string& name, - Ogre::Vector3 position, Ogre::Vector3 lookAt); + CharacterPreview(osgViewer::Viewer* viewer, Resource::ResourceSystem* resourceSystem, MWWorld::Ptr character, int sizeX, int sizeY, + const osg::Vec3f& position, const osg::Vec3f& lookAt); virtual ~CharacterPreview(); - virtual void setup (); - virtual void onSetup(); + int getTextureWidth() const; + int getTextureHeight() const; - virtual void rebuild(); + void redraw(); - void onFrame(); + void rebuild(); - void loadResource(Ogre::Resource *resource); - - private: - bool mRecover; // Texture content was lost and needs to be re-rendered + osg::ref_ptr getTexture(); private: CharacterPreview(const CharacterPreview&); @@ -47,28 +51,23 @@ namespace MWRender protected: virtual bool renderHeadOnly() { return false; } + virtual void onSetup(); - virtual void setupRenderTarget(); + osg::ref_ptr mViewer; + Resource::ResourceSystem* mResourceSystem; + osg::ref_ptr mTexture; + osg::ref_ptr mCamera; + osg::ref_ptr mDrawOnceCallback; - Ogre::TexturePtr mTexture; - Ogre::RenderTarget* mRenderTarget; - Ogre::Viewport* mViewport; - - Ogre::Camera* mCamera; - - Ogre::SceneManager* mSceneMgr; - Ogre::SceneNode* mNode; - - Ogre::Vector3 mPosition; - Ogre::Vector3 mLookAt; + osg::Vec3f mPosition; + osg::Vec3f mLookAt; MWWorld::Ptr mCharacter; - MWRender::NpcAnimation* mAnimation; + std::auto_ptr mAnimation; + osg::ref_ptr mNode; std::string mCurrentAnimGroup; - std::string mName; - int mSizeX; int mSizeY; }; @@ -77,25 +76,21 @@ namespace MWRender { public: - InventoryPreview(MWWorld::Ptr character); - virtual ~InventoryPreview(); - virtual void onSetup(); + InventoryPreview(osgViewer::Viewer* viewer, Resource::ResourceSystem* resourceSystem, MWWorld::Ptr character); + + void updatePtr(const MWWorld::Ptr& ptr); void update(); // Render preview again, e.g. after changed equipment - void resize(int sizeX, int sizeY); + void setViewport(int sizeX, int sizeY); int getSlotSelected(int posX, int posY); protected: - virtual void setupRenderTarget(); - - private: - int mSizeX; - int mSizeY; - - OEngine::Render::SelectionBuffer* mSelectionBuffer; + virtual void onSetup(); }; + class UpdateCameraCallback; + class RaceSelectionPreview : public CharacterPreview { ESM::NPC mBase; @@ -104,16 +99,13 @@ namespace MWRender protected: virtual bool renderHeadOnly() { return true; } - - void updateCamera(); + virtual void onSetup(); public: - RaceSelectionPreview(); + RaceSelectionPreview(osgViewer::Viewer* viewer, Resource::ResourceSystem* resourceSystem); + virtual ~RaceSelectionPreview(); - virtual void onSetup(); - void render(); - - void update(float angle); + void setAngle(float angleRadians); const ESM::NPC &getPrototype() const { return mBase; @@ -123,7 +115,9 @@ namespace MWRender private: - Ogre::Radian mPitch; + osg::ref_ptr mUpdateCameraCallback; + + float mPitchRadians; }; } diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 7260fc6d15..7c447182f2 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -1,30 +1,29 @@ #include "creatureanimation.hpp" -#include -#include -#include - #include +#include +#include +#include +#include +#include + #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" -#include "renderconst.hpp" - namespace MWRender { - -CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr, const std::string& model) - : Animation(ptr, ptr.getRefData().getBaseNode()) +CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr, + const std::string& model, Resource::ResourceSystem* resourceSystem) + : Animation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), resourceSystem) { MWWorld::LiveCellRef *ref = mPtr.get(); if(!model.empty()) { - setObjectRoot(model, false); - setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha); + setObjectRoot(model, false, false, true); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) addAnimSource("meshes\\xbase_anim.nif"); @@ -33,8 +32,8 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr, const std::string& } -CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const std::string& model) - : Animation(ptr, ptr.getRefData().getBaseNode()) +CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const std::string& model, Resource::ResourceSystem* resourceSystem) + : Animation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), resourceSystem) , mShowWeapons(false) , mShowCarriedLeft(false) { @@ -42,8 +41,7 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const if(!model.empty()) { - setObjectRoot(model, false); - setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha); + setObjectRoot(model, true, false, true); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) addAnimSource("meshes\\xbase_anim.nif"); @@ -54,7 +52,7 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const updateParts(); } - mWeaponAnimationTime = Ogre::SharedPtr(new WeaponAnimationTime(this)); + mWeaponAnimationTime = boost::shared_ptr(new WeaponAnimationTime(this)); } void CreatureWeaponAnimation::showWeapons(bool showWeapon) @@ -77,8 +75,8 @@ void CreatureWeaponAnimation::showCarriedLeft(bool show) void CreatureWeaponAnimation::updateParts() { - mWeapon.setNull(); - mShield.setNull(); + mWeapon.reset(); + mShield.reset(); if (mShowWeapons) updatePart(mWeapon, MWWorld::InventoryStore::Slot_CarriedRight); @@ -86,9 +84,9 @@ void CreatureWeaponAnimation::updateParts() updatePart(mShield, MWWorld::InventoryStore::Slot_CarriedLeft); } -void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slot) +void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) { - if (!mSkelBase) + if (!mObjectRoot) return; MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); @@ -96,7 +94,7 @@ void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slo if (it == inv.end()) { - scene.setNull(); + scene.reset(); return; } MWWorld::Ptr item = *it; @@ -107,13 +105,17 @@ void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slo else bonename = "Shield Bone"; - scene = NifOgre::Loader::createObjects(mSkelBase, bonename, bonename, mInsert, item.getClass().getModel(item)); - Ogre::Vector3 glowColor = getEnchantmentColor(item); + osg::ref_ptr node = mResourceSystem->getSceneManager()->createInstance(item.getClass().getModel(item)); + osg::ref_ptr attached = SceneUtil::attach(node, mObjectRoot, bonename, bonename); + mResourceSystem->getSceneManager()->notifyAttached(attached); - setRenderProperties(scene, RV_Actors, RQG_Main, RQG_Alpha, 0, - !item.getClass().getEnchantment(item).empty(), &glowColor); + scene.reset(new PartHolder(attached)); + + if (!item.getClass().getEnchantment(item).empty()) + addGlow(attached, getEnchantmentColor(item)); // Crossbows start out with a bolt attached + // FIXME: code duplicated from NpcAnimation if (slot == MWWorld::InventoryStore::Slot_CarriedRight && item.getTypeName() == typeid(ESM::Weapon).name() && item.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) @@ -122,53 +124,20 @@ void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slo if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt) attachArrow(); else - mAmmunition.setNull(); + mAmmunition.reset(); } else - mAmmunition.setNull(); + mAmmunition.reset(); - if(scene->mSkelBase) - { - Ogre::SkeletonInstance *skel = scene->mSkelBase->getSkeleton(); - if(scene->mSkelBase->isParentTagPoint()) - { - Ogre::Node *root = scene->mSkelBase->getParentNode(); - if(skel->hasBone("BoneOffset")) - { - Ogre::Bone *offset = skel->getBone("BoneOffset"); + boost::shared_ptr source; - root->translate(offset->getPosition()); + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + source = mWeaponAnimationTime; + else + source.reset(new NullAnimationTime); - // It appears that the BoneOffset rotation is completely bogus, at least for light models. - //root->rotate(offset->getOrientation()); - root->pitch(Ogre::Degree(-90.0f)); - - root->scale(offset->getScale()); - root->setInitialState(); - } - } - updateSkeletonInstance(mSkelBase->getSkeleton(), skel); - } - - std::vector >::iterator ctrl(scene->mControllers.begin()); - for(;ctrl != scene->mControllers.end();++ctrl) - { - if(ctrl->getSource().isNull()) - { - if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - ctrl->setSource(mWeaponAnimationTime); - else - ctrl->setSource(Ogre::SharedPtr(new NullAnimationTime())); - } - } -} - -void CreatureWeaponAnimation::configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) -{ - Ogre::Vector3 glowColor = getEnchantmentColor(ptr); - - setRenderProperties(object, RV_Actors, RQG_Main, RQG_Alpha, 0, - !ptr.getClass().getEnchantment(ptr).empty(), &glowColor); + SceneUtil::AssignControllerSourcesVisitor assignVisitor(source); + attached->accept(assignVisitor); } void CreatureWeaponAnimation::attachArrow() @@ -176,28 +145,43 @@ void CreatureWeaponAnimation::attachArrow() WeaponAnimation::attachArrow(mPtr); } -void CreatureWeaponAnimation::releaseArrow() +void CreatureWeaponAnimation::releaseArrow(float attackStrength) { - WeaponAnimation::releaseArrow(mPtr); + WeaponAnimation::releaseArrow(mPtr, attackStrength); } -Ogre::Vector3 CreatureWeaponAnimation::runAnimation(float duration) +osg::Group *CreatureWeaponAnimation::getArrowBone() { - Ogre::Vector3 ret = Animation::runAnimation(duration); + if (!mWeapon) + return NULL; - if (mSkelBase) - pitchSkeleton(mPtr.getRefData().getPosition().rot[0], mSkelBase->getSkeleton()); + SceneUtil::FindByNameVisitor findVisitor ("ArrowBone"); + mWeapon->getNode()->accept(findVisitor); - if (!mWeapon.isNull()) - { - for (unsigned int i=0; imControllers.size(); ++i) - mWeapon->mControllers[i].update(); - } - if (!mShield.isNull()) - { - for (unsigned int i=0; imControllers.size(); ++i) - mShield->mControllers[i].update(); - } + return findVisitor.mFoundNode; +} + +osg::Node *CreatureWeaponAnimation::getWeaponNode() +{ + return mWeapon ? mWeapon->getNode().get() : NULL; +} + +Resource::ResourceSystem *CreatureWeaponAnimation::getResourceSystem() +{ + return mResourceSystem; +} + +void CreatureWeaponAnimation::addControllers() +{ + Animation::addControllers(); + WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); +} + +osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration) +{ + osg::Vec3f ret = Animation::runAnimation(duration); + + WeaponAnimation::configureControllers(mPtr.getRefData().getPosition().rot[0]); return ret; } diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index 6201c7af4a..a62160253a 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -15,7 +15,7 @@ namespace MWRender class CreatureAnimation : public Animation { public: - CreatureAnimation(const MWWorld::Ptr& ptr, const std::string &model); + CreatureAnimation(const MWWorld::Ptr &ptr, const std::string& model, Resource::ResourceSystem* resourceSystem); virtual ~CreatureAnimation() {} }; @@ -25,7 +25,7 @@ namespace MWRender class CreatureWeaponAnimation : public Animation, public WeaponAnimation, public MWWorld::InventoryStoreListener { public: - CreatureWeaponAnimation(const MWWorld::Ptr& ptr, const std::string &model); + CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const std::string& model, Resource::ResourceSystem* resourceSystem); virtual ~CreatureWeaponAnimation() {} virtual void equipmentChanged() { updateParts(); } @@ -35,31 +35,33 @@ namespace MWRender void updateParts(); - void updatePart(NifOgre::ObjectScenePtr& scene, int slot); + void updatePart(PartHolderPtr& scene, int slot); virtual void attachArrow(); - virtual void releaseArrow(); + virtual void releaseArrow(float attackStrength); + // WeaponAnimation + virtual osg::Group* getArrowBone(); + virtual osg::Node* getWeaponNode(); + virtual Resource::ResourceSystem* getResourceSystem(); + virtual void showWeapon(bool show) { showWeapons(show); } + virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); } - virtual Ogre::Vector3 runAnimation(float duration); + virtual void addControllers(); + + virtual osg::Vec3f runAnimation(float duration); /// A relative factor (0-1) that decides if and how much the skeleton should be pitched /// to indicate the facing orientation of the character. virtual void setPitchFactor(float factor) { mPitchFactor = factor; } - virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); } - - // WeaponAnimation - virtual NifOgre::ObjectScenePtr getWeapon() { return mWeapon; } - virtual void showWeapon(bool show) { showWeapons(show); } - virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot); private: - NifOgre::ObjectScenePtr mWeapon; - NifOgre::ObjectScenePtr mShield; + PartHolderPtr mWeapon; + PartHolderPtr mShield; bool mShowWeapons; bool mShowCarriedLeft; - Ogre::SharedPtr mWeaponAnimationTime; + boost::shared_ptr mWeaponAnimationTime; }; } diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp deleted file mode 100644 index 49e65490d5..0000000000 --- a/apps/openmw/mwrender/debugging.cpp +++ /dev/null @@ -1,302 +0,0 @@ -#include "debugging.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include "../mwbase/world.hpp" // these includes can be removed once the static-hack is gone -#include "../mwbase/environment.hpp" - -#include "../mwworld/ptr.hpp" -#include "../mwworld/cellstore.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwmechanics/pathfinding.hpp" - -#include "renderconst.hpp" - -using namespace Ogre; - -namespace MWRender -{ - -static const std::string PATHGRID_POINT_MATERIAL = "pathgridPointMaterial"; -static const std::string PATHGRID_LINE_MATERIAL = "pathgridLineMaterial"; -static const std::string DEBUGGING_GROUP = "debugging"; -static const int POINT_MESH_BASE = 35; - -void Debugging::createGridMaterials() -{ - if (mGridMatsCreated) return; - - if (MaterialManager::getSingleton().getByName(PATHGRID_LINE_MATERIAL, DEBUGGING_GROUP).isNull()) - { - MaterialPtr lineMatPtr = MaterialManager::getSingleton().create(PATHGRID_LINE_MATERIAL, DEBUGGING_GROUP); - lineMatPtr->setReceiveShadows(false); - lineMatPtr->getTechnique(0)->setLightingEnabled(true); - lineMatPtr->getTechnique(0)->getPass(0)->setDiffuse(1,1,0,0); - lineMatPtr->getTechnique(0)->getPass(0)->setAmbient(1,1,0); - lineMatPtr->getTechnique(0)->getPass(0)->setSelfIllumination(1,1,0); - } - - if (MaterialManager::getSingleton().getByName(PATHGRID_POINT_MATERIAL, DEBUGGING_GROUP).isNull()) - { - MaterialPtr pointMatPtr = MaterialManager::getSingleton().create(PATHGRID_POINT_MATERIAL, DEBUGGING_GROUP); - pointMatPtr->setReceiveShadows(false); - pointMatPtr->getTechnique(0)->setLightingEnabled(true); - pointMatPtr->getTechnique(0)->getPass(0)->setDiffuse(1,0,0,0); - pointMatPtr->getTechnique(0)->getPass(0)->setAmbient(1,0,0); - pointMatPtr->getTechnique(0)->getPass(0)->setSelfIllumination(1,0,0); - } - mGridMatsCreated = true; -} - -void Debugging::destroyGridMaterials() -{ - if (mGridMatsCreated) - { - MaterialManager::getSingleton().remove(PATHGRID_POINT_MATERIAL); - MaterialManager::getSingleton().remove(PATHGRID_LINE_MATERIAL); - mGridMatsCreated = false; - } -} - -ManualObject *Debugging::createPathgridLines(const ESM::Pathgrid *pathgrid) -{ - ManualObject *result = mSceneMgr->createManualObject(); - - result->begin(PATHGRID_LINE_MATERIAL, RenderOperation::OT_LINE_LIST); - for(ESM::Pathgrid::EdgeList::const_iterator it = pathgrid->mEdges.begin(); - it != pathgrid->mEdges.end(); - ++it) - { - const ESM::Pathgrid::Edge &edge = *it; - const ESM::Pathgrid::Point &p1 = pathgrid->mPoints[edge.mV0], &p2 = pathgrid->mPoints[edge.mV1]; - Vector3 direction = (MWMechanics::PathFinder::MakeOgreVector3(p2) - MWMechanics::PathFinder::MakeOgreVector3(p1)); - Vector3 lineDisplacement = direction.crossProduct(Vector3::UNIT_Z).normalisedCopy(); - lineDisplacement = lineDisplacement * POINT_MESH_BASE + - Vector3(0, 0, 10); // move lines up a little, so they will be less covered by meshes/landscape - result->position(MWMechanics::PathFinder::MakeOgreVector3(p1) + lineDisplacement); - result->position(MWMechanics::PathFinder::MakeOgreVector3(p2) + lineDisplacement); - } - result->end(); - - result->setVisibilityFlags (RV_Debug); - - return result; -} - -ManualObject *Debugging::createPathgridPoints(const ESM::Pathgrid *pathgrid) -{ - ManualObject *result = mSceneMgr->createManualObject(); - const float height = POINT_MESH_BASE * sqrtf(2); - - result->begin(PATHGRID_POINT_MATERIAL, RenderOperation::OT_TRIANGLE_STRIP); - - bool first = true; - uint32 startIndex = 0; - for(ESM::Pathgrid::PointList::const_iterator it = pathgrid->mPoints.begin(); - it != pathgrid->mPoints.end(); - ++it, startIndex += 6) - { - Vector3 pointPos(MWMechanics::PathFinder::MakeOgreVector3(*it)); - - if (!first) - { - // degenerate triangle from previous octahedron - result->index(startIndex - 4); // 2nd point of previous octahedron - result->index(startIndex); // start point of current octahedron - } - - Ogre::Real pointMeshBase = static_cast(POINT_MESH_BASE); - - result->position(pointPos + Vector3(0, 0, height)); // 0 - result->position(pointPos + Vector3(-pointMeshBase, -pointMeshBase, 0)); // 1 - result->position(pointPos + Vector3(pointMeshBase, -pointMeshBase, 0)); // 2 - result->position(pointPos + Vector3(pointMeshBase, pointMeshBase, 0)); // 3 - result->position(pointPos + Vector3(-pointMeshBase, pointMeshBase, 0)); // 4 - result->position(pointPos + Vector3(0, 0, -height)); // 5 - - result->index(startIndex + 0); - result->index(startIndex + 1); - result->index(startIndex + 2); - result->index(startIndex + 5); - result->index(startIndex + 3); - result->index(startIndex + 4); - // degenerates - result->index(startIndex + 4); - result->index(startIndex + 5); - result->index(startIndex + 5); - // end degenerates - result->index(startIndex + 1); - result->index(startIndex + 4); - result->index(startIndex + 0); - result->index(startIndex + 3); - result->index(startIndex + 2); - - first = false; - } - - result->end(); - - result->setVisibilityFlags (RV_Debug); - - return result; -} - -Debugging::Debugging(SceneNode *root, OEngine::Physic::PhysicEngine *engine) : - mEngine(engine), mSceneMgr(root->getCreator()), - mPathgridEnabled(false), - mRootNode(root), - mPathGridRoot(NULL), mInteriorPathgridNode(NULL), - mGridMatsCreated(false) -{ - ResourceGroupManager::getSingleton().createResourceGroup(DEBUGGING_GROUP); -} - -Debugging::~Debugging() -{ - if (mPathgridEnabled) - { - togglePathgrid(); - } - - ResourceGroupManager::getSingleton().destroyResourceGroup(DEBUGGING_GROUP); -} - - -bool Debugging::toggleRenderMode (int mode){ - switch (mode) - { - case MWBase::World::Render_CollisionDebug: - - return mEngine->toggleDebugRendering(); - - case MWBase::World::Render_Pathgrid: - togglePathgrid(); - return mPathgridEnabled; - } - - return false; -} - -void Debugging::cellAdded(MWWorld::CellStore *store) -{ - mActiveCells.push_back(store); - if (mPathgridEnabled) - enableCellPathgrid(store); -} - -void Debugging::cellRemoved(MWWorld::CellStore *store) -{ - mActiveCells.erase(std::remove(mActiveCells.begin(), mActiveCells.end(), store), mActiveCells.end()); - if (mPathgridEnabled) - disableCellPathgrid(store); -} - -void Debugging::togglePathgrid() -{ - mPathgridEnabled = !mPathgridEnabled; - if (mPathgridEnabled) - { - createGridMaterials(); - - // add path grid meshes to already loaded cells - mPathGridRoot = mRootNode->createChildSceneNode(); - for(CellList::iterator it = mActiveCells.begin(); it != mActiveCells.end(); ++it) - { - enableCellPathgrid(*it); - } - } - else - { - // remove path grid meshes from already loaded cells - for(CellList::iterator it = mActiveCells.begin(); it != mActiveCells.end(); ++it) - { - disableCellPathgrid(*it); - } - mPathGridRoot->removeAndDestroyAllChildren(); - mSceneMgr->destroySceneNode(mPathGridRoot); - mPathGridRoot = NULL; - destroyGridMaterials(); - } -} - -void Debugging::enableCellPathgrid(MWWorld::CellStore *store) -{ - MWBase::World* world = MWBase::Environment::get().getWorld(); - const ESM::Pathgrid *pathgrid = - world->getStore().get().search(*store->getCell()); - if (!pathgrid) return; - - Vector3 cellPathGridPos(0, 0, 0); - if (store->getCell()->isExterior()) - { - cellPathGridPos.x = static_cast(store->getCell()->mData.mX * ESM::Land::REAL_SIZE); - cellPathGridPos.y = static_cast(store->getCell()->mData.mY * ESM::Land::REAL_SIZE); - } - SceneNode *cellPathGrid = mPathGridRoot->createChildSceneNode(cellPathGridPos); - cellPathGrid->attachObject(createPathgridLines(pathgrid)); - cellPathGrid->attachObject(createPathgridPoints(pathgrid)); - - if (store->getCell()->isExterior()) - { - mExteriorPathgridNodes[std::make_pair(store->getCell()->getGridX(), store->getCell()->getGridY())] = cellPathGrid; - } - else - { - assert(mInteriorPathgridNode == NULL); - mInteriorPathgridNode = cellPathGrid; - } -} - -void Debugging::disableCellPathgrid(MWWorld::CellStore *store) -{ - if (store->getCell()->isExterior()) - { - ExteriorPathgridNodes::iterator it = - mExteriorPathgridNodes.find(std::make_pair(store->getCell()->getGridX(), store->getCell()->getGridY())); - if (it != mExteriorPathgridNodes.end()) - { - destroyCellPathgridNode(it->second); - mExteriorPathgridNodes.erase(it); - } - } - else - { - if (mInteriorPathgridNode) - { - destroyCellPathgridNode(mInteriorPathgridNode); - mInteriorPathgridNode = NULL; - } - } -} - -void Debugging::destroyCellPathgridNode(SceneNode *node) -{ - mPathGridRoot->removeChild(node); - destroyAttachedObjects(node); - mSceneMgr->destroySceneNode(node); -} - -void Debugging::destroyAttachedObjects(SceneNode *node) -{ - SceneNode::ObjectIterator objIt = node->getAttachedObjectIterator(); - while (objIt.hasMoreElements()) - { - MovableObject *mesh = static_cast(objIt.getNext()); - mSceneMgr->destroyMovableObject(mesh); - } -} - -} diff --git a/apps/openmw/mwrender/debugging.hpp b/apps/openmw/mwrender/debugging.hpp deleted file mode 100644 index 39be34cb02..0000000000 --- a/apps/openmw/mwrender/debugging.hpp +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef GAME_RENDER_MWSCENE_H -#define GAME_RENDER_MWSCENE_H - -#include -#include - -#include -#include - -namespace ESM -{ - struct Pathgrid; -} - -namespace OEngine -{ - namespace Physic - { - class PhysicEngine; - } -} - -namespace Ogre -{ - class Camera; - class Viewport; - class SceneManager; - class SceneNode; - class RaySceneQuery; - class Quaternion; - class Vector3; -} - -namespace MWWorld -{ - class Ptr; - class CellStore; -} - -namespace MWRender -{ - class Debugging - { - OEngine::Physic::PhysicEngine* mEngine; - Ogre::SceneManager *mSceneMgr; - - // Path grid stuff - bool mPathgridEnabled; - - void togglePathgrid(); - - typedef std::vector CellList; - CellList mActiveCells; - - Ogre::SceneNode *mRootNode; - - Ogre::SceneNode *mPathGridRoot; - - typedef std::map, Ogre::SceneNode *> ExteriorPathgridNodes; - ExteriorPathgridNodes mExteriorPathgridNodes; - Ogre::SceneNode *mInteriorPathgridNode; - - void enableCellPathgrid(MWWorld::CellStore *store); - void disableCellPathgrid(MWWorld::CellStore *store); - - // utility - void destroyCellPathgridNode(Ogre::SceneNode *node); - void destroyAttachedObjects(Ogre::SceneNode *node); - - // materials - bool mGridMatsCreated; - void createGridMaterials(); - void destroyGridMaterials(); - - // path grid meshes - Ogre::ManualObject *createPathgridLines(const ESM::Pathgrid *pathgrid); - Ogre::ManualObject *createPathgridPoints(const ESM::Pathgrid *pathgrid); - public: - Debugging(Ogre::SceneNode* root, OEngine::Physic::PhysicEngine *engine); - ~Debugging(); - bool toggleRenderMode (int mode); - - void cellAdded(MWWorld::CellStore* store); - void cellRemoved(MWWorld::CellStore* store); - }; - - -} - -#endif diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index 503a0223e8..c4e457a1fd 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -1,102 +1,82 @@ #include "effectmanager.hpp" -#include +#include -#include -#include -#include -#include +#include +#include + +#include #include "animation.hpp" -#include "renderconst.hpp" +#include "vismask.hpp" +#include "util.hpp" namespace MWRender { -EffectManager::EffectManager(Ogre::SceneManager *sceneMgr) - : mSceneMgr(sceneMgr) +EffectManager::EffectManager(osg::ref_ptr parent, Resource::ResourceSystem* resourceSystem) + : mParentNode(parent) + , mResourceSystem(resourceSystem) { } -void EffectManager::addEffect(const std::string &model, std::string textureOverride, const Ogre::Vector3 &worldPosition, float scale) +EffectManager::~EffectManager() { - Ogre::SceneNode* sceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(worldPosition); - sceneNode->setScale(scale,scale,scale); - - NifOgre::ObjectScenePtr scene = NifOgre::Loader::createObjects(sceneNode, model); - - MWRender::Animation::setRenderProperties(scene, RV_Effects, - RQG_Main, RQG_Alpha, 0.f, false, NULL); - - for(size_t i = 0;i < scene->mControllers.size();i++) - { - if(scene->mControllers[i].getSource().isNull()) - scene->mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationTime())); - } - - if (!textureOverride.empty()) - { - std::string correctedTexture = Misc::ResourceHelpers::correctTexturePath(textureOverride); - for(size_t i = 0;i < scene->mParticles.size(); ++i) - { - Ogre::ParticleSystem* partSys = scene->mParticles[i]; - - Ogre::MaterialPtr mat = scene->mMaterialControllerMgr.getWritableMaterial(partSys); - - for (int t=0; tgetNumTechniques(); ++t) - { - Ogre::Technique* tech = mat->getTechnique(t); - for (int p=0; pgetNumPasses(); ++p) - { - Ogre::Pass* pass = tech->getPass(p); - for (int tex=0; texgetNumTextureUnitStates(); ++tex) - { - Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); - tus->setTextureName(correctedTexture); - } - } - } - } - } - - mEffects.push_back(std::make_pair(sceneNode, scene)); + clear(); } -void EffectManager::update(float dt, Ogre::Camera* camera) +void EffectManager::addEffect(const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, float scale) { - for (std::vector >::iterator it = mEffects.begin(); it != mEffects.end(); ) + osg::ref_ptr node = mResourceSystem->getSceneManager()->createInstance(model); + + node->setNodeMask(Mask_Effect); + + Effect effect; + effect.mAnimTime.reset(new EffectAnimationTime); + + SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; + node->accept(findMaxLengthVisitor); + effect.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); + + osg::ref_ptr trans = new osg::PositionAttitudeTransform; + trans->setPosition(worldPosition); + trans->setScale(osg::Vec3f(scale, scale, scale)); + trans->addChild(node); + + SceneUtil::AssignControllerSourcesVisitor assignVisitor(effect.mAnimTime); + node->accept(assignVisitor); + + overrideTexture(textureOverride, mResourceSystem, node); + + mParentNode->addChild(trans); + mResourceSystem->getSceneManager()->notifyAttached(node); + + mEffects[trans] = effect; +} + +void EffectManager::update(float dt) +{ + for (EffectMap::iterator it = mEffects.begin(); it != mEffects.end(); ) { - NifOgre::ObjectScenePtr objects = it->second; - for(size_t i = 0; i < objects->mControllers.size() ;i++) - { - EffectAnimationTime* value = dynamic_cast(objects->mControllers[i].getSource().get()); - if (value) - value->addTime(dt); + it->second.mAnimTime->addTime(dt); - objects->mControllers[i].update(); - } - objects->rotateBillboardNodes(camera); - - // Finished playing? - if (objects->mControllers[0].getSource()->getValue() >= objects->mMaxControllerLength) + if (it->second.mAnimTime->getTime() >= it->second.mMaxControllerLength) { - Ogre::SceneNode* node = it->first; - it = mEffects.erase(it); - mSceneMgr->destroySceneNode(node); - continue; + mParentNode->removeChild(it->first); + mEffects.erase(it++); } - ++it; + else + ++it; } } void EffectManager::clear() { - for (std::vector >::iterator it = mEffects.begin(); it != mEffects.end(); ) + for (EffectMap::iterator it = mEffects.begin(); it != mEffects.end(); ++it) { - Ogre::SceneNode* node = it->first; - it = mEffects.erase(it); - mSceneMgr->destroySceneNode(node); + mParentNode->removeChild(it->first); } + mEffects.clear(); } } diff --git a/apps/openmw/mwrender/effectmanager.hpp b/apps/openmw/mwrender/effectmanager.hpp index eb68636555..6d7aaaf4ff 100644 --- a/apps/openmw/mwrender/effectmanager.hpp +++ b/apps/openmw/mwrender/effectmanager.hpp @@ -1,43 +1,60 @@ #ifndef OPENMW_MWRENDER_EFFECTMANAGER_H #define OPENMW_MWRENDER_EFFECTMANAGER_H -#include +#include +#include + +#include + +#include + +namespace osg +{ + class Group; + class Vec3f; + class PositionAttitudeTransform; +} + +namespace Resource +{ + class ResourceSystem; +} namespace MWRender { - - class EffectAnimationTime : public Ogre::ControllerValue - { - private: - float mTime; - public: - EffectAnimationTime() : mTime(0) { } - void addTime(float time) { mTime += time; } - - virtual Ogre::Real getValue() const { return mTime; } - virtual void setValue(Ogre::Real value) {} - }; - + class EffectAnimationTime; // Note: effects attached to another object should be managed by MWRender::Animation::addEffect. // This class manages "free" effects, i.e. attached to a dedicated scene node in the world. class EffectManager { public: - EffectManager(Ogre::SceneManager* sceneMgr); - ~EffectManager() { clear(); } + EffectManager(osg::ref_ptr parent, Resource::ResourceSystem* resourceSystem); + ~EffectManager(); /// Add an effect. When it's finished playing, it will be removed automatically. - void addEffect (const std::string& model, std::string textureOverride, const Ogre::Vector3& worldPosition, float scale); + void addEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPosition, float scale); - void update(float dt, Ogre::Camera* camera); + void update(float dt); /// Remove all effects void clear(); private: - std::vector > mEffects; - Ogre::SceneManager* mSceneMgr; + struct Effect + { + float mMaxControllerLength; + boost::shared_ptr mAnimTime; + }; + + typedef std::map, Effect> EffectMap; + EffectMap mEffects; + + osg::ref_ptr mParentNode; + Resource::ResourceSystem* mResourceSystem; + + EffectManager(const EffectManager&); + void operator=(const EffectManager&); }; } diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 90cf270497..9e8a545f85 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -1,17 +1,19 @@ #include "globalmap.hpp" -#include -#include +#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include + +#include #include #include +#include #include @@ -20,11 +22,81 @@ #include "../mwworld/esmstore.hpp" +#include "vismask.hpp" + +namespace +{ + + // Create a screen-aligned quad with given texture coordinates. + // Assumes a top-left origin of the sampled image. + osg::ref_ptr createTexturedQuad(float leftTexCoord, float topTexCoord, float rightTexCoord, float bottomTexCoord) + { + osg::ref_ptr geom = new osg::Geometry; + + osg::ref_ptr verts = new osg::Vec3Array; + verts->push_back(osg::Vec3f(-1, -1, 0)); + verts->push_back(osg::Vec3f(-1, 1, 0)); + verts->push_back(osg::Vec3f(1, 1, 0)); + verts->push_back(osg::Vec3f(1, -1, 0)); + + geom->setVertexArray(verts); + + osg::ref_ptr texcoords = new osg::Vec2Array; + texcoords->push_back(osg::Vec2f(leftTexCoord, 1.f-bottomTexCoord)); + texcoords->push_back(osg::Vec2f(leftTexCoord, 1.f-topTexCoord)); + texcoords->push_back(osg::Vec2f(rightTexCoord, 1.f-topTexCoord)); + texcoords->push_back(osg::Vec2f(rightTexCoord, 1.f-bottomTexCoord)); + + osg::ref_ptr colors = new osg::Vec4Array; + colors->push_back(osg::Vec4(1.f, 1.f, 1.f, 1.f)); + geom->setColorArray(colors, osg::Array::BIND_OVERALL); + + geom->setTexCoordArray(0, texcoords, osg::Array::BIND_PER_VERTEX); + + geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4)); + + return geom; + } + + + class CameraUpdateGlobalCallback : public osg::NodeCallback + { + public: + CameraUpdateGlobalCallback(MWRender::GlobalMap* parent) + : mRendered(false) + , mParent(parent) + { + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if (mRendered) + { + node->setNodeMask(0); + return; + } + + traverse(node, nv); + + if (!mRendered) + { + mRendered = true; + mParent->markForRemoval(static_cast(node)); + } + } + + private: + bool mRendered; + MWRender::GlobalMap* mParent; + }; + +} + namespace MWRender { - GlobalMap::GlobalMap(const std::string &cacheDir) - : mCacheDir(cacheDir) + GlobalMap::GlobalMap(osg::Group* root) + : mRoot(root) , mWidth(0) , mHeight(0) , mMinX(0), mMaxX(0) @@ -36,13 +108,10 @@ namespace MWRender GlobalMap::~GlobalMap() { - Ogre::TextureManager::getSingleton().remove(mOverlayTexture->getName()); } void GlobalMap::render (Loading::Listener* loadingListener) { - Ogre::TexturePtr tex; - const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -68,7 +137,9 @@ namespace MWRender loadingListener->setProgressRange((mMaxX-mMinX+1) * (mMaxY-mMinY+1)); loadingListener->setProgress(0); - std::vector data (mWidth * mHeight * 3); + osg::ref_ptr image = new osg::Image; + image->allocateImage(mWidth, mHeight, 1, GL_RGB, GL_UNSIGNED_BYTE); + unsigned char* data = image->data(); for (int x = mMinX; x <= mMaxX; ++x) { @@ -83,6 +154,9 @@ namespace MWRender land->loadData(mask); } + const ESM::Land::LandData *landData = + land ? land->getLandData (ESM::Land::DATA_WNAM) : 0; + for (int cellY=0; cellY(float(cellX)/float(mCellSize) * 9); int vertexY = static_cast(float(cellY) / float(mCellSize) * 9); - int texelX = (x-mMinX) * mCellSize + cellX; - int texelY = (mHeight-1) - ((y-mMinY) * mCellSize + cellY); + int texelY = (y-mMinY) * mCellSize + cellY; unsigned char r,g,b; float y = 0; - if (land && land->mDataTypes & ESM::Land::DATA_WNAM) - y = (land->mLandData->mWnam[vertexY * 9 + vertexX] << 4) / 2048.f; + if (landData) + y = (landData->mWnam[vertexY * 9 + vertexX] << 4) / 2048.f; else y = (SCHAR_MIN << 4) / 2048.f; if (y < 0) @@ -140,16 +213,13 @@ namespace MWRender } } - Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size())); - - tex = Ogre::TextureManager::getSingleton ().createManual ("GlobalMap.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_B8G8R8, Ogre::TU_STATIC); - tex->loadRawData(stream, mWidth, mHeight, Ogre::PF_B8G8R8); - - tex->load(); - - mOverlayTexture = Ogre::TextureManager::getSingleton().createManual("GlobalMapOverlay", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_A8B8G8R8, Ogre::TU_DYNAMIC, this); + mBaseTexture = new osg::Texture2D; + mBaseTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mBaseTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mBaseTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mBaseTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mBaseTexture->setImage(image); + mBaseTexture->setResizeNonPowerOfTwoHint(false); clear(); @@ -171,62 +241,112 @@ namespace MWRender imageY = 1.f-float(y - mMinY + 1) / (mMaxY - mMinY + 1); } - void GlobalMap::exploreCell(int cellX, int cellY) + void GlobalMap::requestOverlayTextureUpdate(int x, int y, int width, int height, osg::ref_ptr texture, bool clear, bool cpuCopy, + float srcLeft, float srcTop, float srcRight, float srcBottom) { - float originX = static_cast((cellX - mMinX) * mCellSize); - // NB y + 1, because we want the top left corner, not bottom left where the origin of the cell is - float originY = static_cast(mHeight - (cellY + 1 - mMinY) * mCellSize); + osg::ref_ptr camera (new osg::Camera); + camera->setNodeMask(Mask_RenderToTexture); + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setViewMatrix(osg::Matrix::identity()); + camera->setProjectionMatrix(osg::Matrix::identity()); + camera->setProjectionResizePolicy(osg::Camera::FIXED); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + y = mHeight - y - height; // convert top-left origin to bottom-left + camera->setViewport(x, y, width, height); + + if (clear) + { + camera->setClearMask(GL_COLOR_BUFFER_BIT); + camera->setClearColor(osg::Vec4(0,0,0,0)); + } + else + camera->setClearMask(GL_NONE); + + camera->setUpdateCallback(new CameraUpdateGlobalCallback(this)); + + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); + camera->attach(osg::Camera::COLOR_BUFFER, mOverlayTexture); + + // no need for a depth buffer + camera->setImplicitBufferAttachmentMask(osg::DisplaySettings::IMPLICIT_COLOR_BUFFER_ATTACHMENT); + + if (cpuCopy) + { + // Attach an image to copy the render back to the CPU when finished + osg::ref_ptr image (new osg::Image); + image->setPixelFormat(mOverlayImage->getPixelFormat()); + image->setDataType(mOverlayImage->getDataType()); + camera->attach(osg::Camera::COLOR_BUFFER, image); + + ImageDest imageDest; + imageDest.mImage = image; + imageDest.mX = x; + imageDest.mY = y; + mPendingImageDest.push_back(imageDest); + } + + // Create a quad rendering the updated texture + if (texture) + { + osg::ref_ptr geom = createTexturedQuad(srcLeft, srcTop, srcRight, srcBottom); + osg::ref_ptr depth = new osg::Depth; + depth->setWriteMask(0); + osg::StateSet* stateset = geom->getOrCreateStateSet(); + stateset->setAttribute(depth); + stateset->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON); + stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + osg::ref_ptr geode = new osg::Geode; + geode->addDrawable(geom); + camera->addChild(geode); + } + + mRoot->addChild(camera); + + mActiveCameras.push_back(camera); + } + + void GlobalMap::exploreCell(int cellX, int cellY, osg::ref_ptr localMapTexture) + { + if (!localMapTexture) + return; + + int originX = (cellX - mMinX) * mCellSize; + int originY = (cellY - mMinY + 1) * mCellSize; // +1 because we want the top left corner of the cell, not the bottom left if (cellX > mMaxX || cellX < mMinX || cellY > mMaxY || cellY < mMinY) return; - Ogre::TexturePtr localMapTexture = Ogre::TextureManager::getSingleton().getByName("Cell_" - + boost::lexical_cast(cellX) + "_" + boost::lexical_cast(cellY)); - - if (!localMapTexture.isNull()) - { - int mapWidth = localMapTexture->getWidth(); - int mapHeight = localMapTexture->getHeight(); - mOverlayTexture->load(); - mOverlayTexture->getBuffer()->blit(localMapTexture->getBuffer(), Ogre::Image::Box(0,0,mapWidth,mapHeight), - Ogre::Image::Box(static_cast(originX), static_cast(originY), - static_cast(originX + mCellSize), static_cast(originY + mCellSize))); - - Ogre::Image backup; - std::vector data; - data.resize(mCellSize*mCellSize*4, 0); - backup.loadDynamicImage(&data[0], mCellSize, mCellSize, Ogre::PF_A8B8G8R8); - - localMapTexture->getBuffer()->blitToMemory(Ogre::Image::Box(0,0,mapWidth,mapHeight), backup.getPixelBox()); - - for (int x=0; x(originX + x), static_cast(originY + y), 0); - } - } + requestOverlayTextureUpdate(originX, mHeight - originY, mCellSize, mCellSize, localMapTexture, false, true); } void GlobalMap::clear() { - Ogre::uchar* buffer = OGRE_ALLOC_T(Ogre::uchar, mWidth * mHeight * 4, Ogre::MEMCATEGORY_GENERAL); - memset(buffer, 0, mWidth * mHeight * 4); + if (!mOverlayImage) + { + mOverlayImage = new osg::Image; + mOverlayImage->allocateImage(mWidth, mHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE); + assert(mOverlayImage->isDataContiguous()); + } + memset(mOverlayImage->data(), 0, mOverlayImage->getTotalSizeInBytes()); - mOverlayImage.loadDynamicImage(&buffer[0], mWidth, mHeight, 1, Ogre::PF_A8B8G8R8, true); // pass ownership of buffer to image + if (!mOverlayTexture) + { + mOverlayTexture = new osg::Texture2D; + mOverlayTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mOverlayTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mOverlayTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mOverlayTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mOverlayTexture->setResizeNonPowerOfTwoHint(false); + mOverlayTexture->setInternalFormat(GL_RGBA); + mOverlayTexture->setTextureSize(mWidth, mHeight); + } - mOverlayTexture->load(); - } + mPendingImageDest.clear(); - void GlobalMap::loadResource(Ogre::Resource *resource) - { - Ogre::Texture* tex = static_cast(resource); - Ogre::ConstImagePtrList list; - list.push_back(&mOverlayImage); - tex->_loadImages(list); + // just push a Camera to clear the FBO, instead of setImage()/dirty() + // easier, since we don't need to worry about synchronizing access :) + requestOverlayTextureUpdate(0, 0, mWidth, mHeight, osg::ref_ptr(), true, false); } void GlobalMap::write(ESM::GlobalMap& map) @@ -236,11 +356,39 @@ namespace MWRender map.mBounds.mMinY = mMinY; map.mBounds.mMaxY = mMaxY; - Ogre::DataStreamPtr encoded = mOverlayImage.encode("png"); - map.mImageData.resize(encoded->size()); - encoded->read(&map.mImageData[0], encoded->size()); + std::ostringstream ostream; + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); + if (!readerwriter) + { + std::cerr << "Can't write map overlay: no png readerwriter found" << std::endl; + return; + } + + osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mOverlayImage, ostream); + if (!result.success()) + { + std::cerr << "Can't write map overlay: " << result.message() << " code " << result.status() << std::endl; + return; + } + + std::string data = ostream.str(); + map.mImageData = std::vector(data.begin(), data.end()); } + struct Box + { + int mLeft, mTop, mRight, mBottom; + + Box(int left, int top, int right, int bottom) + : mLeft(left), mTop(top), mRight(right), mBottom(bottom) + { + } + bool operator == (const Box& other) + { + return mLeft == other.mLeft && mTop == other.mTop && mRight == other.mRight && mBottom == other.mBottom; + } + }; + void GlobalMap::read(ESM::GlobalMap& map) { const ESM::GlobalMap::Bounds& bounds = map.mBounds; @@ -254,16 +402,35 @@ namespace MWRender || bounds.mMinY > bounds.mMaxY) throw std::runtime_error("invalid map bounds"); - Ogre::Image image; - Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&map.mImageData[0], map.mImageData.size())); - image.load(stream, "png"); + if (!map.mImageData.size()) + return; + + Files::IMemStream istream(&map.mImageData[0], map.mImageData.size()); + + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); + if (!readerwriter) + { + std::cerr << "Can't read map overlay: no png readerwriter found" << std::endl; + return; + } + + osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(istream); + if (!result.success()) + { + std::cerr << "Can't read map overlay: " << result.message() << " code " << result.status() << std::endl; + return; + } + + osg::ref_ptr image = result.getImage(); + int imageWidth = image->s(); + int imageHeight = image->t(); int xLength = (bounds.mMaxX-bounds.mMinX+1); int yLength = (bounds.mMaxY-bounds.mMinY+1); // Size of one cell in image space - int cellImageSizeSrc = image.getWidth() / xLength; - if (int(image.getHeight() / yLength) != cellImageSizeSrc) + int cellImageSizeSrc = imageWidth / xLength; + if (int(imageHeight / yLength) != cellImageSizeSrc) throw std::runtime_error("cell size must be quadratic"); // If cell bounds of the currently loaded content and the loaded savegame do not match, @@ -282,33 +449,82 @@ namespace MWRender int topDiff = (bounds.mMaxY - mMaxY); int rightDiff = (bounds.mMaxX - mMaxX); int bottomDiff = (mMinY - bounds.mMinY); - Ogre::Image::Box srcBox ( std::max(0, leftDiff * cellImageSizeSrc), + + Box srcBox ( std::max(0, leftDiff * cellImageSizeSrc), std::max(0, topDiff * cellImageSizeSrc), - std::min(image.getWidth(), image.getWidth() - rightDiff * cellImageSizeSrc), - std::min(image.getHeight(), image.getHeight() - bottomDiff * cellImageSizeSrc)); + std::min(imageWidth, imageWidth - rightDiff * cellImageSizeSrc), + std::min(imageHeight, imageHeight - bottomDiff * cellImageSizeSrc)); - Ogre::Image::Box destBox ( std::max(0, -leftDiff * cellImageSizeDst), + Box destBox ( std::max(0, -leftDiff * cellImageSizeDst), std::max(0, -topDiff * cellImageSizeDst), - std::min(mOverlayTexture->getWidth(), mOverlayTexture->getWidth() + rightDiff * cellImageSizeDst), - std::min(mOverlayTexture->getHeight(), mOverlayTexture->getHeight() + bottomDiff * cellImageSizeDst)); + std::min(mWidth, mWidth + rightDiff * cellImageSizeDst), + std::min(mHeight, mHeight + bottomDiff * cellImageSizeDst)); - // Looks like there is no interface for blitting from memory with src/dst boxes. - // So we create a temporary texture for blitting. - Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().createManual("@temp", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, image.getWidth(), - image.getHeight(), 0, Ogre::PF_A8B8G8R8); - tex->loadImage(image); + osg::ref_ptr texture (new osg::Texture2D); + texture->setImage(image); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + texture->setResizeNonPowerOfTwoHint(false); - mOverlayTexture->load(); - mOverlayTexture->getBuffer()->blit(tex->getBuffer(), srcBox, destBox); + if (srcBox == destBox && imageWidth == mWidth && imageHeight == mHeight) + { + mOverlayImage->copySubImage(0, 0, 0, image); - if (srcBox.left == destBox.left && srcBox.right == destBox.right - && srcBox.top == destBox.top && srcBox.bottom == destBox.bottom - && int(image.getWidth()) == mWidth && int(image.getHeight()) == mHeight) - mOverlayImage = image; + requestOverlayTextureUpdate(0, 0, mWidth, mHeight, texture, true, false); + } else - mOverlayTexture->convertToImage(mOverlayImage); + { + // Dimensions don't match. This could mean a changed map region, or a changed map resolution. + // In the latter case, we'll want filtering. + // Create a RTT Camera and draw the image onto mOverlayImage in the next frame. + requestOverlayTextureUpdate(destBox.mLeft, destBox.mTop, destBox.mRight-destBox.mLeft, destBox.mBottom-destBox.mTop, texture, true, true, + srcBox.mLeft/float(imageWidth), srcBox.mTop/float(imageHeight), + srcBox.mRight/float(imageWidth), srcBox.mBottom/float(imageHeight)); + } + } - Ogre::TextureManager::getSingleton().remove("@temp"); + osg::ref_ptr GlobalMap::getBaseTexture() + { + return mBaseTexture; + } + + osg::ref_ptr GlobalMap::getOverlayTexture() + { + return mOverlayTexture; + } + + void GlobalMap::markForRemoval(osg::Camera *camera) + { + CameraVector::iterator found = std::find(mActiveCameras.begin(), mActiveCameras.end(), camera); + if (found == mActiveCameras.end()) + { + std::cerr << "GlobalMap trying to remove an inactive camera" << std::endl; + return; + } + mActiveCameras.erase(found); + mCamerasPendingRemoval.push_back(camera); + } + + void GlobalMap::cleanupCameras() + { + for (CameraVector::iterator it = mCamerasPendingRemoval.begin(); it != mCamerasPendingRemoval.end(); ++it) + mRoot->removeChild(*it); + mCamerasPendingRemoval.clear(); + + for (ImageDestVector::iterator it = mPendingImageDest.begin(); it != mPendingImageDest.end();) + { + ImageDest& imageDest = *it; + if (--imageDest.mFramesUntilDone > 0) + { + ++it; + continue; + } + + mOverlayImage->copySubImage(imageDest.mX, imageDest.mY, 0, imageDest.mImage); + + it = mPendingImageDest.erase(it); + } } } diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index a162ab68fb..07ae7cdae7 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -2,8 +2,17 @@ #define GAME_RENDER_GLOBALMAP_H #include +#include -#include +#include + +namespace osg +{ + class Texture2D; + class Image; + class Group; + class Camera; +} namespace Loading { @@ -18,10 +27,10 @@ namespace ESM namespace MWRender { - class GlobalMap : public Ogre::ManualResourceLoader + class GlobalMap { public: - GlobalMap(const std::string& cacheDir); + GlobalMap(osg::Group* root); ~GlobalMap(); void render(Loading::Listener* loadingListener); @@ -35,25 +44,74 @@ namespace MWRender void cellTopLeftCornerToImageSpace(int x, int y, float& imageX, float& imageY); - void exploreCell (int cellX, int cellY); - - virtual void loadResource(Ogre::Resource* resource); + void exploreCell (int cellX, int cellY, osg::ref_ptr localMapTexture); /// Clears the overlay void clear(); + /** + * Removes cameras that have already been rendered. Should be called every frame to ensure that + * we do not render the same map more than once. Note, this cleanup is difficult to implement in an + * automated fashion, since we can't alter the scene graph structure from within an update callback. + */ + void cleanupCameras(); + + /** + * Mark a camera for cleanup in the next update. For internal use only. + */ + void markForRemoval(osg::Camera* camera); + void write (ESM::GlobalMap& map); void read (ESM::GlobalMap& map); + osg::ref_ptr getBaseTexture(); + osg::ref_ptr getOverlayTexture(); + private: - std::string mCacheDir; + /** + * Request rendering a 2d quad onto mOverlayTexture. + * x, y, width and height are the destination coordinates (top-left coordinate origin) + * @param cpuCopy copy the resulting render onto mOverlayImage as well? + */ + void requestOverlayTextureUpdate(int x, int y, int width, int height, osg::ref_ptr texture, bool clear, bool cpuCopy, + float srcLeft = 0.f, float srcTop = 0.f, float srcRight = 1.f, float srcBottom = 1.f); int mCellSize; + osg::ref_ptr mRoot; + + typedef std::vector > CameraVector; + CameraVector mActiveCameras; + + CameraVector mCamerasPendingRemoval; + + struct ImageDest + { + ImageDest() + : mX(0), mY(0) + , mFramesUntilDone(3) // wait an extra frame to ensure the draw thread has completed its frame. + { + } + + osg::ref_ptr mImage; + int mX, mY; + int mFramesUntilDone; + }; + + typedef std::vector ImageDestVector; + + ImageDestVector mPendingImageDest; + std::vector< std::pair > mExploredCells; - Ogre::TexturePtr mOverlayTexture; - Ogre::Image mOverlayImage; // Backup in system memory + osg::ref_ptr mBaseTexture; + + // GPU copy of overlay + // Note, uploads are pushed through a Camera, instead of through mOverlayImage + osg::ref_ptr mOverlayTexture; + + // CPU copy of overlay + osg::ref_ptr mOverlayImage; int mWidth; int mHeight; diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index b692226642..9d5950a90e 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -1,130 +1,139 @@ #include "localmap.hpp" -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include + +#include +#include +#include +#include + +#include + +#include #include +#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwbase/windowmanager.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" -#include "renderconst.hpp" -#include "renderingmanager.hpp" +#include "vismask.hpp" -using namespace MWRender; -using namespace Ogre; +namespace +{ -LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWRender::RenderingManager* rendering) - : mMapResolution(Settings::Manager::getInt("local map resolution", "Map")) + class CameraLocalUpdateCallback : public osg::NodeCallback + { + public: + CameraLocalUpdateCallback(MWRender::LocalMap* parent) + : mRendered(false) + , mParent(parent) + { + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor*) + { + if (mRendered) + node->setNodeMask(0); + + if (!mRendered) + { + mRendered = true; + mParent->markForRemoval(static_cast(node)); + } + + // Note, we intentionally do not traverse children here. The map camera's scene data is the same as the master camera's, + // so it has been updated already. + //traverse(node, nv); + } + + private: + bool mRendered; + MWRender::LocalMap* mParent; + }; + + float square(float val) + { + return val*val; + } + +} + +namespace MWRender +{ + +LocalMap::LocalMap(osgViewer::Viewer* viewer) + : mViewer(viewer) + , mMapResolution(Settings::Manager::getInt("local map resolution", "Map")) + , mMapWorldSize(8192.f) , mAngle(0.f) , mInterior(false) { - mRendering = rend; - mRenderingManager = rendering; + mRoot = mViewer->getSceneData()->asGroup(); - mCameraPosNode = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); - mCameraRotNode = mCameraPosNode->createChildSceneNode(); - mCameraNode = mCameraRotNode->createChildSceneNode(); - - mCellCamera = mRendering->getScene()->createCamera("CellCamera"); - mCellCamera->setProjectionType(PT_ORTHOGRAPHIC); - - mCameraNode->attachObject(mCellCamera); - - mLight = mRendering->getScene()->createLight(); - mLight->setType (Ogre::Light::LT_DIRECTIONAL); - mLight->setDirection (Ogre::Vector3(0.3f, 0.3f, -0.7f)); - mLight->setVisible (false); - mLight->setDiffuseColour (ColourValue(0.7f,0.7f,0.7f)); - - mRenderTexture = TextureManager::getSingleton().createManual( - "localmap/rtt", - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - TEX_TYPE_2D, - mMapResolution, mMapResolution, - 0, - PF_R8G8B8, - TU_RENDERTARGET); - - mRenderTarget = mRenderTexture->getBuffer()->getRenderTarget(); - mRenderTarget->setAutoUpdated(false); - Viewport* vp = mRenderTarget->addViewport(mCellCamera); - vp->setOverlaysEnabled(false); - vp->setShadowsEnabled(false); - vp->setBackgroundColour(ColourValue(0, 0, 0)); - vp->setVisibilityMask(RV_Map); - vp->setMaterialScheme("local_map"); + SceneUtil::FindByNameVisitor find("Scene Root"); + mRoot->accept(find); + mSceneRoot = find.mFoundNode; + if (!mSceneRoot) + throw std::runtime_error("no scene root found"); } LocalMap::~LocalMap() { + for (CameraVector::iterator it = mActiveCameras.begin(); it != mActiveCameras.end(); ++it) + mRoot->removeChild(*it); + for (CameraVector::iterator it = mCamerasPendingRemoval.begin(); it != mCamerasPendingRemoval.end(); ++it) + mRoot->removeChild(*it); } -const Ogre::Vector2 LocalMap::rotatePoint(const Ogre::Vector2& p, const Ogre::Vector2& c, const float angle) +const osg::Vec2f LocalMap::rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle) { - return Vector2( Math::Cos(angle) * (p.x - c.x) - Math::Sin(angle) * (p.y - c.y) + c.x, - Math::Sin(angle) * (p.x - c.x) + Math::Cos(angle) * (p.y - c.y) + c.y); -} - -std::string LocalMap::coordStr(const int x, const int y) -{ - return StringConverter::toString(x) + "_" + StringConverter::toString(y); + return osg::Vec2f( std::cos(angle) * (point.x() - center.x()) - std::sin(angle) * (point.y() - center.y()) + center.x(), + std::sin(angle) * (point.x() - center.x()) + std::cos(angle) * (point.y() - center.y()) + center.y()); } void LocalMap::clear() { - // Not actually removing the Textures here. That doesnt appear to work properly. It seems MyGUI still keeps some pointers. - mBuffers.clear(); + mSegments.clear(); } void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) { if (!mInterior) { - std::string textureName = "Cell_"+coordStr(cell->getCell()->getGridX(), cell->getCell()->getGridY())+"_fog"; - std::auto_ptr fog (new ESM::FogState()); - fog->mFogTextures.push_back(ESM::FogTexture()); + const MapSegment& segment = mSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; - TexturePtr tex = TextureManager::getSingleton().getByName(textureName); - if (tex.isNull()) - return; + if (segment.mFogOfWarImage && segment.mHasFogState) + { + std::auto_ptr fog (new ESM::FogState()); + fog->mFogTextures.push_back(ESM::FogTexture()); - Ogre::Image image; - tex->load(); - tex->convertToImage(image); + segment.saveFogOfWar(fog->mFogTextures.back()); - Ogre::DataStreamPtr encoded = image.encode("tga"); - fog->mFogTextures.back().mImageData.resize(encoded->size()); - encoded->read(&fog->mFogTextures.back().mImageData[0], encoded->size()); - - cell->setFog(fog.release()); + cell->setFog(fog.release()); + } } else { - Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y); - Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().y); - Vector2 length = max-min; - const int segsX = static_cast(std::ceil(length.x / sSize)); - const int segsY = static_cast(std::ceil(length.y / sSize)); - - mInteriorName = cell->getCell()->mName; + // FIXME: segmenting code duplicated from requestMap + osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); + osg::Vec2f max(mBounds.xMax(), mBounds.yMax()); + osg::Vec2f length = max-min; + const int segsX = static_cast(std::ceil(length.x() / mMapWorldSize)); + const int segsY = static_cast(std::ceil(length.y() / mMapWorldSize)); std::auto_ptr fog (new ESM::FogState()); - fog->mBounds.mMinX = mBounds.getMinimum().x; - fog->mBounds.mMaxX = mBounds.getMaximum().x; - fog->mBounds.mMinY = mBounds.getMinimum().y; - fog->mBounds.mMaxY = mBounds.getMaximum().y; + fog->mBounds.mMinX = mBounds.xMin(); + fog->mBounds.mMaxX = mBounds.xMax(); + fog->mBounds.mMinY = mBounds.yMin(); + fog->mBounds.mMaxY = mBounds.yMax(); fog->mNorthMarkerAngle = mAngle; fog->mFogTextures.reserve(segsX*segsY); @@ -133,21 +142,13 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) { for (int y=0; ygetCell()->mName + "_" + coordStr(x,y) + "_fog"; - - TexturePtr tex = TextureManager::getSingleton().getByName(textureName); - if (tex.isNull()) - return; - - Ogre::Image image; - tex->load(); - tex->convertToImage(image); + const MapSegment& segment = mSegments[std::make_pair(x,y)]; fog->mFogTextures.push_back(ESM::FogTexture()); - Ogre::DataStreamPtr encoded = image.encode("tga"); - fog->mFogTextures.back().mImageData.resize(encoded->size()); - encoded->read(&fog->mFogTextures.back().mImageData[0], encoded->size()); + // saving even if !segment.mHasFogState so we don't mess up the segmenting + // plus, older openmw versions can't deal with empty images + segment.saveFogOfWar(fog->mFogTextures.back()); fog->mFogTextures.back().mX = x; fog->mFogTextures.back().mY = y; @@ -158,40 +159,179 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) } } -void LocalMap::requestMap(MWWorld::CellStore* cell, float zMin, float zMax) +osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax) +{ + osg::ref_ptr camera (new osg::Camera); + + camera->setProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10); + camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); + camera->setViewMatrixAsLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector); + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); + camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f)); + camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + + camera->setCullMask(Mask_Scene|Mask_SimpleWater|Mask_Terrain); + camera->setNodeMask(Mask_RenderToTexture); + + osg::ref_ptr stateset = new osg::StateSet; + stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); + stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); + stateset->setMode(GL_FOG, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + + osg::ref_ptr lightmodel = new osg::LightModel; + lightmodel->setAmbientIntensity(osg::Vec4(0.3f, 0.3f, 0.3f, 1.f)); + stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::ref_ptr light = new osg::Light; + light->setPosition(osg::Vec4(-0.3f, -0.3f, 0.7f, 0.f)); + light->setDiffuse(osg::Vec4(0.7f, 0.7f, 0.7f, 1.f)); + light->setAmbient(osg::Vec4(0,0,0,1)); + light->setSpecular(osg::Vec4(0,0,0,0)); + light->setLightNum(0); + light->setConstantAttenuation(1.f); + light->setLinearAttenuation(0.f); + light->setQuadraticAttenuation(0.f); + + osg::ref_ptr lightSource = new osg::LightSource; + lightSource->setLight(light); + + lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + camera->addChild(lightSource); + camera->setStateSet(stateset); + camera->setGraphicsContext(mViewer->getCamera()->getGraphicsContext()); + camera->setViewport(0, 0, mMapResolution, mMapResolution); + camera->setUpdateCallback(new CameraLocalUpdateCallback(this)); + + return camera; +} + +void LocalMap::setupRenderToTexture(osg::ref_ptr camera, int x, int y) +{ + osg::ref_ptr texture (new osg::Texture2D); + texture->setTextureSize(mMapResolution, mMapResolution); + texture->setInternalFormat(GL_RGB); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + camera->attach(osg::Camera::COLOR_BUFFER, texture); + + camera->addChild(mSceneRoot); + mRoot->addChild(camera); + mActiveCameras.push_back(camera); + + MapSegment& segment = mSegments[std::make_pair(x, y)]; + segment.mMapTexture = texture; +} + +void LocalMap::requestMap(std::set cells) +{ + for (std::set::iterator it = cells.begin(); it != cells.end(); ++it) + { + MWWorld::CellStore* cell = *it; + if (cell->isExterior()) + requestExteriorMap(cell); + else + requestInteriorMap(cell); + } +} + +void LocalMap::removeCell(MWWorld::CellStore *cell) +{ + saveFogOfWar(cell); + + if (cell->isExterior()) + mSegments.erase(std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())); + else + mSegments.clear(); +} + +osg::ref_ptr LocalMap::getMapTexture(int x, int y) +{ + SegmentMap::iterator found = mSegments.find(std::make_pair(x, y)); + if (found == mSegments.end()) + return osg::ref_ptr(); + else + return found->second.mMapTexture; +} + +osg::ref_ptr LocalMap::getFogOfWarTexture(int x, int y) +{ + SegmentMap::iterator found = mSegments.find(std::make_pair(x, y)); + if (found == mSegments.end()) + return osg::ref_ptr(); + else + return found->second.mFogOfWarTexture; +} + +void LocalMap::markForRemoval(osg::Camera *cam) +{ + CameraVector::iterator found = std::find(mActiveCameras.begin(), mActiveCameras.end(), cam); + if (found == mActiveCameras.end()) + { + std::cerr << "trying to remove an inactive camera" << std::endl; + return; + } + mActiveCameras.erase(found); + mCamerasPendingRemoval.push_back(cam); +} + +void LocalMap::cleanupCameras() +{ + if (mCamerasPendingRemoval.empty()) + return; + + for (CameraVector::iterator it = mCamerasPendingRemoval.begin(); it != mCamerasPendingRemoval.end(); ++it) + { + (*it)->removeChildren(0, (*it)->getNumChildren()); + (*it)->setGraphicsContext(NULL); + mRoot->removeChild(*it); + } + + mCamerasPendingRemoval.clear(); +} + +void LocalMap::requestExteriorMap(MWWorld::CellStore* cell) { mInterior = false; - mCameraRotNode->setOrientation(Quaternion::IDENTITY); - mCellCamera->setOrientation(Quaternion(Ogre::Math::Cos(Ogre::Degree(0)/2.f), 0, 0, -Ogre::Math::Sin(Ogre::Degree(0)/2.f))); - int x = cell->getCell()->getGridX(); int y = cell->getCell()->getGridY(); - std::string name = "Cell_"+coordStr(x, y); + osg::BoundingSphere bound = mViewer->getSceneData()->getBound(); + float zmin = bound.center().z() - bound.radius(); + float zmax = bound.center().z() + bound.radius(); - mCameraPosNode->setPosition(Vector3(0,0,0)); + osg::ref_ptr camera = createOrthographicCamera(x*mMapWorldSize + mMapWorldSize/2.f, y*mMapWorldSize + mMapWorldSize/2.f, mMapWorldSize, mMapWorldSize, + osg::Vec3d(0,1,0), zmin, zmax); + setupRenderToTexture(camera, cell->getCell()->getGridX(), cell->getCell()->getGridY()); - // Note: using force=true for exterior cell maps. - // They must be updated even if they were visited before, because the set of surrounding active cells might be different - // (and objects in a different cell can "bleed" into another cell's map if they cross the border) - render((x+0.5f)*sSize, (y+0.5f)*sSize, zMin, zMax, static_cast(sSize), static_cast(sSize), name, true); - - if (mBuffers.find(name) == mBuffers.end()) + MapSegment& segment = mSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; + if (!segment.mFogOfWarImage) { if (cell->getFog()) - loadFogOfWar(name, cell->getFog()->mFogTextures.back()); + segment.loadFogOfWar(cell->getFog()->mFogTextures.back()); else - createFogOfWar(name); + segment.initFogOfWar(); } } -void LocalMap::requestMap(MWWorld::CellStore* cell, - AxisAlignedBox bounds) +void LocalMap::requestInteriorMap(MWWorld::CellStore* cell) { + osg::ComputeBoundsVisitor computeBoundsVisitor; + computeBoundsVisitor.setTraversalMask(Mask_Scene|Mask_Terrain); + mSceneRoot->accept(computeBoundsVisitor); + + osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); + // If we're in an empty cell, bail out // The operations in this function are only valid for finite bounds - if (bounds.isNull ()) + if (!bounds.valid() || bounds.radius2() == 0.0) return; mInterior = true; @@ -199,41 +339,35 @@ void LocalMap::requestMap(MWWorld::CellStore* cell, mBounds = bounds; // Get the cell's NorthMarker rotation. This is used to rotate the entire map. - const Vector2& north = MWBase::Environment::get().getWorld()->getNorthVector(cell); - Radian angle = Ogre::Math::ATan2 (north.x, north.y); - mAngle = angle.valueRadians(); + osg::Vec2f north = MWBase::Environment::get().getWorld()->getNorthVector(cell); + + mAngle = std::atan2(north.x(), north.y()); // Rotate the cell and merge the rotated corners to the bounding box - Vector2 _center(bounds.getCenter().x, bounds.getCenter().y); - Vector3 _c1 = bounds.getCorner(AxisAlignedBox::FAR_LEFT_BOTTOM); - Vector3 _c2 = bounds.getCorner(AxisAlignedBox::FAR_RIGHT_BOTTOM); - Vector3 _c3 = bounds.getCorner(AxisAlignedBox::FAR_LEFT_TOP); - Vector3 _c4 = bounds.getCorner(AxisAlignedBox::FAR_RIGHT_TOP); + osg::Vec2f origCenter(bounds.center().x(), bounds.center().y()); + osg::Vec3f origCorners[8]; + for (int i=0; i<8; ++i) + origCorners[i] = mBounds.corner(i); - Vector2 c1(_c1.x, _c1.y); - Vector2 c2(_c2.x, _c2.y); - Vector2 c3(_c3.x, _c3.y); - Vector2 c4(_c4.x, _c4.y); - c1 = rotatePoint(c1, _center, mAngle); - c2 = rotatePoint(c2, _center, mAngle); - c3 = rotatePoint(c3, _center, mAngle); - c4 = rotatePoint(c4, _center, mAngle); - mBounds.merge(Vector3(c1.x, c1.y, 0)); - mBounds.merge(Vector3(c2.x, c2.y, 0)); - mBounds.merge(Vector3(c3.x, c3.y, 0)); - mBounds.merge(Vector3(c4.x, c4.y, 0)); + for (int i=0; i<8; ++i) + { + osg::Vec3f corner = origCorners[i]; + osg::Vec2f corner2d (corner.x(), corner.y()); + corner2d = rotatePoint(corner2d, origCenter, mAngle); + mBounds.expandBy(osg::Vec3f(corner2d.x(), corner2d.y(), 0)); + } // Do NOT change padding! This will break older savegames. // If the padding really needs to be changed, then it must be saved in the ESM::FogState and // assume the old (500) value as default for older savegames. - const Ogre::Real padding = 500.0f; + const float padding = 500.0f; // Apply a little padding - mBounds.setMinimum (mBounds.getMinimum() - Vector3(padding,padding,0)); - mBounds.setMaximum (mBounds.getMaximum() + Vector3(padding,padding,0)); + mBounds.set(mBounds._min - osg::Vec3f(padding,padding,0.f), + mBounds._max + osg::Vec3f(padding,padding,0.f)); - float zMin = mBounds.getMinimum().z; - float zMax = mBounds.getMaximum().z; + float zMin = mBounds.zMin(); + float zMax = mBounds.zMax(); // If there is fog state in the CellStore (e.g. when it came from a savegame) we need to do some checks // to see if this state is still valid. @@ -245,15 +379,15 @@ void LocalMap::requestMap(MWWorld::CellStore* cell, { ESM::FogState* fog = cell->getFog(); - Ogre::Vector3 newMin (fog->mBounds.mMinX, fog->mBounds.mMinY, zMin); - Ogre::Vector3 newMax (fog->mBounds.mMaxX, fog->mBounds.mMaxY, zMax); + osg::Vec3f newMin (fog->mBounds.mMinX, fog->mBounds.mMinY, zMin); + osg::Vec3f newMax (fog->mBounds.mMaxX, fog->mBounds.mMaxY, zMax); - Ogre::Vector3 minDiff = newMin - mBounds.getMinimum(); - Ogre::Vector3 maxDiff = newMax - mBounds.getMaximum(); + osg::Vec3f minDiff = newMin - mBounds._min; + osg::Vec3f maxDiff = newMax - mBounds._max; - if (std::abs(minDiff.x) > 500 || std::abs(minDiff.y) > 500 - || std::abs(maxDiff.x) > 500 || std::abs(maxDiff.y) > 500 - || std::abs(mAngle - fog->mNorthMarkerAngle) > Ogre::Degree(5).valueRadians()) + if (std::abs(minDiff.x()) > padding || std::abs(minDiff.y()) > padding + || std::abs(maxDiff.x()) > padding || std::abs(maxDiff.y()) > padding + || std::abs(mAngle - fog->mNorthMarkerAngle) > osg::DegreesToRadians(5.f)) { // Nuke it cell->setFog(NULL); @@ -261,208 +395,93 @@ void LocalMap::requestMap(MWWorld::CellStore* cell, else { // Looks sane, use it - mBounds = Ogre::AxisAlignedBox(newMin, newMax); + mBounds = osg::BoundingBox(newMin, newMax); mAngle = fog->mNorthMarkerAngle; } } - Vector2 center(mBounds.getCenter().x, mBounds.getCenter().y); + osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); + osg::Vec2f max(mBounds.xMax(), mBounds.yMax()); - Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y); - Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().y); + osg::Vec2f length = max-min; - Vector2 length = max-min; - - mCellCamera->setOrientation(Quaternion::IDENTITY); - mCameraRotNode->setOrientation(Quaternion(Math::Cos(mAngle/2.f), 0, 0, -Math::Sin(mAngle/2.f))); - - mCameraPosNode->setPosition(Vector3(center.x, center.y, 0)); + osg::Vec2f center(bounds.center().x(), bounds.center().y()); // divide into segments - const int segsX = static_cast(std::ceil(length.x / sSize)); - const int segsY = static_cast(std::ceil(length.y / sSize)); + const int segsX = static_cast(std::ceil(length.x() / mMapWorldSize)); + const int segsY = static_cast(std::ceil(length.y() / mMapWorldSize)); - mInteriorName = cell->getCell()->mName; - - int i=0; + int i = 0; for (int x=0; x(sSize*x), static_cast(sSize*y)); - Vector2 newcenter = start + sSize/2; + osg::Vec2f start = min + osg::Vec2f(mMapWorldSize*x, mMapWorldSize*y); + osg::Vec2f newcenter = start + osg::Vec2f(mMapWorldSize/2.f, mMapWorldSize/2.f); - std::string texturePrefix = cell->getCell()->mName + "_" + coordStr(x,y); + osg::Quat cameraOrient (mAngle, osg::Vec3d(0,0,-1)); + osg::Vec2f a = newcenter - center; + osg::Vec3f rotatedCenter = cameraOrient * (osg::Vec3f(a.x(), a.y(), 0)); - render(newcenter.x - center.x, newcenter.y - center.y, zMin, zMax, static_cast(sSize), static_cast(sSize), texturePrefix); + osg::Vec2f pos = osg::Vec2f(rotatedCenter.x(), rotatedCenter.y()) + center; - if (!cell->getFog()) - createFogOfWar(texturePrefix); - else + osg::ref_ptr camera = createOrthographicCamera(pos.x(), pos.y(), + mMapWorldSize, mMapWorldSize, + osg::Vec3f(north.x(), north.y(), 0.f), zMin, zMax); + + setupRenderToTexture(camera, x, y); + + MapSegment& segment = mSegments[std::make_pair(x,y)]; + if (!segment.mFogOfWarImage) { - ESM::FogState* fog = cell->getFog(); + if (!cell->getFog()) + segment.initFogOfWar(); + else + { + ESM::FogState* fog = cell->getFog(); - // We are using the same bounds and angle as we were using when the textures were originally made. Segments should come out the same. - if (i >= int(fog->mFogTextures.size())) - throw std::runtime_error("fog texture count mismatch"); + // We are using the same bounds and angle as we were using when the textures were originally made. Segments should come out the same. + if (i >= int(fog->mFogTextures.size())) + { + std::cout << "Warning: fog texture count mismatch" << std::endl; + break; + } - ESM::FogTexture& esm = fog->mFogTextures[i]; - loadFogOfWar(texturePrefix, esm); + segment.loadFogOfWar(fog->mFogTextures[i]); + } } ++i; } } } -void LocalMap::createFogOfWar(const std::string& texturePrefix) +void LocalMap::worldToInteriorMapPosition (osg::Vec2f pos, float& nX, float& nY, int& x, int& y) { - const std::string texName = texturePrefix + "_fog"; - TexturePtr tex = createFogOfWarTexture(texName); + pos = rotatePoint(pos, osg::Vec2f(mBounds.center().x(), mBounds.center().y()), mAngle); - // create a buffer to use for dynamic operations - std::vector buffer; + osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); - // initialize to (0, 0, 0, 1) - buffer.resize(sFogOfWarResolution*sFogOfWarResolution, 0xFF000000); + x = static_cast(std::ceil((pos.x() - min.x()) / mMapWorldSize) - 1); + y = static_cast(std::ceil((pos.y() - min.y()) / mMapWorldSize) - 1); - // upload to the texture - tex->load(); - memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4); - tex->getBuffer()->unlock(); - - mBuffers[texturePrefix] = buffer; + nX = (pos.x() - min.x() - mMapWorldSize*x)/mMapWorldSize; + nY = 1.0f-(pos.y() - min.y() - mMapWorldSize*y)/mMapWorldSize; } -Ogre::TexturePtr LocalMap::createFogOfWarTexture(const std::string &texName) +osg::Vec2f LocalMap::interiorMapToWorldPosition (float nX, float nY, int x, int y) { - TexturePtr tex = TextureManager::getSingleton().getByName(texName); - if (tex.isNull()) - { - tex = TextureManager::getSingleton().createManual( - texName, - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - TEX_TYPE_2D, - sFogOfWarResolution, sFogOfWarResolution, - 0, - PF_A8R8G8B8, - TU_DYNAMIC_WRITE_ONLY, - this // ManualResourceLoader required if the texture contents are lost (due to lost devices nonsense that can occur with D3D) - ); - } + osg::Vec2f min(mBounds.xMin(), mBounds.yMin()); + osg::Vec2f pos (mMapWorldSize * (nX + x) + min.x(), + mMapWorldSize * (1.0f-nY + y) + min.y()); - return tex; -} - -void LocalMap::loadFogOfWar (const std::string& texturePrefix, ESM::FogTexture& esm) -{ - std::vector& data = esm.mImageData; - Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size())); - Ogre::Image image; - image.load(stream, "tga"); - - if (int(image.getWidth()) != sFogOfWarResolution || int(image.getHeight()) != sFogOfWarResolution) - throw std::runtime_error("fog texture size mismatch"); - - std::string texName = texturePrefix + "_fog"; - - Ogre::TexturePtr tex = createFogOfWarTexture(texName); - - tex->unload(); - tex->loadImage(image); - - // create a buffer to use for dynamic operations - std::vector buffer; - buffer.resize(sFogOfWarResolution*sFogOfWarResolution); - memcpy(&buffer[0], image.getData(), image.getSize()); - - mBuffers[texturePrefix] = buffer; -} - -void LocalMap::render(const float x, const float y, - const float zlow, const float zhigh, - const float xw, const float yw, const std::string& texture, bool force) -{ - mCellCamera->setFarClipDistance( (zhigh-zlow) + 2000 ); - mCellCamera->setNearClipDistance(50); - - mCellCamera->setOrthoWindow(xw, yw); - mCameraNode->setPosition(Vector3(x, y, zhigh+1000)); - - // disable fog (only necessary for fixed function, the shader based - // materials already do this through local_map material configuration) - float oldFogStart = mRendering->getScene()->getFogStart(); - float oldFogEnd = mRendering->getScene()->getFogEnd(); - Ogre::ColourValue oldFogColour = mRendering->getScene()->getFogColour(); - mRendering->getScene()->setFog(FOG_NONE); - - // set up lighting - Ogre::ColourValue oldAmbient = mRendering->getScene()->getAmbientLight(); - mRendering->getScene()->setAmbientLight(Ogre::ColourValue(0.3f, 0.3f, 0.3f)); - mRenderingManager->disableLights(true); - mLight->setVisible(true); - - TexturePtr tex; - // try loading from memory - tex = TextureManager::getSingleton().getByName(texture); - if (tex.isNull()) - { - // render - mRenderTarget->update(); - - // create a new texture and blit to it - Ogre::TexturePtr tex = TextureManager::getSingleton().createManual( - texture, - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - TEX_TYPE_2D, - mMapResolution, mMapResolution, - 0, - PF_R8G8B8); - tex->getBuffer()->blit(mRenderTexture->getBuffer()); - } - else if (force) - { - mRenderTarget->update(); - tex->getBuffer()->blit(mRenderTexture->getBuffer()); - } - - mRenderingManager->enableLights(true); - mLight->setVisible(false); - - // re-enable fog - mRendering->getScene()->setFog(FOG_LINEAR, oldFogColour, 0, oldFogStart, oldFogEnd); - mRendering->getScene()->setAmbientLight(oldAmbient); -} - -void LocalMap::worldToInteriorMapPosition (Ogre::Vector2 pos, float& nX, float& nY, int& x, int& y) -{ - pos = rotatePoint(pos, Vector2(mBounds.getCenter().x, mBounds.getCenter().y), mAngle); - - Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y); - - x = static_cast(std::ceil((pos.x - min.x) / sSize) - 1); - y = static_cast(std::ceil((pos.y - min.y) / sSize) - 1); - - nX = (pos.x - min.x - sSize*x)/sSize; - nY = 1.0f-(pos.y - min.y - sSize*y)/sSize; -} - -Ogre::Vector2 LocalMap::interiorMapToWorldPosition (float nX, float nY, int x, int y) -{ - Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y); - Ogre::Vector2 pos; - - pos.x = sSize * (nX + x) + min.x; - pos.y = sSize * (1.0f-nY + y) + min.y; - - pos = rotatePoint(pos, Vector2(mBounds.getCenter().x, mBounds.getCenter().y), -mAngle); + pos = rotatePoint(pos, osg::Vec2f(mBounds.center().x(), mBounds.center().y()), -mAngle); return pos; } bool LocalMap::isPositionExplored (float nX, float nY, int x, int y, bool interior) { - std::string texName = (interior ? mInteriorName + "_" : "Cell_") + coordStr(x, y); - - if (mBuffers.find(texName) == mBuffers.end()) + const MapSegment& segment = mSegments[std::make_pair(x, y)]; + if (!segment.mFogOfWarImage) return false; nX = std::max(0.f, std::min(1.f, nX)); @@ -471,82 +490,44 @@ bool LocalMap::isPositionExplored (float nX, float nY, int x, int y, bool interi int texU = static_cast((sFogOfWarResolution - 1) * nX); int texV = static_cast((sFogOfWarResolution - 1) * nY); - Ogre::uint32 clr = mBuffers[texName][texV * sFogOfWarResolution + texU]; - uint8 alpha = (clr >> 24); + uint32_t clr = ((const uint32_t*)segment.mFogOfWarImage->data())[texV * sFogOfWarResolution + texU]; + uint8_t alpha = (clr >> 24); return alpha < 200; } -void LocalMap::loadResource(Ogre::Resource* resource) +osg::Group* LocalMap::getRoot() { - std::string resourceName = resource->getName(); - size_t pos = resourceName.find("_fog"); - if (pos != std::string::npos) - resourceName = resourceName.substr(0, pos); - if (mBuffers.find(resourceName) == mBuffers.end()) - { - // create a buffer to use for dynamic operations - std::vector buffer; - - // initialize to (0, 0, 0, 1) - buffer.resize(sFogOfWarResolution*sFogOfWarResolution, 0xFF000000); - mBuffers[resourceName] = buffer; - } - - std::vector& buffer = mBuffers[resourceName]; - - Ogre::Texture* tex = static_cast(resource); - tex->createInternalResources(); - memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4); - tex->getBuffer()->unlock(); + return mRoot; } -void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaternion& orientation) +void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orientation, + float& u, float& v, int& x, int& y, osg::Vec3f& direction) { - if (sFogOfWarSkip != 0) - { - static int count=0; - if (++count % sFogOfWarSkip != 0) - return; - } - // retrieve the x,y grid coordinates the player is in - int x,y; - float u,v; - - Vector2 pos(position.x, position.y); + osg::Vec2f pos(position.x(), position.y()); if (mInterior) + { worldToInteriorMapPosition(pos, u,v, x,y); - Vector3 playerdirection = mCameraRotNode->convertWorldToLocalOrientation(orientation).yAxis(); - - if (!mInterior) - { - x = static_cast(std::ceil(pos.x / sSize) - 1); - y = static_cast(std::ceil(pos.y / sSize) - 1); - } - else - MWBase::Environment::get().getWindowManager()->setActiveMap(x,y,mInterior); - - // convert from world coordinates to texture UV coordinates - std::string texBaseName; - if (!mInterior) - { - u = std::abs((pos.x - (sSize*x))/sSize); - v = 1.0f-std::abs((pos.y - (sSize*y))/sSize); - texBaseName = "Cell_"; + osg::Quat cameraOrient (mAngle, osg::Vec3(0,0,-1)); + direction = orientation * cameraOrient.inverse() * osg::Vec3f(0,1,0); } else { - texBaseName = mInteriorName + "_"; - } + direction = orientation * osg::Vec3f(0,1,0); - MWBase::Environment::get().getWindowManager()->setPlayerPos(x, y, u, v); - MWBase::Environment::get().getWindowManager()->setPlayerDir(playerdirection.x, playerdirection.y); + x = static_cast(std::ceil(pos.x() / mMapWorldSize) - 1); + y = static_cast(std::ceil(pos.y() / mMapWorldSize) - 1); + + // convert from world coordinates to texture UV coordinates + u = std::abs((pos.x() - (mMapWorldSize*x))/mMapWorldSize); + v = 1.0f-std::abs((pos.y() - (mMapWorldSize*y))/mMapWorldSize); + } // explore radius (squared) const float exploreRadius = (mInterior ? 0.1f : 0.3f) * (sFogOfWarResolution-1); // explore radius from 0 to sFogOfWarResolution-1 - const float sqrExploreRadius = Math::Sqr(exploreRadius); + const float sqrExploreRadius = square(exploreRadius); const float exploreRadiusUV = exploreRadius / sFogOfWarResolution; // explore radius from 0 to 1 (UV space) // change the affected fog of war textures (in a 3x3 grid around the player) @@ -554,7 +535,6 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni { for (int my = -1; my<2; ++my) { - // is this texture affected at all? bool affected = false; if (mx == 0 && my == 0) // the player is always in the center of the 3x3 grid @@ -569,42 +549,140 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni if (!affected) continue; - std::string texName = texBaseName + coordStr(x+mx,y+my*-1); + int texX = x + mx; + int texY = y + my*-1; - TexturePtr tex = TextureManager::getSingleton().getByName(texName+"_fog"); - if (!tex.isNull()) + MapSegment& segment = mSegments[std::make_pair(texX, texY)]; + + if (!segment.mFogOfWarImage || !segment.mMapTexture) + continue; + + unsigned char* data = segment.mFogOfWarImage->data(); + for (int texV = 0; texV >::iterator anIter; - - // get its buffer - anIter = mBuffers.find(texName); - if (anIter == mBuffers.end()) return; - - std::vector& aBuffer = (*anIter).second; - int i=0; - for (int texV = 0; texV> 24); - alpha = std::min( alpha, (uint8) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); - aBuffer[i] = (uint32) (alpha << 24); + float sqrDist = square((texU + mx*(sFogOfWarResolution-1)) - u*(sFogOfWarResolution-1)) + + square((texV + my*(sFogOfWarResolution-1)) - v*(sFogOfWarResolution-1)); - ++i; - } + uint32_t clr = *(uint32_t*)data; + uint8_t alpha = (clr >> 24); + + alpha = std::min( alpha, (uint8_t) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); + *(uint32_t*)data = (uint32_t) (alpha << 24); + + data += 4; } - - tex->load(); - - // copy to the texture - // NOTE: Could be optimized later. We actually only need to update the region that changed. - // Not a big deal at the moment, the FoW is only 32x32 anyway. - memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &aBuffer[0], sFogOfWarResolution*sFogOfWarResolution*4); - tex->getBuffer()->unlock(); } + + segment.mHasFogState = true; + segment.mFogOfWarImage->dirty(); } } } + +LocalMap::MapSegment::MapSegment() + : mHasFogState(false) +{ +} + +LocalMap::MapSegment::~MapSegment() +{ + +} + +void LocalMap::MapSegment::createFogOfWarTexture() +{ + if (mFogOfWarTexture) + return; + mFogOfWarTexture = new osg::Texture2D; + // TODO: synchronize access? for now, the worst that could happen is the draw thread jumping a frame ahead. + //mFogOfWarTexture->setDataVariance(osg::Object::DYNAMIC); + mFogOfWarTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mFogOfWarTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mFogOfWarTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mFogOfWarTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mFogOfWarTexture->setUnRefImageDataAfterApply(false); +} + +void LocalMap::MapSegment::initFogOfWar() +{ + mFogOfWarImage = new osg::Image; + // Assign a PixelBufferObject for asynchronous transfer of data to the GPU + mFogOfWarImage->setPixelBufferObject(new osg::PixelBufferObject); + mFogOfWarImage->allocateImage(sFogOfWarResolution, sFogOfWarResolution, 1, GL_RGBA, GL_UNSIGNED_BYTE); + assert(mFogOfWarImage->isDataContiguous()); + std::vector data; + data.resize(sFogOfWarResolution*sFogOfWarResolution, 0xff000000); + + memcpy(mFogOfWarImage->data(), &data[0], data.size()*4); + + createFogOfWarTexture(); + mFogOfWarTexture->setImage(mFogOfWarImage); +} + +void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture &esm) +{ + const std::vector& data = esm.mImageData; + if (!data.size()) + { + initFogOfWar(); + return; + } + + // TODO: deprecate tga and use raw data instead + + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("tga"); + if (!readerwriter) + { + std::cerr << "Unable to load fog, can't find a tga ReaderWriter" << std::endl; + return; + } + + Files::IMemStream in(&data[0], data.size()); + + osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(in); + if (!result.success()) + { + std::cerr << "Failed to read fog: " << result.message() << " code " << result.status() << std::endl; + return; + } + + mFogOfWarImage = result.getImage(); + mFogOfWarImage->flipVertical(); + mFogOfWarImage->dirty(); + + createFogOfWarTexture(); + mFogOfWarTexture->setImage(mFogOfWarImage); + mHasFogState = true; +} + +void LocalMap::MapSegment::saveFogOfWar(ESM::FogTexture &fog) const +{ + if (!mFogOfWarImage) + return; + + std::ostringstream ostream; + + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("tga"); + if (!readerwriter) + { + std::cerr << "Unable to write fog, can't find a tga ReaderWriter" << std::endl; + return; + } + + // extra flips are unfortunate, but required for compatibility with older versions + mFogOfWarImage->flipVertical(); + osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mFogOfWarImage, ostream); + if (!result.success()) + { + std::cerr << "Unable to write fog: " << result.message() << " code " << result.status() << std::endl; + return; + } + mFogOfWarImage->flipVertical(); + + std::string data = ostream.str(); + fog.mImageData = std::vector(data.begin(), data.end()); +} + +} diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 014c67f164..72ee0354ed 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -1,11 +1,13 @@ #ifndef GAME_RENDER_LOCALMAP_H #define GAME_RENDER_LOCALMAP_H -#include +#include +#include +#include -#include -#include -#include +#include +#include +#include namespace MWWorld { @@ -17,52 +19,69 @@ namespace ESM struct FogTexture; } +namespace osgViewer +{ + class Viewer; +} + +namespace osg +{ + class Texture2D; + class Image; + class Camera; + class Group; + class Node; +} + namespace MWRender { - class RenderingManager; - /// /// \brief Local map rendering /// - class LocalMap : public Ogre::ManualResourceLoader + class LocalMap { public: - LocalMap(OEngine::Render::OgreRenderer*, MWRender::RenderingManager* rendering); + LocalMap(osgViewer::Viewer* viewer); ~LocalMap(); - virtual void loadResource(Ogre::Resource* resource); - /** * Clear all savegame-specific data (i.e. fog of war textures) */ void clear(); /** - * Request the local map for an exterior cell. - * @remarks It will either be loaded from a disk cache, - * or rendered if it is not already cached. - * @param cell exterior cell - * @param zMin min height of objects or terrain in cell - * @param zMax max height of objects or terrain in cell + * Request a map render for the given cells. Render textures will be immediately created and can be retrieved with the getMapTexture function. */ - void requestMap (MWWorld::CellStore* cell, float zMin, float zMax); + void requestMap (std::set cells); /** - * Request the local map for an interior cell. - * @remarks It will either be loaded from a disk cache, - * or rendered if it is not already cached. - * @param cell interior cell - * @param bounds bounding box of the cell + * Remove map and fog textures for the given cell. */ - void requestMap (MWWorld::CellStore* cell, - Ogre::AxisAlignedBox bounds); + void removeCell (MWWorld::CellStore* cell); + + osg::ref_ptr getMapTexture (int x, int y); + + osg::ref_ptr getFogOfWarTexture (int x, int y); /** - * Set the position & direction of the player. + * Indicates a camera has been queued for rendering and can be cleaned up in the next frame. For internal use only. + */ + void markForRemoval(osg::Camera* cam); + + /** + * Removes cameras that have already been rendered. Should be called every frame to ensure that + * we do not render the same map more than once. Note, this cleanup is difficult to implement in an + * automated fashion, since we can't alter the scene graph structure from within an update callback. + */ + void cleanupCameras(); + + /** + * Set the position & direction of the player, and returns the position in map space through the reference parameters. * @remarks This is used to draw a "fog of war" effect * to hide areas on the map the player has not discovered yet. */ - void updatePlayer (const Ogre::Vector3& position, const Ogre::Quaternion& orientation); + void updatePlayer (const osg::Vec3f& position, const osg::Quat& orientation, + float& u, float& v, int& x, int& y, osg::Vec3f& direction); /** * Save the fog of war for this cell to its CellStore. @@ -71,71 +90,70 @@ namespace MWRender void saveFogOfWar(MWWorld::CellStore* cell); /** - * Get the interior map texture index and normalized position - * on this texture, given a world position + * Get the interior map texture index and normalized position on this texture, given a world position */ - void worldToInteriorMapPosition (Ogre::Vector2 pos, float& nX, float& nY, int& x, int& y); + void worldToInteriorMapPosition (osg::Vec2f pos, float& nX, float& nY, int& x, int& y); - Ogre::Vector2 interiorMapToWorldPosition (float nX, float nY, int x, int y); + osg::Vec2f interiorMapToWorldPosition (float nX, float nY, int x, int y); /** * Check if a given position is explored by the player (i.e. not obscured by fog of war) */ bool isPositionExplored (float nX, float nY, int x, int y, bool interior); + osg::Group* getRoot(); + private: - OEngine::Render::OgreRenderer* mRendering; - MWRender::RenderingManager* mRenderingManager; + osg::ref_ptr mViewer; + + osg::ref_ptr mRoot; + osg::ref_ptr mSceneRoot; + + typedef std::vector< osg::ref_ptr > CameraVector; + + CameraVector mActiveCameras; + + CameraVector mCamerasPendingRemoval; + + struct MapSegment + { + MapSegment(); + ~MapSegment(); + + void initFogOfWar(); + void loadFogOfWar(const ESM::FogTexture& fog); + void saveFogOfWar(ESM::FogTexture& fog) const; + void createFogOfWarTexture(); + + osg::ref_ptr mMapTexture; + osg::ref_ptr mFogOfWarTexture; + osg::ref_ptr mFogOfWarImage; + + bool mHasFogState; + }; + + typedef std::map, MapSegment> SegmentMap; + SegmentMap mSegments; int mMapResolution; // the dynamic texture is a bottleneck, so don't set this too high static const int sFogOfWarResolution = 32; - // frames to skip before rendering fog of war - static const int sFogOfWarSkip = 2; - // size of a map segment (for exteriors, 1 cell) - static const int sSize = 8192; - - Ogre::Camera* mCellCamera; - Ogre::SceneNode* mCameraNode; - Ogre::SceneNode* mCameraPosNode; - Ogre::SceneNode* mCameraRotNode; - - // directional light from a fixed angle - Ogre::Light* mLight; + float mMapWorldSize; float mAngle; - const Ogre::Vector2 rotatePoint(const Ogre::Vector2& p, const Ogre::Vector2& c, const float angle); + const osg::Vec2f rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle); - /// @param force Always render, even if we already have a cached map - void render(const float x, const float y, - const float zlow, const float zhigh, - const float xw, const float yw, - const std::string& texture, bool force=false); + void requestExteriorMap(MWWorld::CellStore* cell); + void requestInteriorMap(MWWorld::CellStore* cell); - // Creates a fog of war texture and initializes it to full black - void createFogOfWar(const std::string& texturePrefix); - - // Loads a fog of war texture from its ESM struct - void loadFogOfWar(const std::string& texturePrefix, ESM::FogTexture& esm); // FogTexture not const because MemoryDataStream doesn't accept it - - Ogre::TexturePtr createFogOfWarTexture(const std::string& name); - - std::string coordStr(const int x, const int y); - - // A buffer for the "fog of war" textures of the current cell. - // Both interior and exterior maps are possibly divided into multiple textures. - std::map > mBuffers; - - // The render texture we will use to create the map images - Ogre::TexturePtr mRenderTexture; - Ogre::RenderTarget* mRenderTarget; + osg::ref_ptr createOrthographicCamera(float left, float top, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax); + void setupRenderToTexture(osg::ref_ptr camera, int x, int y); bool mInterior; - Ogre::AxisAlignedBox mBounds; - std::string mInteriorName; + osg::BoundingBox mBounds; }; } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index f4943ba554..4e367b3b18 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -1,34 +1,39 @@ #include "npcanimation.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include -#include +#include +#include -#include +#include #include +#include +#include +#include +#include + +#include // TextKeyMapHolder + #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" -#include "renderconst.hpp" #include "camera.hpp" +#include "rotatecontroller.hpp" +#include "renderbin.hpp" +#include "vismask.hpp" namespace { @@ -69,33 +74,96 @@ std::string getVampireHead(const std::string& race, bool female) return "meshes\\" + bodyPart->mModel; } -bool isSkinned (NifOgre::ObjectScenePtr scene) -{ - if (scene->mSkelBase == NULL) - return false; - for(size_t j = 0; j < scene->mEntities.size(); j++) - { - Ogre::Entity *ent = scene->mEntities[j]; - if(scene->mSkelBase != ent && ent->hasSkeleton()) - { - return true; - } - } - return false; -} - } namespace MWRender { +class HeadAnimationTime : public SceneUtil::ControllerSource +{ +private: + MWWorld::Ptr mReference; + float mTalkStart; + float mTalkStop; + float mBlinkStart; + float mBlinkStop; + + float mBlinkTimer; + + bool mEnabled; + + float mValue; +private: + void resetBlinkTimer(); +public: + HeadAnimationTime(MWWorld::Ptr reference); + + void updatePtr(const MWWorld::Ptr& updated); + + void update(float dt); + + void setEnabled(bool enabled); + + void setTalkStart(float value); + void setTalkStop(float value); + void setBlinkStart(float value); + void setBlinkStop(float value); + + virtual float getValue(osg::NodeVisitor* nv); +}; + +// -------------------------------------------------------------------------------- + +/// Subclass RotateController to add a Z-offset for sneaking in first person mode. +/// @note We use inheritance instead of adding another controller, so that we do not have to compute the worldOrient twice. +/// @note Must be set on a MatrixTransform. +class NeckController : public RotateController +{ +public: + NeckController(osg::Node* relativeTo) + : RotateController(relativeTo) + { + } + + void setOffset(osg::Vec3f offset) + { + mOffset = offset; + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osg::MatrixTransform* transform = static_cast(node); + osg::Matrix matrix = transform->getMatrix(); + + osg::Quat worldOrient = getWorldOrientation(node); + osg::Quat orient = worldOrient * mRotate * worldOrient.inverse() * matrix.getRotate(); + + matrix.setRotate(orient); + matrix.setTrans(matrix.getTrans() + worldOrient.inverse() * mOffset); + + transform->setMatrix(matrix); + + traverse(node,nv); + } + +private: + osg::Vec3f mOffset; +}; + +// -------------------------------------------------------------------------------------------------------------- + HeadAnimationTime::HeadAnimationTime(MWWorld::Ptr reference) : mReference(reference), mTalkStart(0), mTalkStop(0), mBlinkStart(0), mBlinkStop(0), mEnabled(true), mValue(0) { resetBlinkTimer(); } +void HeadAnimationTime::updatePtr(const MWWorld::Ptr &updated) +{ + mReference = updated; +} + void HeadAnimationTime::setEnabled(bool enabled) { mEnabled = enabled; @@ -103,7 +171,7 @@ void HeadAnimationTime::setEnabled(bool enabled) void HeadAnimationTime::resetBlinkTimer() { - mBlinkTimer = -(2.0f + OEngine::Misc::Rng::rollDice(6)); + mBlinkTimer = -(2.0f + Misc::Rng::rollDice(6)); } void HeadAnimationTime::update(float dt) @@ -129,13 +197,14 @@ void HeadAnimationTime::update(float dt) } else { + // FIXME: would be nice to hold on to the SoundPtr so we don't have to retrieve it every frame mValue = mTalkStart + (mTalkStop - mTalkStart) * std::min(1.f, MWBase::Environment::get().getSoundManager()->getSaySoundLoudness(mReference)*2); // Rescale a bit (most voices are not very loud) } } -float HeadAnimationTime::getValue() const +float HeadAnimationTime::getValue(osg::NodeVisitor*) { return mValue; } @@ -160,6 +229,8 @@ void HeadAnimationTime::setBlinkStop(float value) mBlinkStop = value; } +// ---------------------------------------------------- + static NpcAnimation::PartBoneMap createPartListMap() { NpcAnimation::PartBoneMap result; @@ -200,29 +271,27 @@ NpcAnimation::~NpcAnimation() // No need to getInventoryStore() to reset, if none exists // This is to avoid triggering the listener via ensureCustomData()->autoEquip()->fireEquipmentChanged() // all from within this destructor. ouch! - && mPtr.getRefData().getCustomData()) + && mPtr.getRefData().getCustomData() && mPtr.getClass().getInventoryStore(mPtr).getListener() == this) mPtr.getClass().getInventoryStore(mPtr).setListener(NULL, mPtr); } - -NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, bool disableListener, bool disableSounds, ViewMode viewMode) - : Animation(ptr, node), +NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, + bool disableListener, bool disableSounds, ViewMode viewMode, float firstPersonFieldOfView) + : Animation(ptr, parentNode, resourceSystem), mListenerDisabled(disableListener), mViewMode(viewMode), mShowWeapons(false), mShowCarriedLeft(true), mNpcType(Type_Normal), - mVisibilityFlags(visibilityFlags), - mFirstPersonOffset(0.f, 0.f, 0.f), - mAlpha(1.f), + mFirstPersonFieldOfView(firstPersonFieldOfView), mSoundsDisabled(disableSounds), - mHeadYaw(0.f), - mHeadPitch(0.f) + mAccurateAiming(false), + mAimingFactor(0.f) { mNpc = mPtr.get()->mBase; - mHeadAnimationTime = Ogre::SharedPtr(new HeadAnimationTime(mPtr)); - mWeaponAnimationTime = Ogre::SharedPtr(new WeaponAnimationTime(this)); + mHeadAnimationTime = boost::shared_ptr(new HeadAnimationTime(mPtr)); + mWeaponAnimationTime = boost::shared_ptr(new WeaponAnimationTime(this)); for(size_t i = 0;i < ESM::PRT_Count;i++) { @@ -244,6 +313,81 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) mViewMode = viewMode; rebuild(); + + setRenderBin(); +} + +/// @brief A RenderBin callback to clear the depth buffer before rendering. +class DepthClearCallback : public osgUtil::RenderBin::DrawCallback +{ +public: + DepthClearCallback() + { + mDepth = new osg::Depth; + mDepth->setWriteMask(true); + } + + virtual void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) + { + renderInfo.getState()->applyAttribute(mDepth); + + glClear(GL_DEPTH_BUFFER_BIT); + + bin->drawImplementation(renderInfo, previous); + } + + osg::ref_ptr mDepth; +}; + +/// Overrides Field of View to given value for rendering the subgraph. +/// Must be added as cull callback. +class OverrideFieldOfViewCallback : public osg::NodeCallback +{ +public: + OverrideFieldOfViewCallback(float fov) + : mFov(fov) + { + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); + osg::RefMatrix* projectionMatrix = new osg::RefMatrix(*cv->getProjectionMatrix()); + float fov, aspect, zNear, zFar; + if (projectionMatrix->getPerspective(fov, aspect, zNear, zFar)) + { + fov = mFov; + projectionMatrix->makePerspective(fov, aspect, zNear, zFar); + cv->pushProjectionMatrix(projectionMatrix); + traverse(node, nv); + cv->popProjectionMatrix(); + } + else + traverse(node, nv); + } + +private: + float mFov; +}; + +void NpcAnimation::setRenderBin() +{ + if (mViewMode == VM_FirstPerson) + { + static bool prototypeAdded = false; + if (!prototypeAdded) + { + osg::ref_ptr depthClearBin (new osgUtil::RenderBin); + depthClearBin->setDrawCallback(new DepthClearCallback); + osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin); + prototypeAdded = true; + } + + osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet(); + stateset->setRenderBinDetails(RenderBin_FirstPerson, "DepthClear", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + } + else + Animation::setRenderBin(); } void NpcAnimation::rebuild() @@ -253,6 +397,21 @@ void NpcAnimation::rebuild() MWBase::Environment::get().getMechanicsManager()->forceStateUpdate(mPtr); } +int NpcAnimation::getSlot(const osg::NodePath &path) const +{ + for (int i=0; igetNode().get()) != path.end()) + { + return mPartslots[i]; + } + } + return -1; +} + void NpcAnimation::updateNpcBase() { clearAnimSources(); @@ -300,8 +459,9 @@ void NpcAnimation::updateNpcBase() (!isWerewolf ? !isBeast ? "meshes\\base_anim.1st.nif" : "meshes\\base_animkna.1st.nif" : "meshes\\wolf\\skin.1st.nif"); - smodel = Misc::ResourceHelpers::correctActorModelPath(smodel); - setObjectRoot(smodel, true); + smodel = Misc::ResourceHelpers::correctActorModelPath(smodel, mResourceSystem->getVFS()); + + setObjectRoot(smodel, true, true, false); if(mViewMode != VM_FirstPerson) { @@ -318,13 +478,14 @@ void NpcAnimation::updateNpcBase() } else { + mObjectRoot->setNodeMask(Mask_FirstPerson); + mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView)); if(isWerewolf) addAnimSource(smodel); else { - /* A bit counter-intuitive, but unlike third-person anims, it seems - * beast races get both base_anim.1st.nif and base_animkna.1st.nif. - */ + // A bit counter-intuitive, but unlike third-person anims, it seems + // beast races get both base_anim.1st.nif and base_animkna.1st.nif. addAnimSource("meshes\\xbase_anim.1st.nif"); if(isBeast) addAnimSource("meshes\\xbase_animkna.1st.nif"); @@ -342,10 +503,9 @@ void NpcAnimation::updateNpcBase() void NpcAnimation::updateParts() { - if (!mSkelBase) + if (!mObjectRoot.get()) return; - mAlpha = 1.f; const MWWorld::Class &cls = mPtr.getClass(); NpcType curType = Type_Normal; @@ -400,7 +560,7 @@ void NpcAnimation::updateParts() int prio = 1; bool enchantedGlow = !store->getClass().getEnchantment(*store).empty(); - Ogre::Vector3 glowColor = getEnchantmentColor(*store); + osg::Vec4f glowColor = getEnchantmentColor(*store); if(store->getTypeName() == typeid(ESM::Clothing).name()) { prio = ((slotlist[i].mBasePriority+1)<<1) + 0; @@ -452,7 +612,7 @@ void NpcAnimation::updateParts() const ESM::Light *light = part.get()->mBase; addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, "meshes\\"+light->mModel); - addExtraLight(mInsert->getCreator(), mObjectParts[ESM::PRT_Shield], light); + addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), light); } } @@ -546,7 +706,7 @@ void NpcAnimation::updateParts() continue; } - if (!mNpc->isMale() != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) + if ((!mNpc->isMale()) != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) { // Allow opposite gender's parts as fallback if parts for our gender are missing BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); @@ -584,98 +744,37 @@ void NpcAnimation::updateParts() attachArrow(); } -void NpcAnimation::addFirstPersonOffset(const Ogre::Vector3 &offset) +PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const std::string& bonename, const std::string& bonefilter, bool enchantedGlow, osg::Vec4f* glowColor) { - mFirstPersonOffset += offset; + osg::ref_ptr instance = mResourceSystem->getSceneManager()->createInstance(model); + osg::ref_ptr attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, bonename); + mResourceSystem->getSceneManager()->notifyAttached(attached); + if (enchantedGlow) + addGlow(attached, *glowColor); + + return PartHolderPtr(new PartHolder(attached)); } -class SetObjectGroup { - int mGroup; - -public: - SetObjectGroup(int group) : mGroup(group) { } - - void operator()(Ogre::MovableObject *obj) const - { - obj->getUserObjectBindings().setUserAny(Ogre::Any(mGroup)); - } -}; - -NifOgre::ObjectScenePtr NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename, const std::string &bonefilter, bool enchantedGlow, Ogre::Vector3* glowColor) -{ - NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(mSkelBase, bonename, bonefilter, mInsert, model); - setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, - enchantedGlow, glowColor); - - std::for_each(objects->mEntities.begin(), objects->mEntities.end(), SetObjectGroup(group)); - std::for_each(objects->mParticles.begin(), objects->mParticles.end(), SetObjectGroup(group)); - - if(objects->mSkelBase) - { - Ogre::AnimationStateSet *aset = objects->mSkelBase->getAllAnimationStates(); - Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator(); - while(asiter.hasMoreElements()) - { - Ogre::AnimationState *state = asiter.getNext(); - state->setEnabled(false); - state->setLoop(false); - } - Ogre::SkeletonInstance *skelinst = objects->mSkelBase->getSkeleton(); - Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator(); - while(boneiter.hasMoreElements()) - boneiter.getNext()->setManuallyControlled(true); - } - - return objects; -} - -Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) +osg::Vec3f NpcAnimation::runAnimation(float timepassed) { - Ogre::Vector3 ret = Animation::runAnimation(timepassed); + osg::Vec3f ret = Animation::runAnimation(timepassed); mHeadAnimationTime->update(timepassed); - if (mSkelBase) + if (mFirstPersonNeckController) { - Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); - if(mViewMode == VM_FirstPerson) - { - float pitch = mPtr.getRefData().getPosition().rot[0]; - Ogre::Node *node = baseinst->getBone("Bip01 Neck"); - node->pitch(Ogre::Radian(-pitch), Ogre::Node::TS_WORLD); - - // This has to be done before this function ends; - // updateSkeletonInstance, below, touches the hands. - node->translate(mFirstPersonOffset, Ogre::Node::TS_WORLD); - } + if (mAccurateAiming) + mAimingFactor = 1.f; else - { - // In third person mode we may still need pitch for ranged weapon targeting - pitchSkeleton(mPtr.getRefData().getPosition().rot[0], baseinst); + mAimingFactor = std::max(0.f, mAimingFactor - timepassed * 0.5f); - Ogre::Node* node = baseinst->getBone("Bip01 Head"); - if (node) - node->rotate(Ogre::Quaternion(mHeadYaw, Ogre::Vector3::UNIT_Z) * Ogre::Quaternion(mHeadPitch, Ogre::Vector3::UNIT_X), Ogre::Node::TS_WORLD); - } + float rotateFactor = 0.75f + 0.25f * mAimingFactor; + + mFirstPersonNeckController->setRotate(osg::Quat(mPtr.getRefData().getPosition().rot[0] * rotateFactor, osg::Vec3f(-1,0,0))); + mFirstPersonNeckController->setOffset(mFirstPersonOffset); } - mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame. - for(size_t i = 0;i < ESM::PRT_Count;i++) - { - if (mObjectParts[i].isNull()) - continue; - std::vector >::iterator ctrl(mObjectParts[i]->mControllers.begin()); - for(;ctrl != mObjectParts[i]->mControllers.end();++ctrl) - ctrl->update(); - - if (!isSkinned(mObjectParts[i])) - continue; - - if (mSkelBase) - updateSkeletonInstance(mSkelBase->getSkeleton(), mObjectParts[i]->mSkelBase->getSkeleton()); - - mObjectParts[i]->mSkelBase->getAllAnimationStates()->_notifyDirty(); - } + WeaponAnimation::configureControllers(mPtr.getRefData().getPosition().rot[0]); return ret; } @@ -685,7 +784,7 @@ void NpcAnimation::removeIndividualPart(ESM::PartReferenceType type) mPartPriorities[type] = 0; mPartslots[type] = -1; - mObjectParts[type].setNull(); + mObjectParts[type].reset(); if (!mSoundIds[type].empty() && !mSoundsDisabled) { MWBase::Environment::get().getSoundManager()->stopSound3D(mPtr, mSoundIds[type]); @@ -712,7 +811,7 @@ void NpcAnimation::removePartGroup(int group) } } -bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, bool enchantedGlow, Ogre::Vector3* glowColor) +bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, bool enchantedGlow, osg::Vec4f* glowColor) { if(priority <= mPartPriorities[type]) return false; @@ -725,7 +824,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g const std::string& bonename = sPartList.at(type); // PRT_Hair seems to be the only type that breaks consistency and uses a filter that's different from the attachment bone const std::string bonefilter = (type == ESM::PRT_Hair) ? "hair" : bonename; - mObjectParts[type] = insertBoundedPart(mesh, group, bonename, bonefilter, enchantedGlow, glowColor); + mObjectParts[type] = insertBoundedPart(mesh, bonename, bonefilter, enchantedGlow, glowColor); } catch (std::exception& e) { @@ -747,63 +846,49 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g } } } - if(mObjectParts[type]->mSkelBase) + + boost::shared_ptr src; + if (type == ESM::PRT_Head) { - Ogre::SkeletonInstance *skel = mObjectParts[type]->mSkelBase->getSkeleton(); - if(mObjectParts[type]->mSkelBase->isParentTagPoint()) + src = mHeadAnimationTime; + + osg::Node* node = mObjectParts[type]->getNode(); + if (node->getUserDataContainer()) { - Ogre::Node *root = mObjectParts[type]->mSkelBase->getParentNode(); - if(skel->hasBone("BoneOffset")) + for (unsigned int i=0; igetUserDataContainer()->getNumUserObjects(); ++i) { - Ogre::Bone *offset = skel->getBone("BoneOffset"); - - root->translate(offset->getPosition()); - - // It appears that the BoneOffset rotation is completely bogus, at least for light models. - //root->rotate(offset->getOrientation()); - root->pitch(Ogre::Degree(-90.0f)); - - root->scale(offset->getScale()); - root->setInitialState(); - } - } - - if (isSkinned(mObjectParts[type])) - updateSkeletonInstance(mSkelBase->getSkeleton(), skel); - } - - std::vector >::iterator ctrl(mObjectParts[type]->mControllers.begin()); - for(;ctrl != mObjectParts[type]->mControllers.end();++ctrl) - { - if(ctrl->getSource().isNull()) - { - ctrl->setSource(mNullAnimationTimePtr); - - if (type == ESM::PRT_Head) - { - ctrl->setSource(mHeadAnimationTime); - const NifOgre::TextKeyMap& keys = mObjectParts[type]->mTextKeys; - for (NifOgre::TextKeyMap::const_iterator it = keys.begin(); it != keys.end(); ++it) + osg::Object* obj = node->getUserDataContainer()->getUserObject(i); + if (NifOsg::TextKeyMapHolder* keys = dynamic_cast(obj)) { - if (Misc::StringUtils::ciEqual(it->second, "talk: start")) - mHeadAnimationTime->setTalkStart(it->first); - if (Misc::StringUtils::ciEqual(it->second, "talk: stop")) - mHeadAnimationTime->setTalkStop(it->first); - if (Misc::StringUtils::ciEqual(it->second, "blink: start")) - mHeadAnimationTime->setBlinkStart(it->first); - if (Misc::StringUtils::ciEqual(it->second, "blink: stop")) - mHeadAnimationTime->setBlinkStop(it->first); + for (NifOsg::TextKeyMap::const_iterator it = keys->mTextKeys.begin(); it != keys->mTextKeys.end(); ++it) + { + if (Misc::StringUtils::ciEqual(it->second, "talk: start")) + mHeadAnimationTime->setTalkStart(it->first); + if (Misc::StringUtils::ciEqual(it->second, "talk: stop")) + mHeadAnimationTime->setTalkStop(it->first); + if (Misc::StringUtils::ciEqual(it->second, "blink: start")) + mHeadAnimationTime->setBlinkStart(it->first); + if (Misc::StringUtils::ciEqual(it->second, "blink: stop")) + mHeadAnimationTime->setBlinkStop(it->first); + } + + break; } } - else if (type == ESM::PRT_Weapon) - ctrl->setSource(mWeaponAnimationTime); } } + else if (type == ESM::PRT_Weapon) + src = mWeaponAnimationTime; + else + src.reset(new NullAnimationTime); + + SceneUtil::AssignControllerSourcesVisitor assignVisitor(src); + mObjectParts[type]->getNode()->accept(assignVisitor); return true; } -void NpcAnimation::addPartGroup(int group, int priority, const std::vector &parts, bool enchantedGlow, Ogre::Vector3* glowColor) +void NpcAnimation::addPartGroup(int group, int priority, const std::vector &parts, bool enchantedGlow, osg::Vec4f* glowColor) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::Store &partStore = store.get(); @@ -851,6 +936,30 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vector(found->second.get())) + { + osg::Node* node = found->second; + mFirstPersonNeckController = new NeckController(mObjectRoot.get()); + node->addUpdateCallback(mFirstPersonNeckController); + mActiveControllers.insert(std::make_pair(node, mFirstPersonNeckController)); + } + } + else if (mViewMode == VM_Normal) + { + WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); + } +} + void NpcAnimation::showWeapons(bool showWeapon) { mShowWeapons = showWeapon; @@ -860,7 +969,7 @@ void NpcAnimation::showWeapons(bool showWeapon) MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon != inv.end()) { - Ogre::Vector3 glowColor = getEnchantmentColor(*weapon); + osg::Vec4f glowColor = getEnchantmentColor(*weapon); std::string mesh = weapon->getClass().getModel(*weapon); addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); @@ -873,17 +982,16 @@ void NpcAnimation::showWeapons(bool showWeapon) if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt) attachArrow(); else - mAmmunition.setNull(); + mAmmunition.reset(); } else - mAmmunition.setNull(); + mAmmunition.reset(); } } else { removeIndividualPart(ESM::PRT_Weapon); } - mAlpha = 1.f; } void NpcAnimation::showCarriedLeft(bool show) @@ -893,37 +1001,52 @@ void NpcAnimation::showCarriedLeft(bool show) MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if(show && iter != inv.end()) { - Ogre::Vector3 glowColor = getEnchantmentColor(*iter); + osg::Vec4f glowColor = getEnchantmentColor(*iter); std::string mesh = iter->getClass().getModel(*iter); if (addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor)) { if (iter->getTypeName() == typeid(ESM::Light).name()) - addExtraLight(mInsert->getCreator(), mObjectParts[ESM::PRT_Shield], iter->get()->mBase); + addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), iter->get()->mBase); } } else removeIndividualPart(ESM::PRT_Shield); } -void NpcAnimation::configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) -{ - Ogre::Vector3 glowColor = getEnchantmentColor(ptr); - setRenderProperties(object, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, - !ptr.getClass().getEnchantment(ptr).empty(), &glowColor); - - std::for_each(object->mEntities.begin(), object->mEntities.end(), SetObjectGroup(slot)); - std::for_each(object->mParticles.begin(), object->mParticles.end(), SetObjectGroup(slot)); -} - void NpcAnimation::attachArrow() { WeaponAnimation::attachArrow(mPtr); } -void NpcAnimation::releaseArrow() +void NpcAnimation::releaseArrow(float attackStrength) { - WeaponAnimation::releaseArrow(mPtr); + WeaponAnimation::releaseArrow(mPtr, attackStrength); +} + +osg::Group* NpcAnimation::getArrowBone() +{ + PartHolderPtr part = mObjectParts[ESM::PRT_Weapon]; + if (!part) + return NULL; + + SceneUtil::FindByNameVisitor findVisitor ("ArrowBone"); + part->getNode()->accept(findVisitor); + + return findVisitor.mFoundNode; +} + +osg::Node* NpcAnimation::getWeaponNode() +{ + PartHolderPtr part = mObjectParts[ESM::PRT_Weapon]; + if (!part) + return NULL; + return part->getNode(); +} + +Resource::ResourceSystem* NpcAnimation::getResourceSystem() +{ + return mResourceSystem; } void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) @@ -954,73 +1077,14 @@ void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, boo } } -void NpcAnimation::setAlpha(float alpha) -{ - if (alpha == mAlpha) - return; - mAlpha = alpha; - - for (int i=0; imEntities.size(); ++j) - { - Ogre::Entity* ent = mObjectParts[i]->mEntities[j]; - if (ent != mObjectParts[i]->mSkelBase) - applyAlpha(alpha, ent, mObjectParts[i]); - } - } -} - void NpcAnimation::enableHeadAnimation(bool enable) { mHeadAnimationTime->setEnabled(enable); } -void NpcAnimation::preRender(Ogre::Camera *camera) +void NpcAnimation::setWeaponGroup(const std::string &group) { - Animation::preRender(camera); - for (int i=0; irotateBillboardNodes(camera); - } -} - -void NpcAnimation::applyAlpha(float alpha, Ogre::Entity *ent, NifOgre::ObjectScenePtr scene) -{ - sh::Factory::getInstance()._ensureMaterial(ent->getSubEntity(0)->getMaterial()->getName(), "Default"); - ent->getSubEntity(0)->setRenderQueueGroup(alpha != 1.f || ent->getSubEntity(0)->getMaterial()->isTransparent() - ? RQG_Alpha : RQG_Main); - - - Ogre::MaterialPtr mat = scene->mMaterialControllerMgr.getWritableMaterial(ent); - if (mAlpha == 1.f) - { - // Don't bother remembering what the original values were. Just remove the techniques and let the factory restore them. - mat->removeAllTechniques(); - sh::Factory::getInstance()._ensureMaterial(mat->getName(), "Default"); - return; - } - - Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); - while(techs.hasMoreElements()) - { - Ogre::Technique *tech = techs.getNext(); - Ogre::Technique::PassIterator passes = tech->getPassIterator(); - while(passes.hasMoreElements()) - { - Ogre::Pass *pass = passes.getNext(); - pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); - Ogre::ColourValue diffuse = pass->getDiffuse(); - diffuse.a = alpha; - pass->setDiffuse(diffuse); - pass->setVertexColourTracking(pass->getVertexColourTracking() &~Ogre::TVC_DIFFUSE); - } - } + mWeaponAnimationTime->setGroup(group); } void NpcAnimation::equipmentChanged() @@ -1034,31 +1098,27 @@ void NpcAnimation::setVampire(bool vampire) return; if ((mNpcType == Type_Vampire) != vampire) { - if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (mPtr == MWMechanics::getPlayer()) MWBase::Environment::get().getWorld()->reattachPlayerCamera(); else rebuild(); } } -void NpcAnimation::setHeadPitch(Ogre::Radian pitch) +void NpcAnimation::setFirstPersonOffset(const osg::Vec3f &offset) { - mHeadPitch = pitch; + mFirstPersonOffset = offset; } -void NpcAnimation::setHeadYaw(Ogre::Radian yaw) +void NpcAnimation::updatePtr(const MWWorld::Ptr &updated) { - mHeadYaw = yaw; + Animation::updatePtr(updated); + mHeadAnimationTime->updatePtr(updated); } -Ogre::Radian NpcAnimation::getHeadPitch() const +void NpcAnimation::setAccurateAiming(bool enabled) { - return mHeadPitch; -} - -Ogre::Radian NpcAnimation::getHeadYaw() const -{ - return mHeadYaw; + mAccurateAiming = enabled; } } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 90b1c269bf..c5fc62f9cc 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -15,38 +15,8 @@ namespace ESM namespace MWRender { -class HeadAnimationTime : public Ogre::ControllerValue -{ -private: - MWWorld::Ptr mReference; - float mTalkStart; - float mTalkStop; - float mBlinkStart; - float mBlinkStop; - - float mBlinkTimer; - - bool mEnabled; - - float mValue; -private: - void resetBlinkTimer(); -public: - HeadAnimationTime(MWWorld::Ptr reference); - - void update(float dt); - - void setEnabled(bool enabled); - - void setTalkStart(float value); - void setTalkStop(float value); - void setBlinkStart(float value); - void setBlinkStop(float value); - - virtual Ogre::Real getValue() const; - virtual void setValue(Ogre::Real value) - { } -}; +class NeckController; +class HeadAnimationTime; class NpcAnimation : public Animation, public WeaponAnimation, public MWWorld::InventoryStoreListener { @@ -69,7 +39,7 @@ private: bool mListenerDisabled; // Bounded Parts - NifOgre::ObjectScenePtr mObjectParts[ESM::PRT_Count]; + PartHolderPtr mObjectParts[ESM::PRT_Count]; std::string mSoundIds[ESM::PRT_Count]; const ESM::NPC *mNpc; @@ -87,44 +57,45 @@ private: }; NpcType mNpcType; - int mVisibilityFlags; - int mPartslots[ESM::PRT_Count]; //Each part slot is taken by clothing, armor, or is empty int mPartPriorities[ESM::PRT_Count]; - Ogre::Vector3 mFirstPersonOffset; + osg::Vec3f mFirstPersonOffset; + // Field of view to use when rendering first person meshes + float mFirstPersonFieldOfView; - Ogre::SharedPtr mHeadAnimationTime; - Ogre::SharedPtr mWeaponAnimationTime; + boost::shared_ptr mHeadAnimationTime; + boost::shared_ptr mWeaponAnimationTime; - float mAlpha; bool mSoundsDisabled; - Ogre::Radian mHeadYaw; - Ogre::Radian mHeadPitch; + bool mAccurateAiming; + float mAimingFactor; void updateNpcBase(); - NifOgre::ObjectScenePtr insertBoundedPart(const std::string &model, int group, const std::string &bonename, - const std::string &bonefilter, - bool enchantedGlow, Ogre::Vector3* glowColor=NULL); + PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename, + const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor=NULL); void removeIndividualPart(ESM::PartReferenceType type); void reserveIndividualPart(ESM::PartReferenceType type, int group, int priority); bool addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, - bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); + bool enchantedGlow=false, osg::Vec4f* glowColor=NULL); void removePartGroup(int group); void addPartGroup(int group, int priority, const std::vector &parts, - bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); + bool enchantedGlow=false, osg::Vec4f* glowColor=NULL); - void applyAlpha(float alpha, Ogre::Entity* ent, NifOgre::ObjectScenePtr scene); + virtual void setRenderBin(); + + osg::ref_ptr mFirstPersonNeckController; + +protected: + virtual void addControllers(); public: /** * @param ptr - * @param node - * @param visibilityFlags * @param disableListener Don't listen for equipment changes and magic effects. InventoryStore only supports * one listener at a time, so you shouldn't do this if creating several NpcAnimations * for the same Ptr, eg preview dolls for the player. @@ -132,55 +103,53 @@ public: * @param disableSounds Same as \a disableListener but for playing items sounds * @param viewMode */ - NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, bool disableListener = false, - bool disableSounds = false, ViewMode viewMode=VM_Normal); + NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, bool disableListener = false, + bool disableSounds = false, ViewMode viewMode=VM_Normal, float firstPersonFieldOfView=55.f); virtual ~NpcAnimation(); virtual void enableHeadAnimation(bool enable); - virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); } + /// 1: the first person meshes follow the camera's rotation completely + /// 0: the first person meshes follow the camera with a reduced factor, so you can look down at your own hands + virtual void setAccurateAiming(bool enabled); - virtual Ogre::Vector3 runAnimation(float timepassed); + virtual void setWeaponGroup(const std::string& group); + + virtual osg::Vec3f runAnimation(float timepassed); /// A relative factor (0-1) that decides if and how much the skeleton should be pitched /// to indicate the facing orientation of the character. virtual void setPitchFactor(float factor) { mPitchFactor = factor; } - virtual void setHeadPitch(Ogre::Radian pitch); - virtual void setHeadYaw(Ogre::Radian yaw); - virtual Ogre::Radian getHeadPitch() const; - virtual Ogre::Radian getHeadYaw() const; - virtual void showWeapons(bool showWeapon); virtual void showCarriedLeft(bool show); virtual void attachArrow(); - virtual void releaseArrow(); + virtual void releaseArrow(float attackStrength); + + virtual osg::Group* getArrowBone(); + virtual osg::Node* getWeaponNode(); + virtual Resource::ResourceSystem* getResourceSystem(); // WeaponAnimation - virtual NifOgre::ObjectScenePtr getWeapon() { return mObjectParts[ESM::PRT_Weapon]; } virtual void showWeapon(bool show) { showWeapons(show); } - virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot); void setViewMode(ViewMode viewMode); void updateParts(); - /// \brief Applies a translation to the arms and hands. - /// This may be called multiple times before the animation - /// is updated to add additional offsets. - void addFirstPersonOffset(const Ogre::Vector3 &offset); - /// Rebuilds the NPC, updating their root model, animation sources, and equipment. void rebuild(); - /// Make the NPC only partially visible - virtual void setAlpha(float alpha); + /// Get the inventory slot that the given node path leads into, or -1 if not found. + int getSlot(const osg::NodePath& path) const; virtual void setVampire(bool vampire); - /// Prepare this animation for being rendered with \a camera (rotates billboard nodes) - virtual void preRender (Ogre::Camera* camera); + /// Set a translation offset (in object root space) to apply to meshes when in first person mode. + void setFirstPersonOffset(const osg::Vec3f& offset); + + virtual void updatePtr(const MWWorld::Ptr& updated); }; } diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index bdaa2d5157..f58ebb9174 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -2,163 +2,180 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include -#include -#include +#include +#include -#include -#include +#include + +#include +#include #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/cellstore.hpp" -#include "renderconst.hpp" #include "animation.hpp" +#include "npcanimation.hpp" +#include "creatureanimation.hpp" +#include "vismask.hpp" -using namespace MWRender; - -int Objects::uniqueID = 0; - -void Objects::setRootNode(Ogre::SceneNode* root) +namespace { - mRootNode = root; + + /// Removes all particle systems and related nodes in a subgraph. + class RemoveParticlesVisitor : public osg::NodeVisitor + { + public: + RemoveParticlesVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { } + + virtual void apply(osg::Node &node) + { + if (dynamic_cast(&node)) + mToRemove.push_back(&node); + + traverse(node); + } + + virtual void apply(osg::Geode& geode) + { + std::vector partsysVector; + for (unsigned int i=0; i(drw)) + partsysVector.push_back(partsys); + } + + for (std::vector::iterator it = partsysVector.begin(); it != partsysVector.end(); ++it) + geode.removeDrawable(*it); + } +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) + virtual void apply(osg::Drawable& drw) + { + if (osgParticle::ParticleSystem* partsys = dynamic_cast(&drw)) + mToRemove.push_back(partsys); + } +#endif + + void remove() + { + for (std::vector >::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) + { + // FIXME: a Drawable might have more than one parent + osg::Node* node = *it; + if (node->getNumParents()) + node->getParent(0)->removeChild(node); + } + mToRemove.clear(); + } + + private: + std::vector > mToRemove; + }; + +} + + +namespace MWRender +{ + +Objects::Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode) + : mRootNode(rootNode) + , mResourceSystem(resourceSystem) +{ +} + +Objects::~Objects() +{ + for(PtrAnimationMap::iterator iter = mObjects.begin();iter != mObjects.end();++iter) + delete iter->second; + mObjects.clear(); + + for (CellMap::iterator iter = mCellSceneNodes.begin(); iter != mCellSceneNodes.end(); ++iter) + iter->second->getParent(0)->removeChild(iter->second); + mCellSceneNodes.clear(); } void Objects::insertBegin(const MWWorld::Ptr& ptr) { - Ogre::SceneNode* root = mRootNode; - Ogre::SceneNode* cellnode; - if(mCellSceneNodes.find(ptr.getCell()) == mCellSceneNodes.end()) + osg::ref_ptr cellnode; + + CellMap::iterator found = mCellSceneNodes.find(ptr.getCell()); + if (found == mCellSceneNodes.end()) { - //Create the scenenode and put it in the map - cellnode = root->createChildSceneNode(); + cellnode = new osg::Group; + mRootNode->addChild(cellnode); mCellSceneNodes[ptr.getCell()] = cellnode; } else - { - cellnode = mCellSceneNodes[ptr.getCell()]; - } + cellnode = found->second; + + osg::ref_ptr insert (new SceneUtil::PositionAttitudeTransform); + cellnode->addChild(insert); + + insert->getOrCreateUserDataContainer()->addUserObject(new PtrHolder(ptr)); - Ogre::SceneNode* insert = cellnode->createChildSceneNode(); const float *f = ptr.getRefData().getPosition().pos; - insert->setPosition(f[0], f[1], f[2]); - insert->setScale(ptr.getCellRef().getScale(), ptr.getCellRef().getScale(), ptr.getCellRef().getScale()); + insert->setPosition(osg::Vec3(f[0], f[1], f[2])); - - // Convert MW rotation to a quaternion: - f = ptr.getCellRef().getPosition().rot; - - // Rotate around X axis - Ogre::Quaternion xr(Ogre::Radian(-f[0]), Ogre::Vector3::UNIT_X); - - // Rotate around Y axis - Ogre::Quaternion yr(Ogre::Radian(-f[1]), Ogre::Vector3::UNIT_Y); - - // Rotate around Z axis - Ogre::Quaternion zr(Ogre::Radian(-f[2]), Ogre::Vector3::UNIT_Z); - - // Rotates first around z, then y, then x - insert->setOrientation(xr*yr*zr); + const float scale = ptr.getCellRef().getScale(); + osg::Vec3f scaleVec(scale, scale, scale); + ptr.getClass().adjustScale(ptr, scaleVec, true); + insert->setScale(scaleVec); ptr.getRefData().setBaseNode(insert); } -void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool batch) +void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool animated, bool allowLight) { insertBegin(ptr); - std::auto_ptr anim(new ObjectAnimation(ptr, mesh)); + std::auto_ptr anim (new ObjectAnimation(ptr, mesh, mResourceSystem, animated, allowLight)); - if (!mesh.empty()) + if (!allowLight) { - Ogre::AxisAlignedBox bounds = anim->getWorldBounds(); - Ogre::Vector3 extents = bounds.getSize(); - extents *= ptr.getRefData().getBaseNode()->getScale(); - float size = std::max(std::max(extents.x, extents.y), extents.z); - - bool small = (size < Settings::Manager::getInt("small object size", "Viewing distance")) && - Settings::Manager::getBool("limit small object distance", "Viewing distance"); - // do not fade out doors. that will cause holes and look stupid - if(ptr.getTypeName().find("Door") != std::string::npos) - small = false; - - if (mBounds.find(ptr.getCell()) == mBounds.end()) - mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL; - mBounds[ptr.getCell()].merge(bounds); - - if(batch && - Settings::Manager::getBool("use static geometry", "Objects") && - anim->canBatch()) - { - Ogre::StaticGeometry* sg = 0; - - if (small) - { - if(mStaticGeometrySmall.find(ptr.getCell()) == mStaticGeometrySmall.end()) - { - uniqueID = uniqueID+1; - sg = mRenderer.getScene()->createStaticGeometry("sg" + Ogre::StringConverter::toString(uniqueID)); - sg->setOrigin(ptr.getRefData().getBaseNode()->getPosition()); - mStaticGeometrySmall[ptr.getCell()] = sg; - - sg->setRenderingDistance(static_cast(Settings::Manager::getInt("small object distance", "Viewing distance"))); - } - else - sg = mStaticGeometrySmall[ptr.getCell()]; - } - else - { - if(mStaticGeometry.find(ptr.getCell()) == mStaticGeometry.end()) - { - uniqueID = uniqueID+1; - sg = mRenderer.getScene()->createStaticGeometry("sg" + Ogre::StringConverter::toString(uniqueID)); - sg->setOrigin(ptr.getRefData().getBaseNode()->getPosition()); - mStaticGeometry[ptr.getCell()] = sg; - } - else - sg = mStaticGeometry[ptr.getCell()]; - } - - // This specifies the size of a single batch region. - // If it is set too high: - // - there will be problems choosing the correct lights - // - the culling will be more inefficient - // If it is set too low: - // - there will be too many batches. - if(ptr.getCell()->isExterior()) - sg->setRegionDimensions(Ogre::Vector3(2048,2048,2048)); - else - sg->setRegionDimensions(Ogre::Vector3(1024,1024,1024)); - - sg->setVisibilityFlags(small ? RV_StaticsSmall : RV_Statics); - - sg->setCastShadows(true); - - sg->setRenderQueueGroup(RQG_Main); - - anim->fillBatch(sg); - /* TODO: We could hold on to this and just detach it from the scene graph, so if the Ptr - * ever needs to modify we can reattach it and rebuild the StaticGeometry object without - * it. Would require associating the Ptr with the StaticGeometry. */ - anim.reset(); - } + RemoveParticlesVisitor visitor; + anim->getObjectRoot()->accept(visitor); + visitor.remove(); } - if(anim.get() != NULL) - mObjects.insert(std::make_pair(ptr, anim.release())); + mObjects.insert(std::make_pair(ptr, anim.release())); } -bool Objects::deleteObject (const MWWorld::Ptr& ptr) +void Objects::insertCreature(const MWWorld::Ptr &ptr, const std::string &mesh, bool weaponsShields) +{ + insertBegin(ptr); + ptr.getRefData().getBaseNode()->setNodeMask(Mask_Actor); + + // CreatureAnimation + std::auto_ptr anim; + + if (weaponsShields) + anim.reset(new CreatureWeaponAnimation(ptr, mesh, mResourceSystem)); + else + anim.reset(new CreatureAnimation(ptr, mesh, mResourceSystem)); + + mObjects.insert(std::make_pair(ptr, anim.release())); +} + +void Objects::insertNPC(const MWWorld::Ptr &ptr) +{ + insertBegin(ptr); + ptr.getRefData().getBaseNode()->setNodeMask(Mask_Actor); + + std::auto_ptr anim (new NpcAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), mResourceSystem)); + + mObjects.insert(std::make_pair(ptr, anim.release())); +} + +bool Objects::removeObject (const MWWorld::Ptr& ptr) { if(!ptr.getRefData().getBaseNode()) return true; @@ -169,16 +186,15 @@ bool Objects::deleteObject (const MWWorld::Ptr& ptr) delete iter->second; mObjects.erase(iter); - mRenderer.getScene()->destroySceneNode(ptr.getRefData().getBaseNode()); - ptr.getRefData().setBaseNode(0); + ptr.getRefData().getBaseNode()->getParent(0)->removeChild(ptr.getRefData().getBaseNode()); + ptr.getRefData().setBaseNode(NULL); return true; } - return false; } -void Objects::removeCell(MWWorld::CellStore* store) +void Objects::removeCell(const MWWorld::CellStore* store) { for(PtrAnimationMap::iterator iter = mObjects.begin();iter != mObjects.end();) { @@ -191,108 +207,69 @@ void Objects::removeCell(MWWorld::CellStore* store) ++iter; } - std::map::iterator geom = mStaticGeometry.find(store); - if(geom != mStaticGeometry.end()) - { - Ogre::StaticGeometry *sg = geom->second; - mStaticGeometry.erase(geom); - mRenderer.getScene()->destroyStaticGeometry(sg); - } - - geom = mStaticGeometrySmall.find(store); - if(geom != mStaticGeometrySmall.end()) - { - Ogre::StaticGeometry *sg = geom->second; - mStaticGeometrySmall.erase(store); - mRenderer.getScene()->destroyStaticGeometry(sg); - } - - mBounds.erase(store); - - std::map::iterator cell = mCellSceneNodes.find(store); + CellMap::iterator cell = mCellSceneNodes.find(store); if(cell != mCellSceneNodes.end()) { - cell->second->removeAndDestroyAllChildren(); - mRenderer.getScene()->destroySceneNode(cell->second); + cell->second->getParent(0)->removeChild(cell->second); mCellSceneNodes.erase(cell); } } -void Objects::buildStaticGeometry(MWWorld::CellStore& cell) +void Objects::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) { - if(mStaticGeometry.find(&cell) != mStaticGeometry.end()) - { - Ogre::StaticGeometry* sg = mStaticGeometry[&cell]; - sg->build(); - } - if(mStaticGeometrySmall.find(&cell) != mStaticGeometrySmall.end()) - { - Ogre::StaticGeometry* sg = mStaticGeometrySmall[&cell]; - sg->build(); - } -} + osg::Node* objectNode = cur.getRefData().getBaseNode(); + if (!objectNode) + return; -Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::CellStore* cell) -{ - return mBounds[cell]; -} - -void Objects::update(float dt, Ogre::Camera* camera) -{ - PtrAnimationMap::const_iterator it = mObjects.begin(); - for(;it != mObjects.end();++it) - it->second->runAnimation(dt); - - it = mObjects.begin(); - for(;it != mObjects.end();++it) - it->second->preRender(camera); - -} - -void Objects::rebuildStaticGeometry() -{ - for (std::map::iterator it = mStaticGeometry.begin(); it != mStaticGeometry.end(); ++it) - { - it->second->destroy(); - it->second->build(); - } - - for (std::map::iterator it = mStaticGeometrySmall.begin(); it != mStaticGeometrySmall.end(); ++it) - { - it->second->destroy(); - it->second->build(); - } -} - -void Objects::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) -{ - Ogre::SceneNode *node; MWWorld::CellStore *newCell = cur.getCell(); + osg::Group* cellnode; if(mCellSceneNodes.find(newCell) == mCellSceneNodes.end()) { - node = mRootNode->createChildSceneNode(); - mCellSceneNodes[newCell] = node; + cellnode = new osg::Group; + mRootNode->addChild(cellnode); + mCellSceneNodes[newCell] = cellnode; } else { - node = mCellSceneNodes[newCell]; + cellnode = mCellSceneNodes[newCell]; } - node->addChild(cur.getRefData().getBaseNode()); + osg::UserDataContainer* userDataContainer = objectNode->getUserDataContainer(); + if (userDataContainer) + for (unsigned int i=0; igetNumUserObjects(); ++i) + { + if (dynamic_cast(userDataContainer->getUserObject(i))) + userDataContainer->setUserObject(i, new PtrHolder(cur)); + } + + if (objectNode->getNumParents()) + objectNode->getParent(0)->removeChild(objectNode); + cellnode->addChild(objectNode); PtrAnimationMap::iterator iter = mObjects.find(old); if(iter != mObjects.end()) { - ObjectAnimation *anim = iter->second; + Animation *anim = iter->second; mObjects.erase(iter); anim->updatePtr(cur); mObjects[cur] = anim; } } -ObjectAnimation* Objects::getAnimation(const MWWorld::Ptr &ptr) +Animation* Objects::getAnimation(const MWWorld::Ptr &ptr) { PtrAnimationMap::const_iterator iter = mObjects.find(ptr); if(iter != mObjects.end()) return iter->second; + return NULL; } +const Animation* Objects::getAnimation(const MWWorld::ConstPtr &ptr) const +{ + PtrAnimationMap::const_iterator iter = mObjects.find(ptr); + if(iter != mObjects.end()) + return iter->second; + + return NULL; +} + +} diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index adfe5ca26b..3d0c92cb43 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -1,67 +1,99 @@ #ifndef GAME_RENDER_OBJECTS_H #define GAME_RENDER_OBJECTS_H -#include -#include +#include +#include +#include -#include +#include +#include + +#include "../mwworld/ptr.hpp" + +namespace osg +{ + class Group; +} + +namespace osgUtil +{ + class IncrementalCompileOperation; +} + +namespace Resource +{ + class ResourceSystem; +} namespace MWWorld { - class Ptr; class CellStore; } namespace MWRender{ -class ObjectAnimation; +class Animation; + +class PtrHolder : public osg::Object +{ +public: + PtrHolder(MWWorld::Ptr ptr) + : mPtr(ptr) + { + } + + PtrHolder() + { + } + + PtrHolder(const PtrHolder& copy, const osg::CopyOp& copyop) + : mPtr(copy.mPtr) + { + } + + META_Object(MWRender, PtrHolder) + + MWWorld::Ptr mPtr; +}; class Objects{ - typedef std::map PtrAnimationMap; + typedef std::map PtrAnimationMap; - OEngine::Render::OgreRenderer &mRenderer; - - std::map mCellSceneNodes; - std::map mStaticGeometry; - std::map mStaticGeometrySmall; - std::map mBounds; + typedef std::map > CellMap; + CellMap mCellSceneNodes; PtrAnimationMap mObjects; - Ogre::SceneNode* mRootNode; - - static int uniqueID; + osg::ref_ptr mRootNode; void insertBegin(const MWWorld::Ptr& ptr); - + Resource::ResourceSystem* mResourceSystem; public: - Objects(OEngine::Render::OgreRenderer &renderer) - : mRenderer(renderer) - , mRootNode(NULL) - {} - ~Objects(){} - void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool batch=false); + Objects(Resource::ResourceSystem* resourceSystem, osg::ref_ptr rootNode); + ~Objects(); - ObjectAnimation* getAnimation(const MWWorld::Ptr &ptr); + /// @param animated Attempt to load separate keyframes from a .kf file matching the model file? + /// @param allowLight If false, no lights will be created, and particles systems will be removed. + void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool animated=false, bool allowLight=true); - void update (float dt, Ogre::Camera* camera); - ///< per-frame update + void insertNPC(const MWWorld::Ptr& ptr); + void insertCreature (const MWWorld::Ptr& ptr, const std::string& model, bool weaponsShields); - Ogre::AxisAlignedBox getDimensions(MWWorld::CellStore*); - ///< get a bounding box that encloses all objects in the specified cell + Animation* getAnimation(const MWWorld::Ptr &ptr); + const Animation* getAnimation(const MWWorld::ConstPtr &ptr) const; - bool deleteObject (const MWWorld::Ptr& ptr); + bool removeObject (const MWWorld::Ptr& ptr); ///< \return found? - void removeCell(MWWorld::CellStore* store); - void buildStaticGeometry(MWWorld::CellStore &cell); - void setRootNode(Ogre::SceneNode* root); - - void rebuildStaticGeometry(); + void removeCell(const MWWorld::CellStore* store); /// Updates containing cell for object rendering data - void updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur); + void updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &cur); + +private: + void operator = (const Objects&); + Objects(const Objects&); }; } #endif diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp deleted file mode 100644 index 2693d68b2e..0000000000 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include "occlusionquery.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "renderconst.hpp" - -using namespace MWRender; -using namespace Ogre; - -OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : - mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mActiveQuery(0), - mBBQueryVisible(0), mBBQueryTotal(0), mSunNode(sunNode), mBBNodeReal(0), - mSunVisibility(0), - mWasVisible(false), - mActive(false), - mFirstFrame(true), - mDoQuery(0), - mRendering(renderer) -{ - try { - RenderSystem* renderSystem = Root::getSingleton().getRenderSystem(); - - mSunTotalAreaQuery = renderSystem->createHardwareOcclusionQuery(); - mSunVisibleAreaQuery = renderSystem->createHardwareOcclusionQuery(); - - mSupported = (mSunTotalAreaQuery != 0) && (mSunVisibleAreaQuery != 0); - } - catch (Ogre::Exception&) - { - mSupported = false; - } - - if (!mSupported) - { - std::cout << "Hardware occlusion queries not supported." << std::endl; - return; - } - - mBBNodeReal = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); - - static Ogre::Mesh* plane = MeshManager::getSingleton().createPlane("occlusionbillboard", - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::Plane(Ogre::Vector3(0,0,1), 0), 1, 1, 1, 1, true, 1, 1, 1, Vector3::UNIT_Y).get(); - plane->_setBounds(Ogre::AxisAlignedBox::BOX_INFINITE); - - mBBQueryTotal = mRendering->getScene()->createEntity("occlusionbillboard"); - mBBQueryTotal->setCastShadows(false); - mBBQueryTotal->setVisibilityFlags(RV_OcclusionQuery); - mBBQueryTotal->setRenderQueueGroup(RQG_OcclusionQuery+1); - mBBQueryTotal->setMaterialName("QueryTotalPixels"); - mBBNodeReal->attachObject(mBBQueryTotal); - - mBBQueryVisible = mRendering->getScene()->createEntity("occlusionbillboard"); - mBBQueryVisible->setCastShadows(false); - mBBQueryVisible->setVisibilityFlags(RV_OcclusionQuery); - mBBQueryVisible->setRenderQueueGroup(RQG_OcclusionQuery+1); - mBBQueryVisible->setMaterialName("QueryVisiblePixels"); - mBBNodeReal->attachObject(mBBQueryVisible); - - mRendering->getScene()->addRenderObjectListener(this); - mRendering->getScene()->addRenderQueueListener(this); - mDoQuery = true; -} - -OcclusionQuery::~OcclusionQuery() -{ - mRendering->getScene()->removeRenderObjectListener (this); - mRendering->getScene()->removeRenderQueueListener(this); - - RenderSystem* renderSystem = Root::getSingleton().getRenderSystem(); - if (mSunTotalAreaQuery) - renderSystem->destroyHardwareOcclusionQuery(mSunTotalAreaQuery); - if (mSunVisibleAreaQuery) - renderSystem->destroyHardwareOcclusionQuery(mSunVisibleAreaQuery); -} - -bool OcclusionQuery::supported() -{ - return mSupported; -} - -void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass, const AutoParamDataSource* source, - const LightList* pLightList, bool suppressRenderStateChanges) -{ - if (!mActive) return; - - // The following code activates and deactivates the occlusion queries - // so that the queries only include the rendering of the intended meshes - - // Close the last occlusion query - // Each occlusion query should only last a single rendering - if (mActiveQuery != NULL) - { - mActiveQuery->endOcclusionQuery(); - mActiveQuery = NULL; - } - - // Open a new occlusion query - if (mDoQuery == true) - { - if (rend == mBBQueryTotal->getSubEntity(0)) - { - mActiveQuery = mSunTotalAreaQuery; - mWasVisible = true; - } - else if (rend == mBBQueryVisible->getSubEntity(0)) - { - mActiveQuery = mSunVisibleAreaQuery; - } - } - - if (mActiveQuery != NULL) - mActiveQuery->beginOcclusionQuery(); -} - -void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeatThisInvocation) -{ - if (!mActive) return; - - if (mActiveQuery != NULL) - { - mActiveQuery->endOcclusionQuery(); - mActiveQuery = NULL; - } - /** - * for every beginOcclusionQuery(), we want a respective pullOcclusionQuery() and vice versa - * this also means that results can be wrong at other places if we pull, but beginOcclusionQuery() was never called - * this can happen for example if the object that is tested is outside of the view frustum - * to prevent this, check if the queries have been performed after everything has been rendered and if not, start them manually - */ - if (queueGroupId == RQG_SkiesLate) - { - if (mWasVisible == false && mDoQuery) - { - mSunTotalAreaQuery->beginOcclusionQuery(); - mSunTotalAreaQuery->endOcclusionQuery(); - mSunVisibleAreaQuery->beginOcclusionQuery(); - mSunVisibleAreaQuery->endOcclusionQuery(); - } - } -} - -void OcclusionQuery::update(float duration) -{ - if (mFirstFrame) - { - // GLHardwareOcclusionQuery::isStillOutstanding doesn't seem to like getting called when nothing has been rendered yet - mFirstFrame = false; - return; - } - if (!mSupported) return; - - mWasVisible = false; - - // Adjust the position of the sun billboards according to camera viewing distance - // we need to do this to make sure that _everything_ can occlude the sun - float dist = mRendering->getCamera()->getFarClipDistance(); - if (dist==0) dist = 10000000; - dist -= 1000; // bias - dist /= 1000.f; - if (mSunNode) - { - mBBNodeReal->setPosition(mSunNode->getPosition() * dist); - mBBNodeReal->setOrientation(Ogre::Vector3::UNIT_Z.getRotationTo(-mBBNodeReal->getPosition().normalisedCopy())); - mBBNodeReal->setScale(150.f*dist, 150.f*dist, 150.f*dist); - } - - // Stop occlusion queries until we get their information - // (may not happen on the same frame they are requested in) - mDoQuery = false; - - if (!mSunTotalAreaQuery->isStillOutstanding() - && !mSunVisibleAreaQuery->isStillOutstanding()) - { - unsigned int totalPixels; - unsigned int visiblePixels; - - mSunTotalAreaQuery->pullOcclusionQuery(&totalPixels); - mSunVisibleAreaQuery->pullOcclusionQuery(&visiblePixels); - - if (totalPixels == 0) - { - // probably outside of the view frustum - mSunVisibility = 0; - } - else - { - mSunVisibility = float(visiblePixels) / float(totalPixels); - if (mSunVisibility > 1) mSunVisibility = 1; - } - - mDoQuery = true; - } -} - -void OcclusionQuery::setSunNode(Ogre::SceneNode* node) -{ - mSunNode = node; -} diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp deleted file mode 100644 index 6974f37b96..0000000000 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef GAME_OCCLUSION_QUERY_H -#define GAME_OCCLUSION_QUERY_H - -#include -#include - -namespace Ogre -{ - class HardwareOcclusionQuery; - class Entity; - class SceneNode; -} - -#include - -namespace MWRender -{ - /// - /// \brief Implements hardware occlusion queries on the GPU - /// - class OcclusionQuery : public Ogre::RenderObjectListener, public Ogre::RenderQueueListener - { - public: - OcclusionQuery(OEngine::Render::OgreRenderer*, Ogre::SceneNode* sunNode); - ~OcclusionQuery(); - - /** - * @return true if occlusion queries are supported on the user's hardware - */ - bool supported(); - - /** - * make sure to disable occlusion queries before updating unrelated render targets - * @param active - */ - void setActive (bool active) { mActive = active; } - - /** - * per-frame update - */ - void update(float duration); - - float getSunVisibility() const {return mSunVisibility;}; - - void setSunNode(Ogre::SceneNode* node); - - private: - Ogre::HardwareOcclusionQuery* mSunTotalAreaQuery; - Ogre::HardwareOcclusionQuery* mSunVisibleAreaQuery; - Ogre::HardwareOcclusionQuery* mActiveQuery; - - Ogre::Entity* mBBQueryVisible; - Ogre::Entity* mBBQueryTotal; - - Ogre::SceneNode* mSunNode; - Ogre::SceneNode* mBBNodeReal; - float mSunVisibility; - - bool mWasVisible; - - bool mActive; - bool mFirstFrame; - - bool mSupported; - bool mDoQuery; - - OEngine::Render::OgreRenderer* mRendering; - - protected: - virtual void notifyRenderSingleObject(Ogre::Renderable* rend, const Ogre::Pass* pass, const Ogre::AutoParamDataSource* source, - const Ogre::LightList* pLightList, bool suppressRenderStateChanges); - - virtual void renderQueueEnded(Ogre::uint8 queueGroupId, const Ogre::String& invocation, bool& repeatThisInvocation); - }; -} - -#endif diff --git a/apps/openmw/mwrender/pathgrid.cpp b/apps/openmw/mwrender/pathgrid.cpp new file mode 100644 index 0000000000..ae8bda1fb7 --- /dev/null +++ b/apps/openmw/mwrender/pathgrid.cpp @@ -0,0 +1,262 @@ +#include "pathgrid.hpp" + +#include + +#include +#include +#include +#include + +#include +#include + +#include "../mwbase/world.hpp" // these includes can be removed once the static-hack is gone +#include "../mwbase/environment.hpp" + +#include "../mwworld/ptr.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwmechanics/pathfinding.hpp" +#include "../mwmechanics/coordinateconverter.hpp" + +#include "vismask.hpp" + +namespace MWRender +{ + +static const int POINT_MESH_BASE = 35; + +osg::ref_ptr Pathgrid::createPathgridLines(const ESM::Pathgrid *pathgrid) +{ + osg::ref_ptr geom = new osg::Geometry; + + osg::ref_ptr vertices = new osg::Vec3Array; + + for(ESM::Pathgrid::EdgeList::const_iterator it = pathgrid->mEdges.begin(); + it != pathgrid->mEdges.end(); + ++it) + { + const ESM::Pathgrid::Edge &edge = *it; + const ESM::Pathgrid::Point &p1 = pathgrid->mPoints[edge.mV0], &p2 = pathgrid->mPoints[edge.mV1]; + + osg::Vec3f direction = MWMechanics::PathFinder::MakeOsgVec3(p2) - MWMechanics::PathFinder::MakeOsgVec3(p1); + osg::Vec3f lineDisplacement = (direction^osg::Vec3f(0,0,1)); + lineDisplacement.normalize(); + + lineDisplacement = lineDisplacement * POINT_MESH_BASE + + osg::Vec3f(0, 0, 10); // move lines up a little, so they will be less covered by meshes/landscape + + vertices->push_back(MWMechanics::PathFinder::MakeOsgVec3(p1) + lineDisplacement); + vertices->push_back(MWMechanics::PathFinder::MakeOsgVec3(p2) + lineDisplacement); + } + + geom->setVertexArray(vertices); + + geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, vertices->size())); + + osg::ref_ptr colors = new osg::Vec4Array; + colors->push_back(osg::Vec4(1.f, 1.f, 0.f, 1.f)); + geom->setColorArray(colors, osg::Array::BIND_OVERALL); + + return geom; +} + +osg::ref_ptr Pathgrid::createPathgridPoints(const ESM::Pathgrid *pathgrid) +{ + osg::ref_ptr geom = new osg::Geometry; + + const float height = POINT_MESH_BASE * sqrtf(2); + + osg::ref_ptr vertices = new osg::Vec3Array; + osg::ref_ptr indices = new osg::UShortArray; + + bool first = true; + unsigned short startIndex = 0; + for(ESM::Pathgrid::PointList::const_iterator it = pathgrid->mPoints.begin(); + it != pathgrid->mPoints.end(); + ++it, startIndex += 6) + { + osg::Vec3f pointPos(MWMechanics::PathFinder::MakeOsgVec3(*it)); + + if (!first) + { + // degenerate triangle from previous octahedron + indices->push_back(startIndex - 4); // 2nd point of previous octahedron + indices->push_back(startIndex); // start point of current octahedron + } + + float pointMeshBase = static_cast(POINT_MESH_BASE); + + vertices->push_back(pointPos + osg::Vec3f(0, 0, height)); // 0 + vertices->push_back(pointPos + osg::Vec3f(-pointMeshBase, -pointMeshBase, 0)); // 1 + vertices->push_back(pointPos + osg::Vec3f(pointMeshBase, -pointMeshBase, 0)); // 2 + vertices->push_back(pointPos + osg::Vec3f(pointMeshBase, pointMeshBase, 0)); // 3 + vertices->push_back(pointPos + osg::Vec3f(-pointMeshBase, pointMeshBase, 0)); // 4 + vertices->push_back(pointPos + osg::Vec3f(0, 0, -height)); // 5 + + indices->push_back(startIndex + 0); + indices->push_back(startIndex + 1); + indices->push_back(startIndex + 2); + indices->push_back(startIndex + 5); + indices->push_back(startIndex + 3); + indices->push_back(startIndex + 4); + // degenerates + indices->push_back(startIndex + 4); + indices->push_back(startIndex + 5); + indices->push_back(startIndex + 5); + // end degenerates + indices->push_back(startIndex + 1); + indices->push_back(startIndex + 4); + indices->push_back(startIndex + 0); + indices->push_back(startIndex + 3); + indices->push_back(startIndex + 2); + + first = false; + } + + geom->setVertexArray(vertices); + + geom->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, indices->size(), &(*indices)[0])); + + osg::ref_ptr colors = new osg::Vec4Array; + colors->push_back(osg::Vec4(1.f, 0.f, 0.f, 1.f)); + geom->setColorArray(colors, osg::Array::BIND_OVERALL); + + return geom; +} + +Pathgrid::Pathgrid(osg::ref_ptr root) + : mPathgridEnabled(false) + , mRootNode(root) + , mPathGridRoot(NULL) + , mInteriorPathgridNode(NULL) +{ +} + +Pathgrid::~Pathgrid() +{ + if (mPathgridEnabled) + { + togglePathgrid(); + } +} + + +bool Pathgrid::toggleRenderMode (int mode){ + switch (mode) + { + case Render_Pathgrid: + togglePathgrid(); + return mPathgridEnabled; + default: + return false; + } + + return false; +} + +void Pathgrid::addCell(const MWWorld::CellStore *store) +{ + mActiveCells.push_back(store); + if (mPathgridEnabled) + enableCellPathgrid(store); +} + +void Pathgrid::removeCell(const MWWorld::CellStore *store) +{ + mActiveCells.erase(std::remove(mActiveCells.begin(), mActiveCells.end(), store), mActiveCells.end()); + if (mPathgridEnabled) + disableCellPathgrid(store); +} + +void Pathgrid::togglePathgrid() +{ + mPathgridEnabled = !mPathgridEnabled; + if (mPathgridEnabled) + { + // add path grid meshes to already loaded cells + mPathGridRoot = new osg::Group; + mPathGridRoot->setNodeMask(Mask_Debug); + mRootNode->addChild(mPathGridRoot); + + for(CellList::iterator it = mActiveCells.begin(); it != mActiveCells.end(); ++it) + { + enableCellPathgrid(*it); + } + } + else + { + // remove path grid meshes from already loaded cells + for(CellList::iterator it = mActiveCells.begin(); it != mActiveCells.end(); ++it) + { + disableCellPathgrid(*it); + } + + if (mPathGridRoot) + { + mRootNode->removeChild(mPathGridRoot); + mPathGridRoot = NULL; + } + } +} + +void Pathgrid::enableCellPathgrid(const MWWorld::CellStore *store) +{ + MWBase::World* world = MWBase::Environment::get().getWorld(); + const ESM::Pathgrid *pathgrid = + world->getStore().get().search(*store->getCell()); + if (!pathgrid) return; + + osg::Vec3f cellPathGridPos(0, 0, 0); + MWMechanics::CoordinateConverter(store->getCell()).toWorld(cellPathGridPos); + + osg::ref_ptr cellPathGrid = new osg::PositionAttitudeTransform; + cellPathGrid->setPosition(cellPathGridPos); + + osg::ref_ptr lineGeode = new osg::Geode; + osg::ref_ptr lines = createPathgridLines(pathgrid); + lineGeode->addDrawable(lines); + + osg::ref_ptr pointGeode = new osg::Geode; + osg::ref_ptr points = createPathgridPoints(pathgrid); + pointGeode->addDrawable(points); + + cellPathGrid->addChild(lineGeode); + cellPathGrid->addChild(pointGeode); + + mPathGridRoot->addChild(cellPathGrid); + + if (store->getCell()->isExterior()) + { + mExteriorPathgridNodes[std::make_pair(store->getCell()->getGridX(), store->getCell()->getGridY())] = cellPathGrid; + } + else + { + assert(mInteriorPathgridNode == NULL); + mInteriorPathgridNode = cellPathGrid; + } +} + +void Pathgrid::disableCellPathgrid(const MWWorld::CellStore *store) +{ + if (store->getCell()->isExterior()) + { + ExteriorPathgridNodes::iterator it = + mExteriorPathgridNodes.find(std::make_pair(store->getCell()->getGridX(), store->getCell()->getGridY())); + if (it != mExteriorPathgridNodes.end()) + { + mPathGridRoot->removeChild(it->second); + mExteriorPathgridNodes.erase(it); + } + } + else + { + if (mInteriorPathgridNode) + { + mPathGridRoot->removeChild(mInteriorPathgridNode); + mInteriorPathgridNode = NULL; + } + } +} + +} diff --git a/apps/openmw/mwrender/pathgrid.hpp b/apps/openmw/mwrender/pathgrid.hpp new file mode 100644 index 0000000000..39a6d71ed9 --- /dev/null +++ b/apps/openmw/mwrender/pathgrid.hpp @@ -0,0 +1,66 @@ +#ifndef GAME_RENDER_MWSCENE_H +#define GAME_RENDER_MWSCENE_H + +#include + +#include +#include +#include + +#include + +namespace ESM +{ + struct Pathgrid; +} + +namespace osg +{ + class Group; + class Geometry; +} + +namespace MWWorld +{ + class Ptr; + class CellStore; +} + +namespace MWRender +{ + class Pathgrid + { + bool mPathgridEnabled; + + void togglePathgrid(); + + typedef std::vector CellList; + CellList mActiveCells; + + osg::ref_ptr mRootNode; + + osg::ref_ptr mPathGridRoot; + + typedef std::map, osg::ref_ptr > ExteriorPathgridNodes; + ExteriorPathgridNodes mExteriorPathgridNodes; + osg::ref_ptr mInteriorPathgridNode; + + void enableCellPathgrid(const MWWorld::CellStore *store); + void disableCellPathgrid(const MWWorld::CellStore *store); + + // path grid meshes + osg::ref_ptr createPathgridLines(const ESM::Pathgrid *pathgrid); + osg::ref_ptr createPathgridPoints(const ESM::Pathgrid *pathgrid); + public: + Pathgrid(osg::ref_ptr root); + ~Pathgrid(); + bool toggleRenderMode (int mode); + + void addCell(const MWWorld::CellStore* store); + void removeCell(const MWWorld::CellStore* store); + }; + + +} + +#endif diff --git a/apps/openmw/mwrender/refraction.cpp b/apps/openmw/mwrender/refraction.cpp deleted file mode 100644 index 739ed24d9f..0000000000 --- a/apps/openmw/mwrender/refraction.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "refraction.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "renderconst.hpp" - -namespace MWRender -{ - - Refraction::Refraction(Ogre::Camera *parentCamera) - : mParentCamera(parentCamera) - , mRenderActive(false) - , mIsUnderwater(false) - { - mCamera = mParentCamera->getSceneManager()->createCamera("RefractionCamera"); - - mParentCamera->getSceneManager()->addRenderQueueListener(this); - - Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().createManual("WaterRefraction", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, 512, 512, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET); - - mRenderTarget = texture->getBuffer()->getRenderTarget(); - Ogre::Viewport* vp = mRenderTarget->addViewport(mCamera); - vp->setOverlaysEnabled(false); - vp->setShadowsEnabled(false); - vp->setVisibilityMask(RV_Refraction); - vp->setMaterialScheme("water_refraction"); - vp->setBackgroundColour (Ogre::ColourValue(0.090195f, 0.115685f, 0.12745f)); - mRenderTarget->setAutoUpdated(true); - mRenderTarget->addListener(this); - } - - Refraction::~Refraction() - { - mRenderTarget->removeListener(this); - Ogre::TextureManager::getSingleton().remove("WaterRefraction"); - mParentCamera->getSceneManager()->destroyCamera(mCamera); - mParentCamera->getSceneManager()->removeRenderQueueListener(this); - } - - void Refraction::preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt) - { - if (mParentCamera->isAttached()) - mParentCamera->getParentSceneNode ()->needUpdate (); - mCamera->setOrientation(mParentCamera->getDerivedOrientation()); - mCamera->setPosition(mParentCamera->getDerivedPosition()); - mCamera->setNearClipDistance(mParentCamera->getNearClipDistance()); - mCamera->setFarClipDistance(mParentCamera->getFarClipDistance()); - mCamera->setAspectRatio(mParentCamera->getAspectRatio()); - mCamera->setFOVy(mParentCamera->getFOVy()); - - // for depth calculation, we want the original viewproj matrix _without_ the custom near clip plane. - // since all we are interested in is depth, we only need the third row of the matrix. - Ogre::Matrix4 projMatrix = mCamera->getProjectionMatrixWithRSDepth () * mCamera->getViewMatrix (); - sh::Vector4* row3 = new sh::Vector4(projMatrix[2][0], projMatrix[2][1], projMatrix[2][2], projMatrix[2][3]); - sh::Factory::getInstance ().setSharedParameter ("vpRow2Fix", sh::makeProperty (row3)); - - // enable clip plane here to take advantage of CPU culling for overwater or underwater objects - mCamera->enableCustomNearClipPlane(mIsUnderwater ? mNearClipPlaneUnderwater : mNearClipPlane); - - mRenderActive = true; - } - - void Refraction::postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt) - { - mCamera->disableCustomNearClipPlane (); - mRenderActive = false; - } - - void Refraction::setHeight(float height) - { - mNearClipPlane = Ogre::Plane( -Ogre::Vector3(0,0,1), -(height + 5)); - mNearClipPlaneUnderwater = Ogre::Plane( Ogre::Vector3(0,0,1), height - 5); - } - - void Refraction::renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation) - { - // We don't want the sky to get clipped by custom near clip plane (the water plane) - if (queueGroupId < 20 && mRenderActive) - { - mCamera->disableCustomNearClipPlane(); - Ogre::Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mCamera->getProjectionMatrixRS()); - } - } - - void Refraction::renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation) - { - if (queueGroupId < 20 && mRenderActive) - { - mCamera->enableCustomNearClipPlane(mIsUnderwater ? mNearClipPlaneUnderwater : mNearClipPlane); - Ogre::Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mCamera->getProjectionMatrixRS()); - } - } - - void Refraction::setActive(bool active) - { - mRenderTarget->setActive(active); - } - -} diff --git a/apps/openmw/mwrender/refraction.hpp b/apps/openmw/mwrender/refraction.hpp deleted file mode 100644 index b9ab8deac2..0000000000 --- a/apps/openmw/mwrender/refraction.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef MWRENDER_REFRACTION_H -#define MWRENDER_REFRACTION_H - -#include -#include -#include - -namespace Ogre -{ - class Camera; - class RenderTarget; -} - -namespace MWRender -{ - - class Refraction : public Ogre::RenderTargetListener, public Ogre::RenderQueueListener - { - - public: - Refraction(Ogre::Camera* parentCamera); - ~Refraction(); - - void setHeight (float height); - void preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); - void postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); - void setUnderwater(bool underwater) {mIsUnderwater = underwater;} - void setActive (bool active); - - void renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation); - void renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation); - - private: - Ogre::Camera* mParentCamera; - Ogre::Camera* mCamera; - Ogre::RenderTarget* mRenderTarget; - Ogre::Plane mNearClipPlane; - Ogre::Plane mNearClipPlaneUnderwater; - bool mRenderActive; - bool mIsUnderwater; - }; - -} - -#endif diff --git a/apps/openmw/mwrender/renderbin.hpp b/apps/openmw/mwrender/renderbin.hpp new file mode 100644 index 0000000000..c14f611426 --- /dev/null +++ b/apps/openmw/mwrender/renderbin.hpp @@ -0,0 +1,21 @@ +#ifndef OPENMW_MWRENDER_RENDERBIN_H +#define OPENMW_MWRENDER_RENDERBIN_H + +namespace MWRender +{ + + /// Defines the render bin numbers used in the OpenMW scene graph. The bin with the lowest number is rendered first. + enum RenderBins + { + RenderBin_Sky = -1, + RenderBin_Default = 0, // osg::StateSet::OPAQUE_BIN + RenderBin_Water = 9, + RenderBin_DepthSorted = 10, // osg::StateSet::TRANSPARENT_BIN + RenderBin_OcclusionQuery = 11, + RenderBin_FirstPerson = 12, + RenderBin_SunGlare = 13 + }; + +} + +#endif diff --git a/apps/openmw/mwrender/renderconst.hpp b/apps/openmw/mwrender/renderconst.hpp deleted file mode 100644 index cfd84cb32e..0000000000 --- a/apps/openmw/mwrender/renderconst.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef GAME_RENDER_CONST_H -#define GAME_RENDER_CONST_H - -#include - -namespace MWRender -{ - -// Render queue groups -enum RenderQueueGroups -{ - // Sky early (atmosphere, clouds, moons) - RQG_SkiesEarly = Ogre::RENDER_QUEUE_SKIES_EARLY, - - RQG_Main = Ogre::RENDER_QUEUE_MAIN, - - RQG_Alpha = Ogre::RENDER_QUEUE_MAIN+1, - - RQG_OcclusionQuery = Ogre::RENDER_QUEUE_6, - - RQG_UnderWater = Ogre::RENDER_QUEUE_4, - - RQG_Water = RQG_Alpha, - RQG_Ripples = RQG_Water+1, - - // Sky late (sun & sun flare) - RQG_SkiesLate = Ogre::RENDER_QUEUE_SKIES_LATE -}; - -// Visibility flags -enum VisibilityFlags -{ - // Terrain - RV_Terrain = (1<<0), - - // Statics (e.g. trees, houses) - RV_Statics = (1<<1), - - // Small statics - RV_StaticsSmall = (1<<2), - - // Water - RV_Water = (1<<3), - - // Actors (npcs, creatures) - RV_Actors = (1<<4), - - // Misc objects (containers, dynamic objects) - RV_Misc = (1<<5), - - // VFX, don't appear on map and don't cast shadows - RV_Effects = (1<<6), - - RV_Sky = (1<<7), - - // not visible in reflection - RV_NoReflection = (1<<8), - - RV_OcclusionQuery = (1<<9), - - RV_Debug = (1<<10), - - // overlays, we only want these on the main render target - RV_Overlay = (1<<11), - - // First person meshes do not cast shadows - RV_FirstPerson = (1<<12), - - RV_Map = RV_Terrain + RV_Statics + RV_StaticsSmall + RV_Misc + RV_Water, - - RV_Refraction = RV_Actors + RV_Misc + RV_Statics + RV_StaticsSmall + RV_Terrain + RV_Effects + RV_Sky + RV_FirstPerson -}; - -} - -#endif diff --git a/apps/openmw/mwrender/renderinginterface.hpp b/apps/openmw/mwrender/renderinginterface.hpp index 02f3c804a0..63039b612a 100644 --- a/apps/openmw/mwrender/renderinginterface.hpp +++ b/apps/openmw/mwrender/renderinginterface.hpp @@ -10,8 +10,7 @@ namespace MWRender { public: virtual MWRender::Objects& getObjects() = 0; - virtual MWRender::Actors& getActors() = 0; - virtual ~RenderingInterface(){}; + virtual ~RenderingInterface(){} }; } #endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index f00ebb3039..ee9d93c0f2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1,1072 +1,942 @@ #include "renderingmanager.hpp" -#include +#include +#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include -#include +#include +#include -#include -#include +#include -#include +#include +#include +#include #include -#include + +#include +#include +#include +#include + #include -#include "../mwworld/esmstore.hpp" -#include "../mwworld/class.hpp" +#include + +#include "../mwworld/fallback.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwbase/world.hpp" // these includes can be removed once the static-hack is gone -#include "../mwbase/environment.hpp" -#include "../mwbase/inputmanager.hpp" // FIXME -#include "../mwbase/windowmanager.hpp" // FIXME -#include "../mwbase/statemanager.hpp" - -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/npcstats.hpp" - -#include "../mwworld/ptr.hpp" - -#include "shadows.hpp" -#include "localmap.hpp" -#include "water.hpp" -#include "npcanimation.hpp" -#include "globalmap.hpp" -#include "terrainstorage.hpp" +#include "sky.hpp" #include "effectmanager.hpp" +#include "npcanimation.hpp" +#include "vismask.hpp" +#include "pathgrid.hpp" +#include "camera.hpp" +#include "water.hpp" +#include "terrainstorage.hpp" -using namespace MWRender; -using namespace Ogre; - -namespace MWRender { - -RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const boost::filesystem::path& resDir, - const boost::filesystem::path& cacheDir, OEngine::Physic::PhysicEngine* engine, - MWWorld::Fallback* fallback) - : mSunEnabled(0) - , mFallback(fallback) - , mTerrain(NULL) - , mRendering(_rend) - , mEffectManager(NULL) - , mPlayerAnimation(NULL) - , mAmbientMode(0) - , mPhysicsEngine(engine) - , mRenderWorld(true) +namespace MWRender { - mActors = new MWRender::Actors(mRendering, this); - mObjects = new MWRender::Objects(mRendering); - mEffectManager = new EffectManager(mRendering.getScene()); - // select best shader mode - bool openGL = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") != std::string::npos); - bool glES = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL ES") != std::string::npos); - // glsl is only supported in opengl mode and hlsl only in direct3d mode. - std::string currentMode = Settings::Manager::getString("shader mode", "General"); - if (currentMode == "" - || (openGL && currentMode == "hlsl") - || (!openGL && currentMode == "glsl") - || (glES && currentMode != "glsles")) + class StateUpdater : public SceneUtil::StateSetUpdater { - Settings::Manager::setString("shader mode", "General", openGL ? (glES ? "glsles" : "glsl") : "hlsl"); - } - - mRendering.adjustCamera(Settings::Manager::getFloat("field of view", "General"), 5); - - mRendering.getWindow()->addListener(this); - mRendering.setWindowListener(this); - - mWater = 0; - - // material system - sh::OgrePlatform* platform = new sh::OgrePlatform("General", (resDir / "materials").string()); - if (!boost::filesystem::exists (cacheDir)) - boost::filesystem::create_directories (cacheDir); - platform->setCacheFolder (cacheDir.string()); - mFactory = new sh::Factory(platform); - - sh::Language lang; - std::string l = Settings::Manager::getString("shader mode", "General"); - if (l == "glsl") - lang = sh::Language_GLSL; - else if (l == "glsles") - lang = sh::Language_GLSLES; - else if (l == "hlsl") - lang = sh::Language_HLSL; - else - lang = sh::Language_CG; - mFactory->setCurrentLanguage (lang); - mFactory->setWriteSourceCache (true); - mFactory->setReadSourceCache (true); - mFactory->setReadMicrocodeCache (true); - mFactory->setWriteMicrocodeCache (true); - - mFactory->loadAllFiles(); - - TextureManager::getSingleton().setDefaultNumMipmaps(Settings::Manager::getInt("num mipmaps", "General")); - - // Set default texture filtering options - TextureFilterOptions tfo; - std::string filter = Settings::Manager::getString("texture filtering", "General"); - - if (filter == "anisotropic") tfo = TFO_ANISOTROPIC; - else if (filter == "trilinear") tfo = TFO_TRILINEAR; - else if (filter == "bilinear") tfo = TFO_BILINEAR; - else /*if (filter == "none")*/ tfo = TFO_NONE; - - MaterialManager::getSingleton().setDefaultTextureFiltering(tfo); - MaterialManager::getSingleton().setDefaultAnisotropy( (filter == "anisotropic") ? Settings::Manager::getInt("anisotropy", "General") : 1 ); - - Ogre::TextureManager::getSingleton().setMemoryBudget(126*1024*1024); - Ogre::MeshManager::getSingleton().setMemoryBudget(64*1024*1024); - - Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); - - // disable unsupported effects - if (!Settings::Manager::getBool("shaders", "Objects")) - Settings::Manager::setBool("enabled", "Shadows", false); - - sh::Factory::getInstance ().setShadersEnabled (Settings::Manager::getBool("shaders", "Objects")); - - sh::Factory::getInstance ().setGlobalSetting ("fog", "true"); - sh::Factory::getInstance ().setGlobalSetting ("num_lights", Settings::Manager::getString ("num lights", "Objects")); - sh::Factory::getInstance ().setGlobalSetting ("simple_water", Settings::Manager::getBool("shader", "Water") ? "false" : "true"); - sh::Factory::getInstance ().setGlobalSetting ("render_refraction", "false"); - - sh::Factory::getInstance ().setSharedParameter ("waterEnabled", sh::makeProperty (new sh::FloatValue(0.0))); - sh::Factory::getInstance ().setSharedParameter ("waterLevel", sh::makeProperty(new sh::FloatValue(0))); - sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty(new sh::FloatValue(0))); - sh::Factory::getInstance ().setSharedParameter ("windDir_windSpeed", sh::makeProperty(new sh::Vector3(0.5f, -0.8f, 0.2f))); - sh::Factory::getInstance ().setSharedParameter ("waterSunFade_sunHeight", sh::makeProperty(new sh::Vector2(1, 0.6f))); - sh::Factory::getInstance ().setGlobalSetting ("refraction", Settings::Manager::getBool("refraction", "Water") ? "true" : "false"); - sh::Factory::getInstance ().setGlobalSetting ("viewproj_fix", "false"); - sh::Factory::getInstance ().setSharedParameter ("vpRow2Fix", sh::makeProperty (new sh::Vector4(0,0,0,0))); - - mRootNode = mRendering.getScene()->getRootSceneNode(); - mRootNode->createChildSceneNode("player"); - - mObjects->setRootNode(mRootNode); - mActors->setRootNode(mRootNode); - - mCamera = new MWRender::Camera(mRendering.getCamera()); - - mShadows = new Shadows(&mRendering); - - mSkyManager = new SkyManager(mRootNode, mRendering.getCamera()); - - mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode()); - - mSun = 0; - - mDebugging = new Debugging(mRootNode, engine); - mLocalMap = new MWRender::LocalMap(&mRendering, this); - - mWater = new MWRender::Water(mRendering.getCamera(), this, mFallback); - - setMenuTransparency(Settings::Manager::getFloat("menu transparency", "GUI")); -} - -RenderingManager::~RenderingManager () -{ - mRendering.getWindow()->removeListener(this); - - delete mPlayerAnimation; - delete mCamera; - delete mSkyManager; - delete mDebugging; - delete mShadows; - delete mTerrain; - delete mLocalMap; - delete mOcclusionQuery; - delete mWater; - delete mActors; - delete mObjects; - delete mEffectManager; - delete mFactory; -} - -MWRender::SkyManager* RenderingManager::getSkyManager() -{ - return mSkyManager; -} - -MWRender::Objects& RenderingManager::getObjects(){ - return *mObjects; -} -MWRender::Actors& RenderingManager::getActors(){ - return *mActors; -} - -MWRender::Camera* RenderingManager::getCamera() const -{ - return mCamera; -} - -void RenderingManager::removeCell (MWWorld::CellStore *store) -{ - if (store->isExterior()) - mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); - - mLocalMap->saveFogOfWar(store); - mObjects->removeCell(store); - mActors->removeCell(store); - mDebugging->cellRemoved(store); -} - -void RenderingManager::removeWater () -{ - mWater->setActive(false); -} - -bool RenderingManager::toggleWater() -{ - return mWater->toggle(); -} - -bool RenderingManager::toggleWorld() -{ - mRenderWorld = !mRenderWorld; - - int visibilityMask = mRenderWorld ? ~int(0) : 0; - mRendering.getViewport()->setVisibilityMask(visibilityMask); - return mRenderWorld; -} - -void RenderingManager::cellAdded (MWWorld::CellStore *store) -{ - if (store->isExterior()) - mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); - - mObjects->buildStaticGeometry (*store); - sh::Factory::getInstance().unloadUnreferencedMaterials(); - mDebugging->cellAdded(store); -} - -void RenderingManager::addObject (const MWWorld::Ptr& ptr, const std::string& model){ - const MWWorld::Class& class_ = - ptr.getClass(); - class_.insertObjectRendering(ptr, model, *this); -} - -void RenderingManager::removeObject (const MWWorld::Ptr& ptr) -{ - if (!mObjects->deleteObject (ptr)) - mActors->deleteObject (ptr); -} - -void RenderingManager::moveObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& position) -{ - /// \todo move this to the rendering-subsystems - ptr.getRefData().getBaseNode()->setPosition(position); -} - -void RenderingManager::scaleObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& scale) -{ - ptr.getRefData().getBaseNode()->setScale(scale); -} - -void RenderingManager::rotateObject(const MWWorld::Ptr &ptr) -{ - Ogre::Vector3 rot(ptr.getRefData().getPosition().rot); - - if(ptr.getRefData().getHandle() == mCamera->getHandle() && - !mCamera->isVanityOrPreviewModeEnabled()) - mCamera->rotateCamera(-rot, false); - - Ogre::Quaternion newo = Ogre::Quaternion(Ogre::Radian(rot.z), Ogre::Vector3::NEGATIVE_UNIT_Z); - if(!ptr.getClass().isActor()) - newo = Ogre::Quaternion(Ogre::Radian(rot.x), Ogre::Vector3::NEGATIVE_UNIT_X) * - Ogre::Quaternion(Ogre::Radian(rot.y), Ogre::Vector3::NEGATIVE_UNIT_Y) * newo; - ptr.getRefData().getBaseNode()->setOrientation(newo); -} - -void -RenderingManager::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) -{ - if (!old.getRefData().getBaseNode()) - return; - Ogre::SceneNode *child = - mRendering.getScene()->getSceneNode(old.getRefData().getHandle()); - - Ogre::SceneNode *parent = child->getParentSceneNode(); - parent->removeChild(child); - - if (old.getClass().isActor()) { - mActors->updateObjectCell(old, cur); - } else { - mObjects->updateObjectCell(old, cur); - } -} - -void RenderingManager::updatePlayerPtr(const MWWorld::Ptr &ptr) -{ - if(mPlayerAnimation) - mPlayerAnimation->updatePtr(ptr); - if(mCamera->getHandle() == ptr.getRefData().getHandle()) - attachCameraTo(ptr); -} - -void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr) -{ - NpcAnimation *anim = NULL; - if(ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) - anim = mPlayerAnimation; - else if(ptr.getClass().isActor()) - anim = dynamic_cast(mActors->getAnimation(ptr)); - if(anim) - { - anim->rebuild(); - if(mCamera->getHandle() == ptr.getRefData().getHandle()) + public: + StateUpdater() + : mFogStart(0.f) + , mFogEnd(0.f) + , mWireframe(false) { - attachCameraTo(ptr); - mCamera->setAnimation(anim); } - } -} -void RenderingManager::update (float duration, bool paused) -{ - if (MWBase::Environment::get().getStateManager()->getState()== - MWBase::StateManager::State_NoGame) - return; - - MWBase::World *world = MWBase::Environment::get().getWorld(); - - MWWorld::Ptr player = world->getPlayerPtr(); - - int blind = static_cast(player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Blind).getMagnitude()); - MWBase::Environment::get().getWindowManager()->setBlindness(std::max(0, std::min(100, blind))); - setAmbientMode(); - - if (player.getClass().getNpcStats(player).isWerewolf()) - MWBase::Environment::get().getWindowManager()->setWerewolfOverlay(mCamera->isFirstPerson()); - - // player position - MWWorld::RefData &data = player.getRefData(); - Ogre::Vector3 playerPos(data.getPosition().pos); - - mCamera->setCameraDistance(); - if(!mCamera->isFirstPerson()) - { - Ogre::Vector3 orig, dest; - mCamera->getPosition(orig, dest); - - btVector3 btOrig(orig.x, orig.y, orig.z); - btVector3 btDest(dest.x, dest.y, dest.z); - std::pair test = mPhysicsEngine->sphereCast(mRendering.getCamera()->getNearClipDistance()*2.5f, btOrig, btDest); - if(test.first) - mCamera->setCameraDistance(test.second * orig.distance(dest), false, false); - } - - // Sink the camera while sneaking - bool isSneaking = player.getClass().getCreatureStats(player).getStance(MWMechanics::CreatureStats::Stance_Sneak); - bool isInAir = !world->isOnGround(player); - bool isSwimming = world->isSwimming(player); - - static const float i1stPersonSneakDelta = MWBase::Environment::get().getWorld()->getStore().get() - .find("i1stPersonSneakDelta")->getFloat(); - if(!paused && isSneaking && !(isSwimming || isInAir)) - mCamera->setSneakOffset(i1stPersonSneakDelta); - - mOcclusionQuery->update(duration); - - mRendering.update(duration); - - Ogre::ControllerManager::getSingleton().setTimeFactor(paused ? 0.f : 1.f); - - Ogre::Vector3 cam = mRendering.getCamera()->getRealPosition(); - - applyFog(world->isUnderwater(player.getCell(), cam)); - - mCamera->update(duration, paused); - - Ogre::SceneNode *node = data.getBaseNode(); - Ogre::Quaternion orient = node->_getDerivedOrientation(); - mLocalMap->updatePlayer(playerPos, orient); - - if(paused) - return; - - mEffectManager->update(duration, mRendering.getCamera()); - - mActors->update (mRendering.getCamera()); - mPlayerAnimation->preRender(mRendering.getCamera()); - mObjects->update (duration, mRendering.getCamera()); - - mSkyManager->update(duration); - - mSkyManager->setGlare(mOcclusionQuery->getSunVisibility()); - - mWater->changeCell(player.getCell()->getCell()); - - mWater->updateUnderwater(world->isUnderwater(player.getCell(), cam)); - - mWater->update(duration, playerPos); -} - -void RenderingManager::preRenderTargetUpdate(const RenderTargetEvent &evt) -{ - mOcclusionQuery->setActive(true); -} - -void RenderingManager::postRenderTargetUpdate(const RenderTargetEvent &evt) -{ - // deactivate queries to make sure we aren't getting false results from several misc render targets - // (will be reactivated at the bottom of this method) - mOcclusionQuery->setActive(false); -} - -void RenderingManager::setWaterEnabled(bool enable) -{ - mWater->setActive(enable); -} - -void RenderingManager::setWaterHeight(float height) -{ - mWater->setHeight(height); -} - -void RenderingManager::skyEnable () -{ - mSkyManager->enable(); - mOcclusionQuery->setSunNode(mSkyManager->getSunNode()); -} - -void RenderingManager::skyDisable () -{ - mSkyManager->disable(); -} - -void RenderingManager::skySetHour (double hour) -{ - mSkyManager->setHour(hour); -} - - -void RenderingManager::skySetDate (int day, int month) -{ - mSkyManager->setDate(day, month); -} - -int RenderingManager::skyGetMasserPhase() const -{ - - return mSkyManager->getMasserPhase(); -} - -int RenderingManager::skyGetSecundaPhase() const -{ - return mSkyManager->getSecundaPhase(); -} - -void RenderingManager::skySetMoonColour (bool red){ - mSkyManager->setMoonColour(red); -} - -bool RenderingManager::toggleRenderMode(int mode) -{ - if (mode == MWBase::World::Render_CollisionDebug || mode == MWBase::World::Render_Pathgrid) - return mDebugging->toggleRenderMode(mode); - else if (mode == MWBase::World::Render_Wireframe) - { - if (mRendering.getCamera()->getPolygonMode() == PM_SOLID) + virtual void setDefaults(osg::StateSet *stateset) { - mRendering.getCamera()->setPolygonMode(PM_WIREFRAME); - return true; - } - else - { - mRendering.getCamera()->setPolygonMode(PM_SOLID); - return false; - } - } - else //if (mode == MWBase::World::Render_BoundingBoxes) - { - bool show = !mRendering.getScene()->getShowBoundingBoxes(); - mRendering.getScene()->showBoundingBoxes(show); - return show; - } -} - -void RenderingManager::configureFog(const MWWorld::CellStore &mCell) -{ - Ogre::ColourValue color; - color.setAsABGR (mCell.getCell()->mAmbi.mFog); - - configureFog (mCell.getCell()->mAmbi.mFogDensity, color); -} - -void RenderingManager::configureFog(const float density, const Ogre::ColourValue& colour) -{ - mFogColour = colour; - float max = Settings::Manager::getFloat("viewing distance", "Viewing distance"); - - if (density == 0) - { - mFogStart = 0; - mFogEnd = std::numeric_limits::max(); - mRendering.getCamera()->setFarClipDistance (max); - } - else - { - mFogStart = max / (density) * Settings::Manager::getFloat("fog start factor", "Viewing distance"); - mFogEnd = max / (density) * Settings::Manager::getFloat("fog end factor", "Viewing distance"); - mRendering.getCamera()->setFarClipDistance (max / density); - } - -} - -void RenderingManager::applyFog (bool underwater) -{ - if (!underwater) - { - mRendering.getScene()->setFog (FOG_LINEAR, mFogColour, 0, mFogStart, mFogEnd); - mRendering.getViewport()->setBackgroundColour (mFogColour); - mWater->setViewportBackground (mFogColour); - } - else - { - Ogre::ColourValue clv(0.090195f, 0.115685f, 0.12745f); - mRendering.getScene()->setFog (FOG_LINEAR, Ogre::ColourValue(clv), 0, 0, 1000); - mRendering.getViewport()->setBackgroundColour (Ogre::ColourValue(clv)); - mWater->setViewportBackground (Ogre::ColourValue(clv)); - } -} - -void RenderingManager::setAmbientMode() -{ - switch (mAmbientMode) - { - case 0: - setAmbientColour(mAmbientColor); - break; - - case 1: - setAmbientColour(0.7f*mAmbientColor + 0.3f*ColourValue(1,1,1)); - break; - - case 2: - setAmbientColour(ColourValue(1,1,1)); - break; - } -} - -void RenderingManager::configureAmbient(MWWorld::CellStore &mCell) -{ - if (mCell.getCell()->mData.mFlags & ESM::Cell::Interior) - mAmbientColor.setAsABGR (mCell.getCell()->mAmbi.mAmbient); - setAmbientMode(); - - // Create a "sun" that shines light downwards. It doesn't look - // completely right, but leave it for now. - if(!mSun) - { - mSun = mRendering.getScene()->createLight(); - mSun->setType(Ogre::Light::LT_DIRECTIONAL); - } - if (mCell.getCell()->mData.mFlags & ESM::Cell::Interior) - { - Ogre::ColourValue colour; - colour.setAsABGR (mCell.getCell()->mAmbi.mSunlight); - mSun->setDiffuseColour (colour); - mSun->setDirection(1,-1,-1); - sunEnable(false); - } -} - -void RenderingManager::setSunColour(const Ogre::ColourValue& colour) -{ - if (!mSunEnabled) return; - mSun->setDiffuseColour(colour); - mSun->setSpecularColour(colour); -} - -void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour) -{ - mAmbientColor = colour; - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - int nightEye = static_cast(player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::NightEye).getMagnitude()); - Ogre::ColourValue final = colour; - final += Ogre::ColourValue(0.7f,0.7f,0.7f,0) * std::min(1.f, (nightEye/100.f)); - - mRendering.getScene()->setAmbientLight(final); -} - -void RenderingManager::sunEnable(bool real) -{ - if (real && mSun) mSun->setVisible(true); - else - { - // Don't disable the light, as the shaders assume the first light to be directional. - mSunEnabled = true; - } -} - -void RenderingManager::sunDisable(bool real) -{ - if (real && mSun) mSun->setVisible(false); - else - { - // Don't disable the light, as the shaders assume the first light to be directional. - mSunEnabled = false; - if (mSun) - { - mSun->setDiffuseColour(ColourValue(0,0,0)); - mSun->setSpecularColour(ColourValue(0,0,0)); - } - } -} - -void RenderingManager::setSunDirection(const Ogre::Vector3& direction, bool is_night) -{ - // direction * -1 (because 'direction' is camera to sun vector and not sun to camera), - if (mSun) mSun->setDirection(Vector3(-direction.x, -direction.y, -direction.z)); - - mSkyManager->setSunDirection(direction, is_night); -} - -void RenderingManager::setGlare(bool glare) -{ - mSkyManager->setGlare(glare); -} - -void RenderingManager::updateTerrain() -{ - if (mTerrain) - { - // Avoid updating with dims.getCenter for each cell. Player position should be good enough - mTerrain->update(mRendering.getCamera()->getRealPosition()); - mTerrain->syncLoad(); - // need to update again so the chunks that were just loaded can be made visible - mTerrain->update(mRendering.getCamera()->getRealPosition()); - } -} - -void RenderingManager::requestMap(MWWorld::CellStore* cell) -{ - if (cell->getCell()->isExterior()) - { - assert(mTerrain); - - Ogre::AxisAlignedBox dims = mObjects->getDimensions(cell); - Ogre::Vector2 center (cell->getCell()->getGridX() + 0.5f, cell->getCell()->getGridY() + 0.5f); - dims.merge(mTerrain->getWorldBoundingBox(center)); - - mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z); - } - else - mLocalMap->requestMap(cell, mObjects->getDimensions(cell)); -} - -void RenderingManager::writeFog(MWWorld::CellStore* cell) -{ - mLocalMap->saveFogOfWar(cell); -} - -void RenderingManager::disableLights(bool sun) -{ - mActors->disableLights(); - sunDisable(sun); -} - -void RenderingManager::enableLights(bool sun) -{ - mActors->enableLights(); - sunEnable(sun); -} - -void RenderingManager::notifyWorldSpaceChanged() -{ - mEffectManager->clear(); - mWater->clearRipples(); -} - -Ogre::Vector4 RenderingManager::boundingBoxToScreen(Ogre::AxisAlignedBox bounds) -{ - Ogre::Matrix4 mat = mRendering.getCamera()->getViewMatrix(); - - const Ogre::Vector3* corners = bounds.getAllCorners(); - - float min_x = 1.0f, max_x = 0.0f, min_y = 1.0f, max_y = 0.0f; - - // expand the screen-space bounding-box so that it completely encloses - // the object's AABB - for (int i=0; i<8; i++) - { - Ogre::Vector3 corner = corners[i]; - - // multiply the AABB corner vertex by the view matrix to - // get a camera-space vertex - corner = mat * corner; - - // make 2D relative/normalized coords from the view-space vertex - // by dividing out the Z (depth) factor -- this is an approximation - float x = corner.x / corner.z + 0.5f; - float y = corner.y / corner.z + 0.5f; - - if (x < min_x) - min_x = x; - - if (x > max_x) - max_x = x; - - if (y < min_y) - min_y = y; - - if (y > max_y) - max_y = y; - } - - return Vector4(min_x, min_y, max_x, max_y); -} - -void RenderingManager::processChangedSettings(const Settings::CategorySettingVector& settings) -{ - bool changeRes = false; - bool rebuild = false; // rebuild static geometry (necessary after any material changes) - for (Settings::CategorySettingVector::const_iterator it=settings.begin(); - it != settings.end(); ++it) - { - if (it->second == "menu transparency" && it->first == "GUI") - { - setMenuTransparency(Settings::Manager::getFloat("menu transparency", "GUI")); - } - else if (it->second == "viewing distance" && it->first == "Viewing distance") - { - if (!MWBase::Environment::get().getWorld()->isCellExterior() && !MWBase::Environment::get().getWorld()->isCellQuasiExterior() - && MWBase::Environment::get().getWorld()->getPlayerPtr().mCell) - configureFog(*MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()); - } - else if (it->first == "Video" && ( - it->second == "resolution x" - || it->second == "resolution y" - || it->second == "fullscreen")) - changeRes = true; - else if (it->first == "Video" && it->second == "window border") - changeRes = true; - else if (it->second == "field of view" && it->first == "General") - mRendering.setFov(Settings::Manager::getFloat("field of view", "General")); - else if (it->second == "gamma" && it->first == "General") - { - mRendering.setWindowGammaContrast(Settings::Manager::getFloat("gamma", "General"), Settings::Manager::getFloat("contrast", "General")); - } - else if ((it->second == "texture filtering" && it->first == "General") - || (it->second == "anisotropy" && it->first == "General")) - { - TextureFilterOptions tfo; - std::string filter = Settings::Manager::getString("texture filtering", "General"); - if (filter == "anisotropic") tfo = TFO_ANISOTROPIC; - else if (filter == "trilinear") tfo = TFO_TRILINEAR; - else if (filter == "bilinear") tfo = TFO_BILINEAR; - else /*if (filter == "none")*/ tfo = TFO_NONE; - - MaterialManager::getSingleton().setDefaultTextureFiltering(tfo); - MaterialManager::getSingleton().setDefaultAnisotropy( (filter == "anisotropic") ? Settings::Manager::getInt("anisotropy", "General") : 1 ); - } - else if (it->second == "shader" && it->first == "Water") - { - sh::Factory::getInstance ().setGlobalSetting ("simple_water", Settings::Manager::getBool("shader", "Water") ? "false" : "true"); - rebuild = true; - mRendering.getViewport ()->setClearEveryFrame (true); - } - else if (it->second == "refraction" && it->first == "Water") - { - sh::Factory::getInstance ().setGlobalSetting ("refraction", Settings::Manager::getBool("refraction", "Water") ? "true" : "false"); - rebuild = true; - } - else if (it->second == "shaders" && it->first == "Objects") - { - sh::Factory::getInstance ().setShadersEnabled (Settings::Manager::getBool("shaders", "Objects")); - rebuild = true; - } - else if (it->second == "shader mode" && it->first == "General") - { - sh::Language lang; - std::string l = Settings::Manager::getString("shader mode", "General"); - if (l == "glsl") - lang = sh::Language_GLSL; - else if (l == "hlsl") - lang = sh::Language_HLSL; + osg::LightModel* lightModel = new osg::LightModel; + stateset->setAttribute(lightModel, osg::StateAttribute::ON); + osg::Fog* fog = new osg::Fog; + fog->setMode(osg::Fog::LINEAR); + stateset->setAttributeAndModes(fog, osg::StateAttribute::ON); + if (mWireframe) + { + osg::PolygonMode* polygonmode = new osg::PolygonMode; + polygonmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE); + stateset->setAttributeAndModes(polygonmode, osg::StateAttribute::ON); + } else - lang = sh::Language_CG; - sh::Factory::getInstance ().setCurrentLanguage (lang); - rebuild = true; + stateset->removeAttribute(osg::StateAttribute::POLYGONMODE); } - else if (it->first == "Shadows") + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor*) { - mShadows->recreate (); - - rebuild = true; + osg::LightModel* lightModel = static_cast(stateset->getAttribute(osg::StateAttribute::LIGHTMODEL)); + lightModel->setAmbientIntensity(mAmbientColor); + osg::Fog* fog = static_cast(stateset->getAttribute(osg::StateAttribute::FOG)); + fog->setColor(mFogColor); + fog->setStart(mFogStart); + fog->setEnd(mFogEnd); } - } - if (changeRes) + void setAmbientColor(const osg::Vec4f& col) + { + mAmbientColor = col; + } + + void setFogColor(const osg::Vec4f& col) + { + mFogColor = col; + } + + void setFogStart(float start) + { + mFogStart = start; + } + + void setFogEnd(float end) + { + mFogEnd = end; + } + + void setWireframe(bool wireframe) + { + if (mWireframe != wireframe) + { + mWireframe = wireframe; + reset(); + } + } + + bool getWireframe() const + { + return mWireframe; + } + + private: + osg::Vec4f mAmbientColor; + osg::Vec4f mFogColor; + float mFogStart; + float mFogEnd; + bool mWireframe; + }; + + RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, + const MWWorld::Fallback* fallback, const std::string& resourcePath) + : mViewer(viewer) + , mRootNode(rootNode) + , mResourceSystem(resourceSystem) + , mFogDepth(0.f) + , mUnderwaterColor(fallback->getFallbackColour("Water_UnderwaterColor")) + , mUnderwaterWeight(fallback->getFallbackFloat("Water_UnderwaterColorWeight")) + , mUnderwaterFog(0.f) + , mUnderwaterIndoorFog(fallback->getFallbackFloat("Water_UnderwaterIndoorFog")) + , mNightEyeFactor(0.f) + , mFieldOfViewOverride(0.f) + , mFieldOfViewOverridden(false) { - unsigned int x = Settings::Manager::getInt("resolution x", "Video"); - unsigned int y = Settings::Manager::getInt("resolution y", "Video"); - bool fullscreen = Settings::Manager::getBool("fullscreen", "Video"); - bool windowBorder = Settings::Manager::getBool("window border", "Video"); + resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); - SDL_Window* window = mRendering.getSDLWindow(); + osg::ref_ptr lightRoot = new SceneUtil::LightManager; + lightRoot->setLightingMask(Mask_Lighting); + mLightRoot = lightRoot; + lightRoot->setStartLight(1); - SDL_SetWindowFullscreen(window, 0); + mRootNode->addChild(lightRoot); - if (SDL_GetWindowFlags(window) & SDL_WINDOW_MAXIMIZED) - SDL_RestoreWindow(window); + mPathgrid.reset(new Pathgrid(mRootNode)); - if (fullscreen) - { - SDL_DisplayMode mode; - SDL_GetWindowDisplayMode(window, &mode); - mode.w = x; - mode.h = y; - SDL_SetWindowDisplayMode(window, &mode); - SDL_SetWindowFullscreen(window, fullscreen); - } + mObjects.reset(new Objects(mResourceSystem, lightRoot)); + + mViewer->setIncrementalCompileOperation(new osgUtil::IncrementalCompileOperation); + + mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); + + mEffectManager.reset(new EffectManager(lightRoot, mResourceSystem)); + + mWater.reset(new Water(mRootNode, lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath)); + + mTerrain.reset(new Terrain::TerrainGrid(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), + new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain)); + + mCamera.reset(new Camera(mViewer->getCamera())); + + mViewer->setLightingMode(osgViewer::View::NO_LIGHT); + + osg::ref_ptr source = new osg::LightSource; + source->setNodeMask(Mask_Lighting); + mSunLight = new osg::Light; + source->setLight(mSunLight); + mSunLight->setDiffuse(osg::Vec4f(0,0,0,1)); + mSunLight->setAmbient(osg::Vec4f(0,0,0,1)); + mSunLight->setSpecular(osg::Vec4f(0,0,0,0)); + mSunLight->setConstantAttenuation(1.f); + lightRoot->addChild(source); + + lightRoot->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); + lightRoot->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::ON); + lightRoot->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); + + lightRoot->setNodeMask(Mask_Scene); + lightRoot->setName("Scene Root"); + + mSky.reset(new SkyManager(lightRoot, resourceSystem->getSceneManager())); + + source->setStateSetModes(*mRootNode->getOrCreateStateSet(), osg::StateAttribute::ON); + + mStateUpdater = new StateUpdater; + lightRoot->addUpdateCallback(mStateUpdater); + + osg::Camera::CullingMode cullingMode = osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING; + + if (!Settings::Manager::getBool("small feature culling", "Camera")) + cullingMode &= ~(osg::CullStack::SMALL_FEATURE_CULLING); else + cullingMode |= osg::CullStack::SMALL_FEATURE_CULLING; + + mViewer->getCamera()->setCullingMode( cullingMode ); + + mViewer->getCamera()->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); + mViewer->getCamera()->setCullingMode(cullingMode); + + mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater)); + + mNearClip = Settings::Manager::getFloat("near clip", "Camera"); + mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); + mFieldOfView = Settings::Manager::getFloat("field of view", "Camera"); + mFirstPersonFieldOfView = Settings::Manager::getFloat("first person field of view", "Camera"); + updateProjectionMatrix(); + mStateUpdater->setFogEnd(mViewDistance); + + mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); + mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); + } + + RenderingManager::~RenderingManager() + { + } + + MWRender::Objects& RenderingManager::getObjects() + { + return *mObjects.get(); + } + + Resource::ResourceSystem* RenderingManager::getResourceSystem() + { + return mResourceSystem; + } + + osg::Group* RenderingManager::getLightRoot() + { + return mLightRoot.get(); + } + + void RenderingManager::setNightEyeFactor(float factor) + { + if (factor != mNightEyeFactor) { - SDL_SetWindowSize(window, x, y); - SDL_SetWindowBordered(window, windowBorder ? SDL_TRUE : SDL_FALSE); + mNightEyeFactor = factor; + updateAmbient(); } } - mWater->processChangedSettings(settings); - - if (rebuild) + void RenderingManager::setAmbientColour(const osg::Vec4f &colour) { - mObjects->rebuildStaticGeometry(); - if (mTerrain) - mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), - Settings::Manager::getBool("split", "Shadows")); - } -} - -void RenderingManager::setMenuTransparency(float val) -{ - Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().getByName("transparent.png"); std::vector buffer; - buffer.resize(1); - buffer[0] = (int(255*val) << 24) | (255 << 16) | (255 << 8) | 255; - memcpy(tex->getBuffer()->lock(Ogre::HardwareBuffer::HBL_DISCARD), &buffer[0], 1*4); - tex->getBuffer()->unlock(); -} - -void RenderingManager::windowResized(int x, int y) -{ - Settings::Manager::setInt("resolution x", "Video", x); - Settings::Manager::setInt("resolution y", "Video", y); - mRendering.adjustViewport(); - - MWBase::Environment::get().getWindowManager()->windowResized(x,y); -} - -void RenderingManager::getTriangleBatchCount(unsigned int &triangles, unsigned int &batches) -{ - batches = mRendering.getWindow()->getBatchCount(); - triangles = mRendering.getWindow()->getTriangleCount(); -} - -void RenderingManager::setupPlayer(const MWWorld::Ptr &ptr) -{ - ptr.getRefData().setBaseNode(mRendering.getScene()->getSceneNode("player")); - attachCameraTo(ptr); -} - -void RenderingManager::attachCameraTo(const MWWorld::Ptr &ptr) -{ - Ogre::SceneNode* cameraNode = mCamera->attachTo(ptr); - mSkyManager->attachToNode(cameraNode); -} - -void RenderingManager::renderPlayer(const MWWorld::Ptr &ptr) -{ - if(!mPlayerAnimation) - { - mPlayerAnimation = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors); - } - else - { - // Reconstruct the NpcAnimation in-place - mPlayerAnimation->~NpcAnimation(); - new(mPlayerAnimation) NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors); + mAmbientColor = colour; + updateAmbient(); } - mCamera->setAnimation(mPlayerAnimation); - mWater->removeEmitter(ptr); - mWater->addEmitter(ptr); - // apply race height - MWBase::Environment::get().getWorld()->scaleObject(ptr, 1.f); -} + void RenderingManager::skySetDate(int day, int month) + { + mSky->setDate(day, month); + } -bool RenderingManager::vanityRotateCamera(const float *rot) -{ - if(!mCamera->isVanityOrPreviewModeEnabled()) + int RenderingManager::skyGetMasserPhase() const + { + return mSky->getMasserPhase(); + } + + int RenderingManager::skyGetSecundaPhase() const + { + return mSky->getSecundaPhase(); + } + + void RenderingManager::skySetMoonColour(bool red) + { + mSky->setMoonColour(red); + } + + void RenderingManager::configureAmbient(const ESM::Cell *cell) + { + setAmbientColour(SceneUtil::colourFromRGB(cell->mAmbi.mAmbient)); + + mSunLight->setDiffuse(SceneUtil::colourFromRGB(cell->mAmbi.mSunlight)); + mSunLight->setDirection(osg::Vec3f(1.f,-1.f,-1.f)); + } + + void RenderingManager::setSunColour(const osg::Vec4f &colour) + { + // need to wrap this in a StateUpdater? + mSunLight->setDiffuse(colour); + mSunLight->setSpecular(colour); + } + + void RenderingManager::setSunDirection(const osg::Vec3f &direction) + { + osg::Vec3 position = direction * -1; + // need to wrap this in a StateUpdater? + mSunLight->setPosition(osg::Vec4(position.x(), position.y(), position.z(), 0)); + + mSky->setSunDirection(position); + } + + osg::Vec3f RenderingManager::getEyePos() + { + osg::Vec3d eye = mViewer->getCameraManipulator()->getMatrix().getTrans(); + return eye; + } + + void RenderingManager::addCell(const MWWorld::CellStore *store) + { + mPathgrid->addCell(store); + + mWater->changeCell(store); + + if (store->getCell()->isExterior()) + mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + } + + void RenderingManager::removeCell(const MWWorld::CellStore *store) + { + mPathgrid->removeCell(store); + mObjects->removeCell(store); + + if (store->getCell()->isExterior()) + mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + + mWater->removeCell(store); + } + + void RenderingManager::setSkyEnabled(bool enabled) + { + mSky->setEnabled(enabled); + } + + bool RenderingManager::toggleRenderMode(RenderMode mode) + { + if (mode == Render_CollisionDebug || mode == Render_Pathgrid) + return mPathgrid->toggleRenderMode(mode); + else if (mode == Render_Wireframe) + { + bool wireframe = !mStateUpdater->getWireframe(); + mStateUpdater->setWireframe(wireframe); + return wireframe; + } + else if (mode == Render_Water) + { + return mWater->toggle(); + } + else if (mode == Render_Scene) + { + int mask = mViewer->getCamera()->getCullMask(); + bool enabled = mask&Mask_Scene; + enabled = !enabled; + if (enabled) + mask |= Mask_Scene; + else + mask &= ~Mask_Scene; + mViewer->getCamera()->setCullMask(mask); + return enabled; + } + /* + else //if (mode == Render_BoundingBoxes) + { + bool show = !mRendering.getScene()->getShowBoundingBoxes(); + mRendering.getScene()->showBoundingBoxes(show); + return show; + } + */ return false; - - Ogre::Vector3 vRot(rot); - mCamera->rotateCamera(vRot, true); - return true; -} - -void RenderingManager::setCameraDistance(float dist, bool adjust, bool override) -{ - if(!mCamera->isVanityOrPreviewModeEnabled() && !mCamera->isFirstPerson()) - { - if(mCamera->isNearest() && dist > 0.f) - mCamera->toggleViewMode(); - else - mCamera->setCameraDistance(-dist / 120.f * 10, adjust, override); } - else if(mCamera->isFirstPerson() && dist < 0.f) + + void RenderingManager::configureFog(const ESM::Cell *cell) + { + osg::Vec4f color = SceneUtil::colourFromRGB(cell->mAmbi.mFog); + + configureFog (cell->mAmbi.mFogDensity, mUnderwaterIndoorFog, color); + } + + void RenderingManager::configureFog(float fogDepth, float underwaterFog, const osg::Vec4f &color) + { + mFogDepth = fogDepth; + mFogColor = color; + mUnderwaterFog = underwaterFog; + } + + SkyManager* RenderingManager::getSkyManager() + { + return mSky.get(); + } + + void RenderingManager::update(float dt, bool paused) + { + if (!paused) + { + mEffectManager->update(dt); + mSky->update(dt); + } + + mWater->update(dt); + mCamera->update(dt, paused); + + osg::Vec3f focal, cameraPos; + mCamera->getPosition(focal, cameraPos); + mCurrentCameraPos = cameraPos; + if (mWater->isUnderwater(cameraPos)) + { + setFogColor(mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f-mUnderwaterWeight)); + mStateUpdater->setFogStart(mViewDistance * (1 - mUnderwaterFog)); + mStateUpdater->setFogEnd(mViewDistance); + } + else + { + setFogColor(mFogColor); + + if (mFogDepth == 0.f) + { + mStateUpdater->setFogStart(0.f); + mStateUpdater->setFogEnd(std::numeric_limits::max()); + } + else + { + mStateUpdater->setFogStart(mViewDistance * (1 - mFogDepth)); + mStateUpdater->setFogEnd(mViewDistance); + } + } + } + + void RenderingManager::updatePlayerPtr(const MWWorld::Ptr &ptr) + { + if(mPlayerAnimation.get()) + mPlayerAnimation->updatePtr(ptr); + + mCamera->attachTo(ptr); + } + + void RenderingManager::removePlayer(const MWWorld::Ptr &player) + { + mWater->removeEmitter(player); + } + + void RenderingManager::rotateObject(const MWWorld::Ptr &ptr, const osg::Quat& rot) + { + if(ptr == mCamera->getTrackingPtr() && + !mCamera->isVanityOrPreviewModeEnabled()) + { + mCamera->rotateCamera(-ptr.getRefData().getPosition().rot[0], -ptr.getRefData().getPosition().rot[2], false); + } + + ptr.getRefData().getBaseNode()->setAttitude(rot); + } + + void RenderingManager::moveObject(const MWWorld::Ptr &ptr, const osg::Vec3f &pos) + { + ptr.getRefData().getBaseNode()->setPosition(pos); + } + + void RenderingManager::scaleObject(const MWWorld::Ptr &ptr, const osg::Vec3f &scale) + { + ptr.getRefData().getBaseNode()->setScale(scale); + + if (ptr == mCamera->getTrackingPtr()) // update height of camera + mCamera->processViewChange(); + } + + void RenderingManager::removeObject(const MWWorld::Ptr &ptr) + { + mObjects->removeObject(ptr); + mWater->removeEmitter(ptr); + } + + void RenderingManager::setWaterEnabled(bool enabled) + { + mWater->setEnabled(enabled); + mSky->setWaterEnabled(enabled); + } + + void RenderingManager::setWaterHeight(float height) + { + mWater->setHeight(height); + mSky->setWaterHeight(height); + } + + class NotifyDrawCompletedCallback : public osg::Camera::DrawCallback + { + public: + NotifyDrawCompletedCallback() + : mDone(false) + { + } + + virtual void operator () (osg::RenderInfo& renderInfo) const + { + mMutex.lock(); + mDone = true; + mMutex.unlock(); + mCondition.signal(); + } + + void waitTillDone() + { + mMutex.lock(); + if (mDone) + return; + mCondition.wait(&mMutex); + mMutex.unlock(); + } + + mutable OpenThreads::Condition mCondition; + mutable OpenThreads::Mutex mMutex; + mutable bool mDone; + }; + + + class NoTraverseCallback : public osg::NodeCallback + { + public: + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + } + }; + + void RenderingManager::screenshot(osg::Image *image, int w, int h) + { + osg::ref_ptr rttCamera (new osg::Camera); + rttCamera->setNodeMask(Mask_RenderToTexture); + rttCamera->attach(osg::Camera::COLOR_BUFFER, image); + rttCamera->setRenderOrder(osg::Camera::PRE_RENDER); + rttCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + rttCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); + rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); + rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix()); + rttCamera->setViewport(0, 0, w, h); + + osg::ref_ptr texture (new osg::Texture2D); + texture->setInternalFormat(GL_RGB); + texture->setTextureSize(w, h); + texture->setResizeNonPowerOfTwoHint(false); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + rttCamera->attach(osg::Camera::COLOR_BUFFER, texture); + + image->setDataType(GL_UNSIGNED_BYTE); + image->setPixelFormat(texture->getInternalFormat()); + + rttCamera->setUpdateCallback(new NoTraverseCallback); + rttCamera->addChild(mLightRoot); + rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); + + mRootNode->addChild(rttCamera); + + // The draw needs to complete before we can copy back our image. + osg::ref_ptr callback (new NotifyDrawCompletedCallback); + rttCamera->setFinalDrawCallback(callback); + + // at the time this function is called we are in the middle of a frame, + // so out of order calls are necessary to get a correct frameNumber for the next frame. + // refer to the advance() and frame() order in Engine::go() + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + + callback->waitTillDone(); + + // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + + rttCamera->removeChildren(0, rttCamera->getNumChildren()); + rttCamera->setGraphicsContext(NULL); + mRootNode->removeChild(rttCamera); + } + + osg::Vec4f RenderingManager::getScreenBounds(const MWWorld::Ptr& ptr) + { + if (!ptr.getRefData().getBaseNode()) + return osg::Vec4f(); + + osg::ComputeBoundsVisitor computeBoundsVisitor; + computeBoundsVisitor.setTraversalMask(~(Mask_ParticleSystem|Mask_Effect)); + ptr.getRefData().getBaseNode()->accept(computeBoundsVisitor); + + osg::Matrix viewProj = mViewer->getCamera()->getViewMatrix() * mViewer->getCamera()->getProjectionMatrix(); + float min_x = 1.0f, max_x = 0.0f, min_y = 1.0f, max_y = 0.0f; + for (int i=0; i<8; ++i) + { + osg::Vec3f corner = computeBoundsVisitor.getBoundingBox().corner(i); + corner = corner * viewProj; + + float x = (corner.x() + 1.f) * 0.5f; + float y = (corner.y() - 1.f) * (-0.5f); + + if (x < min_x) + min_x = x; + + if (x > max_x) + max_x = x; + + if (y < min_y) + min_y = y; + + if (y > max_y) + max_y = y; + } + + return osg::Vec4f(min_x, min_y, max_x, max_y); + } + + RenderingManager::RayResult getIntersectionResult (osgUtil::LineSegmentIntersector* intersector) + { + RenderingManager::RayResult result; + result.mHit = false; + if (intersector->containsIntersections()) + { + result.mHit = true; + osgUtil::LineSegmentIntersector::Intersection intersection = intersector->getFirstIntersection(); + + result.mHitPointWorld = intersection.getWorldIntersectPoint(); + result.mHitNormalWorld = intersection.getWorldIntersectNormal(); + + PtrHolder* ptrHolder = NULL; + for (osg::NodePath::const_iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) + { + osg::UserDataContainer* userDataContainer = (*it)->getUserDataContainer(); + if (!userDataContainer) + continue; + for (unsigned int i=0; igetNumUserObjects(); ++i) + { + if (PtrHolder* p = dynamic_cast(userDataContainer->getUserObject(i))) + ptrHolder = p; + } + } + + if (ptrHolder) + result.mHitObject = ptrHolder->mPtr; + } + + return result; + + } + + osg::ref_ptr createIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors) + { + osg::ref_ptr intersectionVisitor( new osgUtil::IntersectionVisitor(intersector)); + int mask = intersectionVisitor->getTraversalMask(); + mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater); + if (ignorePlayer) + mask &= ~(Mask_Player); + if (ignoreActors) + mask &= ~(Mask_Actor|Mask_Player); + + intersectionVisitor->setTraversalMask(mask); + return intersectionVisitor; + } + + RenderingManager::RayResult RenderingManager::castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors) + { + osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::LineSegmentIntersector::MODEL, + origin, dest)); + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); + + mRootNode->accept(*createIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); + + return getIntersectionResult(intersector); + } + + RenderingManager::RayResult RenderingManager::castCameraToViewportRay(const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors) + { + osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::LineSegmentIntersector::PROJECTION, + nX * 2.f - 1.f, nY * (-2.f) + 1.f)); + + osg::Vec3d dist (0.f, 0.f, -maxDistance); + + dist = dist * mViewer->getCamera()->getProjectionMatrix(); + + osg::Vec3d end = intersector->getEnd(); + end.z() = dist.z(); + intersector->setEnd(end); + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); + + mViewer->getCamera()->accept(*createIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); + + return getIntersectionResult(intersector); + } + + void RenderingManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) + { + mObjects->updatePtr(old, updated); + } + + void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale) + { + mEffectManager->addEffect(model, texture, worldPosition, scale); + } + + void RenderingManager::notifyWorldSpaceChanged() + { + mEffectManager->clear(); + mWater->clearRipples(); + } + + void RenderingManager::clear() + { + mSky->setMoonColour(false); + + notifyWorldSpaceChanged(); + } + + MWRender::Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) + { + if (mPlayerAnimation.get() && ptr == mPlayerAnimation->getPtr()) + return mPlayerAnimation.get(); + + return mObjects->getAnimation(ptr); + } + + const MWRender::Animation* RenderingManager::getAnimation(const MWWorld::ConstPtr &ptr) const + { + if (mPlayerAnimation.get() && ptr == mPlayerAnimation->getPtr()) + return mPlayerAnimation.get(); + + return mObjects->getAnimation(ptr); + } + + void RenderingManager::setupPlayer(const MWWorld::Ptr &player) + { + if (!mPlayerNode) + { + mPlayerNode = new SceneUtil::PositionAttitudeTransform; + mPlayerNode->setNodeMask(Mask_Player); + mLightRoot->addChild(mPlayerNode); + } + + mPlayerNode->setUserDataContainer(new osg::DefaultUserDataContainer); + mPlayerNode->getUserDataContainer()->addUserObject(new PtrHolder(player)); + + player.getRefData().setBaseNode(mPlayerNode); + + mWater->addEmitter(player); + } + + void RenderingManager::renderPlayer(const MWWorld::Ptr &player) + { + mPlayerAnimation.reset(new NpcAnimation(player, player.getRefData().getBaseNode(), mResourceSystem, 0, false, NpcAnimation::VM_Normal, + mFirstPersonFieldOfView)); + + mCamera->setAnimation(mPlayerAnimation.get()); + mCamera->attachTo(player); + } + + void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr) + { + NpcAnimation *anim = NULL; + if(ptr == mPlayerAnimation->getPtr()) + anim = mPlayerAnimation.get(); + else + anim = dynamic_cast(mObjects->getAnimation(ptr)); + if(anim) + { + anim->rebuild(); + if(mCamera->getTrackingPtr() == ptr) + { + mCamera->attachTo(ptr); + mCamera->setAnimation(anim); + } + } + } + + void RenderingManager::addWaterRippleEmitter(const MWWorld::Ptr &ptr) + { + mWater->addEmitter(ptr); + } + + void RenderingManager::removeWaterRippleEmitter(const MWWorld::Ptr &ptr) + { + mWater->removeEmitter(ptr); + } + + void RenderingManager::emitWaterRipple(const osg::Vec3f &pos) + { + mWater->emitRipple(pos); + } + + void RenderingManager::updateProjectionMatrix() + { + double aspect = mViewer->getCamera()->getViewport()->aspectRatio(); + float fov = mFieldOfView; + if (mFieldOfViewOverridden) + fov = mFieldOfViewOverride; + mViewer->getCamera()->setProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance); + } + + void RenderingManager::updateTextureFiltering() + { + mResourceSystem->getTextureManager()->setFilterSettings( + Settings::Manager::getString("texture mag filter", "General"), + Settings::Manager::getString("texture min filter", "General"), + Settings::Manager::getString("texture mipmap", "General"), + Settings::Manager::getInt("anisotropy", "General"), + mViewer + ); + } + + void RenderingManager::updateAmbient() + { + osg::Vec4f color = mAmbientColor; + + if (mNightEyeFactor > 0.f) + color += osg::Vec4f(0.7, 0.7, 0.7, 0.0) * mNightEyeFactor; + + mStateUpdater->setAmbientColor(color); + } + + void RenderingManager::setFogColor(const osg::Vec4f &color) + { + mViewer->getCamera()->setClearColor(color); + + mStateUpdater->setFogColor(color); + } + + void RenderingManager::processChangedSettings(const Settings::CategorySettingVector &changed) + { + for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) + { + if (it->first == "Camera" && it->second == "field of view") + { + mFieldOfView = Settings::Manager::getFloat("field of view", "Camera"); + updateProjectionMatrix(); + } + else if (it->first == "Camera" && it->second == "viewing distance") + { + mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); + mStateUpdater->setFogEnd(mViewDistance); + updateProjectionMatrix(); + } + else if (it->first == "General" && (it->second == "texture filter" || + it->second == "texture mipmap" || + it->second == "anisotropy")) + updateTextureFiltering(); + else if (it->first == "Water") + mWater->processChangedSettings(changed); + } + } + + float RenderingManager::getNearClipDistance() const + { + return mNearClip; + } + + float RenderingManager::getTerrainHeightAt(const osg::Vec3f &pos) + { + return mTerrain->getHeightAt(pos); + } + + bool RenderingManager::vanityRotateCamera(const float *rot) + { + if(!mCamera->isVanityOrPreviewModeEnabled()) + return false; + + mCamera->rotateCamera(rot[0], rot[2], true); + return true; + } + + void RenderingManager::setCameraDistance(float dist, bool adjust, bool override) + { + if(!mCamera->isVanityOrPreviewModeEnabled() && !mCamera->isFirstPerson()) + { + if(mCamera->isNearest() && dist > 0.f) + mCamera->toggleViewMode(); + else + mCamera->setCameraDistance(-dist / 120.f * 10, adjust, override); + } + else if(mCamera->isFirstPerson() && dist < 0.f) + { + mCamera->toggleViewMode(); + mCamera->setCameraDistance(0.f, false, override); + } + } + + void RenderingManager::resetCamera() + { + mCamera->reset(); + } + + float RenderingManager::getCameraDistance() const + { + return mCamera->getCameraDistance(); + } + + Camera* RenderingManager::getCamera() + { + return mCamera.get(); + } + + const osg::Vec3f &RenderingManager::getCameraPosition() const + { + return mCurrentCameraPos; + } + + void RenderingManager::togglePOV() { mCamera->toggleViewMode(); - mCamera->setCameraDistance(0.f, false, override); } -} -void RenderingManager::worldToInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y) -{ - return mLocalMap->worldToInteriorMapPosition (position, nX, nY, x, y); -} - -Ogre::Vector2 RenderingManager::interiorMapToWorldPosition(float nX, float nY, int x, int y) -{ - return mLocalMap->interiorMapToWorldPosition(nX, nY, x, y); -} - -bool RenderingManager::isPositionExplored (float nX, float nY, int x, int y, bool interior) -{ - return mLocalMap->isPositionExplored(nX, nY, x, y, interior); -} - -Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) -{ - Animation *anim = mActors->getAnimation(ptr); - - if(!anim && ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) - anim = mPlayerAnimation; - - if (!anim) - anim = mObjects->getAnimation(ptr); - - return anim; -} - -void RenderingManager::screenshot(Image &image, int w, int h) -{ - // Create a temporary render target. We do not use the RenderWindow since we want a specific size. - // Also, the GUI should not be visible (and it is only rendered on the RenderWindow's primary viewport) - const std::string tempName = "@temp"; - Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().createManual(tempName, - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, w, h, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET); - - float oldAspect = mRendering.getCamera()->getAspectRatio(); - - mRendering.getCamera()->setAspectRatio(w / static_cast(h)); - - Ogre::RenderTarget* rt = texture->getBuffer()->getRenderTarget(); - Ogre::Viewport* vp = rt->addViewport(mRendering.getCamera()); - vp->setBackgroundColour(mRendering.getViewport()->getBackgroundColour()); - vp->setOverlaysEnabled(false); - vp->setVisibilityMask(mRendering.getViewport()->getVisibilityMask()); - rt->update(); - - Ogre::PixelFormat pf = rt->suggestPixelFormat(); - - image.loadDynamicImage( - OGRE_ALLOC_T(Ogre::uchar, w * h * Ogre::PixelUtil::getNumElemBytes(pf), Ogre::MEMCATEGORY_GENERAL), - w, h, 1, pf, true // autoDelete=true, frees memory we allocate - ); - rt->copyContentsToMemory(image.getPixelBox()); // getPixelBox returns a box sharing the same memory as the image - - Ogre::TextureManager::getSingleton().remove(tempName); - mRendering.getCamera()->setAspectRatio(oldAspect); -} - -void RenderingManager::addWaterRippleEmitter (const MWWorld::Ptr& ptr, float scale, float force) -{ - mWater->addEmitter (ptr, scale, force); -} - -void RenderingManager::removeWaterRippleEmitter (const MWWorld::Ptr& ptr) -{ - mWater->removeEmitter (ptr); -} - -void RenderingManager::updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) -{ - mWater->updateEmitterPtr(old, ptr); -} - -void RenderingManager::frameStarted(float dt, bool paused) -{ - if (mTerrain) - mTerrain->update(mRendering.getCamera()->getRealPosition()); - - if (!paused) - mWater->frameStarted(dt); -} - -void RenderingManager::resetCamera() -{ - mCamera->reset(); -} - -float RenderingManager::getTerrainHeightAt(Ogre::Vector3 worldPos) -{ - if (!mTerrain || !mTerrain->getVisible()) - return -std::numeric_limits::max(); - return mTerrain->getHeightAt(worldPos); -} - -void RenderingManager::enableTerrain(bool enable) -{ - if (enable) + void RenderingManager::togglePreviewMode(bool enable) { - if (!mTerrain) - { - if (Settings::Manager::getBool("distant land", "Terrain")) - mTerrain = new Terrain::DefaultWorld(mRendering.getScene(), new MWRender::TerrainStorage(true), RV_Terrain, - Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY, 1, 64); - else - mTerrain = new Terrain::TerrainGrid(mRendering.getScene(), new MWRender::TerrainStorage(false), RV_Terrain, - Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY); - mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), - Settings::Manager::getBool("split", "Shadows")); - mTerrain->update(mRendering.getCamera()->getRealPosition()); - } - mTerrain->setVisible(true); + mCamera->togglePreviewMode(enable); } - else if (mTerrain) - mTerrain->setVisible(false); -} -float RenderingManager::getCameraDistance() const -{ - return mCamera->getCameraDistance(); -} + bool RenderingManager::toggleVanityMode(bool enable) + { + return mCamera->toggleVanityMode(enable); + } -void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const Vector3 &worldPosition, float scale) -{ - mEffectManager->addEffect(model, texture, worldPosition, scale); -} + void RenderingManager::allowVanityMode(bool allow) + { + mCamera->allowVanityMode(allow); + } -void RenderingManager::clear() -{ - mLocalMap->clear(); - notifyWorldSpaceChanged(); -} + void RenderingManager::togglePlayerLooking(bool enable) + { + mCamera->togglePlayerLooking(enable); + } -} // namespace + void RenderingManager::changeVanityModeScale(float factor) + { + if(mCamera->isVanityOrPreviewModeEnabled()) + mCamera->setCameraDistance(-factor/120.f*10, true, true); + } + + void RenderingManager::overrideFieldOfView(float val) + { + if (mFieldOfViewOverridden != true || mFieldOfViewOverride != val) + { + mFieldOfViewOverridden = true; + mFieldOfViewOverride = val; + updateProjectionMatrix(); + } + } + + void RenderingManager::resetFieldOfView() + { + if (mFieldOfViewOverridden == true) + { + mFieldOfViewOverridden = false; + updateProjectionMatrix(); + } + } + +} diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 9f029c1b91..58012078c4 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -1,36 +1,35 @@ -#ifndef GAME_RENDERING_MANAGER_H -#define GAME_RENDERING_MANAGER_H +#ifndef OPENMW_MWRENDER_RENDERINGMANAGER_H +#define OPENMW_MWRENDER_RENDERINGMANAGER_H -#include "sky.hpp" -#include "debugging.hpp" +#include +#include #include -#include - -#include +#include "objects.hpp" #include "renderinginterface.hpp" +#include "rendermode.hpp" -#include "objects.hpp" -#include "actors.hpp" -#include "camera.hpp" -#include "occlusionquery.hpp" - -namespace Ogre +namespace osg { - class SceneNode; + class Group; + class PositionAttitudeTransform; } -namespace MWWorld +namespace Resource { - class Ptr; - class CellStore; + class ResourceSystem; } -namespace sh +namespace osgViewer { - class Factory; + class Viewer; +} + +namespace ESM +{ + struct Cell; } namespace Terrain @@ -38,240 +37,190 @@ namespace Terrain class World; } +namespace MWWorld +{ + class Fallback; +} + namespace MWRender { - class Shadows; - class LocalMap; - class Water; - class GlobalMap; - class Animation; + + class StateUpdater; + class EffectManager; + class SkyManager; + class NpcAnimation; + class Pathgrid; + class Camera; + class Water; -class RenderingManager: private RenderingInterface, public Ogre::RenderTargetListener, public OEngine::Render::WindowSizeListener -{ -private: - virtual MWRender::Objects& getObjects(); - virtual MWRender::Actors& getActors(); - -public: - RenderingManager(OEngine::Render::OgreRenderer& _rend, const boost::filesystem::path& resDir, - const boost::filesystem::path& cacheDir, OEngine::Physic::PhysicEngine* engine, - MWWorld::Fallback* fallback); - virtual ~RenderingManager(); - - void togglePOV() - { mCamera->toggleViewMode(); } - - void togglePreviewMode(bool enable) - { mCamera->togglePreviewMode(enable); } - - bool toggleVanityMode(bool enable) - { return mCamera->toggleVanityMode(enable); } - - void allowVanityMode(bool allow) - { mCamera->allowVanityMode(allow); } - - void togglePlayerLooking(bool enable) - { mCamera->togglePlayerLooking(enable); } - - void changeVanityModeScale(float factor) + class RenderingManager : public MWRender::RenderingInterface { - if(mCamera->isVanityOrPreviewModeEnabled()) - mCamera->setCameraDistance(-factor/120.f*10, true, true); - } + public: + RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, + const MWWorld::Fallback* fallback, const std::string& resourcePath); + ~RenderingManager(); - void resetCamera(); + MWRender::Objects& getObjects(); - bool vanityRotateCamera(const float *rot); - void setCameraDistance(float dist, bool adjust = false, bool override = true); - float getCameraDistance() const; + Resource::ResourceSystem* getResourceSystem(); - void setupPlayer(const MWWorld::Ptr &ptr); - void renderPlayer(const MWWorld::Ptr &ptr); + osg::Group* getLightRoot(); - SkyManager* getSkyManager(); + void setNightEyeFactor(float factor); - MWRender::Camera* getCamera() const; + void setAmbientColour(const osg::Vec4f& colour); - bool toggleRenderMode(int mode); + void skySetDate(int day, int month); + int skyGetMasserPhase() const; + int skyGetSecundaPhase() const; + void skySetMoonColour(bool red); - void removeCell (MWWorld::CellStore *store); + void setSunDirection(const osg::Vec3f& direction); + void setSunColour(const osg::Vec4f& colour); - /// \todo this function should be removed later. Instead the rendering subsystems should track - /// when rebatching is needed and update automatically at the end of each frame. - void cellAdded (MWWorld::CellStore *store); + void configureAmbient(const ESM::Cell* cell); + void configureFog(const ESM::Cell* cell); + void configureFog(float fogDepth, float underwaterFog, const osg::Vec4f& colour); - /// Clear all savegame-specific data (i.e. fog of war textures) - void clear(); + void addCell(const MWWorld::CellStore* store); + void removeCell(const MWWorld::CellStore* store); - void enableTerrain(bool enable); + void updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& updated); - void removeWater(); + void rotateObject(const MWWorld::Ptr& ptr, const osg::Quat& rot); + void moveObject(const MWWorld::Ptr& ptr, const osg::Vec3f& pos); + void scaleObject(const MWWorld::Ptr& ptr, const osg::Vec3f& scale); - /// Write current fog of war for this cell to the CellStore - void writeFog (MWWorld::CellStore* store); + void removeObject(const MWWorld::Ptr& ptr); - void addObject (const MWWorld::Ptr& ptr, const std::string& model); - void removeObject (const MWWorld::Ptr& ptr); + void setWaterEnabled(bool enabled); + void setWaterHeight(float level); - void moveObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& position); - void scaleObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& scale); + /// Take a screenshot of w*h onto the given image, not including the GUI. + void screenshot(osg::Image* image, int w, int h); - /// Updates an object's rotation - void rotateObject (const MWWorld::Ptr& ptr); + struct RayResult + { + bool mHit; + osg::Vec3f mHitNormalWorld; + osg::Vec3f mHitPointWorld; + MWWorld::Ptr mHitObject; + }; - void setWaterHeight(float height); - void setWaterEnabled(bool enabled); - bool toggleWater(); - bool toggleWorld(); + RayResult castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors=false); - /// Updates object rendering after cell change - /// \param old Object reference in previous cell - /// \param cur Object reference in new cell - void updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur); + /// Return the object under the mouse cursor / crosshair position, given by nX and nY normalized screen coordinates, + /// where (0,0) is the top left corner. + RayResult castCameraToViewportRay(const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors=false); - /// Specifies an updated Ptr object for the player (used on cell change). - void updatePlayerPtr(const MWWorld::Ptr &ptr); + /// Get the bounding box of the given object in screen coordinates as (minX, minY, maxX, maxY), with (0,0) being the top left corner. + osg::Vec4f getScreenBounds(const MWWorld::Ptr& ptr); - /// Currently for NPCs only. Rebuilds the NPC, updating their root model, animation sources, - /// and equipment. - void rebuildPtr(const MWWorld::Ptr &ptr); + void setSkyEnabled(bool enabled); - void update (float duration, bool paused); + bool toggleRenderMode(RenderMode mode); - void setAmbientColour(const Ogre::ColourValue& colour); - void setSunColour(const Ogre::ColourValue& colour); - void setSunDirection(const Ogre::Vector3& direction, bool is_night); - void sunEnable(bool real); ///< @param real whether or not to really disable the sunlight (otherwise just set diffuse to 0) - void sunDisable(bool real); + SkyManager* getSkyManager(); - void disableLights(bool sun); ///< @param sun whether or not to really disable the sunlight (otherwise just set diffuse to 0) - void enableLights(bool sun); + osg::Vec3f getEyePos(); + void spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale = 1.f); - void preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); - void postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); + /// Clear all savegame-specific data + void clear(); - bool occlusionQuerySupported() { return mOcclusionQuery->supported(); } - OcclusionQuery* getOcclusionQuery() { return mOcclusionQuery; } + /// Clear all worldspace-specific data + void notifyWorldSpaceChanged(); - float getTerrainHeightAt (Ogre::Vector3 worldPos); + void update(float dt, bool paused); - void notifyWorldSpaceChanged(); + Animation* getAnimation(const MWWorld::Ptr& ptr); + const Animation* getAnimation(const MWWorld::ConstPtr& ptr) const; - void getTriangleBatchCount(unsigned int &triangles, unsigned int &batches); + void addWaterRippleEmitter(const MWWorld::Ptr& ptr); + void removeWaterRippleEmitter(const MWWorld::Ptr& ptr); + void emitWaterRipple(const osg::Vec3f& pos); - void setGlare(bool glare); - void skyEnable (); - void skyDisable (); - void skySetHour (double hour); - void skySetDate (int day, int month); - int skyGetMasserPhase() const; - int skyGetSecundaPhase() const; - void skySetMoonColour (bool red); - void configureAmbient(MWWorld::CellStore &mCell); + void updatePlayerPtr(const MWWorld::Ptr &ptr); - void addWaterRippleEmitter (const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f); - void removeWaterRippleEmitter (const MWWorld::Ptr& ptr); - void updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); + void removePlayer(const MWWorld::Ptr& player); + void setupPlayer(const MWWorld::Ptr& player); + void renderPlayer(const MWWorld::Ptr& player); - void updateTerrain (); - ///< update the terrain according to the player position. Usually done automatically, but should be done manually - /// before calling requestMap + void rebuildPtr(const MWWorld::Ptr& ptr); - void requestMap (MWWorld::CellStore* cell); - ///< request the local map for a cell + void processChangedSettings(const Settings::CategorySettingVector& settings); - /// configure fog according to cell - void configureFog(const MWWorld::CellStore &mCell); + float getNearClipDistance() const; - /// configure fog manually - void configureFog(const float density, const Ogre::ColourValue& colour); + float getTerrainHeightAt(const osg::Vec3f& pos); - Ogre::Vector4 boundingBoxToScreen(Ogre::AxisAlignedBox bounds); - ///< transform the specified bounding box (in world coordinates) into screen coordinates. - /// @return packed vector4 (min_x, min_y, max_x, max_y) + // camera stuff + bool vanityRotateCamera(const float *rot); + void setCameraDistance(float dist, bool adjust, bool override); + void resetCamera(); + float getCameraDistance() const; + Camera* getCamera(); + const osg::Vec3f& getCameraPosition() const; + void togglePOV(); + void togglePreviewMode(bool enable); + bool toggleVanityMode(bool enable); + void allowVanityMode(bool allow); + void togglePlayerLooking(bool enable); + void changeVanityModeScale(float factor); - void processChangedSettings(const Settings::CategorySettingVector& settings); + /// temporarily override the field of view with given value. + void overrideFieldOfView(float val); + /// reset a previous overrideFieldOfView() call, i.e. revert to field of view specified in the settings file. + void resetFieldOfView(); - Ogre::Viewport* getViewport() { return mRendering.getViewport(); } + private: + void updateProjectionMatrix(); + void updateTextureFiltering(); + void updateAmbient(); + void setFogColor(const osg::Vec4f& color); - void worldToInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y); - ///< see MWRender::LocalMap::worldToInteriorMapPosition + osg::ref_ptr mViewer; + osg::ref_ptr mRootNode; + osg::ref_ptr mLightRoot; + Resource::ResourceSystem* mResourceSystem; - Ogre::Vector2 interiorMapToWorldPosition (float nX, float nY, int x, int y); - ///< see MWRender::LocalMap::interiorMapToWorldPosition + osg::ref_ptr mSunLight; - bool isPositionExplored (float nX, float nY, int x, int y, bool interior); - ///< see MWRender::LocalMap::isPositionExplored + std::auto_ptr mPathgrid; + std::auto_ptr mObjects; + std::auto_ptr mWater; + std::auto_ptr mTerrain; + std::auto_ptr mSky; + std::auto_ptr mEffectManager; + std::auto_ptr mPlayerAnimation; + osg::ref_ptr mPlayerNode; + std::auto_ptr mCamera; + osg::Vec3f mCurrentCameraPos; - Animation* getAnimation(const MWWorld::Ptr &ptr); + osg::ref_ptr mStateUpdater; - void frameStarted(float dt, bool paused); - void screenshot(Ogre::Image& image, int w, int h); + float mFogDepth; + osg::Vec4f mUnderwaterColor; + float mUnderwaterWeight; + float mUnderwaterFog; + float mUnderwaterIndoorFog; + osg::Vec4f mFogColor; - void spawnEffect (const std::string& model, const std::string& texture, const Ogre::Vector3& worldPosition, float scale=1.f); + osg::Vec4f mAmbientColor; + float mNightEyeFactor; -protected: - virtual void windowResized(int x, int y); + float mNearClip; + float mViewDistance; + float mFieldOfViewOverride; + bool mFieldOfViewOverridden; + float mFieldOfView; + float mFirstPersonFieldOfView; -private: - sh::Factory* mFactory; - - void setAmbientMode(); - void applyFog(bool underwater); - - void attachCameraTo(const MWWorld::Ptr& ptr); - - void setMenuTransparency(float val); - - bool mSunEnabled; - - MWWorld::Fallback* mFallback; - - SkyManager* mSkyManager; - - OcclusionQuery* mOcclusionQuery; - - Terrain::World* mTerrain; - - MWRender::Water *mWater; - - GlobalMap* mGlobalMap; - - OEngine::Render::OgreRenderer &mRendering; - - MWRender::Objects* mObjects; - MWRender::Actors* mActors; - - MWRender::EffectManager* mEffectManager; - - MWRender::NpcAnimation *mPlayerAnimation; - - // 0 normal, 1 more bright, 2 max - int mAmbientMode; - - Ogre::ColourValue mAmbientColor; - Ogre::Light* mSun; - - Ogre::SceneNode *mRootNode; - - Ogre::ColourValue mFogColour; - float mFogStart; - float mFogEnd; - - OEngine::Physic::PhysicEngine* mPhysicsEngine; - - MWRender::Camera *mCamera; - - MWRender::Debugging *mDebugging; - - MWRender::LocalMap* mLocalMap; - - MWRender::Shadows* mShadows; - - bool mRenderWorld; -}; + void operator = (const RenderingManager&); + RenderingManager(const RenderingManager&); + }; } diff --git a/apps/openmw/mwrender/rendermode.hpp b/apps/openmw/mwrender/rendermode.hpp new file mode 100644 index 0000000000..ba767bc55b --- /dev/null +++ b/apps/openmw/mwrender/rendermode.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_MWRENDER_RENDERMODE_H +#define OPENMW_MWRENDER_RENDERMODE_H + +namespace MWRender +{ + + enum RenderMode + { + Render_CollisionDebug, + Render_Wireframe, + Render_Pathgrid, + Render_BoundingBoxes, + Render_Water, + Render_Scene + }; + +} + +#endif diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index f75061af49..f232ea475d 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -1,72 +1,122 @@ #include "ripplesimulation.hpp" -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include -#include +#include +#include +#include +#include + +#include "vismask.hpp" -#include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" #include "../mwworld/fallback.hpp" -#include "renderconst.hpp" +#include "../mwmechanics/actorutil.hpp" + +namespace +{ + void createWaterRippleStateSet(Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback, osg::Node* node) + { + int rippleFrameCount = fallback->getFallbackInt("Water_RippleFrameCount"); + if (rippleFrameCount <= 0) + return; + + std::string tex = fallback->getFallbackString("Water_RippleTexture"); + + std::vector > textures; + for (int i=0; igetTextureManager()->getTexture2D(texname.str(), osg::Texture::REPEAT, osg::Texture::REPEAT)); + } + + osg::ref_ptr controller (new NifOsg::FlipController(0, 0.3f/rippleFrameCount, textures)); + controller->setSource(boost::shared_ptr(new SceneUtil::FrameTimeSource)); + node->addUpdateCallback(controller); + + osg::ref_ptr stateset (new osg::StateSet); + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); + + osg::ref_ptr depth (new osg::Depth); + depth->setWriteMask(false); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + + osg::ref_ptr polygonOffset (new osg::PolygonOffset); + polygonOffset->setUnits(-1); + polygonOffset->setFactor(-1); + stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + + osg::ref_ptr mat (new osg::Material); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + mat->setColorMode(osg::Material::DIFFUSE); + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + + node->setStateSet(stateset); + } +} namespace MWRender { -RippleSimulation::RippleSimulation(Ogre::SceneManager* mainSceneManager, const MWWorld::Fallback* fallback) - : mSceneMgr(mainSceneManager) - , mParticleSystem(NULL) - , mSceneNode(NULL) +RippleSimulation::RippleSimulation(osg::Group *parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback) + : mParent(parent) { - mRippleLifeTime = fallback->getFallbackFloat("Water_RippleLifetime"); - mRippleRotSpeed = fallback->getFallbackFloat("Water_RippleRotSpeed"); + osg::ref_ptr geode (new osg::Geode); - // Unknown: - // fallback=Water_RippleScale,0.15, 6.5 - // fallback=Water_RippleAlphas,0.7, 0.1, 0.01 + mParticleSystem = new osgParticle::ParticleSystem; + geode->addDrawable(mParticleSystem); - // Instantiate from ripples.particle file - mParticleSystem = mSceneMgr->createParticleSystem("openmw/Ripples", "openmw/Ripples"); + mParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); + mParticleSystem->setAlignVectorX(osg::Vec3f(1,0,0)); + mParticleSystem->setAlignVectorY(osg::Vec3f(0,1,0)); - mParticleSystem->setRenderQueueGroup(RQG_Ripples); - mParticleSystem->setVisibilityFlags(RV_Effects); + osgParticle::Particle& particleTemplate = mParticleSystem->getDefaultParticleTemplate(); + particleTemplate.setSizeRange(osgParticle::rangef(15, 180)); + particleTemplate.setColorRange(osgParticle::rangev4(osg::Vec4f(1,1,1,0.7), osg::Vec4f(1,1,1,0.7))); + particleTemplate.setAlphaRange(osgParticle::rangef(1.f, 0.f)); + particleTemplate.setAngularVelocity(osg::Vec3f(0,0,fallback->getFallbackFloat("Water_RippleRotSpeed"))); + particleTemplate.setLifeTime(fallback->getFallbackFloat("Water_RippleLifetime")); - int rippleFrameCount = fallback->getFallbackInt("Water_RippleFrameCount"); - std::string tex = fallback->getFallbackString("Water_RippleTexture"); + osg::ref_ptr updater (new osgParticle::ParticleSystemUpdater); + updater->addParticleSystem(mParticleSystem); - sh::MaterialInstance* mat = sh::Factory::getInstance().getMaterialInstance("openmw/Ripple"); - mat->setProperty("anim_texture2", sh::makeProperty(new sh::StringValue(std::string("textures\\water\\") + tex + ".dds " - + Ogre::StringConverter::toString(rippleFrameCount) - + " " - + Ogre::StringConverter::toString(0.3)))); + mParticleNode = new osg::PositionAttitudeTransform; + mParticleNode->addChild(updater); + mParticleNode->addChild(geode); + mParticleNode->setNodeMask(Mask_Effect); - // seems to be required to allocate mFreeParticles. TODO: patch Ogre to handle this better - mParticleSystem->_update(0.f); + createWaterRippleStateSet(resourceSystem, fallback, mParticleNode); - mSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - mSceneNode->attachObject(mParticleSystem); + mParent->addChild(mParticleNode); } RippleSimulation::~RippleSimulation() { - if (mParticleSystem) - mSceneMgr->destroyParticleSystem(mParticleSystem); - mParticleSystem = NULL; - - if (mSceneNode) - mSceneMgr->destroySceneNode(mSceneNode); - mSceneNode = NULL; + mParent->removeChild(mParticleNode); } -void RippleSimulation::update(float dt, Ogre::Vector2 position) +void RippleSimulation::update(float dt) { - bool newParticle = false; for (std::vector::iterator it=mEmitters.begin(); it !=mEmitters.end(); ++it) { if (it->mPtr == MWBase::Environment::get().getWorld ()->getPlayerPtr()) @@ -75,63 +125,38 @@ void RippleSimulation::update(float dt, Ogre::Vector2 position) // for non-player actors this is done in updateObjectCell it->mPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); } - Ogre::Vector3 currentPos (it->mPtr.getRefData().getPosition().pos); - currentPos.z = 0; + + osg::Vec3f currentPos (it->mPtr.getRefData().getPosition().asVec3()); + if ( (currentPos - it->mLastEmitPosition).length() > 10 // Only emit when close to the water surface, not above it and not too deep in the water - && MWBase::Environment::get().getWorld ()->isUnderwater (it->mPtr.getCell(), - Ogre::Vector3(it->mPtr.getRefData().getPosition().pos)) + && MWBase::Environment::get().getWorld ()->isUnderwater (it->mPtr.getCell(), it->mPtr.getRefData().getPosition().asVec3()) && !MWBase::Environment::get().getWorld()->isSubmerged(it->mPtr)) { it->mLastEmitPosition = currentPos; - newParticle = true; - Ogre::Particle* created = mParticleSystem->createParticle(); - if (!created) - break; // TODO: cleanup the oldest particle to make room -#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) - Ogre::Vector3& position = created->mPosition; - Ogre::Vector3& direction = created->mDirection; - Ogre::ColourValue& colour = created->mColour; - float& totalTimeToLive = created->mTotalTimeToLive; - float& timeToLive = created->mTimeToLive; - Ogre::Radian& rotSpeed = created->mRotationSpeed; - Ogre::Radian& rotation = created->mRotation; -#else - Ogre::Vector3& position = created->position; - Ogre::Vector3& direction = created->direction; - Ogre::ColourValue& colour = created->colour; - float& totalTimeToLive = created->totalTimeToLive; - float& timeToLive = created->timeToLive; - Ogre::Radian& rotSpeed = created->rotationSpeed; - Ogre::Radian& rotation = created->rotation; -#endif - timeToLive = totalTimeToLive = mRippleLifeTime; - colour = Ogre::ColourValue(0.f, 0.f, 0.f, 0.7f); // Water_RippleAlphas.x? - direction = Ogre::Vector3(0,0,0); - position = currentPos; - position.z = 0; // Z is set by the Scene Node - rotSpeed = mRippleRotSpeed; - rotation = Ogre::Radian(Ogre::Math::RangeRandom(-Ogre::Math::PI, Ogre::Math::PI)); - created->setDimensions(mParticleSystem->getDefaultWidth(), mParticleSystem->getDefaultHeight()); + currentPos.z() = mParticleNode->getPosition().z(); + + if (mParticleSystem->numParticles()-mParticleSystem->numDeadParticles() > 500) + continue; // TODO: remove the oldest particle to make room? + + emitRipple(currentPos); } } - - if (newParticle) // now apparently needs another update, otherwise it won't render in the first frame after a particle is created. TODO: patch Ogre to handle this better - mParticleSystem->_update(0.f); } -void RippleSimulation::addEmitter(const MWWorld::Ptr& ptr, float scale, float force) + +void RippleSimulation::addEmitter(const MWWorld::ConstPtr& ptr, float scale, float force) { Emitter newEmitter; newEmitter.mPtr = ptr; newEmitter.mScale = scale; newEmitter.mForce = force; - newEmitter.mLastEmitPosition = Ogre::Vector3(0,0,0); + newEmitter.mLastEmitPosition = osg::Vec3f(0,0,0); mEmitters.push_back (newEmitter); } -void RippleSimulation::removeEmitter (const MWWorld::Ptr& ptr) +void RippleSimulation::removeEmitter (const MWWorld::ConstPtr& ptr) { for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it) { @@ -143,7 +168,7 @@ void RippleSimulation::removeEmitter (const MWWorld::Ptr& ptr) } } -void RippleSimulation::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) +void RippleSimulation::updateEmitterPtr (const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& ptr) { for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it) { @@ -155,15 +180,40 @@ void RippleSimulation::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld: } } +void RippleSimulation::removeCell(const MWWorld::CellStore *store) +{ + for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end();) + { + if ((it->mPtr.isInCell() && it->mPtr.getCell() == store) && it->mPtr != MWMechanics::getPlayer()) + { + it = mEmitters.erase(it); + } + else + ++it; + } +} + +void RippleSimulation::emitRipple(const osg::Vec3f &pos) +{ + if (std::abs(pos.z() - mParticleNode->getPosition().z()) < 20) + { + osgParticle::Particle* p = mParticleSystem->createParticle(NULL); + p->setPosition(osg::Vec3f(pos.x(), pos.y(), 0.f)); + p->setAngle(osg::Vec3f(0,0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI)); + } +} + void RippleSimulation::setWaterHeight(float height) { - mSceneNode->setPosition(0,0,height); + mParticleNode->setPosition(osg::Vec3f(0,0,height)); } void RippleSimulation::clear() { - mParticleSystem->clear(); + for (int i=0; inumParticles(); ++i) + mParticleSystem->destroyParticle(i); } + } diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp index 4551476a4c..a4e12f2756 100644 --- a/apps/openmw/mwrender/ripplesimulation.hpp +++ b/apps/openmw/mwrender/ripplesimulation.hpp @@ -1,16 +1,26 @@ -#ifndef RIPPLE_SIMULATION_H -#define RIPPLE_SIMULATION_H +#ifndef OPENMW_MWRENDER_RIPPLESIMULATION_H +#define OPENMW_MWRENDER_RIPPLESIMULATION_H -#include +#include #include "../mwworld/ptr.hpp" -namespace Ogre +namespace osg +{ + class Group; + class PositionAttitudeTransform; +} + +namespace osgParticle { - class SceneManager; class ParticleSystem; } +namespace Resource +{ + class ResourceSystem; +} + namespace MWWorld { class Fallback; @@ -19,45 +29,45 @@ namespace MWWorld namespace MWRender { -struct Emitter -{ - MWWorld::Ptr mPtr; - Ogre::Vector3 mLastEmitPosition; - float mScale; - float mForce; -}; + struct Emitter + { + MWWorld::ConstPtr mPtr; + osg::Vec3f mLastEmitPosition; + float mScale; + float mForce; + }; -class RippleSimulation -{ -public: - RippleSimulation(Ogre::SceneManager* mainSceneManager, const MWWorld::Fallback* fallback); - ~RippleSimulation(); + class RippleSimulation + { + public: + RippleSimulation(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback); + ~RippleSimulation(); - /// @param dt Time since the last frame - /// @param position Position of the player - void update(float dt, Ogre::Vector2 position); + /// @param dt Time since the last frame + void update(float dt); - /// adds an emitter, position will be tracked automatically - void addEmitter (const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f); - void removeEmitter (const MWWorld::Ptr& ptr); - void updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); + /// adds an emitter, position will be tracked automatically + void addEmitter (const MWWorld::ConstPtr& ptr, float scale = 1.f, float force = 1.f); + void removeEmitter (const MWWorld::ConstPtr& ptr); + void updateEmitterPtr (const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& ptr); + void removeCell(const MWWorld::CellStore* store); - /// Change the height of the water surface, thus moving all ripples with it - void setWaterHeight(float height); + void emitRipple(const osg::Vec3f& pos); - /// Remove all active ripples - void clear(); + /// Change the height of the water surface, thus moving all ripples with it + void setWaterHeight(float height); -private: - Ogre::SceneManager* mSceneMgr; - Ogre::ParticleSystem* mParticleSystem; - Ogre::SceneNode* mSceneNode; + /// Remove all active ripples + void clear(); - std::vector mEmitters; + private: + osg::ref_ptr mParent; - float mRippleLifeTime; - float mRippleRotSpeed; -}; + osg::ref_ptr mParticleSystem; + osg::ref_ptr mParticleNode; + + std::vector mEmitters; + }; } diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp new file mode 100644 index 0000000000..11f5b943d7 --- /dev/null +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -0,0 +1,57 @@ +#include "rotatecontroller.hpp" + +#include + +namespace MWRender +{ + +RotateController::RotateController(osg::Node *relativeTo) + : mEnabled(true) + , mRelativeTo(relativeTo) +{ + +} + +void RotateController::setEnabled(bool enabled) +{ + mEnabled = enabled; +} + +void RotateController::setRotate(const osg::Quat &rotate) +{ + mRotate = rotate; +} + +void RotateController::operator()(osg::Node *node, osg::NodeVisitor *nv) +{ + if (!mEnabled) + { + traverse(node, nv); + return; + } + osg::MatrixTransform* transform = static_cast(node); + osg::Matrix matrix = transform->getMatrix(); + osg::Quat worldOrient = getWorldOrientation(node); + + osg::Quat orient = worldOrient * mRotate * worldOrient.inverse() * matrix.getRotate(); + matrix.setRotate(orient); + + transform->setMatrix(matrix); + + traverse(node,nv); +} + +osg::Quat RotateController::getWorldOrientation(osg::Node *node) +{ + // this could be optimized later, we just need the world orientation, not the full matrix + osg::MatrixList worldMats = node->getWorldMatrices(mRelativeTo); + osg::Quat worldOrient; + if (!worldMats.empty()) + { + osg::Matrixf worldMat = worldMats[0]; + worldOrient = worldMat.getRotate(); + } + return worldOrient; +} + +} diff --git a/apps/openmw/mwrender/rotatecontroller.hpp b/apps/openmw/mwrender/rotatecontroller.hpp new file mode 100644 index 0000000000..456a6dd200 --- /dev/null +++ b/apps/openmw/mwrender/rotatecontroller.hpp @@ -0,0 +1,36 @@ +#ifndef OPENMW_MWRENDER_ROTATECONTROLLER_H +#define OPENMW_MWRENDER_ROTATECONTROLLER_H + +#include +#include + +namespace MWRender +{ + +/// Applies a rotation in \a relativeTo's space. +/// @note Assumes that the node being rotated has its "original" orientation set every frame by a different controller. +/// The rotation is then applied on top of that orientation. +/// @note Must be set on a MatrixTransform. +class RotateController : public osg::NodeCallback +{ +public: + RotateController(osg::Node* relativeTo); + + void setEnabled(bool enabled); + + void setRotate(const osg::Quat& rotate); + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv); + +protected: + osg::Quat getWorldOrientation(osg::Node* node); + + bool mEnabled; + osg::Quat mRotate; + osg::Node* mRelativeTo; +}; + + +} + +#endif diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp deleted file mode 100644 index f2e60b11b0..0000000000 --- a/apps/openmw/mwrender/shadows.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include "shadows.hpp" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "renderconst.hpp" - -using namespace Ogre; -using namespace MWRender; - -Shadows::Shadows(OEngine::Render::OgreRenderer* rend) : - mRendering(rend), mSceneMgr(rend->getScene()), mPSSMSetup(NULL), - mShadowFar(1000), mFadeStart(0.9f) -{ - recreate(); -} - -void Shadows::recreate() -{ - bool enabled = Settings::Manager::getBool("enabled", "Shadows"); - - bool split = Settings::Manager::getBool("split", "Shadows"); - - sh::Factory::getInstance ().setGlobalSetting ("shadows", enabled && !split ? "true" : "false"); - sh::Factory::getInstance ().setGlobalSetting ("shadows_pssm", enabled && split ? "true" : "false"); - - if (!enabled) - { - mSceneMgr->setShadowTechnique(SHADOWTYPE_NONE); - return; - } - - int texsize = Settings::Manager::getInt("texture size", "Shadows"); - mSceneMgr->setShadowTextureSize(texsize); - - mSceneMgr->setShadowTechnique(SHADOWTYPE_TEXTURE_MODULATIVE_INTEGRATED); - - // no point light shadows, i'm afraid. might revisit this with Deferred Shading - mSceneMgr->setShadowTextureCountPerLightType(Light::LT_POINT, 0); - - mSceneMgr->setShadowTextureCountPerLightType(Light::LT_DIRECTIONAL, split ? 3 : 1); - mSceneMgr->setShadowTextureCount(split ? 3 : 1); - - mSceneMgr->setShadowTextureSelfShadow(true); - mSceneMgr->setShadowCasterRenderBackFaces(true); - mSceneMgr->setShadowTextureCasterMaterial("openmw_shadowcaster_default"); - mSceneMgr->setShadowTexturePixelFormat(PF_FLOAT32_R); - mSceneMgr->setShadowDirectionalLightExtrusionDistance(1000000); - - mShadowFar = Settings::Manager::getFloat(split ? "split shadow distance" : "shadow distance", "Shadows"); - mSceneMgr->setShadowFarDistance(mShadowFar); - - mFadeStart = Settings::Manager::getFloat("fade start", "Shadows"); - - ShadowCameraSetupPtr shadowCameraSetup; - if (split) - { - mPSSMSetup = new PSSMShadowCameraSetup(); - - // Make sure to keep this in sync with the camera's near clip distance! - mPSSMSetup->setSplitPadding(mRendering->getCamera()->getNearClipDistance()); - - mPSSMSetup->calculateSplitPoints(3, mRendering->getCamera()->getNearClipDistance(), mShadowFar); - - const Real adjustFactors[3] = {64, 64, 64}; - for (int i=0; i < 3; ++i) - { - mPSSMSetup->setOptimalAdjustFactor(i, adjustFactors[i]); - /*if (i==0) - mSceneMgr->setShadowTextureConfig(i, texsize, texsize, Ogre::PF_FLOAT32_R); - else if (i ==1) - mSceneMgr->setShadowTextureConfig(i, texsize/2, texsize/2, Ogre::PF_FLOAT32_R); - else if (i ==2) - mSceneMgr->setShadowTextureConfig(i, texsize/4, texsize/4, Ogre::PF_FLOAT32_R);*/ - } - - // Populate from split point 1, not 0, since split 0 isn't useful (usually 0) - const PSSMShadowCameraSetup::SplitPointList& splitPointList = getPSSMSetup()->getSplitPoints(); - sh::Vector3* splitPoints = new sh::Vector3(splitPointList[1], splitPointList[2], splitPointList[3]); - - sh::Factory::getInstance ().setSharedParameter ("pssmSplitPoints", sh::makeProperty(splitPoints)); - - shadowCameraSetup = ShadowCameraSetupPtr(mPSSMSetup); - } - else - { - LiSPSMShadowCameraSetup* lispsmSetup = new LiSPSMShadowCameraSetup(); - lispsmSetup->setOptimalAdjustFactor(64); - //lispsmSetup->setCameraLightDirectionThreshold(Degree(0)); - //lispsmSetup->setUseAggressiveFocusRegion(false); - shadowCameraSetup = ShadowCameraSetupPtr(lispsmSetup); - } - mSceneMgr->setShadowCameraSetup(shadowCameraSetup); - - sh::Vector4* shadowFar_fadeStart = new sh::Vector4(mShadowFar, mFadeStart * mShadowFar, 0, 0); - sh::Factory::getInstance ().setSharedParameter ("shadowFar_fadeStart", sh::makeProperty(shadowFar_fadeStart)); - - // Set visibility mask for the shadow render textures - int visibilityMask = RV_Actors * Settings::Manager::getBool("actor shadows", "Shadows") - + (RV_Statics + RV_StaticsSmall) * Settings::Manager::getBool("statics shadows", "Shadows") - + RV_Misc * Settings::Manager::getBool("misc shadows", "Shadows") - + RV_Terrain * (Settings::Manager::getBool("terrain shadows", "Shadows")); - for (int i = 0; i < (split ? 3 : 1); ++i) - { - TexturePtr shadowTexture = mSceneMgr->getShadowTexture(i); - Viewport* vp = shadowTexture->getBuffer()->getRenderTarget()->getViewport(0); - vp->setVisibilityMask(visibilityMask); - } - - // -------------------------------------------------------------------------------------------------------------------- - // --------------------------- Debug overlays to display the content of shadow maps ----------------------------------- - // -------------------------------------------------------------------------------------------------------------------- - /* - if (Settings::Manager::getBool("debug", "Shadows")) - { - OverlayManager& mgr = OverlayManager::getSingleton(); - Overlay* overlay; - - // destroy if already exists - if ((overlay = mgr.getByName("DebugOverlay"))) - mgr.destroy(overlay); - - overlay = mgr.create("DebugOverlay"); - for (size_t i = 0; i < (split ? 3 : 1); ++i) { - TexturePtr tex = mRendering->getScene()->getShadowTexture(i); - - // Set up a debug panel to display the shadow - - if (MaterialManager::getSingleton().resourceExists("Ogre/DebugTexture" + StringConverter::toString(i))) - MaterialManager::getSingleton().remove("Ogre/DebugTexture" + StringConverter::toString(i)); - MaterialPtr debugMat = MaterialManager::getSingleton().create( - "Ogre/DebugTexture" + StringConverter::toString(i), - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - - debugMat->getTechnique(0)->getPass(0)->setLightingEnabled(false); - TextureUnitState *t = debugMat->getTechnique(0)->getPass(0)->createTextureUnitState(tex->getName()); - t->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); - - OverlayContainer* debugPanel; - - // destroy container if exists - try - { - if ((debugPanel = - static_cast( - mgr.getOverlayElement("Ogre/DebugTexPanel" + StringConverter::toString(i) - )))) - mgr.destroyOverlayElement(debugPanel); - } - catch (Ogre::Exception&) {} - - debugPanel = (OverlayContainer*) - (OverlayManager::getSingleton().createOverlayElement("Panel", "Ogre/DebugTexPanel" + StringConverter::toString(i))); - debugPanel->_setPosition(0.8, i*0.25); - debugPanel->_setDimensions(0.2, 0.24); - debugPanel->setMaterialName(debugMat->getName()); - debugPanel->show(); - overlay->add2D(debugPanel); - overlay->show(); - } - } - else - { - OverlayManager& mgr = OverlayManager::getSingleton(); - Overlay* overlay; - - if ((overlay = mgr.getByName("DebugOverlay"))) - mgr.destroy(overlay); - } - */ -} - -PSSMShadowCameraSetup* Shadows::getPSSMSetup() -{ - return mPSSMSetup; -} diff --git a/apps/openmw/mwrender/shadows.hpp b/apps/openmw/mwrender/shadows.hpp deleted file mode 100644 index fe125f54c3..0000000000 --- a/apps/openmw/mwrender/shadows.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef GAME_SHADOWS_H -#define GAME_SHADOWS_H - -// forward declares -namespace Ogre -{ - class SceneManager; - class PSSMShadowCameraSetup; -} -namespace OEngine{ - namespace Render{ - class OgreRenderer; - } -} - -namespace MWRender -{ - class Shadows - { - public: - Shadows(OEngine::Render::OgreRenderer* rend); - - void recreate(); - - Ogre::PSSMShadowCameraSetup* getPSSMSetup(); - - protected: - OEngine::Render::OgreRenderer* mRendering; - Ogre::SceneManager* mSceneMgr; - - Ogre::PSSMShadowCameraSetup* mPSSMSetup; - float mShadowFar; - float mFadeStart; - }; -} - -#endif diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index fd439050c5..20e3dc07c7 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1,413 +1,1408 @@ #include "sky.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include +#include +#include +#include +#include +#include +#include -#include +#include -#include #include -#include +#include +#include + +#include + +#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/fallback.hpp" -#include "renderconst.hpp" -#include "renderingmanager.hpp" - -using namespace MWRender; -using namespace Ogre; +#include "vismask.hpp" +#include "renderbin.hpp" namespace { -void setAlpha (NifOgre::ObjectScenePtr scene, Ogre::MovableObject* movable, float alpha) -{ - Ogre::MaterialPtr mat = scene->mMaterialControllerMgr.getWritableMaterial(movable); - Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); - while(techs.hasMoreElements()) + osg::ref_ptr createAlphaTrackingUnlitMaterial() { - Ogre::Technique *tech = techs.getNext(); - Ogre::Technique::PassIterator passes = tech->getPassIterator(); - while(passes.hasMoreElements()) + osg::ref_ptr mat = new osg::Material; + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + mat->setColorMode(osg::Material::DIFFUSE); + return mat; + } + + osg::ref_ptr createUnlitMaterial() + { + osg::ref_ptr mat = new osg::Material; + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + mat->setColorMode(osg::Material::OFF); + return mat; + } + + osg::ref_ptr createTexturedQuad(int numUvSets=1) + { + osg::ref_ptr geom = new osg::Geometry; + + osg::ref_ptr verts = new osg::Vec3Array; + verts->push_back(osg::Vec3f(-0.5, -0.5, 0)); + verts->push_back(osg::Vec3f(-0.5, 0.5, 0)); + verts->push_back(osg::Vec3f(0.5, 0.5, 0)); + verts->push_back(osg::Vec3f(0.5, -0.5, 0)); + + geom->setVertexArray(verts); + + osg::ref_ptr texcoords = new osg::Vec2Array; + texcoords->push_back(osg::Vec2f(0, 0)); + texcoords->push_back(osg::Vec2f(0, 1)); + texcoords->push_back(osg::Vec2f(1, 1)); + texcoords->push_back(osg::Vec2f(1, 0)); + + osg::ref_ptr colors = new osg::Vec4Array; + colors->push_back(osg::Vec4(1.f, 1.f, 1.f, 1.f)); + geom->setColorArray(colors, osg::Array::BIND_OVERALL); + + for (int i=0; isetTexCoordArray(i, texcoords, osg::Array::BIND_PER_VERTEX); + + geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4)); + + return geom; + } + +} + +namespace MWRender +{ + +class AtmosphereUpdater : public SceneUtil::StateSetUpdater +{ +public: + void setEmissionColor(const osg::Vec4f& emissionColor) + { + mEmissionColor = emissionColor; + } + +protected: + virtual void setDefaults(osg::StateSet* stateset) + { + stateset->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) + { + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); + } + +private: + osg::Vec4f mEmissionColor; +}; + +class AtmosphereNightUpdater : public SceneUtil::StateSetUpdater +{ +public: + AtmosphereNightUpdater(Resource::TextureManager* textureManager) + { + // we just need a texture, its contents don't really matter + mTexture = textureManager->getWarningTexture(); + } + + void setFade(const float fade) + { + mColor.a() = fade; + } + +protected: + virtual void setDefaults(osg::StateSet* stateset) + { + osg::ref_ptr texEnv (new osg::TexEnvCombine); + texEnv->setCombine_Alpha(osg::TexEnvCombine::MODULATE); + texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnv->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); + texEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE); + texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + + stateset->setTextureAttributeAndModes(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setTextureAttributeAndModes(1, texEnv, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) + { + osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); + texEnv->setConstantColor(mColor); + } + + osg::ref_ptr mTexture; + + osg::Vec4f mColor; +}; + +class CloudUpdater : public SceneUtil::StateSetUpdater +{ +public: + CloudUpdater() + : mAnimationTimer(0.f) + , mOpacity(0.f) + { + } + + void setAnimationTimer(float timer) + { + mAnimationTimer = timer; + } + + void setTexture(osg::ref_ptr texture) + { + mTexture = texture; + } + void setEmissionColor(const osg::Vec4f& emissionColor) + { + mEmissionColor = emissionColor; + } + void setOpacity(float opacity) + { + mOpacity = opacity; + } + +protected: + virtual void setDefaults(osg::StateSet *stateset) + { + osg::ref_ptr texmat (new osg::TexMat); + stateset->setTextureAttributeAndModes(0, texmat, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(1, texmat, osg::StateAttribute::ON); + stateset->setAttribute(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + // need to set opacity on a separate texture unit, diffuse alpha is used by the vertex colors already + osg::ref_ptr texEnvCombine (new osg::TexEnvCombine); + texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnvCombine->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnvCombine->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); + texEnvCombine->setConstantColor(osg::Vec4f(1,1,1,1)); + texEnvCombine->setCombine_Alpha(osg::TexEnvCombine::MODULATE); + texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); + + stateset->setTextureAttributeAndModes(1, texEnvCombine, osg::StateAttribute::ON); + + stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + + virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) + { + osg::TexMat* texMat = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXMAT)); + texMat->setMatrix(osg::Matrix::translate(osg::Vec3f(0, mAnimationTimer, 0.f))); + + stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setTextureAttribute(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); + + osg::TexEnvCombine* texEnvCombine = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); + texEnvCombine->setConstantColor(osg::Vec4f(1,1,1,mOpacity)); + } + +private: + float mAnimationTimer; + osg::ref_ptr mTexture; + osg::Vec4f mEmissionColor; + float mOpacity; +}; + +/// Transform that removes the eyepoint of the modelview matrix, +/// i.e. its children are positioned relative to the camera. +class CameraRelativeTransform : public osg::Transform +{ +public: + CameraRelativeTransform() + { + // Culling works in node-local space, not in camera space, so we can't cull this node correctly + // That's not a problem though, children of this node can be culled just fine + // Just make sure you do not place a CameraRelativeTransform deep in the scene graph + setCullingActive(false); + + addCullCallback(new CullCallback); + } + + CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop) + : osg::Transform(copy, copyop) + { + } + + META_Node(MWRender, CameraRelativeTransform) + + const osg::Vec3f& getLastEyePoint() const + { + return mEyePoint; + } + + virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const + { + if (nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) { - Ogre::Pass *pass = passes.getNext(); - Ogre::ColourValue diffuse = pass->getDiffuse(); - diffuse.a = alpha; - pass->setDiffuse(diffuse); + mEyePoint = static_cast(nv)->getEyePoint(); + } + + if (_referenceFrame==RELATIVE_RF) + { + matrix.setTrans(osg::Vec3f(0.f,0.f,0.f)); + return false; + } + else // absolute + { + matrix.makeIdentity(); + return true; } } -} - -void setAlpha (NifOgre::ObjectScenePtr scene, float alpha) -{ - for(size_t i = 0; i < scene->mParticles.size(); ++i) - setAlpha(scene, scene->mParticles[i], alpha); - for(size_t i = 0; i < scene->mEntities.size(); ++i) + osg::BoundingSphere computeBound() const { - if (scene->mEntities[i] != scene->mSkelBase) - setAlpha(scene, scene->mEntities[i], alpha); - } -} - -} - -BillboardObject::BillboardObject( const String& textureName, - const float initialSize, - const Vector3& position, - SceneNode* rootNode, - const std::string& material) -: mVisibility(1.0f) -{ - SceneManager* sceneMgr = rootNode->getCreator(); - - Vector3 finalPosition = position.normalisedCopy() * 1000.f; - - static unsigned int bodyCount=0; - - mMaterial = sh::Factory::getInstance().createMaterialInstance ("BillboardMaterial"+StringConverter::toString(bodyCount), material); - mMaterial->setProperty("texture", sh::makeProperty(new sh::StringValue(textureName))); - - static Ogre::Mesh* plane = MeshManager::getSingleton().createPlane("billboard", - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::Plane(Ogre::Vector3(0,0,1), 0), 1, 1, 1, 1, true, 1, 1, 1, Vector3::UNIT_Y).get(); - plane->_setBounds(Ogre::AxisAlignedBox::BOX_INFINITE); - mEntity = sceneMgr->createEntity("billboard"); - mEntity->setMaterialName("BillboardMaterial"+StringConverter::toString(bodyCount)); - mEntity->setVisibilityFlags(RV_Sky); - mEntity->setCastShadows(false); - - mNode = rootNode->createChildSceneNode(); - mNode->setPosition(finalPosition); - mNode->attachObject(mEntity); - mNode->setScale(Ogre::Vector3(450.f*initialSize)); - mNode->setOrientation(Ogre::Vector3::UNIT_Z.getRotationTo(-position.normalisedCopy())); - - sh::Factory::getInstance().getMaterialInstance ("BillboardMaterial"+StringConverter::toString(bodyCount))->setListener(this); - - bodyCount++; -} - -void BillboardObject::requestedConfiguration (sh::MaterialInstance* m, const std::string& configuration) -{ -} - -void BillboardObject::createdConfiguration (sh::MaterialInstance* m, const std::string& configuration) -{ - setVisibility(mVisibility); - setColour(mColour); -} - -void BillboardObject::setVisible(const bool visible) -{ - mEntity->setVisible(visible); -} - -void BillboardObject::setSize(const float size) -{ - mNode->setScale(450.f*size, 450.f*size, 450.f*size); -} - -void BillboardObject::setVisibility(const float visibility) -{ - mVisibility = visibility; - Ogre::MaterialPtr m = static_cast(mMaterial->getMaterial ())->getOgreMaterial (); - for (int i=0; igetNumTechniques(); ++i) - { - Ogre::Technique* t = m->getTechnique(i); - if (t->getNumPasses ()) - t->getPass(0)->setDiffuse (0,0,0, visibility); - } -} - -void BillboardObject::setPosition(const Vector3& pPosition) -{ - Vector3 normalised = pPosition.normalisedCopy(); - Vector3 finalPosition = normalised * 1000.f; - mNode->setOrientation(Ogre::Vector3::UNIT_Z.getRotationTo(-normalised)); - mNode->setPosition(finalPosition); -} - -Vector3 BillboardObject::getPosition() const -{ - return mNode->getPosition(); -} - -void BillboardObject::setVisibilityFlags(int flags) -{ - mEntity->setVisibilityFlags(flags); -} - -void BillboardObject::setColour(const ColourValue& pColour) -{ - mColour = pColour; - Ogre::MaterialPtr m = static_cast(mMaterial->getMaterial ())->getOgreMaterial (); - for (int i=0; igetNumTechniques(); ++i) - { - Ogre::Technique* t = m->getTechnique(i); - if (t->getNumPasses ()) - t->getPass(0)->setSelfIllumination (pColour); - } -} - -void BillboardObject::setRenderQueue(unsigned int id) -{ - mEntity->setRenderQueueGroup(id); -} - -SceneNode* BillboardObject::getNode() -{ - return mNode; -} - -Moon::Moon( const String& textureName, - const float initialSize, - const Vector3& position, - SceneNode* rootNode, - const std::string& material) - : BillboardObject(textureName, initialSize, position, rootNode, material) - , mType(Type_Masser) -{ - setVisibility(1.0); - - mMaterial->setProperty("alphatexture", sh::makeProperty(new sh::StringValue(textureName + "_alpha"))); - - mPhase = Moon::Phase_Full; -} - -void Moon::setType(const Moon::Type& type) -{ - mType = type; -} - -void Moon::setPhase(const Moon::Phase& phase) -{ - // Colour texture - Ogre::String textureName = "textures\\tx_"; - - if (mType == Moon::Type_Secunda) textureName += "secunda_"; - else textureName += "masser_"; - - if (phase == Moon::Phase_New) textureName += "new"; - else if (phase == Moon::Phase_WaxingCrescent) textureName += "one_wax"; - else if (phase == Moon::Phase_WaxingHalf) textureName += "half_wax"; - else if (phase == Moon::Phase_WaxingGibbous) textureName += "three_wax"; - else if (phase == Moon::Phase_WaningCrescent) textureName += "one_wan"; - else if (phase == Moon::Phase_WaningHalf) textureName += "half_wan"; - else if (phase == Moon::Phase_WaningGibbous) textureName += "three_wan"; - else if (phase == Moon::Phase_Full) textureName += "full"; - - textureName += ".dds"; - - if (mType == Moon::Type_Secunda) - { - sh::Factory::getInstance ().setTextureAlias ("secunda_texture", textureName); - sh::Factory::getInstance ().setTextureAlias ("secunda_texture_alpha", "textures\\tx_mooncircle_full_s.dds"); - } - else - { - sh::Factory::getInstance ().setTextureAlias ("masser_texture", textureName); - sh::Factory::getInstance ().setTextureAlias ("masser_texture_alpha", "textures\\tx_mooncircle_full_m.dds"); + return osg::BoundingSphere(osg::Vec3f(0,0,0), 0); } - mPhase = phase; -} + class CullCallback : public osg::NodeCallback + { + public: + virtual void operator() (osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); -unsigned int Moon::getPhaseInt() const + // XXX have to remove unwanted culling plane of the water reflection camera + + // Remove all planes that aren't from the standard frustum + unsigned int numPlanes = 4; + if (cv->getCullingMode() & osg::CullSettings::NEAR_PLANE_CULLING) + ++numPlanes; + if (cv->getCullingMode() & osg::CullSettings::FAR_PLANE_CULLING) + ++numPlanes; + + int mask = 0x1; + int resultMask = cv->getProjectionCullingStack().back().getFrustum().getResultMask(); + for (unsigned int i=0; igetProjectionCullingStack().back().getFrustum().getPlaneList().size(); ++i) + { + if (i >= numPlanes) + { + // turn off this culling plane + resultMask &= (~mask); + } + + mask <<= 1; + } + + cv->getProjectionCullingStack().back().getFrustum().setResultMask(resultMask); + cv->getCurrentCullingSet().getFrustum().setResultMask(resultMask); + + cv->getProjectionCullingStack().back().pushCurrentMask(); + cv->getCurrentCullingSet().pushCurrentMask(); + + traverse(node, nv); + + cv->getProjectionCullingStack().back().popCurrentMask(); + cv->getCurrentCullingSet().popCurrentMask(); + } + }; +private: + // eyePoint for the current frame + mutable osg::Vec3f mEyePoint; +}; + +class ModVertexAlphaVisitor : public osg::NodeVisitor { - if (mPhase == Moon::Phase_New) return 0; - else if (mPhase == Moon::Phase_WaxingCrescent) return 1; - else if (mPhase == Moon::Phase_WaningCrescent) return 1; - else if (mPhase == Moon::Phase_WaxingHalf) return 2; - else if (mPhase == Moon::Phase_WaningHalf) return 2; - else if (mPhase == Moon::Phase_WaxingGibbous) return 3; - else if (mPhase == Moon::Phase_WaningGibbous) return 3; - else if (mPhase == Moon::Phase_Full) return 4; +public: + ModVertexAlphaVisitor(int meshType) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mMeshType(meshType) + { + } - return 0; -} + void apply(osg::Geode &geode) + { + for (unsigned int i=0; i colors = new osg::Vec4Array(geom->getVertexArray()->getNumElements()); + for (unsigned int i=0; isize(); ++i) + { + float alpha = 1.f; + if (mMeshType == 0) alpha = i%2 ? 0.f : 1.f; // this is a cylinder, so every second vertex belongs to the bottom-most row + else if (mMeshType == 1) + { + if (i>= 49 && i <= 64) alpha = 0.f; // bottom-most row + else if (i>= 33 && i <= 48) alpha = 0.25098; // second row + else alpha = 1.f; + } + else if (mMeshType == 2) + { + osg::Vec4Array* origColors = static_cast(geom->getColorArray()); + alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; + } + + (*colors)[i] = osg::Vec4f(0.f, 0.f, 0.f, alpha); + } + + geom->setColorArray(colors, osg::Array::BIND_PER_VERTEX); + } + +private: + int mMeshType; +}; + +/// @brief Hides the node subgraph if the eye point is below water. +/// @note Must be added as cull callback. +/// @note Meant to be used on a node that is child of a CameraRelativeTransform. +/// The current eye point must be retrieved by the CameraRelativeTransform since we can't get it anymore once we are in camera-relative space. +class UnderwaterSwitchCallback : public osg::NodeCallback +{ +public: + UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform) + : mCameraRelativeTransform(cameraRelativeTransform) + , mEnabled(true) + , mWaterLevel(0.f) + { + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osg::Vec3f eyePoint = mCameraRelativeTransform->getLastEyePoint(); + + if (mEnabled && eyePoint.z() < mWaterLevel) + return; + + traverse(node, nv); + } + + void setEnabled(bool enabled) + { + mEnabled = enabled; + } + void setWaterLevel(float waterLevel) + { + mWaterLevel = waterLevel; + } + +private: + osg::ref_ptr mCameraRelativeTransform; + bool mEnabled; + float mWaterLevel; +}; + +/// A base class for the sun and moons. +class CelestialBody +{ +public: + CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets) + { + mGeode = new osg::Geode; + osg::ref_ptr geom = createTexturedQuad(numUvSets); + mGeode->addDrawable(geom); + mTransform = new osg::PositionAttitudeTransform; + mTransform->setScale(osg::Vec3f(450,450,450) * scaleFactor); + mTransform->addChild(mGeode); + + parentNode->addChild(mTransform); + } + + virtual ~CelestialBody() {} + + virtual void adjustTransparency(const float ratio) = 0; + + void setVisible(bool visible) + { + mTransform->setNodeMask(visible ? ~0 : 0); + } + +protected: + static const float mDistance; + osg::ref_ptr mTransform; + osg::ref_ptr mGeode; +}; + +const float CelestialBody::mDistance = 1000.0f; + +class Sun : public CelestialBody +{ +public: + Sun(osg::Group* parentNode, Resource::TextureManager& textureManager) + : CelestialBody(parentNode, 1.0f, 1) + , mUpdater(new Updater) + { + mTransform->addUpdateCallback(mUpdater); + mTransform->setNodeMask(Mask_Sun); + + osg::ref_ptr sunTex = textureManager.getTexture2D("textures/tx_sun_05.dds", + osg::Texture::CLAMP, + osg::Texture::CLAMP); + + mGeode->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); + + osg::ref_ptr queryNode (new osg::Group); + // Need to render after the world geometry so we can correctly test for occlusions + queryNode->getOrCreateStateSet()->setRenderBinDetails(RenderBin_OcclusionQuery, "RenderBin"); + queryNode->getOrCreateStateSet()->setNestRenderBins(false); + // Set up alpha testing on the occlusion testing subgraph, that way we can get the occlusion tested fragments to match the circular shape of the sun + osg::ref_ptr alphaFunc (new osg::AlphaFunc); + alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); + queryNode->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + queryNode->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); + queryNode->getOrCreateStateSet()->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); + + mTransform->addChild(queryNode); + + mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); + mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); + + createSunFlash(textureManager); + createSunGlare(); + } + + ~Sun() + { + mTransform->removeUpdateCallback(mUpdater); + destroySunFlash(); + destroySunGlare(); + } + + void setColor(const osg::Vec4f& color) + { + mUpdater->mColor.r() = color.r(); + mUpdater->mColor.g() = color.g(); + mUpdater->mColor.b() = color.b(); + } + + virtual void adjustTransparency(const float ratio) + { + mUpdater->mColor.a() = ratio; + if (mSunGlareCallback) + mSunGlareCallback->setGlareView(ratio); + if (mSunFlashCallback) + mSunFlashCallback->setGlareView(ratio); + } + + void setDirection(const osg::Vec3f& direction) + { + osg::Vec3f normalizedDirection = direction / direction.length(); + mTransform->setPosition(normalizedDirection * mDistance); + + osg::Quat quat; + quat.makeRotate(osg::Vec3f(0.0f, 0.0f, 1.0f), normalizedDirection); + mTransform->setAttitude(quat); + } + + void setGlareTimeOfDayFade(float val) + { + if (mSunGlareCallback) + mSunGlareCallback->setTimeOfDayFade(val); + } + +private: + /// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels. + osg::ref_ptr createOcclusionQueryNode(osg::Group* parent, bool queryVisible) + { + osg::ref_ptr oqn = new osg::OcclusionQueryNode; + oqn->setQueriesEnabled(true); + + // Make it fast! A DYNAMIC query geometry means we can't break frame until the flare is rendered (which is rendered after all the other geometry, + // so that would be pretty bad). STATIC should be safe, since our node's local bounds are static, thus computeBounds() which modifies the queryGeometry + // is only called once. + // Note the debug geometry setDebugDisplay(true) is always DYNAMIC and that can't be changed, not a big deal. + oqn->getQueryGeometry()->setDataVariance(osg::Object::STATIC); + + osg::ref_ptr queryGeode = osg::clone(mGeode.get(), osg::CopyOp::DEEP_COPY_ALL); + // Disable writing to the color buffer. We are using this geode for visibility tests only. + osg::ref_ptr colormask (new osg::ColorMask(0, 0, 0, 0)); + queryGeode->getOrCreateStateSet()->setAttributeAndModes(colormask, osg::StateAttribute::ON); + + oqn->addChild(queryGeode); + + // Remove the default OFF|PROTECTED setting for texturing. We *want* to enable texturing for alpha testing purposes + oqn->getQueryStateSet()->removeTextureMode(0, GL_TEXTURE_2D); + + // Need to add texture coordinates so that texturing works. A bit ugly, relies on the vertex ordering + // used within OcclusionQueryNode. + osg::ref_ptr texCoordArray (new osg::Vec2Array); + for (int i=0; i<8; ++i) + { + texCoordArray->push_back(osg::Vec2(0,0)); + texCoordArray->push_back(osg::Vec2(1,0)); + texCoordArray->push_back(osg::Vec2(0,0)); + texCoordArray->push_back(osg::Vec2(1,0)); + texCoordArray->push_back(osg::Vec2(1,1)); + texCoordArray->push_back(osg::Vec2(0,1)); + texCoordArray->push_back(osg::Vec2(0,1)); + texCoordArray->push_back(osg::Vec2(1,1)); + } + + oqn->getQueryGeometry()->setTexCoordArray(0, texCoordArray, osg::Array::BIND_PER_VERTEX); + + if (queryVisible) + { + osg::ref_ptr depth (new osg::Depth); + depth->setFunction(osg::Depth::LESS); + // This is a trick to make fragments written by the query always use the maximum depth value, + // without having to retrieve the current far clipping distance. + // We want the sun glare to be "infinitely" far away. + depth->setZNear(1.0); + depth->setZFar(1.0); + oqn->getQueryStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + else + { + oqn->getQueryStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + } + + parent->addChild(oqn); + + return oqn; + } + + void createSunFlash(Resource::TextureManager& textureManager) + { + osg::ref_ptr tex = textureManager.getTexture2D("textures/tx_sun_flash_grey_05.dds", + osg::Texture::CLAMP, + osg::Texture::CLAMP); + + osg::ref_ptr transform (new osg::PositionAttitudeTransform); + const float scale = 2.6f; + transform->setScale(osg::Vec3f(scale,scale,scale)); + + mTransform->addChild(transform); + + osg::ref_ptr geode (new osg::Geode); + transform->addChild(geode); + + geode->addDrawable(createTexturedQuad()); + + osg::StateSet* stateset = geode->getOrCreateStateSet(); + + stateset->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); + stateset->setNestRenderBins(false); + + mSunFlashNode = transform; + + mSunFlashCallback = new SunFlashCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels); + mSunFlashNode->addCullCallback(mSunFlashCallback); + } + void destroySunFlash() + { + if (mSunFlashNode) + { + mSunFlashNode->removeCullCallback(mSunFlashCallback); + mSunFlashCallback = NULL; + } + } + + void createSunGlare() + { + osg::ref_ptr camera (new osg::Camera); + camera->setProjectionMatrix(osg::Matrix::identity()); + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); // add to skyRoot instead? + camera->setViewMatrix(osg::Matrix::identity()); + camera->setClearMask(0); + camera->setRenderOrder(osg::Camera::NESTED_RENDER); + camera->setAllowEventFocus(false); + + osg::ref_ptr geode (new osg::Geode); + osg::ref_ptr geom = osg::createTexturedQuadGeometry(osg::Vec3f(-1,-1,0), osg::Vec3f(2,0,0), osg::Vec3f(0,2,0)); + geode->addDrawable(geom); + + camera->addChild(geode); + + osg::StateSet* stateset = geom->getOrCreateStateSet(); + + stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); + stateset->setNestRenderBins(false); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + // set up additive blending + osg::ref_ptr blendFunc (new osg::BlendFunc); + blendFunc->setSource(osg::BlendFunc::SRC_ALPHA); + blendFunc->setDestination(osg::BlendFunc::ONE); + stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + + mSunGlareCallback = new SunGlareCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels, mTransform); + mSunGlareNode = camera; + + mSunGlareNode->addCullCallback(mSunGlareCallback); + + mTransform->addChild(camera); + } + void destroySunGlare() + { + if (mSunGlareNode) + { + mSunGlareNode->removeCullCallback(mSunGlareCallback); + mSunGlareCallback = NULL; + } + } + + class Updater : public SceneUtil::StateSetUpdater + { + public: + osg::Vec4f mColor; + + Updater() + : mColor(1.f, 1.f, 1.f, 1.f) + { + } + + virtual void setDefaults(osg::StateSet* stateset) + { + stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); + } + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor*) + { + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mColor.a())); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(mColor.r(), mColor.g(), mColor.b(), 1)); + } + }; + + class OcclusionCallback : public osg::NodeCallback + { + public: + OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) + : mOcclusionQueryVisiblePixels(oqnVisible) + , mOcclusionQueryTotalPixels(oqnTotal) + { + } + + protected: + float getVisibleRatio (osg::Camera* camera) + { + int visible = mOcclusionQueryVisiblePixels->getQueryGeometry()->getNumPixels(camera); + int total = mOcclusionQueryTotalPixels->getQueryGeometry()->getNumPixels(camera); + + float visibleRatio = 0.f; + if (total > 0) + visibleRatio = static_cast(visible) / static_cast(total); + + float dt = MWBase::Environment::get().getFrameDuration(); + + float lastRatio = mLastRatio[osg::observer_ptr(camera)]; + + float change = dt*10; + + if (visibleRatio > lastRatio) + visibleRatio = std::min(visibleRatio, lastRatio + change); + else + visibleRatio = std::max(visibleRatio, lastRatio - change); + + mLastRatio[osg::observer_ptr(camera)] = visibleRatio; + + return visibleRatio; + } + + private: + osg::ref_ptr mOcclusionQueryVisiblePixels; + osg::ref_ptr mOcclusionQueryTotalPixels; + + std::map, float> mLastRatio; + }; + + /// SunFlashCallback handles fading/scaling of a node depending on occlusion query result. Must be attached as a cull callback. + class SunFlashCallback : public OcclusionCallback + { + public: + SunFlashCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) + : OcclusionCallback(oqnVisible, oqnTotal) + , mGlareView(1.f) + { + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); + + float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); + + osg::ref_ptr stateset; + + if (visibleRatio > 0.f) + { + const float fadeThreshold = 0.1; + if (visibleRatio < fadeThreshold) + { + float fade = 1.f - (fadeThreshold - visibleRatio) / fadeThreshold; + osg::ref_ptr mat (createUnlitMaterial()); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade*mGlareView)); + stateset = new osg::StateSet; + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + + const float threshold = 0.6; + visibleRatio = visibleRatio * (1.f - threshold) + threshold; + } + + float scale = visibleRatio; + + if (scale == 0.f) + { + // no traverse + return; + } + else + { + osg::Matrix modelView = *cv->getModelViewMatrix(); + + modelView.preMultScale(osg::Vec3f(visibleRatio, visibleRatio, visibleRatio)); + + if (stateset) + cv->pushStateSet(stateset); + + cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); + + traverse(node, nv); + + cv->popModelViewMatrix(); + + if (stateset) + cv->popStateSet(); + } + } + + void setGlareView(float value) + { + mGlareView = value; + } + + private: + float mGlareView; + }; + + + /// SunGlareCallback controls a full-screen glare effect depending on occlusion query result and the angle between sun and camera. + /// Must be attached as a cull callback to the node above the glare node. + class SunGlareCallback : public OcclusionCallback + { + public: + SunGlareCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal, + osg::ref_ptr sunTransform) + : OcclusionCallback(oqnVisible, oqnTotal) + , mSunTransform(sunTransform) + , mTimeOfDayFade(1.f) + , mGlareView(1.f) + { + const MWWorld::Fallback* fallback = MWBase::Environment::get().getWorld()->getFallback(); + mColor = fallback->getFallbackColour("Weather_Sun_Glare_Fader_Color"); + mSunGlareFaderMax = fallback->getFallbackFloat("Weather_Sun_Glare_Fader_Max"); + mSunGlareFaderAngleMax = fallback->getFallbackFloat("Weather_Sun_Glare_Fader_Angle_Max"); + + // Replicating a design flaw in MW. The color was being set on both ambient and emissive properties, which multiplies the result by two, + // then finally gets clamped by the fixed function pipeline. With the default INI settings, only the red component gets clamped, + // so the resulting color looks more orange than red. + mColor *= 2; + for (int i=0; i<3; ++i) + mColor[i] = std::min(1.f, mColor[i]); + } + + virtual void operator ()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); + + float angleRadians = getAngleToSunInRadians(*cv->getCurrentRenderStage()->getInitialViewMatrix()); + float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); + + const float angleMaxRadians = osg::DegreesToRadians(mSunGlareFaderAngleMax); + + float value = 1.f - std::min(1.f, angleRadians / angleMaxRadians); + float fade = value * mSunGlareFaderMax; + + fade *= mTimeOfDayFade * mGlareView * visibleRatio; + + if (fade == 0.f) + { + // no traverse + return; + } + else + { + osg::ref_ptr stateset (new osg::StateSet); + + osg::ref_ptr mat (createUnlitMaterial()); + + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade)); + mat->setEmission(osg::Material::FRONT_AND_BACK, mColor); + + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + + cv->pushStateSet(stateset); + traverse(node, nv); + cv->popStateSet(); + } + } + + void setTimeOfDayFade(float val) + { + mTimeOfDayFade = val; + } + + void setGlareView(float glareView) + { + mGlareView = glareView; + } + + private: + float getAngleToSunInRadians(const osg::Matrix& viewMatrix) const + { + osg::Vec3d eye, center, up; + viewMatrix.getLookAt(eye, center, up); + + osg::Vec3d forward = center - eye; + osg::Vec3d sun = mSunTransform->getPosition(); + + forward.normalize(); + sun.normalize(); + float angleRadians = std::acos(forward * sun); + return angleRadians; + } + + osg::ref_ptr mSunTransform; + float mTimeOfDayFade; + float mGlareView; + osg::Vec4f mColor; + float mSunGlareFaderMax; + float mSunGlareFaderAngleMax; + }; + + osg::ref_ptr mUpdater; + osg::ref_ptr mSunFlashCallback; + osg::ref_ptr mSunFlashNode; + osg::ref_ptr mSunGlareCallback; + osg::ref_ptr mSunGlareNode; + osg::ref_ptr mOcclusionQueryVisiblePixels; + osg::ref_ptr mOcclusionQueryTotalPixels; +}; + +class Moon : public CelestialBody +{ +public: + enum Type + { + Type_Masser = 0, + Type_Secunda + }; + + Moon(osg::Group* parentNode, Resource::TextureManager& textureManager, float scaleFactor, Type type) + : CelestialBody(parentNode, scaleFactor, 2) + , mType(type) + , mPhase(MoonState::Phase_Unspecified) + , mUpdater(new Updater(textureManager)) + { + setPhase(MoonState::Phase_Full); + setVisible(true); + + mGeode->addUpdateCallback(mUpdater); + } + + ~Moon() + { + mGeode->removeUpdateCallback(mUpdater); + } + + virtual void adjustTransparency(const float ratio) + { + mUpdater->mTransparency *= ratio; + } + + void setState(const MoonState& state) + { + float radsX = ((state.mRotationFromHorizon) * M_PI) / 180.0f; + float radsZ = ((state.mRotationFromNorth) * M_PI) / 180.0f; + + osg::Quat rotX(radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); + osg::Quat rotZ(radsZ, osg::Vec3f(0.0f, 0.0f, 1.0f)); + + osg::Vec3f direction = rotX * rotZ * osg::Vec3f(0.0f, 1.0f, 0.0f); + mTransform->setPosition(direction * mDistance); + + // The moon quad is initially oriented facing down, so we need to offset its X-axis + // rotation to rotate it to face the camera when sitting at the horizon. + osg::Quat attX((-M_PI / 2.0f) + radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); + mTransform->setAttitude(attX * rotZ); + + setPhase(state.mPhase); + mUpdater->mTransparency = state.mMoonAlpha; + mUpdater->mShadowBlend = state.mShadowBlend; + } + + void setAtmosphereColor(const osg::Vec4f& color) + { + mUpdater->mAtmosphereColor = color; + } + + void setColor(const osg::Vec4f& color) + { + mUpdater->mMoonColor = color; + } + + unsigned int getPhaseInt() const + { + if (mPhase == MoonState::Phase_New) return 0; + else if (mPhase == MoonState::Phase_WaxingCrescent) return 1; + else if (mPhase == MoonState::Phase_WaningCrescent) return 1; + else if (mPhase == MoonState::Phase_FirstQuarter) return 2; + else if (mPhase == MoonState::Phase_ThirdQuarter) return 2; + else if (mPhase == MoonState::Phase_WaxingGibbous) return 3; + else if (mPhase == MoonState::Phase_WaningGibbous) return 3; + else if (mPhase == MoonState::Phase_Full) return 4; + return 0; + } + +private: + struct Updater : public SceneUtil::StateSetUpdater + { + Resource::TextureManager& mTextureManager; + osg::ref_ptr mPhaseTex; + osg::ref_ptr mCircleTex; + float mTransparency; + float mShadowBlend; + osg::Vec4f mAtmosphereColor; + osg::Vec4f mMoonColor; + + Updater(Resource::TextureManager& textureManager) + : mTextureManager(textureManager) + , mPhaseTex() + , mCircleTex() + , mTransparency(1.0f) + , mShadowBlend(1.0f) + , mAtmosphereColor(1.0f, 1.0f, 1.0f, 1.0f) + , mMoonColor(1.0f, 1.0f, 1.0f, 1.0f) + { + } + + virtual void setDefaults(osg::StateSet* stateset) + { + stateset->setTextureAttributeAndModes(0, mPhaseTex, osg::StateAttribute::ON); + osg::ref_ptr texEnv = new osg::TexEnvCombine; + texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE); + texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); + texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); + texEnv->setConstantColor(osg::Vec4f(1.f, 0.f, 0.f, 1.f)); // mShadowBlend * mMoonColor + stateset->setTextureAttributeAndModes(0, texEnv, osg::StateAttribute::ON); + + stateset->setTextureAttributeAndModes(1, mCircleTex, osg::StateAttribute::ON); + osg::ref_ptr texEnv2 = new osg::TexEnvCombine; + texEnv2->setCombine_RGB(osg::TexEnvCombine::ADD); + texEnv2->setCombine_Alpha(osg::TexEnvCombine::MODULATE); + texEnv2->setSource0_Alpha(osg::TexEnvCombine::TEXTURE); + texEnv2->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); + texEnv2->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnv2->setSource1_RGB(osg::TexEnvCombine::CONSTANT); + texEnv2->setConstantColor(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); // mAtmosphereColor.rgb, mTransparency + stateset->setTextureAttributeAndModes(1, texEnv2, osg::StateAttribute::ON); + + stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor*) + { + osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXENV)); + texEnv->setConstantColor(mMoonColor * mShadowBlend); + + osg::TexEnvCombine* texEnv2 = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); + texEnv2->setConstantColor(osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency)); + } + + void setTextures(const std::string& phaseTex, const std::string& circleTex) + { + mPhaseTex = mTextureManager.getTexture2D(phaseTex, osg::Texture::CLAMP, osg::Texture::CLAMP); + mCircleTex = mTextureManager.getTexture2D(circleTex, osg::Texture::CLAMP, osg::Texture::CLAMP); + + reset(); + } + }; + + Type mType; + MoonState::Phase mPhase; + osg::ref_ptr mUpdater; + + void setPhase(const MoonState::Phase& phase) + { + if(mPhase == phase) + return; + + mPhase = phase; + + std::string textureName = "textures/tx_"; + + if (mType == Moon::Type_Secunda) + textureName += "secunda_"; + else + textureName += "masser_"; + + if (phase == MoonState::Phase_New) textureName += "new"; + else if(phase == MoonState::Phase_WaxingCrescent) textureName += "one_wax"; + else if(phase == MoonState::Phase_FirstQuarter) textureName += "half_wax"; + else if(phase == MoonState::Phase_WaxingGibbous) textureName += "three_wax"; + else if(phase == MoonState::Phase_WaningCrescent) textureName += "one_wan"; + else if(phase == MoonState::Phase_ThirdQuarter) textureName += "half_wan"; + else if(phase == MoonState::Phase_WaningGibbous) textureName += "three_wan"; + else if(phase == MoonState::Phase_Full) textureName += "full"; + + textureName += ".dds"; + + if (mType == Moon::Type_Secunda) + mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_s.dds"); + else + mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_m.dds"); + } +}; + +SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager) + : mSceneManager(sceneManager) + , mAtmosphereNightRoll(0.f) + , mCreated(false) , mIsStorm(false) - , mHour(0.0f) , mDay(0) , mMonth(0) , mCloudAnimationTimer(0.f) - , mSun(NULL) - , mSunGlare(NULL) - , mMasser(NULL) - , mSecunda(NULL) - , mCamera(pCamera) - , mRootNode(NULL) - , mSceneMgr(NULL) - , mAtmosphereDay(NULL) - , mAtmosphereNight(NULL) - , mCloudNode(NULL) - , mParticleNode(NULL) - , mRainTimer(0) + , mRainTimer(0.f) , mStormDirection(0,-1,0) , mClouds() , mNextClouds() , mCloudBlendFactor(0.0f) - , mCloudOpacity(0.0f) , mCloudSpeed(0.0f) , mStarsOpacity(0.0f) - , mLightning(NULL) , mRemainingTransitionTime(0.0f) - , mGlare(0.0f) - , mGlareFade(0.0f) , mRainEnabled(false) , mRainSpeed(0) , mRainFrequency(1) + , mWindSpeed(0.f) , mEnabled(true) , mSunEnabled(true) - , mMasserEnabled(true) - , mSecundaEnabled(true) { - mSceneMgr = root->getCreator(); - mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); + osg::ref_ptr skyroot (new CameraRelativeTransform); + + skyroot->setNodeMask(Mask_Sky); + parentNode->addChild(skyroot); + + mRootNode = skyroot; + + mEarlyRenderBinRoot = new osg::Group; + // render before the world is rendered + mEarlyRenderBinRoot->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Sky, "RenderBin"); + // Prevent unwanted clipping by water reflection camera's clipping plane + mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_CLIP_PLANE0, osg::StateAttribute::OFF); + mRootNode->addChild(mEarlyRenderBinRoot); + + mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); } void SkyManager::create() { assert(!mCreated); - sh::Factory::getInstance().setSharedParameter ("cloudBlendFactor", - sh::makeProperty(new sh::FloatValue(0))); - sh::Factory::getInstance().setSharedParameter ("cloudOpacity", - sh::makeProperty(new sh::FloatValue(1))); - sh::Factory::getInstance().setSharedParameter ("cloudColour", - sh::makeProperty(new sh::Vector3(1,1,1))); - sh::Factory::getInstance().setSharedParameter ("cloudAnimationTimer", - sh::makeProperty(new sh::FloatValue(0))); - sh::Factory::getInstance().setSharedParameter ("nightFade", - sh::makeProperty(new sh::FloatValue(0))); - sh::Factory::getInstance().setSharedParameter ("atmosphereColour", sh::makeProperty(new sh::Vector4(0,0,0,1))); - sh::Factory::getInstance().setSharedParameter ("horizonColour", sh::makeProperty(new sh::Vector4(0,0,0,1))); + mAtmosphereDay = mSceneManager->createInstance("meshes/sky_atmosphere.nif", mEarlyRenderBinRoot); + ModVertexAlphaVisitor modAtmosphere(0); + mAtmosphereDay->accept(modAtmosphere); - sh::Factory::getInstance().setTextureAlias ("cloud_texture_1", ""); - sh::Factory::getInstance().setTextureAlias ("cloud_texture_2", ""); + mAtmosphereUpdater = new AtmosphereUpdater; + mAtmosphereDay->addUpdateCallback(mAtmosphereUpdater); - // Create light used for thunderstorm - mLightning = mSceneMgr->createLight(); - mLightning->setType (Ogre::Light::LT_DIRECTIONAL); - mLightning->setDirection (Ogre::Vector3(0.3f, -0.7f, 0.3f)); - mLightning->setVisible (false); - mLightning->setDiffuseColour (ColourValue(3,3,3)); + mAtmosphereNightNode = new osg::PositionAttitudeTransform; + mAtmosphereNightNode->setNodeMask(0); + mEarlyRenderBinRoot->addChild(mAtmosphereNightNode); + + osg::ref_ptr atmosphereNight; + if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif")) + atmosphereNight = mSceneManager->createInstance("meshes/sky_night_02.nif", mAtmosphereNightNode); + else + atmosphereNight = mSceneManager->createInstance("meshes/sky_night_01.nif", mAtmosphereNightNode); + atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + ModVertexAlphaVisitor modStars(2); + atmosphereNight->accept(modStars); + mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getTextureManager()); + atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); + + mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getTextureManager())); const MWWorld::Fallback* fallback=MWBase::Environment::get().getWorld()->getFallback(); - mSecunda = new Moon("secunda_texture", fallback->getFallbackFloat("Moons_Secunda_Size")/100, Vector3(-0.4f, 0.4f, 0.5f), mRootNode, "openmw_moon"); - mSecunda->setType(Moon::Type_Secunda); - mSecunda->setRenderQueue(RQG_SkiesEarly+4); + mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getTextureManager(), fallback->getFallbackFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); + mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getTextureManager(), fallback->getFallbackFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); - mMasser = new Moon("masser_texture", fallback->getFallbackFloat("Moons_Masser_Size")/100, Vector3(-0.4f, 0.4f, 0.5f), mRootNode, "openmw_moon"); - mMasser->setRenderQueue(RQG_SkiesEarly+3); - mMasser->setType(Moon::Type_Masser); + mCloudNode = new osg::PositionAttitudeTransform; + mEarlyRenderBinRoot->addChild(mCloudNode); + mCloudMesh = mSceneManager->createInstance("meshes/sky_clouds_01.nif", mCloudNode); + ModVertexAlphaVisitor modClouds(1); + mCloudMesh->accept(modClouds); + mCloudUpdater = new CloudUpdater; + mCloudUpdater->setOpacity(1.f); + mCloudMesh->addUpdateCallback(mCloudUpdater); - mSun = new BillboardObject("textures\\tx_sun_05.dds", 1, Vector3(0.4f, 0.4f, 0.4f), mRootNode, "openmw_sun"); - mSun->setRenderQueue(RQG_SkiesEarly+4); - mSunGlare = new BillboardObject("textures\\tx_sun_flash_grey_05.dds", 3, Vector3(0.4f, 0.4f, 0.4f), mRootNode, "openmw_sun"); - mSunGlare->setRenderQueue(RQG_SkiesLate); - mSunGlare->setVisibilityFlags(RV_NoReflection); + mCloudMesh2 = mSceneManager->createInstance("meshes/sky_clouds_01.nif", mCloudNode); + mCloudMesh2->accept(modClouds); + mCloudUpdater2 = new CloudUpdater; + mCloudUpdater2->setOpacity(0.f); + mCloudMesh2->addUpdateCallback(mCloudUpdater2); + mCloudMesh2->setNodeMask(0); - Ogre::AxisAlignedBox aabInf = Ogre::AxisAlignedBox::BOX_INFINITE; + osg::ref_ptr depth = new osg::Depth; + depth->setWriteMask(false); + mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); + mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF); - // Stars - mAtmosphereNight = mRootNode->createChildSceneNode(); - NifOgre::ObjectScenePtr objects; - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("meshes\\sky_night_02.nif")) - objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_02.nif"); - else - objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_01.nif"); - - for(size_t i = 0, matidx = 0;i < objects->mEntities.size();i++) - { - Entity* night1_ent = objects->mEntities[i]; - night1_ent->setRenderQueueGroup(RQG_SkiesEarly+1); - night1_ent->setVisibilityFlags(RV_Sky); - night1_ent->setCastShadows(false); - night1_ent->getMesh()->_setBounds (aabInf); - - for (unsigned int j=0; jgetNumSubEntities(); ++j) - { - std::string matName = "openmw_stars_" + boost::lexical_cast(matidx++); - sh::MaterialInstance* m = sh::Factory::getInstance().createMaterialInstance(matName, "openmw_stars"); - - std::string textureName = sh::retrieveValue( - sh::Factory::getInstance().getMaterialInstance(night1_ent->getSubEntity(j)->getMaterialName())->getProperty("diffuseMap"), NULL).get(); - - m->setProperty("texture", sh::makeProperty(new sh::StringValue(textureName))); - - night1_ent->getSubEntity(j)->setMaterialName(matName); - } - } - mObjects.push_back(objects); - - // Atmosphere (day) - mAtmosphereDay = mRootNode->createChildSceneNode(); - objects = NifOgre::Loader::createObjects(mAtmosphereDay, "meshes\\sky_atmosphere.nif"); - for(size_t i = 0;i < objects->mEntities.size();i++) - { - Entity* atmosphere_ent = objects->mEntities[i]; - atmosphere_ent->setCastShadows(false); - atmosphere_ent->setRenderQueueGroup(RQG_SkiesEarly); - atmosphere_ent->setVisibilityFlags(RV_Sky); - - for(unsigned int j = 0;j < atmosphere_ent->getNumSubEntities();j++) - atmosphere_ent->getSubEntity (j)->setMaterialName("openmw_atmosphere"); - - // Using infinite AAB here to prevent being clipped by the custom near clip plane used for reflections/refractions - atmosphere_ent->getMesh()->_setBounds (aabInf); - } - mObjects.push_back(objects); - - // Clouds - mCloudNode = mRootNode->createChildSceneNode(); - objects = NifOgre::Loader::createObjects(mCloudNode, "meshes\\sky_clouds_01.nif"); - for(size_t i = 0;i < objects->mEntities.size();i++) - { - Entity* clouds_ent = objects->mEntities[i]; - clouds_ent->setVisibilityFlags(RV_Sky); - clouds_ent->setRenderQueueGroup(RQG_SkiesEarly+5); - for(unsigned int j = 0;j < clouds_ent->getNumSubEntities();j++) - clouds_ent->getSubEntity(j)->setMaterialName("openmw_clouds"); - clouds_ent->setCastShadows(false); - // Using infinite AAB here to prevent being clipped by the custom near clip plane used for reflections/refractions - clouds_ent->getMesh()->_setBounds (aabInf); - } - mObjects.push_back(objects); + mMoonScriptColor = fallback->getFallbackColour("Moons_Script_Color"); mCreated = true; } +class RainShooter : public osgParticle::Shooter +{ +public: + RainShooter() + : mAngle(0.f) + { + } + + virtual void shoot(osgParticle::Particle* particle) const + { + particle->setVelocity(mVelocity); + particle->setAngle(osg::Vec3f(-mAngle, 0, (Misc::Rng::rollProbability() * 2 - 1) * osg::PI)); + } + + void setVelocity(const osg::Vec3f& velocity) + { + mVelocity = velocity; + } + + void setAngle(float angle) + { + mAngle = angle; + } + + virtual osg::Object* cloneType() const + { + return new RainShooter; + } + virtual osg::Object* clone(const osg::CopyOp &) const + { + return new RainShooter(*this); + } + +private: + osg::Vec3f mVelocity; + float mAngle; +}; + +// Updater for alpha value on a node's StateSet. Assumes the node has an existing Material StateAttribute. +class AlphaFader : public SceneUtil::StateSetUpdater +{ +public: + AlphaFader() + : mAlpha(1.f) + { + } + + void setAlpha(float alpha) + { + mAlpha = alpha; + } + + virtual void setDefaults(osg::StateSet* stateset) + { + // need to create a deep copy of StateAttributes we will modify + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); + } + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) + { + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mAlpha)); + } + + // Helper for adding AlphaFaders to a subgraph + class SetupVisitor : public osg::NodeVisitor + { + public: + SetupVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + virtual void apply(osg::Node &node) + { + if (osg::StateSet* stateset = node.getStateSet()) + { + if (stateset->getAttribute(osg::StateAttribute::MATERIAL)) + { + SceneUtil::CompositeStateSetUpdater* composite = NULL; +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) + osg::Callback* callback = node.getUpdateCallback(); +#else + osg::NodeCallback* callback = node.getUpdateCallback(); +#endif + while (callback) + { + if ((composite = dynamic_cast(callback))) + break; + callback = callback->getNestedCallback(); + } + + osg::ref_ptr alphaFader (new AlphaFader); + + if (composite) + composite->addController(alphaFader); + else + node.addUpdateCallback(alphaFader); + + mAlphaFaders.push_back(alphaFader); + } + } + traverse(node); + } + + std::vector > getAlphaFaders() + { + return mAlphaFaders; + } + + private: + std::vector > mAlphaFaders; + }; + +private: + float mAlpha; +}; + +class RainFader : public AlphaFader +{ +public: + virtual void setDefaults(osg::StateSet* stateset) + { + osg::ref_ptr mat (new osg::Material); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,1)); + mat->setColorMode(osg::Material::OFF); + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + } +}; + +void SkyManager::createRain() +{ + if (mRainNode) + return; + + mRainNode = new osg::Group; + + mRainParticleSystem = new osgParticle::ParticleSystem; + mRainParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); + mRainParticleSystem->setAlignVectorX(osg::Vec3f(0.1,0,0)); + mRainParticleSystem->setAlignVectorY(osg::Vec3f(0,0,-1)); + + osg::ref_ptr stateset (mRainParticleSystem->getOrCreateStateSet()); + stateset->setTextureAttributeAndModes(0, mSceneManager->getTextureManager()->getTexture2D("textures/tx_raindrop_01.dds", + osg::Texture::CLAMP, osg::Texture::CLAMP), osg::StateAttribute::ON); + stateset->setNestRenderBins(false); + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + + osgParticle::Particle& particleTemplate = mRainParticleSystem->getDefaultParticleTemplate(); + particleTemplate.setSizeRange(osgParticle::rangef(5.f, 15.f)); + particleTemplate.setAlphaRange(osgParticle::rangef(1.f, 1.f)); + particleTemplate.setLifeTime(1); + + osg::ref_ptr emitter (new osgParticle::ModularEmitter); + emitter->setParticleSystem(mRainParticleSystem); + + osg::ref_ptr placer (new osgParticle::BoxPlacer); + placer->setXRange(-300, 300); // Rain_Diameter + placer->setYRange(-300, 300); + placer->setZRange(300, 300); + emitter->setPlacer(placer); + + osg::ref_ptr counter (new osgParticle::ConstantRateCounter); + counter->setNumberOfParticlesPerSecondToCreate(600.0); + emitter->setCounter(counter); + + osg::ref_ptr shooter (new RainShooter); + mRainShooter = shooter; + emitter->setShooter(shooter); + + osg::ref_ptr updater (new osgParticle::ParticleSystemUpdater); + updater->addParticleSystem(mRainParticleSystem); + + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(mRainParticleSystem); + + mRainNode->addChild(emitter); + mRainNode->addChild(geode); + mRainNode->addChild(updater); + + mRainFader = new RainFader; + mRainNode->addUpdateCallback(mRainFader); + mRainNode->addCullCallback(mUnderwaterSwitch); + mRainNode->setNodeMask(Mask_WeatherParticles); + + mRootNode->addChild(mRainNode); +} + +void SkyManager::destroyRain() +{ + if (!mRainNode) + return; + + mRootNode->removeChild(mRainNode); + mRainNode = NULL; + mRainParticleSystem = NULL; + mRainShooter = NULL; + mRainFader = NULL; +} + SkyManager::~SkyManager() { - clearRain(); - delete mSun; - delete mSunGlare; - delete mMasser; - delete mSecunda; + if (mRootNode) + { + mRootNode->getParent(0)->removeChild(mRootNode); + mRootNode = NULL; + } } int SkyManager::getMasserPhase() const @@ -422,423 +1417,246 @@ int SkyManager::getSecundaPhase() const return mSecunda->getPhaseInt(); } -void SkyManager::clearRain() -{ - for (std::map::iterator it = mRainModels.begin(); it != mRainModels.end();) - { - it->second.setNull(); - mSceneMgr->destroySceneNode(it->first); - mRainModels.erase(it++); - } -} - -void SkyManager::updateRain(float dt) -{ - // Move existing rain - // Note: if rain gets disabled, we let the existing rain drops finish falling down. - float minHeight = 200; - for (std::map::iterator it = mRainModels.begin(); it != mRainModels.end();) - { - Ogre::Vector3 pos = it->first->getPosition(); - pos.z -= mRainSpeed * dt; - it->first->setPosition(pos); - if (pos.z < -minHeight - // Here we might want to add a "splash" effect later - || MWBase::Environment::get().getWorld()->isUnderwater( - MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), it->first->_getDerivedPosition())) - { - it->second.setNull(); - mSceneMgr->destroySceneNode(it->first); - mRainModels.erase(it++); - } - else - ++it; - } - - // Spawn new rain - float rainFrequency = mRainFrequency; - if (mRainEnabled) - { - mRainTimer += dt; - if (mRainTimer >= 1.f/rainFrequency) - { - mRainTimer = 0; - - // TODO: handle rain settings from Morrowind.ini - const float rangeRandom = 100; - float xOffs = OEngine::Misc::Rng::rollProbability() * rangeRandom - (rangeRandom / 2); - float yOffs = OEngine::Misc::Rng::rollProbability() * rangeRandom - (rangeRandom / 2); - - // Create a separate node to control the offset, since a node with setInheritOrientation(false) will still - // consider the orientation of the parent node for its position, just not for its orientation - float startHeight = 700; - Ogre::Vector3 worldPos = mParticleNode->_getDerivedPosition(); - worldPos += Ogre::Vector3(xOffs, yOffs, startHeight); - if (MWBase::Environment::get().getWorld()->isUnderwater( - MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), worldPos)) - return; - - Ogre::SceneNode* offsetNode = mParticleNode->createChildSceneNode(Ogre::Vector3(xOffs,yOffs,startHeight)); - - // Spawn a new rain object for each instance. - // TODO: this is inefficient. We could try to use an Ogre::ParticleSystem instead, but then we would need to make assumptions - // about the rain meshes being Quads and their dimensions. - // Or we could clone meshes into one vertex buffer manually. - NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(offsetNode, mRainEffect); - for (unsigned int i=0; imEntities.size(); ++i) - { - objects->mEntities[i]->setRenderQueueGroup(RQG_Alpha); - objects->mEntities[i]->setVisibilityFlags(RV_Sky); - } - for (unsigned int i=0; imParticles.size(); ++i) - { - objects->mParticles[i]->setRenderQueueGroup(RQG_Alpha); - objects->mParticles[i]->setVisibilityFlags(RV_Sky); - } - mRainModels[offsetNode] = objects; - } - } -} - void SkyManager::update(float duration) { if (!mEnabled) return; - const MWWorld::Fallback* fallback=MWBase::Environment::get().getWorld()->getFallback(); - - if (!mParticle.isNull()) - { - for (unsigned int i=0; imControllers.size(); ++i) - mParticle->mControllers[i].update(); - - for (unsigned int i=0; imParticles.size(); ++i) - { - Ogre::ParticleSystem* psys = mParticle->mParticles[i]; - Ogre::ParticleIterator pi = psys->_getIterator(); - while (!pi.end()) - { - Ogre::Particle *p = pi.getNext(); - #if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) - Ogre::Vector3 pos = p->mPosition; - Ogre::Real& timeToLive = p->mTimeToLive; - #else - Ogre::Vector3 pos = p->position; - Ogre::Real& timeToLive = p->timeToLive; - #endif - - if (psys->getKeepParticlesInLocalSpace() && psys->getParentNode()) - pos = psys->getParentNode()->convertLocalToWorldPosition(pos); - - if (MWBase::Environment::get().getWorld()->isUnderwater( - MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), pos)) - timeToLive = 0; - } - } - - if (mIsStorm) - mParticleNode->setOrientation(Ogre::Vector3::UNIT_Y.getRotationTo(mStormDirection)); - } if (mIsStorm) - mCloudNode->setOrientation(Ogre::Vector3::UNIT_Y.getRotationTo(mStormDirection)); - else - mCloudNode->setOrientation(Ogre::Quaternion::IDENTITY); + { + osg::Quat quat; + quat.makeRotate(osg::Vec3f(0,1,0), mStormDirection); - updateRain(duration); + if (mParticleNode) + mParticleNode->setAttitude(quat); + mCloudNode->setAttitude(quat); + } + else + mCloudNode->setAttitude(osg::Quat()); // UV Scroll the clouds - mCloudAnimationTimer += duration * mCloudSpeed; - sh::Factory::getInstance().setSharedParameter ("cloudAnimationTimer", - sh::makeProperty(new sh::FloatValue(mCloudAnimationTimer))); - - /// \todo improve this - mMasser->setPhase( static_cast( (int) ((mDay % 32)/4.f)) ); - mSecunda->setPhase ( static_cast( (int) ((mDay % 32)/4.f)) ); - - mSecunda->setColour ( mMoonRed ? fallback->getFallbackColour("Moons_Script_Color") : ColourValue(1,1,1,1)); - mMasser->setColour (ColourValue(1,1,1,1)); - - if (mSunEnabled) - { - // take 1/10 sec for fading the glare effect from invisible to full - if (mGlareFade > mGlare) - { - mGlareFade -= duration*10; - if (mGlareFade < mGlare) mGlareFade = mGlare; - } - else if (mGlareFade < mGlare) - { - mGlareFade += duration*10; - if (mGlareFade > mGlare) mGlareFade = mGlare; - } - - // increase the strength of the sun glare effect depending - // on how directly the player is looking at the sun - Vector3 sun = mSunGlare->getPosition(); - Vector3 cam = mCamera->getRealDirection(); - const Degree angle = sun.angleBetween( cam ); - float val = 1- (angle.valueDegrees() / 180.f); - val = (val*val*val*val)*6; - mSunGlare->setSize(val * mGlareFade); - } - - mSunGlare->setVisible(mSunEnabled); - mSun->setVisible(mSunEnabled); - mMasser->setVisible(mMasserEnabled); - mSecunda->setVisible(mSecundaEnabled); + mCloudAnimationTimer += duration * mCloudSpeed * 0.003; + mCloudUpdater->setAnimationTimer(mCloudAnimationTimer); + mCloudUpdater2->setAnimationTimer(mCloudAnimationTimer); // rotate the stars by 360 degrees every 4 days - mAtmosphereNight->roll(Degree(MWBase::Environment::get().getWorld()->getTimeScaleFactor()*duration*360 / (3600*96.f))); + mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeScaleFactor()*duration*osg::DegreesToRadians(360.f) / (3600*96.f); + if (mAtmosphereNightNode->getNodeMask() != 0) + mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0,0,1))); } -void SkyManager::enable() +void SkyManager::setEnabled(bool enabled) { - if (!mCreated) + if (enabled && !mCreated) create(); - if (mParticleNode) - mParticleNode->setVisible(true); + mRootNode->setNodeMask(enabled ? Mask_Sky : 0); - mRootNode->setVisible(true); - mEnabled = true; -} - -void SkyManager::disable() -{ - if (mParticleNode) - mParticleNode->setVisible(false); - - clearRain(); - - mRootNode->setVisible(false); - - mEnabled = false; + mEnabled = enabled; } void SkyManager::setMoonColour (bool red) { - mMoonRed = red; + if (!mCreated) return; + mSecunda->setColor(red ? mMoonScriptColor : osg::Vec4f(1,1,1,1)); } -void SkyManager::setWeather(const MWWorld::WeatherResult& weather) +void SkyManager::updateRainParameters() +{ + if (mRainShooter) + { + float windFactor = mWindSpeed/3.f; + float angle = windFactor * osg::PI/4; + mRainShooter->setVelocity(osg::Vec3f(0, mRainSpeed * windFactor, -mRainSpeed)); + mRainShooter->setAngle(angle); + } +} + +void SkyManager::setWeather(const WeatherResult& weather) { if (!mCreated) return; - mRainEffect = weather.mRainEffect; - mRainEnabled = !mRainEffect.empty(); + if (mRainEffect != weather.mRainEffect) + { + mRainEffect = weather.mRainEffect; + if (!mRainEffect.empty()) + { + createRain(); + } + else + { + destroyRain(); + } + } + mRainFrequency = weather.mRainFrequency; mRainSpeed = weather.mRainSpeed; + mWindSpeed = weather.mWindSpeed; + updateRainParameters(); + mIsStorm = weather.mIsStorm; if (mCurrentParticleEffect != weather.mParticleEffect) { mCurrentParticleEffect = weather.mParticleEffect; + // cleanup old particles + if (mParticleEffect) + { + mParticleNode->removeChild(mParticleEffect); + mParticleEffect = NULL; + mParticleFaders.clear(); + } + if (mCurrentParticleEffect.empty()) { - mParticle.setNull(); + if (mParticleNode) + { + mRootNode->removeChild(mParticleNode); + mParticleNode = NULL; + } } else { - mParticle = NifOgre::Loader::createObjects(mParticleNode, mCurrentParticleEffect); - for(size_t i = 0; i < mParticle->mParticles.size(); ++i) + if (!mParticleNode) { - ParticleSystem* particle = mParticle->mParticles[i]; - particle->setRenderQueueGroup(RQG_Alpha); - particle->setVisibilityFlags(RV_Sky); - } - for (size_t i = 0; i < mParticle->mControllers.size(); ++i) - { - if (mParticle->mControllers[i].getSource().isNull()) - mParticle->mControllers[i].setSource(Ogre::ControllerManager::getSingleton().getFrameTimeSource()); + mParticleNode = new osg::PositionAttitudeTransform; + mParticleNode->addCullCallback(mUnderwaterSwitch); + mParticleNode->setNodeMask(Mask_WeatherParticles); + mRootNode->addChild(mParticleNode); } + mParticleEffect = mSceneManager->createInstance(mCurrentParticleEffect, mParticleNode); + + SceneUtil::AssignControllerSourcesVisitor assignVisitor(boost::shared_ptr(new SceneUtil::FrameTimeSource)); + mParticleEffect->accept(assignVisitor); + + AlphaFader::SetupVisitor alphaFaderSetupVisitor; + mParticleEffect->accept(alphaFaderSetupVisitor); + mParticleFaders = alphaFaderSetupVisitor.getAlphaFaders(); + + SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; + mParticleEffect->accept(disableFreezeOnCullVisitor); } } if (mClouds != weather.mCloudTexture) { - sh::Factory::getInstance().setTextureAlias ("cloud_texture_1", Misc::ResourceHelpers::correctTexturePath(weather.mCloudTexture)); mClouds = weather.mCloudTexture; + + std::string texture = Misc::ResourceHelpers::correctTexturePath(mClouds, mSceneManager->getVFS()); + + mCloudUpdater->setTexture(mSceneManager->getTextureManager()->getTexture2D(texture, + osg::Texture::REPEAT, osg::Texture::REPEAT)); } if (mNextClouds != weather.mNextCloudTexture) { - sh::Factory::getInstance().setTextureAlias ("cloud_texture_2", Misc::ResourceHelpers::correctTexturePath(weather.mNextCloudTexture)); mNextClouds = weather.mNextCloudTexture; + + if (!mNextClouds.empty()) + { + std::string texture = Misc::ResourceHelpers::correctTexturePath(mNextClouds, mSceneManager->getVFS()); + + mCloudUpdater2->setTexture(mSceneManager->getTextureManager()->getTexture2D(texture, + osg::Texture::REPEAT, osg::Texture::REPEAT)); + } } if (mCloudBlendFactor != weather.mCloudBlendFactor) { mCloudBlendFactor = weather.mCloudBlendFactor; - sh::Factory::getInstance().setSharedParameter ("cloudBlendFactor", - sh::makeProperty(new sh::FloatValue(weather.mCloudBlendFactor))); + + mCloudUpdater->setOpacity((1.f-mCloudBlendFactor)); + mCloudUpdater2->setOpacity(mCloudBlendFactor); + mCloudMesh2->setNodeMask(mCloudBlendFactor > 0.f ? ~0 : 0); } - if (mCloudOpacity != weather.mCloudOpacity) + if (mCloudColour != weather.mFogColor) { - mCloudOpacity = weather.mCloudOpacity; - sh::Factory::getInstance().setSharedParameter ("cloudOpacity", - sh::makeProperty(new sh::FloatValue(weather.mCloudOpacity))); - } + osg::Vec4f clr (weather.mFogColor); + clr += osg::Vec4f(0.13f, 0.13f, 0.13f, 0.f); - if (mCloudColour != weather.mSunColor) - { - ColourValue clr( weather.mSunColor.r*0.7f + weather.mAmbientColor.r*0.7f, - weather.mSunColor.g*0.7f + weather.mAmbientColor.g*0.7f, - weather.mSunColor.b*0.7f + weather.mAmbientColor.b*0.7f); + mCloudUpdater->setEmissionColor(clr); + mCloudUpdater2->setEmissionColor(clr); - sh::Factory::getInstance().setSharedParameter ("cloudColour", - sh::makeProperty(new sh::Vector3(clr.r, clr.g, clr.b))); - - mCloudColour = weather.mSunColor; + mCloudColour = weather.mFogColor; } if (mSkyColour != weather.mSkyColor) { mSkyColour = weather.mSkyColor; - sh::Factory::getInstance().setSharedParameter ("atmosphereColour", sh::makeProperty(new sh::Vector4( - weather.mSkyColor.r, weather.mSkyColor.g, weather.mSkyColor.b, weather.mSkyColor.a))); + + mAtmosphereUpdater->setEmissionColor(mSkyColour); + mMasser->setAtmosphereColor(mSkyColour); + mSecunda->setAtmosphereColor(mSkyColour); } if (mFogColour != weather.mFogColor) { mFogColour = weather.mFogColor; - sh::Factory::getInstance().setSharedParameter ("horizonColour", sh::makeProperty(new sh::Vector4( - weather.mFogColor.r, weather.mFogColor.g, weather.mFogColor.b, weather.mFogColor.a))); } mCloudSpeed = weather.mCloudSpeed; - if (weather.mNight && mStarsOpacity != weather.mNightFade) + mMasser->adjustTransparency(weather.mGlareView); + mSecunda->adjustTransparency(weather.mGlareView); + + mSun->setColor(weather.mSunDiscColor); + mSun->adjustTransparency(weather.mGlareView * weather.mSunDiscColor.a()); + + float nextStarsOpacity = weather.mNightFade * weather.mGlareView; + if(weather.mNight && mStarsOpacity != nextStarsOpacity) { - if (weather.mNightFade == 0) - mAtmosphereNight->setVisible(false); - else - { - mAtmosphereNight->setVisible(true); + mStarsOpacity = nextStarsOpacity; - sh::Factory::getInstance().setSharedParameter ("nightFade", - sh::makeProperty(new sh::FloatValue(weather.mNightFade))); - - mStarsOpacity = weather.mNightFade; - } + mAtmosphereNightUpdater->setFade(mStarsOpacity); } + mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0 : 0); - float strength; - float timeofday_angle = std::abs(mSunGlare->getPosition().z/mSunGlare->getPosition().length()); - if (timeofday_angle <= 0.44) - strength = timeofday_angle/0.44f; - else - strength = 1.f; - - mSunGlare->setVisibility(weather.mGlareView * mGlareFade * strength); - - mSun->setVisibility(weather.mGlareView * strength); - - mAtmosphereNight->setVisible(weather.mNight && mEnabled); - - if (mParticle.get()) - setAlpha(mParticle, weather.mEffectFade); - for (std::map::iterator it = mRainModels.begin(); it != mRainModels.end(); ++it) - setAlpha(it->second, weather.mEffectFade); -} - -void SkyManager::setGlare(const float glare) -{ - mGlare = glare; -} - -Vector3 SkyManager::getRealSunPos() -{ - if (!mCreated) return Vector3(0,0,0); - return mSun->getNode()->getPosition() + mCamera->getRealPosition(); + if (mRainFader) + mRainFader->setAlpha(weather.mEffectFade * 0.6); // * Rain_Threshold? + for (std::vector >::const_iterator it = mParticleFaders.begin(); it != mParticleFaders.end(); ++it) + (*it)->setAlpha(weather.mEffectFade); } void SkyManager::sunEnable() { - mSunEnabled = true; + if (!mCreated) return; + + mSun->setVisible(true); } void SkyManager::sunDisable() { - mSunEnabled = false; + if (!mCreated) return; + + mSun->setVisible(false); } -void SkyManager::setStormDirection(const Vector3 &direction) +void SkyManager::setStormDirection(const osg::Vec3f &direction) { mStormDirection = direction; } -void SkyManager::setSunDirection(const Vector3& direction, bool is_night) +void SkyManager::setSunDirection(const osg::Vec3f& direction) { if (!mCreated) return; - mSun->setPosition(direction); - mSunGlare->setPosition(direction); - float height = direction.z; - float fade = is_night ? 0.0f : (( height > 0.5) ? 1.0f : height * 2); - sh::Factory::getInstance ().setSharedParameter ("waterSunFade_sunHeight", sh::makeProperty(new sh::Vector2(fade, height))); + mSun->setDirection(direction); } -void SkyManager::setMasserDirection(const Vector3& direction) +void SkyManager::setMasserState(const MoonState& state) { - if (!mCreated) return; - mMasser->setPosition(direction); + if(!mCreated) return; + + mMasser->setState(state); } -void SkyManager::setSecundaDirection(const Vector3& direction) +void SkyManager::setSecundaState(const MoonState& state) { - if (!mCreated) return; - mSecunda->setPosition(direction); -} + if(!mCreated) return; -void SkyManager::masserEnable() -{ - mMasserEnabled = true; -} - -void SkyManager::secundaEnable() -{ - mSecundaEnabled = true; -} - -void SkyManager::masserDisable() -{ - mMasserEnabled = false; -} - -void SkyManager::secundaDisable() -{ - mSecundaEnabled = false; -} - -void SkyManager::setLightningStrength(const float factor) -{ - if (!mCreated) return; - if (factor > 0.f) - { - mLightning->setDiffuseColour (ColourValue(2*factor, 2*factor, 2*factor)); - mLightning->setVisible(true); - } - else - mLightning->setVisible(false); -} - -void SkyManager::setMasserFade(const float fade) -{ - if (!mCreated) return; - mMasser->setVisibility(fade); -} - -void SkyManager::setSecundaFade(const float fade) -{ - if (!mCreated) return; - mSecunda->setVisibility(fade); -} - -void SkyManager::setHour(double hour) -{ - mHour = static_cast(hour); + mSecunda->setState(state); } void SkyManager::setDate(int day, int month) @@ -847,28 +1665,19 @@ void SkyManager::setDate(int day, int month) mMonth = month; } -Ogre::SceneNode* SkyManager::getSunNode() +void SkyManager::setGlareTimeOfDayFade(float val) { - if (!mCreated) return 0; - return mSun->getNode(); + mSun->setGlareTimeOfDayFade(val); } -void SkyManager::setGlareEnabled (bool enabled) +void SkyManager::setWaterHeight(float height) { - if (!mCreated || !mEnabled) - return; - mSunGlare->setVisible (mSunEnabled && enabled); + mUnderwaterSwitch->setWaterLevel(height); } -void SkyManager::attachToNode(SceneNode *sceneNode) +void SkyManager::setWaterEnabled(bool enabled) { - if (!mParticleNode) - { - mParticleNode = sceneNode->createChildSceneNode(); - mParticleNode->setInheritOrientation(false); - } - else - { - sceneNode->addChild(mParticleNode); - } + mUnderwaterSwitch->setEnabled(enabled); +} + } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 6950dbab34..0caadaa073 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -1,128 +1,117 @@ -#ifndef GAME_RENDER_SKY_H -#define GAME_RENDER_SKY_H +#ifndef OPENMW_MWRENDER_SKY_H +#define OPENMW_MWRENDER_SKY_H -#include +#include +#include -#include -#include -#include -#include -#include +#include +#include +#include -#include - -#include - - -#include "../mwworld/weather.hpp" - -namespace Ogre +namespace osg +{ + class Group; + class Node; + class Material; +} + +namespace osgParticle +{ + class ParticleSystem; +} + +namespace Resource { - class RenderWindow; - class SceneNode; - class Camera; - class Viewport; class SceneManager; - class Entity; - class BillboardSet; - class TextureUnitState; } namespace MWRender { - class BillboardObject : public sh::MaterialInstanceListener + class AtmosphereUpdater; + class AtmosphereNightUpdater; + class CloudUpdater; + class Sun; + class Moon; + class RainShooter; + class RainFader; + class AlphaFader; + class UnderwaterSwitchCallback; + + struct WeatherResult { - public: - BillboardObject( const Ogre::String& textureName, - const float size, - const Ogre::Vector3& position, - Ogre::SceneNode* rootNode, - const std::string& material - ); + std::string mCloudTexture; + std::string mNextCloudTexture; + float mCloudBlendFactor; - void requestedConfiguration (sh::MaterialInstance* m, const std::string& configuration); - void createdConfiguration (sh::MaterialInstance* m, const std::string& configuration); + osg::Vec4f mFogColor; - virtual ~BillboardObject() {} + osg::Vec4f mAmbientColor; - void setColour(const Ogre::ColourValue& pColour); - void setPosition(const Ogre::Vector3& pPosition); - void setVisible(const bool visible); - void setRenderQueue(unsigned int id); - void setVisibilityFlags(int flags); - void setSize(const float size); - Ogre::Vector3 getPosition() const; + osg::Vec4f mSkyColor; - void setVisibility(const float visibility); + // sun light color + osg::Vec4f mSunColor; - Ogre::SceneNode* getNode(); + // alpha is the sun transparency + osg::Vec4f mSunDiscColor; - protected: - float mVisibility; - Ogre::ColourValue mColour; - Ogre::SceneNode* mNode; - sh::MaterialInstance* mMaterial; - Ogre::Entity* mEntity; + float mFogDepth; + + float mWindSpeed; + + float mCloudSpeed; + + float mGlareView; + + bool mNight; // use night skybox + float mNightFade; // fading factor for night skybox + + bool mIsStorm; + + std::string mAmbientLoopSoundID; + float mAmbientSoundVolume; + + std::string mParticleEffect; + std::string mRainEffect; + float mEffectFade; + + float mRainSpeed; + float mRainFrequency; }; - - /* - * The moons need a seperate class because of their shader (which allows them to be partially transparent) - */ - class Moon : public BillboardObject + struct MoonState { - public: - Moon( const Ogre::String& textureName, - const float size, - const Ogre::Vector3& position, - Ogre::SceneNode* rootNode, - const std::string& material - ); - - virtual ~Moon() {} - enum Phase { - Phase_New = 0, - Phase_WaxingCrescent, - Phase_WaxingHalf, - Phase_WaxingGibbous, - Phase_Full, + Phase_Full = 0, Phase_WaningGibbous, - Phase_WaningHalf, - Phase_WaningCrescent + Phase_ThirdQuarter, + Phase_WaningCrescent, + Phase_New, + Phase_WaxingCrescent, + Phase_FirstQuarter, + Phase_WaxingGibbous, + Phase_Unspecified }; - enum Type - { - Type_Masser = 0, - Type_Secunda - }; - - void setPhase(const Phase& phase); - void setType(const Type& type); - - unsigned int getPhaseInt() const; - - private: - Type mType; + float mRotationFromHorizon; + float mRotationFromNorth; Phase mPhase; + float mShadowBlend; + float mMoonAlpha; }; + ///@brief The SkyManager handles rendering of the sky domes, celestial bodies as well as other objects that need to be rendered + /// relative to the camera (e.g. weather particle effects) class SkyManager { public: - SkyManager(Ogre::SceneNode* root, Ogre::Camera* pCamera); + SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager); ~SkyManager(); - /// Attach weather particle effects to this scene node (should be the Camera's parent node) - void attachToNode(Ogre::SceneNode* sceneNode); - void update(float duration); - void enable(); - - void disable(); + void setEnabled(bool enabled); void setHour (double hour); ///< will be called even when sky is disabled. @@ -141,9 +130,7 @@ namespace MWRender void setMoonColour (bool red); ///< change Secunda colour to red - void setWeather(const MWWorld::WeatherResult& weather); - - Ogre::SceneNode* getSunNode(); + void setWeather(const WeatherResult& weather); void sunEnable(); @@ -151,102 +138,100 @@ namespace MWRender void setRainSpeed(float speed); - void setStormDirection(const Ogre::Vector3& direction); + void setStormDirection(const osg::Vec3f& direction); - void setSunDirection(const Ogre::Vector3& direction, bool is_night); + void setSunDirection(const osg::Vec3f& direction); - void setMasserDirection(const Ogre::Vector3& direction); + void setMasserState(const MoonState& state); + void setSecundaState(const MoonState& state); - void setSecundaDirection(const Ogre::Vector3& direction); + void setGlareTimeOfDayFade(float val); - void setMasserFade(const float fade); + /// Enable or disable the water plane (used to remove underwater weather particles) + void setWaterEnabled(bool enabled); - void setSecundaFade(const float fade); - - void masserEnable(); - void masserDisable(); - - void secundaEnable(); - void secundaDisable(); - - void setLightningStrength(const float factor); - - void setGlare(const float glare); - void setGlareEnabled(bool enabled); - Ogre::Vector3 getRealSunPos(); + /// Set height of water plane (used to remove underwater weather particles) + void setWaterHeight(float height); private: void create(); ///< no need to call this, automatically done on first enable() - void updateRain(float dt); - void clearRain(); + void createRain(); + void destroyRain(); + void updateRainParameters(); + + Resource::SceneManager* mSceneManager; + + osg::ref_ptr mRootNode; + osg::ref_ptr mEarlyRenderBinRoot; + + osg::ref_ptr mParticleNode; + osg::ref_ptr mParticleEffect; + std::vector > mParticleFaders; + osg::ref_ptr mUnderwaterSwitch; + + osg::ref_ptr mCloudNode; + + osg::ref_ptr mCloudUpdater; + osg::ref_ptr mCloudUpdater2; + osg::ref_ptr mCloudMesh; + osg::ref_ptr mCloudMesh2; + + osg::ref_ptr mAtmosphereDay; + + osg::ref_ptr mAtmosphereNightNode; + float mAtmosphereNightRoll; + osg::ref_ptr mAtmosphereNightUpdater; + + osg::ref_ptr mAtmosphereUpdater; + + std::auto_ptr mSun; + std::auto_ptr mMasser; + std::auto_ptr mSecunda; + + osg::ref_ptr mRainNode; + osg::ref_ptr mRainParticleSystem; + osg::ref_ptr mRainShooter; + osg::ref_ptr mRainFader; bool mCreated; - bool mMoonRed; - bool mIsStorm; - float mHour; int mDay; int mMonth; float mCloudAnimationTimer; - BillboardObject* mSun; - BillboardObject* mSunGlare; - Moon* mMasser; - Moon* mSecunda; - - Ogre::Camera* mCamera; - Ogre::SceneNode* mRootNode; - Ogre::SceneManager* mSceneMgr; - - Ogre::SceneNode* mAtmosphereDay; - Ogre::SceneNode* mAtmosphereNight; - - Ogre::SceneNode* mCloudNode; - - std::vector mObjects; - - Ogre::SceneNode* mParticleNode; - NifOgre::ObjectScenePtr mParticle; - - std::map mRainModels; float mRainTimer; - Ogre::Vector3 mStormDirection; + osg::Vec3f mStormDirection; // remember some settings so we don't have to apply them again if they didnt change - Ogre::String mClouds; - Ogre::String mNextClouds; + std::string mClouds; + std::string mNextClouds; float mCloudBlendFactor; - float mCloudOpacity; float mCloudSpeed; float mStarsOpacity; - Ogre::ColourValue mCloudColour; - Ogre::ColourValue mSkyColour; - Ogre::ColourValue mFogColour; + osg::Vec4f mCloudColour; + osg::Vec4f mSkyColour; + osg::Vec4f mFogColour; std::string mCurrentParticleEffect; - Ogre::Light* mLightning; - float mRemainingTransitionTime; - float mGlare; // target - float mGlareFade; // actual - bool mRainEnabled; std::string mRainEffect; float mRainSpeed; float mRainFrequency; + float mWindSpeed; bool mEnabled; bool mSunEnabled; - bool mMasserEnabled; - bool mSecundaEnabled; + + osg::Vec4f mMoonScriptColor; }; } diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 8ad2ea3213..ed1d8b6779 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -9,7 +9,8 @@ namespace MWRender { - TerrainStorage::TerrainStorage(bool preload) + TerrainStorage::TerrainStorage(const VFS::Manager* vfs, bool preload) + : ESMTerrain::Storage(vfs) { if (preload) { @@ -19,7 +20,7 @@ namespace MWRender MWWorld::Store::iterator it = esmStore.get().begin(); for (; it != esmStore.get().end(); ++it) { - ESM::Land* land = const_cast(&*it); // TODO: fix store interface + const ESM::Land* land = &*it; land->loadData(ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX); } } @@ -50,7 +51,7 @@ namespace MWRender maxY += 1; } - ESM::Land* TerrainStorage::getLand(int cellX, int cellY) + const ESM::Land* TerrainStorage::getLand(int cellX, int cellY) { const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -68,7 +69,7 @@ namespace MWRender { const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); - return esmStore.get().find(index, plugin); + return esmStore.get().search(index, plugin); } } diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index e6f4a04ad3..a12ffd540f 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -10,13 +10,13 @@ namespace MWRender class TerrainStorage : public ESMTerrain::Storage { private: - virtual ESM::Land* getLand (int cellX, int cellY); + virtual const ESM::Land* getLand (int cellX, int cellY); virtual const ESM::LandTexture* getLandTexture(int index, short plugin); public: ///@param preload Preload all Land records at startup? If using the multithreaded terrain component, this /// should be set to "true" in order to avoid race conditions. - TerrainStorage(bool preload); + TerrainStorage(const VFS::Manager* vfs, bool preload); /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp new file mode 100644 index 0000000000..e1af1c3393 --- /dev/null +++ b/apps/openmw/mwrender/util.cpp @@ -0,0 +1,31 @@ +#include "util.hpp" + +#include + +#include +#include +#include + +namespace MWRender +{ + +void overrideTexture(const std::string &texture, Resource::ResourceSystem *resourceSystem, osg::ref_ptr node) +{ + if (texture.empty()) + return; + std::string correctedTexture = Misc::ResourceHelpers::correctTexturePath(texture, resourceSystem->getVFS()); + // Not sure if wrap settings should be pulled from the overridden texture? + osg::ref_ptr tex = resourceSystem->getTextureManager()->getTexture2D(correctedTexture, osg::Texture2D::CLAMP, + osg::Texture2D::CLAMP); + osg::ref_ptr stateset; + if (node->getStateSet()) + stateset = static_cast(node->getStateSet()->clone(osg::CopyOp::SHALLOW_COPY)); + else + stateset = new osg::StateSet; + + stateset->setTextureAttribute(0, tex, osg::StateAttribute::OVERRIDE); + + node->setStateSet(stateset); +} + +} diff --git a/apps/openmw/mwrender/util.hpp b/apps/openmw/mwrender/util.hpp new file mode 100644 index 0000000000..d078f0d982 --- /dev/null +++ b/apps/openmw/mwrender/util.hpp @@ -0,0 +1,24 @@ +#ifndef OPENMW_MWRENDER_UTIL_H +#define OPENMW_MWRENDER_UTIL_H + +#include +#include + +namespace osg +{ + class Node; +} + +namespace Resource +{ + class ResourceSystem; +} + +namespace MWRender +{ + + void overrideTexture(const std::string& texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node); + +} + +#endif diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp new file mode 100644 index 0000000000..dd6e85e2ca --- /dev/null +++ b/apps/openmw/mwrender/vismask.hpp @@ -0,0 +1,45 @@ +#ifndef OPENMW_MWRENDER_VISMASK_H +#define OPENMW_MWRENDER_VISMASK_H + +namespace MWRender +{ + + /// Node masks used for controlling visibility of game objects. + enum VisMask + { + Mask_UpdateVisitor = 0x1, // reserved for separating UpdateVisitors from CullVisitors + + // child of Scene + Mask_Effect = (1<<1), + Mask_Debug = (1<<2), + Mask_Actor = (1<<3), + Mask_Player = (1<<4), + Mask_Sky = (1<<5), + Mask_Water = (1<<6), // choose Water or SimpleWater depending on detail required + Mask_SimpleWater = (1<<7), + Mask_Terrain = (1<<8), + Mask_FirstPerson = (1<<9), + + // child of Sky + Mask_Sun = (1<<10), + Mask_WeatherParticles = (1<<11), + + // child of Water + + // top level masks + Mask_Scene = (1<<12), + Mask_GUI = (1<<13), + + // Set on a Geode + Mask_ParticleSystem = (1<<14), + + // Set on cameras within the main scene graph + Mask_RenderToTexture = (1<<15), + + // Set on a camera's cull mask to enable the LightManager + Mask_Lighting = (1<<16) + }; + +} + +#endif diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index f2175ced57..cd1f4c5113 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -1,310 +1,672 @@ #include "water.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include -#include "sky.hpp" -#include "renderingmanager.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include + +#include + +#include "../mwworld/cellstore.hpp" +#include "../mwworld/fallback.hpp" + +#include "vismask.hpp" #include "ripplesimulation.hpp" -#include "refraction.hpp" +#include "renderbin.hpp" -#include -#include +namespace +{ -using namespace Ogre; + osg::ref_ptr createWaterGeometry(float size, int segments, float textureRepeats) + { + osg::ref_ptr verts (new osg::Vec3Array); + osg::ref_ptr texcoords (new osg::Vec2Array); + + // some drivers don't like huge triangles, so we do some subdivisons + // a paged solution would be even better + const float step = size/segments; + const float texCoordStep = textureRepeats / segments; + for (int x=0; xpush_back(osg::Vec3f(x1, y2, 0.f)); + verts->push_back(osg::Vec3f(x1, y1, 0.f)); + verts->push_back(osg::Vec3f(x2, y1, 0.f)); + verts->push_back(osg::Vec3f(x2, y2, 0.f)); + + float u1 = x*texCoordStep; + float v1 = y*texCoordStep; + float u2 = u1 + texCoordStep; + float v2 = v1 + texCoordStep; + + texcoords->push_back(osg::Vec2f(u1, v2)); + texcoords->push_back(osg::Vec2f(u1, v1)); + texcoords->push_back(osg::Vec2f(u2, v1)); + texcoords->push_back(osg::Vec2f(u2, v2)); + } + } + + osg::ref_ptr waterGeom (new osg::Geometry); + waterGeom->setVertexArray(verts); + waterGeom->setTexCoordArray(0, texcoords); + + osg::ref_ptr normal (new osg::Vec3Array); + normal->push_back(osg::Vec3f(0,0,1)); + waterGeom->setNormalArray(normal, osg::Array::BIND_OVERALL); + + waterGeom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,verts->size())); + return waterGeom; + } + +} namespace MWRender { -CubeReflection::CubeReflection(Ogre::SceneManager* sceneManager) - : Reflection(sceneManager) +// -------------------------------------------------------------------------------------------------------------------------------- + +/// @brief Allows to cull and clip meshes that are below a plane. Useful for reflection & refraction camera effects. +/// Also handles flipping of the plane when the eye point goes below it. +/// To use, simply create the scene as subgraph of this node, then do setPlane(const osg::Plane& plane); +class ClipCullNode : public osg::Group { - Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton ().createManual("CubeReflection", - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_CUBE_MAP, - 512,512, 0, PF_R8G8B8, TU_RENDERTARGET); - - mCamera = mSceneMgr->createCamera ("CubeCamera"); - mCamera->setNearClipDistance (5); - mCamera->setFarClipDistance (1000); - - for (int face = 0; face < 6; ++face) + class PlaneCullCallback : public osg::NodeCallback { - mRenderTargets[face] = texture->getBuffer (face)->getRenderTarget(); - mRenderTargets[face]->removeAllViewports (); - Viewport* vp = mRenderTargets[face]->addViewport (mCamera); - vp->setOverlaysEnabled(false); - vp->setShadowsEnabled(false); - vp->setMaterialScheme ("water_reflection"); - mRenderTargets[face]->setAutoUpdated(false); - - /* - Vector3 lookAt(0,0,0), up(0,0,0), right(0,0,0); - switch(face) + public: + /// @param cullPlane The culling plane (in world space). + PlaneCullCallback(const osg::Plane* cullPlane) + : osg::NodeCallback() + , mCullPlane(cullPlane) { - case 0: lookAt.x =-1; up.y = 1; right.z = 1; break; // +X - case 1: lookAt.x = 1; up.y = 1; right.z =-1; break; // -X - case 2: lookAt.y =-1; up.z = 1; right.x = 1; break; // +Y - case 3: lookAt.y = 1; up.z =-1; right.x = 1; break; // -Y - case 4: lookAt.z = 1; up.y = 1; right.x =-1; break; // +Z - case 5: lookAt.z =-1; up.y = 1; right.x =-1; break; // -Z } - Quaternion orient(right, up, lookAt); - mCamera->setOrientation(orient); - */ - } -} -CubeReflection::~CubeReflection () -{ - Ogre::TextureManager::getSingleton ().remove("CubeReflection"); - mSceneMgr->destroyCamera (mCamera); -} + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); -void CubeReflection::update () -{ - if (mParentCamera->isAttached()) - mParentCamera->getParentSceneNode ()->needUpdate (); - mCamera->setPosition(mParentCamera->getDerivedPosition()); -} + osg::Polytope::PlaneList origPlaneList = cv->getProjectionCullingStack().back().getFrustum().getPlaneList(); -// -------------------------------------------------------------------------------------------------------------------------------- + osg::Plane plane = *mCullPlane; + plane.transform(*cv->getCurrentRenderStage()->getInitialViewMatrix()); -PlaneReflection::PlaneReflection(Ogre::SceneManager* sceneManager, SkyManager* sky) - : Reflection(sceneManager) - , mSky(sky) - , mRenderActive(false) -{ - mCamera = mSceneMgr->createCamera ("PlaneReflectionCamera"); - mSceneMgr->addRenderQueueListener(this); + osg::Vec3d eyePoint = cv->getEyePoint(); + if (mCullPlane->intersect(osg::BoundingSphere(osg::Vec3d(0,0,eyePoint.z()), 0)) > 0) + plane.flip(); - mTexture = TextureManager::getSingleton().createManual("WaterReflection", - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, 512, 512, 0, PF_R8G8B8, TU_RENDERTARGET); + cv->getProjectionCullingStack().back().getFrustum().add(plane); - mRenderTarget = mTexture->getBuffer()->getRenderTarget(); - Viewport* vp = mRenderTarget->addViewport(mCamera); - vp->setOverlaysEnabled(false); - vp->setBackgroundColour(ColourValue(0.8f, 0.9f, 1.0f)); - vp->setShadowsEnabled(false); - vp->setMaterialScheme("water_reflection"); - mRenderTarget->addListener(this); - mRenderTarget->setActive(true); - mRenderTarget->setAutoUpdated(true); + traverse(node, nv); - sh::Factory::getInstance ().setTextureAlias ("WaterReflection", mTexture->getName()); -} + // undo + cv->getProjectionCullingStack().back().getFrustum().set(origPlaneList); + } -PlaneReflection::~PlaneReflection () -{ - mRenderTarget->removeListener (this); - mSceneMgr->destroyCamera (mCamera); - mSceneMgr->removeRenderQueueListener(this); - TextureManager::getSingleton ().remove("WaterReflection"); -} + private: + const osg::Plane* mCullPlane; + }; -void PlaneReflection::renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation) -{ - // We don't want the sky to get clipped by custom near clip plane (the water plane) - if (queueGroupId < 20 && mRenderActive) + class FlipCallback : public osg::NodeCallback { - mCamera->disableCustomNearClipPlane(); - Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mCamera->getProjectionMatrixRS()); - } -} + public: + FlipCallback(const osg::Plane* cullPlane) + : mCullPlane(cullPlane) + { + } -void PlaneReflection::renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation) -{ - if (queueGroupId < 20 && mRenderActive) + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); + osg::Vec3d eyePoint = cv->getEyePoint(); + + osg::RefMatrix* modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); + + // apply the height of the plane + // we can't apply this height in the addClipPlane() since the "flip the below graph" function would otherwise flip the height as well + modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * ((*mCullPlane)[3] * -1)); + + // flip the below graph if the eye point is above the plane + if (mCullPlane->intersect(osg::BoundingSphere(osg::Vec3d(0,0,eyePoint.z()), 0)) > 0) + { + modelViewMatrix->preMultScale(osg::Vec3(1,1,-1)); + } + + // move the plane back along its normal a little bit to prevent bleeding at the water shore + const float clipFudge = -5; + modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * clipFudge); + + cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); + traverse(node, nv); + cv->popModelViewMatrix(); + } + + private: + const osg::Plane* mCullPlane; + }; + +public: + ClipCullNode() { - mCamera->enableCustomNearClipPlane(mIsUnderwater ? mErrorPlaneUnderwater : mErrorPlane); - Root::getSingleton().getRenderSystem()->_setProjectionMatrix(mCamera->getProjectionMatrixRS()); + addCullCallback (new PlaneCullCallback(&mPlane)); + + mClipNodeTransform = new osg::Group; + mClipNodeTransform->addCullCallback(new FlipCallback(&mPlane)); + addChild(mClipNodeTransform); + + mClipNode = new osg::ClipNode; + + mClipNodeTransform->addChild(mClipNode); } + + void setPlane (const osg::Plane& plane) + { + if (plane == mPlane) + return; + mPlane = plane; + + mClipNode->getClipPlaneList().clear(); + mClipNode->addClipPlane(new osg::ClipPlane(0, osg::Plane(mPlane.getNormal(), 0))); // mPlane.d() applied in FlipCallback + mClipNode->setStateSetModes(*getOrCreateStateSet(), osg::StateAttribute::ON); + mClipNode->setCullingActive(false); + } + +private: + osg::ref_ptr mClipNodeTransform; + osg::ref_ptr mClipNode; + + osg::Plane mPlane; +}; + +// Node callback to entirely skip the traversal. +class NoTraverseCallback : public osg::NodeCallback +{ +public: + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + // no traverse() + } +}; + +/// Moves water mesh away from the camera slightly if the camera gets too close on the Z axis. +/// The offset works around graphics artifacts that occured with the GL_DEPTH_CLAMP when the camera gets extremely close to the mesh (seen on NVIDIA at least). +/// Must be added as a Cull callback. +class FudgeCallback : public osg::NodeCallback +{ +public: + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); + + const float fudge = 0.2; + if (std::abs(cv->getEyeLocal().z()) < fudge) + { + float diff = fudge - cv->getEyeLocal().z(); + osg::RefMatrix* modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); + + if (cv->getEyeLocal().z() > 0) + modelViewMatrix->preMultTranslate(osg::Vec3f(0,0,-diff)); + else + modelViewMatrix->preMultTranslate(osg::Vec3f(0,0,diff)); + + cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); + traverse(node, nv); + cv->popModelViewMatrix(); + } + else + traverse(node, nv); + } +}; + +osg::ref_ptr readShader (osg::Shader::Type type, const std::string& file, const std::map& defineMap = std::map()) +{ + osg::ref_ptr shader (new osg::Shader(type)); + + // use boost in favor of osg::Shader::readShaderFile, to handle utf-8 path issues on Windows + boost::filesystem::ifstream inStream; + inStream.open(boost::filesystem::path(file)); + std::stringstream strstream; + strstream << inStream.rdbuf(); + + std::string shaderSource = strstream.str(); + + for (std::map::const_iterator it = defineMap.begin(); it != defineMap.end(); ++it) + { + size_t pos = shaderSource.find(it->first); + if (pos != std::string::npos) + shaderSource.replace(pos, it->first.length(), it->second); + } + + shader->setShaderSource(shaderSource); + return shader; } -void PlaneReflection::preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt) +osg::ref_ptr readPngImage (const std::string& file) { - if (mParentCamera->isAttached()) - mParentCamera->getParentSceneNode ()->needUpdate (); - mCamera->setOrientation(mParentCamera->getDerivedOrientation()); - mCamera->setPosition(mParentCamera->getDerivedPosition()); - mCamera->setNearClipDistance(mParentCamera->getNearClipDistance()); - mCamera->setFarClipDistance(mParentCamera->getFarClipDistance()); - mCamera->setAspectRatio(mParentCamera->getAspectRatio()); - mCamera->setFOVy(mParentCamera->getFOVy()); - mRenderActive = true; + // use boost in favor of osgDB::readImage, to handle utf-8 path issues on Windows + boost::filesystem::ifstream inStream; + inStream.open(file, std::ios_base::in | std::ios_base::binary); + if (inStream.fail()) + std::cerr << "Failed to open " << file << std::endl; + osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png"); + if (!reader) + { + std::cerr << "Failed to read " << file << ", no png readerwriter found" << std::endl; + return osg::ref_ptr(); + } + osgDB::ReaderWriter::ReadResult result = reader->readImage(inStream); + if (!result.success()) + std::cerr << "Failed to read " << file << ": " << result.message() << " code " << result.status() << std::endl; - mCamera->enableReflection(mWaterPlane); - - // for depth calculation, we want the original viewproj matrix _without_ the custom near clip plane. - // since all we are interested in is depth, we only need the third row of the matrix. - Ogre::Matrix4 projMatrix = mCamera->getProjectionMatrixWithRSDepth () * mCamera->getViewMatrix (); - sh::Vector4* row3 = new sh::Vector4(projMatrix[2][0], projMatrix[2][1], projMatrix[2][2], projMatrix[2][3]); - sh::Factory::getInstance ().setSharedParameter ("vpRow2Fix", sh::makeProperty (row3)); - - // enable clip plane here to take advantage of CPU culling for overwater or underwater objects - mCamera->enableCustomNearClipPlane(mIsUnderwater ? mErrorPlaneUnderwater : mErrorPlane); + return result.getImage(); } -void PlaneReflection::postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt) + +class Refraction : public osg::Camera { - mCamera->disableReflection(); - mCamera->disableCustomNearClipPlane(); - mRenderActive = false; -} +public: + Refraction() + { + unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); + setRenderOrder(osg::Camera::PRE_RENDER); + setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + setReferenceFrame(osg::Camera::RELATIVE_RF); -void PlaneReflection::setHeight (float height) + setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); + setNodeMask(Mask_RenderToTexture); + setViewport(0, 0, rttSize, rttSize); + + // No need for Update traversal since the scene is already updated as part of the main scene graph + // A double update would mess with the light collection (in addition to being plain redundant) + setUpdateCallback(new NoTraverseCallback); + + // No need for fog here, we are already applying fog on the water surface itself as well as underwater fog + getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + + mClipCullNode = new ClipCullNode; + addChild(mClipCullNode); + + mRefractionTexture = new osg::Texture2D; + mRefractionTexture->setTextureSize(rttSize, rttSize); + mRefractionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mRefractionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mRefractionTexture->setInternalFormat(GL_RGB); + mRefractionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mRefractionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + + attach(osg::Camera::COLOR_BUFFER, mRefractionTexture); + + mRefractionDepthTexture = new osg::Texture2D; + mRefractionDepthTexture->setSourceFormat(GL_DEPTH_COMPONENT); + mRefractionDepthTexture->setInternalFormat(GL_DEPTH_COMPONENT24); + mRefractionDepthTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mRefractionDepthTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mRefractionDepthTexture->setSourceType(GL_UNSIGNED_INT); + mRefractionDepthTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mRefractionDepthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + + attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture); + } + + void setScene(osg::Node* scene) + { + if (mScene) + mClipCullNode->removeChild(mScene); + mScene = scene; + mClipCullNode->addChild(scene); + } + + void setWaterLevel(float waterLevel) + { + mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0,0,-1), osg::Vec3d(0,0, waterLevel))); + } + + osg::Texture2D* getRefractionTexture() const + { + return mRefractionTexture.get(); + } + + osg::Texture2D* getRefractionDepthTexture() const + { + return mRefractionDepthTexture.get(); + } + +private: + osg::ref_ptr mClipCullNode; + osg::ref_ptr mRefractionTexture; + osg::ref_ptr mRefractionDepthTexture; + osg::ref_ptr mScene; +}; + +class Reflection : public osg::Camera { - mWaterPlane = Plane(Ogre::Vector3(0,0,1), height); - mErrorPlane = Plane(Ogre::Vector3(0,0,1), height - 5); - mErrorPlaneUnderwater = Plane(Ogre::Vector3(0,0,-1), -height - 5); -} +public: + Reflection() + { + setRenderOrder(osg::Camera::PRE_RENDER); + setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + setReferenceFrame(osg::Camera::RELATIVE_RF); -void PlaneReflection::setActive (bool active) + setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting); + setNodeMask(Mask_RenderToTexture); + + unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); + setViewport(0, 0, rttSize, rttSize); + + // No need for Update traversal since the mSceneRoot is already updated as part of the main scene graph + // A double update would mess with the light collection (in addition to being plain redundant) + setUpdateCallback(new NoTraverseCallback); + + mReflectionTexture = new osg::Texture2D; + mReflectionTexture->setInternalFormat(GL_RGB); + mReflectionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mReflectionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mReflectionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mReflectionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + attach(osg::Camera::COLOR_BUFFER, mReflectionTexture); + + // XXX: should really flip the FrontFace on each renderable instead of forcing clockwise. + osg::ref_ptr frontFace (new osg::FrontFace); + frontFace->setMode(osg::FrontFace::CLOCKWISE); + getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON); + + mClipCullNode = new ClipCullNode; + addChild(mClipCullNode); + } + + void setWaterLevel(float waterLevel) + { + setViewMatrix(osg::Matrix::translate(0,0,-waterLevel) * osg::Matrix::scale(1,1,-1) * osg::Matrix::translate(0,0,waterLevel)); + + mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0,0,1), osg::Vec3d(0,0,waterLevel))); + } + + void setScene(osg::Node* scene) + { + if (mScene) + mClipCullNode->removeChild(mScene); + mScene = scene; + mClipCullNode->addChild(scene); + } + + osg::Texture2D* getReflectionTexture() const + { + return mReflectionTexture.get(); + } + +private: + osg::ref_ptr mReflectionTexture; + osg::ref_ptr mClipCullNode; + osg::ref_ptr mScene; +}; + +/// DepthClampCallback enables GL_DEPTH_CLAMP for the current draw, if supported. +class DepthClampCallback : public osg::Drawable::DrawCallback { - mRenderTarget->setActive(active); -} +public: + virtual void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* drawable) const + { + static bool supported = osg::isGLExtensionOrVersionSupported(renderInfo.getState()->getContextID(), "GL_ARB_depth_clamp", 3.3); + if (!supported) + { + drawable->drawImplementation(renderInfo); + return; + } -void PlaneReflection::setViewportBackground(Ogre::ColourValue colour) + glEnable(GL_DEPTH_CLAMP); + + drawable->drawImplementation(renderInfo); + + // restore default + glDisable(GL_DEPTH_CLAMP); + } +}; + +Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem *resourceSystem, osgUtil::IncrementalCompileOperation *ico, + const MWWorld::Fallback* fallback, const std::string& resourcePath) + : mParent(parent) + , mSceneRoot(sceneRoot) + , mResourceSystem(resourceSystem) + , mFallback(fallback) + , mResourcePath(resourcePath) + , mEnabled(true) + , mToggled(true) + , mTop(0) { - mRenderTarget->getViewport (0)->setBackgroundColour (colour); -} + mSimulation.reset(new RippleSimulation(parent, resourceSystem, fallback)); -void PlaneReflection::setVisibilityMask (int flags) -{ - mRenderTarget->getViewport (0)->setVisibilityMask (flags); -} + osg::ref_ptr waterGeom = createWaterGeometry(CELL_SIZE*150, 40, 900); + waterGeom->setDrawCallback(new DepthClampCallback); -// -------------------------------------------------------------------------------------------------------------------------------- + mWaterGeode = new osg::Geode; + mWaterGeode->addDrawable(waterGeom); + mWaterGeode->setNodeMask(Mask_Water); -Water::Water (Ogre::Camera *camera, RenderingManager* rend, const MWWorld::Fallback* fallback) : - mCamera (camera), mSceneMgr (camera->getSceneManager()), - mIsUnderwater(false), mActive(1), - mToggled(1), mWaterTimer(0.f), - mRendering(rend), - mVisibilityFlags(0), - mReflection(NULL), - mRefraction(NULL), - mSimulation(NULL), - mPlayer(0,0) -{ - mSimulation = new RippleSimulation(mSceneMgr, fallback); + if (ico) + ico->add(mWaterGeode); - mSky = rend->getSkyManager(); + mWaterNode = new osg::PositionAttitudeTransform; + mWaterNode->addChild(mWaterGeode); + mWaterNode->addCullCallback(new FudgeCallback); - mMaterial = MaterialManager::getSingleton().getByName("Water"); + // simple water fallback for the local map + osg::ref_ptr geode2 (osg::clone(mWaterGeode.get(), osg::CopyOp::DEEP_COPY_NODES)); + createSimpleWaterStateSet(geode2, mFallback->getFallbackFloat("Water_Map_Alpha")); + geode2->setNodeMask(Mask_SimpleWater); + mWaterNode->addChild(geode2); - mTop = 0; - - mIsUnderwater = false; - - mWaterPlane = Plane(Vector3::UNIT_Z, 0); - - int waterScale = 30; - - MeshManager::getSingleton().createPlane("water", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane, - static_cast(CELL_SIZE*5*waterScale), static_cast(CELL_SIZE*5*waterScale), - 40, 40, true, 1, static_cast(3 * waterScale), static_cast(3 * waterScale), Vector3::UNIT_Y); - - mWater = mSceneMgr->createEntity("water"); - mWater->setVisibilityFlags(RV_Water); - mWater->setCastShadows(false); - mWater->setRenderQueueGroup(RQG_Alpha); - - mWaterNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - - mWaterNode->attachObject(mWater); - - applyRTT(); - applyVisibilityMask(); - - mWater->setMaterial(mMaterial); + mSceneRoot->addChild(mWaterNode); setHeight(mTop); - sh::MaterialInstance* m = sh::Factory::getInstance ().getMaterialInstance ("Water"); - m->setListener (this); - - // ---------------------------------------------------------------------------------------------- - // ---------------------------------- reflection debug overlay ---------------------------------- - // ---------------------------------------------------------------------------------------------- -/* - if (Settings::Manager::getBool("shader", "Water")) - { - OverlayManager& mgr = OverlayManager::getSingleton(); - Overlay* overlay; - // destroy if already exists - if ((overlay = mgr.getByName("ReflectionDebugOverlay"))) - mgr.destroy(overlay); - - overlay = mgr.create("ReflectionDebugOverlay"); - - if (MaterialManager::getSingleton().resourceExists("Ogre/ReflectionDebugTexture")) - MaterialManager::getSingleton().remove("Ogre/ReflectionDebugTexture"); - MaterialPtr debugMat = MaterialManager::getSingleton().create( - "Ogre/ReflectionDebugTexture", - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - - debugMat->getTechnique(0)->getPass(0)->setLightingEnabled(false); - debugMat->getTechnique(0)->getPass(0)->createTextureUnitState(mReflectionTexture->getName()); - - OverlayContainer* debugPanel; - - // destroy container if exists - try - { - if ((debugPanel = - static_cast( - mgr.getOverlayElement("Ogre/ReflectionDebugTexPanel" - )))) - mgr.destroyOverlayElement(debugPanel); - } - catch (Ogre::Exception&) {} - - debugPanel = (OverlayContainer*) - (OverlayManager::getSingleton().createOverlayElement("Panel", "Ogre/ReflectionDebugTexPanel")); - debugPanel->_setPosition(0, 0.55); - debugPanel->_setDimensions(0.3, 0.3); - debugPanel->setMaterialName(debugMat->getName()); - debugPanel->show(); - overlay->add2D(debugPanel); - overlay->show(); - } -*/ + updateWaterMaterial(); } -void Water::setActive(bool active) +void Water::updateWaterMaterial() { - mActive = active; - updateVisible(); + if (mReflection) + { + mParent->removeChild(mReflection); + mReflection = NULL; + } + if (mRefraction) + { + mParent->removeChild(mRefraction); + mRefraction = NULL; + } - sh::Factory::getInstance ().setSharedParameter ("waterEnabled", sh::makeProperty (new sh::FloatValue(active ? 1.0f : 0.0f))); + if (Settings::Manager::getBool("shader", "Water")) + { + mReflection = new Reflection; + mReflection->setWaterLevel(mTop); + mReflection->setScene(mSceneRoot); + mParent->addChild(mReflection); + + if (Settings::Manager::getBool("refraction", "Water")) + { + mRefraction = new Refraction; + mRefraction->setWaterLevel(mTop); + mRefraction->setScene(mSceneRoot); + mParent->addChild(mRefraction); + } + + createShaderWaterStateSet(mWaterGeode, mReflection, mRefraction); + } + else + createSimpleWaterStateSet(mWaterGeode, mFallback->getFallbackFloat("Water_World_Alpha")); + + updateVisible(); +} + +void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) +{ + osg::ref_ptr stateset (new osg::StateSet); + + osg::ref_ptr material (new osg::Material); + material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, alpha)); + material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); + material->setColorMode(osg::Material::OFF); + stateset->setAttributeAndModes(material, osg::StateAttribute::ON); + + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + + osg::ref_ptr depth (new osg::Depth); + depth->setWriteMask(false); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + + stateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); + + node->setStateSet(stateset); + + std::vector > textures; + int frameCount = mFallback->getFallbackInt("Water_SurfaceFrameCount"); + std::string texture = mFallback->getFallbackString("Water_SurfaceTexture"); + for (int i=0; igetTextureManager()->getTexture2D(texname.str(), osg::Texture::REPEAT, osg::Texture::REPEAT)); + } + + if (!textures.size()) + return; + + float fps = mFallback->getFallbackFloat("Water_SurfaceFPS"); + + osg::ref_ptr controller (new NifOsg::FlipController(0, 1.f/fps, textures)); + controller->setSource(boost::shared_ptr(new SceneUtil::FrameTimeSource)); + node->setUpdateCallback(controller); + + stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); +} + +void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, Refraction* refraction) +{ + // use a define map to conditionally compile the shader + std::map defineMap; + defineMap.insert(std::make_pair(std::string("@refraction_enabled"), std::string(refraction ? "1" : "0"))); + + osg::ref_ptr vertexShader (readShader(osg::Shader::VERTEX, mResourcePath + "/shaders/water_vertex.glsl", defineMap)); + osg::ref_ptr fragmentShader (readShader(osg::Shader::FRAGMENT, mResourcePath + "/shaders/water_fragment.glsl", defineMap)); + + osg::ref_ptr normalMap (new osg::Texture2D(readPngImage(mResourcePath + "/shaders/water_nm.png"))); + normalMap->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + normalMap->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + normalMap->setMaxAnisotropy(16); + normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); + normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + normalMap->getImage()->flipVertical(); + + osg::ref_ptr shaderStateset = new osg::StateSet; + shaderStateset->addUniform(new osg::Uniform("normalMap", 0)); + shaderStateset->addUniform(new osg::Uniform("reflectionMap", 1)); + + shaderStateset->setTextureAttributeAndModes(0, normalMap, osg::StateAttribute::ON); + shaderStateset->setTextureAttributeAndModes(1, reflection->getReflectionTexture(), osg::StateAttribute::ON); + if (refraction) + { + shaderStateset->setTextureAttributeAndModes(2, refraction->getRefractionTexture(), osg::StateAttribute::ON); + shaderStateset->setTextureAttributeAndModes(3, refraction->getRefractionDepthTexture(), osg::StateAttribute::ON); + shaderStateset->addUniform(new osg::Uniform("refractionMap", 2)); + shaderStateset->addUniform(new osg::Uniform("refractionDepthMap", 3)); + shaderStateset->setRenderBinDetails(MWRender::RenderBin_Default, "RenderBin"); + } + else + { + shaderStateset->setMode(GL_BLEND, osg::StateAttribute::ON); + + shaderStateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); + + osg::ref_ptr depth (new osg::Depth); + depth->setWriteMask(false); + shaderStateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + + shaderStateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + + osg::ref_ptr program (new osg::Program); + program->addShader(vertexShader); + program->addShader(fragmentShader); + shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON); + + node->setStateSet(shaderStateset); + node->setUpdateCallback(NULL); +} + +void Water::processChangedSettings(const Settings::CategorySettingVector& settings) +{ + updateWaterMaterial(); } Water::~Water() { - MeshManager::getSingleton().remove("water"); + mParent->removeChild(mWaterNode); - mWaterNode->detachObject(mWater); - mSceneMgr->destroyEntity(mWater); - mSceneMgr->destroySceneNode(mWaterNode); - - delete mReflection; - delete mRefraction; - delete mSimulation; + if (mReflection) + { + mParent->removeChild(mReflection); + mReflection = NULL; + } + if (mRefraction) + { + mParent->removeChild(mRefraction); + mRefraction = NULL; + } } -void Water::changeCell(const ESM::Cell* cell) +void Water::setEnabled(bool enabled) { - if(cell->isExterior()) - mWaterNode->setPosition(getSceneNodeCoordinates(cell->mData.mX, cell->mData.mY)); + mEnabled = enabled; + updateVisible(); +} + +void Water::changeCell(const MWWorld::CellStore* store) +{ + if (store->getCell()->isExterior()) + mWaterNode->setPosition(getSceneNodeCoordinates(store->getCell()->mData.mX, store->getCell()->mData.mY)); + else + mWaterNode->setPosition(osg::Vec3f(0,0,mTop)); + + // create a new StateSet to prevent threading issues + osg::ref_ptr nodeStateSet (new osg::StateSet); + nodeStateSet->addUniform(new osg::Uniform("nodePosition", osg::Vec3f(mWaterNode->getPosition()))); + mWaterNode->setStateSet(nodeStateSet); } void Water::setHeight(const float height) @@ -313,15 +675,29 @@ void Water::setHeight(const float height) mSimulation->setWaterHeight(height); - mWaterPlane = Plane(Vector3::UNIT_Z, -height); + osg::Vec3f pos = mWaterNode->getPosition(); + pos.z() = height; + mWaterNode->setPosition(pos); if (mReflection) - mReflection->setHeight(height); + mReflection->setWaterLevel(mTop); if (mRefraction) - mRefraction->setHeight(height); + mRefraction->setWaterLevel(mTop); +} - mWaterNode->setPosition(0, 0, height); - sh::Factory::getInstance ().setSharedParameter ("waterLevel", sh::makeProperty(new sh::FloatValue(height))); +void Water::update(float dt) +{ + mSimulation->update(dt); +} + +void Water::updateVisible() +{ + bool visible = mEnabled && mToggled; + mWaterNode->setNodeMask(visible ? ~0 : 0); + if (mRefraction) + mRefraction->setNodeMask(visible ? Mask_RenderToTexture : 0); + if (mReflection) + mReflection->setNodeMask(visible ? Mask_RenderToTexture : 0); } bool Water::toggle() @@ -331,159 +707,14 @@ bool Water::toggle() return mToggled; } -void -Water::updateUnderwater(bool underwater) +bool Water::isUnderwater(const osg::Vec3f &pos) const { - if (!mActive) { - return; - } - mIsUnderwater = - underwater && - mWater->isVisible() && - mCamera->getPolygonMode() == Ogre::PM_SOLID; - - if (mReflection) - mReflection->setUnderwater (mIsUnderwater); - if (mRefraction) - mRefraction->setUnderwater (mIsUnderwater); - - updateVisible(); + return pos.z() < mTop && mToggled && mEnabled; } -Vector3 Water::getSceneNodeCoordinates(int gridX, int gridY) +osg::Vec3f Water::getSceneNodeCoordinates(int gridX, int gridY) { - return Vector3(static_cast(gridX * CELL_SIZE + (CELL_SIZE / 2)), static_cast(gridY * CELL_SIZE + (CELL_SIZE / 2)), mTop); -} - -void Water::setViewportBackground(const ColourValue& bg) -{ - if (mReflection) - mReflection->setViewportBackground(bg); -} - -void Water::updateVisible() -{ - mWater->setVisible(mToggled && mActive); - if (mReflection) - mReflection->setActive(mToggled && mActive); - if (mRefraction) - mRefraction->setActive(mToggled && mActive); -} - -void Water::update(float dt, Ogre::Vector3 player) -{ - mWaterTimer += dt; - sh::Factory::getInstance ().setSharedParameter ("waterTimer", sh::makeProperty(new sh::FloatValue(mWaterTimer))); - - mRendering->getSkyManager ()->setGlareEnabled (!mIsUnderwater); - - mPlayer = Ogre::Vector2(player.x, player.y); -} - -void Water::frameStarted(float dt) -{ - if (!mActive) - return; - - mSimulation->update(dt, mPlayer); - - if (mReflection) - { - mReflection->update(); - } -} - -void Water::applyRTT() -{ - delete mReflection; - mReflection = NULL; - delete mRefraction; - mRefraction = NULL; - - // Create rendertarget for reflection - //int rttsize = Settings::Manager::getInt("rtt size", "Water"); - - if (Settings::Manager::getBool("shader", "Water")) - { - mReflection = new PlaneReflection(mSceneMgr, mSky); - mReflection->setParentCamera (mCamera); - mReflection->setHeight(mTop); - - if (Settings::Manager::getBool("refraction", "Water")) - { - mRefraction = new Refraction(mCamera); - mRefraction->setHeight(mTop); - } - } - - updateVisible(); -} - -void Water::applyVisibilityMask() -{ - mVisibilityFlags = RV_Terrain * Settings::Manager::getBool("reflect terrain", "Water") - + (RV_Statics + RV_StaticsSmall + RV_Misc) * Settings::Manager::getBool("reflect statics", "Water") - + RV_Actors * Settings::Manager::getBool("reflect actors", "Water") - + RV_Effects - + RV_Sky; - - if (mReflection) - mReflection->setVisibilityMask(mVisibilityFlags); -} - -void Water::processChangedSettings(const Settings::CategorySettingVector& settings) -{ - bool applyRT = false; - bool applyVisMask = false; - for (Settings::CategorySettingVector::const_iterator it=settings.begin(); - it != settings.end(); ++it) - { - if ( it->first == "Water" && ( - it->second == "shader" - || it->second == "refraction" - || it->second == "rtt size")) - applyRT = true; - - if ( it->first == "Water" && ( - it->second == "reflect actors" - || it->second == "reflect terrain" - || it->second == "reflect statics")) - applyVisMask = true; - } - - if(applyRT) - { - applyRTT(); - applyVisibilityMask(); - mWater->setMaterial(mMaterial); - } - if (applyVisMask) - applyVisibilityMask(); -} - -void Water::requestedConfiguration (sh::MaterialInstance* m, const std::string& configuration) -{ -} - -void Water::createdConfiguration (sh::MaterialInstance* m, const std::string& configuration) -{ - if (configuration == "local_map" || !Settings::Manager::getBool("shader", "Water")) - { - // for simple water, set animated texture names - // these have to be set in code - std::string textureNames[32]; - for (int i=0; i<32; ++i) - { - textureNames[i] = "textures\\water\\water" + StringConverter::toString(i, 2, '0') + ".dds"; - } - - Ogre::Technique* t = static_cast(m->getMaterial())->getOgreTechniqueForConfiguration(configuration); - if (t->getPass(0)->getNumTextureUnitStates () == 0) - return; - t->getPass(0)->getTextureUnitState(0)->setAnimatedTextureName(textureNames, 32, 2); - t->getPass(0)->setDepthWriteEnabled (false); - t->getPass(0)->setSceneBlending (Ogre::SBT_TRANSPARENT_ALPHA); - } + return osg::Vec3f(static_cast(gridX * CELL_SIZE + (CELL_SIZE / 2)), static_cast(gridY * CELL_SIZE + (CELL_SIZE / 2)), mTop); } void Water::addEmitter (const MWWorld::Ptr& ptr, float scale, float force) @@ -501,9 +732,19 @@ void Water::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) mSimulation->updateEmitterPtr(old, ptr); } +void Water::emitRipple(const osg::Vec3f &pos) +{ + mSimulation->emitRipple(pos); +} + +void Water::removeCell(const MWWorld::CellStore *store) +{ + mSimulation->removeCell(store); +} + void Water::clearRipples() { mSimulation->clear(); } -} // namespace +} diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 53d54cdc92..b26782873f 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -1,183 +1,108 @@ -#ifndef GAME_MWRENDER_WATER_H -#define GAME_MWRENDER_WATER_H +#ifndef OPENMW_MWRENDER_WATER_H +#define OPENMW_MWRENDER_WATER_H -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include -#include #include -#include - - -#include "renderconst.hpp" - -#include "../mwworld/ptr.hpp" - -namespace Ogre +namespace osg { - class Camera; - class SceneManager; - class SceneNode; - class Entity; - class Vector3; - class Rectangle2D; - struct RenderTargetEvent; + class Group; + class PositionAttitudeTransform; + class Geode; + class Node; +} + +namespace osgUtil +{ + class IncrementalCompileOperation; +} + +namespace Resource +{ + class ResourceSystem; } namespace MWWorld { class Fallback; + class CellStore; + class Ptr; } -namespace MWRender { +namespace MWRender +{ - class SkyManager; - class RenderingManager; - class RippleSimulation; class Refraction; - - class Reflection - { - public: - Reflection(Ogre::SceneManager* sceneManager) - : mCamera(NULL) - , mParentCamera(NULL) - , mSceneMgr(sceneManager) - , mIsUnderwater(false) - {} - virtual ~Reflection() {} - - virtual void setHeight (float height) {} - virtual void setParentCamera (Ogre::Camera* parent) { mParentCamera = parent; } - void setUnderwater(bool underwater) { mIsUnderwater = underwater; } - virtual void setActive (bool active) {} - virtual void setViewportBackground(Ogre::ColourValue colour) {} - virtual void update() {} - virtual void setVisibilityMask (int flags) {} - - protected: - Ogre::Camera* mCamera; - Ogre::Camera* mParentCamera; - Ogre::TexturePtr mTexture; - Ogre::SceneManager* mSceneMgr; - bool mIsUnderwater; - }; - - class CubeReflection : public Reflection - { - public: - CubeReflection(Ogre::SceneManager* sceneManager); - virtual ~CubeReflection(); - - virtual void update(); - protected: - Ogre::RenderTarget* mRenderTargets[6]; - }; - - class PlaneReflection : public Reflection, public Ogre::RenderQueueListener, public Ogre::RenderTargetListener - { - public: - PlaneReflection(Ogre::SceneManager* sceneManager, SkyManager* sky); - virtual ~PlaneReflection(); - - virtual void setHeight (float height); - virtual void setActive (bool active); - virtual void setVisibilityMask (int flags); - - void preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); - void postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt); - - void renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation); - void renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation); - - virtual void setViewportBackground(Ogre::ColourValue colour); - - protected: - Ogre::RenderTarget* mRenderTarget; - SkyManager* mSky; - Ogre::Plane mWaterPlane; - Ogre::Plane mErrorPlane; - Ogre::Plane mErrorPlaneUnderwater; - bool mRenderActive; - }; + class Reflection; + class RippleSimulation; /// Water rendering - class Water : public sh::MaterialInstanceListener + class Water { static const int CELL_SIZE = 8192; - Ogre::Camera *mCamera; - Ogre::SceneManager *mSceneMgr; - Ogre::Plane mWaterPlane; + osg::ref_ptr mParent; + osg::ref_ptr mSceneRoot; + osg::ref_ptr mWaterNode; + osg::ref_ptr mWaterGeode; + Resource::ResourceSystem* mResourceSystem; + const MWWorld::Fallback* mFallback; + osg::ref_ptr mIncrementalCompileOperation; - Ogre::SceneNode *mWaterNode; - Ogre::Entity *mWater; + std::auto_ptr mSimulation; - bool mIsUnderwater; - bool mActive; + osg::ref_ptr mRefraction; + osg::ref_ptr mReflection; + + const std::string mResourcePath; + + bool mEnabled; bool mToggled; float mTop; - float mWaterTimer; - - - Ogre::Vector3 getSceneNodeCoordinates(int gridX, int gridY); - - protected: - void applyRTT(); - void applyVisibilityMask(); - + osg::Vec3f getSceneNodeCoordinates(int gridX, int gridY); void updateVisible(); - RenderingManager* mRendering; - SkyManager* mSky; + void createSimpleWaterStateSet(osg::Node* node, float alpha); - Ogre::MaterialPtr mMaterial; + /// @param reflection the reflection camera (required) + /// @param refraction the refraction camera (optional) + void createShaderWaterStateSet(osg::Node* node, Reflection* reflection, Refraction* refraction); - bool mUnderwaterEffect; - int mVisibilityFlags; - - Reflection* mReflection; - Refraction* mRefraction; - RippleSimulation* mSimulation; - - Ogre::Vector2 mPlayer; + void updateWaterMaterial(); public: - Water (Ogre::Camera *camera, RenderingManager* rend, const MWWorld::Fallback* fallback); + Water(osg::Group* parent, osg::Group* sceneRoot, + Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, const MWWorld::Fallback* fallback, + const std::string& resourcePath); ~Water(); - void clearRipples(); - - void setActive(bool active); + void setEnabled(bool enabled); bool toggle(); - void update(float dt, Ogre::Vector3 player); - void frameStarted(float dt); + + bool isUnderwater(const osg::Vec3f& pos) const; /// adds an emitter, position will be tracked automatically using its scene node void addEmitter (const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f); void removeEmitter (const MWWorld::Ptr& ptr); void updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); + void emitRipple(const osg::Vec3f& pos); - void setViewportBackground(const Ogre::ColourValue& bg); + void removeCell(const MWWorld::CellStore* store); ///< remove all emitters in this cell - void processChangedSettings(const Settings::CategorySettingVector& settings); + void clearRipples(); - /// Updates underwater state accordingly - void updateUnderwater(bool underwater); - void changeCell(const ESM::Cell* cell); + void changeCell(const MWWorld::CellStore* store); void setHeight(const float height); - virtual void requestedConfiguration (sh::MaterialInstance* m, const std::string& configuration); - virtual void createdConfiguration (sh::MaterialInstance* m, const std::string& configuration); + void update(float dt); + void processChangedSettings(const Settings::CategorySettingVector& settings); }; } diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index d16afe3ce2..4328d5a3d7 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -1,9 +1,9 @@ #include "weaponanimation.hpp" -#include -#include -#include -#include +#include + +#include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -17,14 +17,16 @@ #include "../mwmechanics/combat.hpp" #include "animation.hpp" +#include "rotatecontroller.hpp" namespace MWRender { -float WeaponAnimationTime::getValue() const +float WeaponAnimationTime::getValue(osg::NodeVisitor*) { if (mWeaponGroup.empty()) return 0; + float current = mAnimation->getCurrentTime(mWeaponGroup); if (current == -1) return 0; @@ -42,6 +44,16 @@ void WeaponAnimationTime::updateStartTime() setGroup(mWeaponGroup); } +WeaponAnimation::WeaponAnimation() + : mPitchFactor(0) +{ +} + +WeaponAnimation::~WeaponAnimation() +{ + +} + void WeaponAnimation::attachArrow(MWWorld::Ptr actor) { MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); @@ -63,8 +75,8 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor) } else if (weaponType == ESM::Weapon::MarksmanBow || weaponType == ESM::Weapon::MarksmanCrossbow) { - NifOgre::ObjectScenePtr weapon = getWeapon(); - if (!weapon.get()) + osg::Group* parent = getArrowBone(); + if (!parent) return; MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); @@ -72,16 +84,13 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor) return; std::string model = ammo->getClass().getModel(*ammo); - if (!weapon->mSkelBase) - throw std::runtime_error("Need a skeleton to attach the arrow to"); + osg::ref_ptr arrow = getResourceSystem()->getSceneManager()->createInstance(model, parent); - const std::string bonename = "ArrowBone"; - mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, bonename, bonename, weapon->mSkelBase->getParentSceneNode(), model); - configureAddedObject(mAmmunition, *ammo, MWWorld::InventoryStore::Slot_Ammunition); + mAmmunition = PartHolderPtr(new PartHolder(arrow)); } } -void WeaponAnimation::releaseArrow(MWWorld::Ptr actor) +void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) { MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); @@ -91,36 +100,30 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor) return; // The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise. - Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * - Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) + * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - MWMechanics::applyFatigueLoss(actor, *weapon); + MWMechanics::applyFatigueLoss(actor, *weapon, attackStrength); if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) { // Thrown weapons get detached now - NifOgre::ObjectScenePtr objects = getWeapon(); - - Ogre::Vector3 launchPos(0,0,0); - if (objects->mSkelBase) - { - launchPos = objects->mSkelBase->getParentNode()->_getDerivedPosition(); - } - else if (objects->mEntities.size()) - { - objects->mEntities[0]->getParentNode()->needUpdate(true); - launchPos = objects->mEntities[0]->getParentNode()->_getDerivedPosition(); - } + osg::Node* weaponNode = getWeaponNode(); + if (!weaponNode) + return; + osg::MatrixList mats = weaponNode->getWorldMatrices(); + if (mats.empty()) + return; + osg::Vec3f launchPos = mats[0].getTrans(); float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->getFloat(); float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat(); - float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * - actor.getClass().getCreatureStats(actor).getAttackStrength(); + float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * attackStrength; - MWBase::Environment::get().getWorld()->launchProjectile(actor, *weapon, launchPos, orient, *weapon, speed); + MWBase::Environment::get().getWorld()->launchProjectile(actor, *weapon, launchPos, orient, *weapon, speed, attackStrength); showWeapon(false); @@ -133,51 +136,69 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor) if (ammo == inv.end()) return; - if (!mAmmunition.get()) + if (!mAmmunition) return; - Ogre::Vector3 launchPos(0,0,0); - if (mAmmunition->mSkelBase) - { - launchPos = mAmmunition->mSkelBase->getParentNode()->_getDerivedPosition(); - } - else if (mAmmunition->mEntities.size()) - { - mAmmunition->mEntities[0]->getParentNode()->needUpdate(true); - launchPos = mAmmunition->mEntities[0]->getParentNode()->_getDerivedPosition(); - } + osg::ref_ptr ammoNode = mAmmunition->getNode(); + osg::MatrixList mats = ammoNode->getWorldMatrices(); + if (mats.empty()) + return; + osg::Vec3f launchPos = mats[0].getTrans(); float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->getFloat(); float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); - float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * actor.getClass().getCreatureStats(actor).getAttackStrength(); + float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * attackStrength; - MWBase::Environment::get().getWorld()->launchProjectile(actor, *ammo, launchPos, orient, *weapon, speed); + MWBase::Environment::get().getWorld()->launchProjectile(actor, *ammo, launchPos, orient, *weapon, speed, attackStrength); inv.remove(*ammo, 1, actor); - mAmmunition.setNull(); + mAmmunition.reset(); } } -void WeaponAnimation::pitchSkeleton(float xrot, Ogre::SkeletonInstance* skel) +void WeaponAnimation::addControllers(const std::map >& nodes, + std::multimap, osg::ref_ptr > &map, osg::Node* objectRoot) { - if (mPitchFactor == 0) + for (int i=0; i<2; ++i) + { + mSpineControllers[i] = NULL; + + std::map >::const_iterator found = nodes.find(i == 0 ? "bip01 spine1" : "bip01 spine2"); + if (found != nodes.end()) + { + osg::Node* node = found->second; + mSpineControllers[i] = new RotateController(objectRoot); + node->addUpdateCallback(mSpineControllers[i]); + map.insert(std::make_pair(node, mSpineControllers[i])); + } + } +} + +void WeaponAnimation::deleteControllers() +{ + for (int i=0; i<2; ++i) + mSpineControllers[i] = NULL; +} + +void WeaponAnimation::configureControllers(float characterPitchRadians) +{ + if (!mSpineControllers[0]) return; - float pitch = xrot * mPitchFactor; - Ogre::Node *node; + if (mPitchFactor == 0.f || characterPitchRadians == 0.f) + { + for (int i=0; i<2; ++i) + mSpineControllers[i]->setEnabled(false); + return; + } - // In spherearcher.nif, we have spine, not Spine. Not sure if all bone names should be case insensitive? - if (skel->hasBone("Bip01 spine2")) - node = skel->getBone("Bip01 spine2"); - else - node = skel->getBone("Bip01 Spine2"); - node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); - - if (skel->hasBone("Bip01 spine1")) // in spherearcher.nif - node = skel->getBone("Bip01 spine1"); - else - node = skel->getBone("Bip01 Spine1"); - node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); + float pitch = characterPitchRadians * mPitchFactor; + osg::Quat rotate (pitch/2, osg::Vec3f(-1,0,0)); + for (int i=0; i<2; ++i) + { + mSpineControllers[i]->setRotate(rotate); + mSpineControllers[i]->setEnabled(true); + } } } diff --git a/apps/openmw/mwrender/weaponanimation.hpp b/apps/openmw/mwrender/weaponanimation.hpp index e1ccd94656..3bf0fb7211 100644 --- a/apps/openmw/mwrender/weaponanimation.hpp +++ b/apps/openmw/mwrender/weaponanimation.hpp @@ -1,18 +1,17 @@ #ifndef OPENMW_MWRENDER_WEAPONANIMATION_H #define OPENMW_MWRENDER_WEAPONANIMATION_H -#include - -#include +#include #include "../mwworld/ptr.hpp" +#include "animation.hpp" namespace MWRender { - class Animation; + class RotateController; - class WeaponAnimationTime : public Ogre::ControllerValue + class WeaponAnimationTime : public SceneUtil::ControllerSource { private: Animation* mAnimation; @@ -23,36 +22,45 @@ namespace MWRender void setGroup(const std::string& group); void updateStartTime(); - virtual Ogre::Real getValue() const; - virtual void setValue(Ogre::Real value) - { } + virtual float getValue(osg::NodeVisitor* nv); }; /// Handles attach & release of projectiles for ranged weapons class WeaponAnimation { public: - WeaponAnimation() : mPitchFactor(0) {} - virtual ~WeaponAnimation() {} + WeaponAnimation(); + virtual ~WeaponAnimation(); /// @note If no weapon (or an invalid weapon) is equipped, this function is a no-op. void attachArrow(MWWorld::Ptr actor); /// @note If no weapon (or an invalid weapon) is equipped, this function is a no-op. - void releaseArrow(MWWorld::Ptr actor); + void releaseArrow(MWWorld::Ptr actor, float attackStrength); + + /// Add WeaponAnimation-related controllers to \a nodes and store the added controllers in \a map. + void addControllers(const std::map >& nodes, + std::multimap, osg::ref_ptr >& map, osg::Node* objectRoot); + + void deleteControllers(); + + /// Configure controllers, should be called every animation frame. + void configureControllers(float characterPitchRadians); protected: - NifOgre::ObjectScenePtr mAmmunition; + PartHolderPtr mAmmunition; + + osg::ref_ptr mSpineControllers[2]; + + virtual osg::Group* getArrowBone() = 0; + virtual osg::Node* getWeaponNode() = 0; + virtual Resource::ResourceSystem* getResourceSystem() = 0; - virtual NifOgre::ObjectScenePtr getWeapon() = 0; virtual void showWeapon(bool show) = 0; - virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) = 0; /// A relative factor (0-1) that decides if and how much the skeleton should be pitched /// to indicate the facing orientation of the character, for ranged weapon aiming. float mPitchFactor; - - void pitchSkeleton(float xrot, Ogre::SkeletonInstance* skel); }; } diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index f0cb8a9677..db60d14d18 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -1,6 +1,8 @@ - #include "aiextensions.hpp" +#include +#include + #include #include @@ -9,6 +11,7 @@ #include #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/aiactivate.hpp" @@ -19,13 +22,11 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "interpretercontext.hpp" #include "ref.hpp" -#include - -#include "../mwbase/mechanicsmanager.hpp" namespace MWScript { @@ -145,6 +146,11 @@ namespace MWScript // discard additional arguments (reset), because we have no idea what they mean. for (unsigned int i=0; igetStore().get().find(cellID); + MWMechanics::AiEscort escortPackage(actorID, cellID, static_cast(duration), x, y, z); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr); @@ -425,7 +431,7 @@ namespace MWScript MWWorld::Ptr targetPtr; if (creatureStats.getAiSequence().getCombatTarget (targetPtr)) { - if (targetPtr.getRefData().getHandle() == testedTargetId) + if (!targetPtr.isEmpty() && targetPtr.getCellRef().getRefId() == testedTargetId) targetsAreEqual = true; } runtime.push(int(targetsAreEqual)); diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index c43cdf5650..07a8a300af 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -1,4 +1,3 @@ - #include "animationextensions.hpp" #include diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index 43d213c5af..513c1cc42e 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -1,6 +1,7 @@ - #include "cellextensions.hpp" +#include + #include "../mwworld/esmstore.hpp" #include @@ -15,6 +16,8 @@ #include "../mwworld/player.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "interpretercontext.hpp" namespace MWScript @@ -90,14 +93,14 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - if (!MWBase::Environment::get().getWorld()->getPlayerPtr().isInCell()) + if (!MWMechanics::getPlayer().isInCell()) { runtime.push (0); return; } bool interior = - !MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()->getCell()->isExterior(); + !MWMechanics::getPlayer().getCell()->getCell()->isExterior(); runtime.push (interior ? 1 : 0); } @@ -112,15 +115,15 @@ namespace MWScript std::string name = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - if (!MWBase::Environment::get().getWorld()->getPlayerPtr().isInCell()) + if (!MWMechanics::getPlayer().isInCell()) { runtime.push(0); return; } - const MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); + const MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); std::string current = MWBase::Environment::get().getWorld()->getCellName(cell); - Misc::StringUtils::toLower(current); + Misc::StringUtils::lowerCaseInPlace(current); bool match = current.length()>=name.length() && current.substr (0, name.length())==name; @@ -135,13 +138,15 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - if (!MWBase::Environment::get().getWorld()->getPlayerPtr().isInCell()) + if (!MWMechanics::getPlayer().isInCell()) { runtime.push(0.f); return; } - MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); - if (cell->getCell()->hasWater()) + MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); + if (cell->isExterior()) + runtime.push(0.f); // vanilla oddity, return 0 even though water is actually at -1 + else if (cell->getCell()->hasWater()) runtime.push (cell->getWaterLevel()); else runtime.push (-std::numeric_limits::max()); @@ -156,12 +161,12 @@ namespace MWScript { Interpreter::Type_Float level = runtime[0].mFloat; - if (!MWBase::Environment::get().getWorld()->getPlayerPtr().isInCell()) + if (!MWMechanics::getPlayer().isInCell()) { return; } - MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); + MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); if (cell->getCell()->isExterior()) throw std::runtime_error("Can't set water level in exterior cell"); @@ -179,12 +184,12 @@ namespace MWScript { Interpreter::Type_Float level = runtime[0].mFloat; - if (!MWBase::Environment::get().getWorld()->getPlayerPtr().isInCell()) + if (!MWMechanics::getPlayer().isInCell()) { return; } - MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); + MWWorld::CellStore *cell = MWMechanics::getPlayer().getCell(); if (cell->getCell()->isExterior()) throw std::runtime_error("Can't set water level in exterior cell"); diff --git a/apps/openmw/mwscript/compilercontext.cpp b/apps/openmw/mwscript/compilercontext.cpp index 3ff75eeae8..4a7038e1cb 100644 --- a/apps/openmw/mwscript/compilercontext.cpp +++ b/apps/openmw/mwscript/compilercontext.cpp @@ -1,4 +1,3 @@ - #include "compilercontext.hpp" #include "../mwworld/esmstore.hpp" @@ -13,6 +12,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/manualref.hpp" namespace MWScript { @@ -43,9 +43,9 @@ namespace MWScript } else { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPtr (id, false); + MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id); - script = ptr.getClass().getScript (ptr); + script = ref.getPtr().getClass().getScript (ref.getPtr()); reference = true; } @@ -83,7 +83,8 @@ namespace MWScript store.get().search (name) || store.get().search (name) || store.get().search (name) || - store.get().search (name); + store.get().search (name) || + store.get().search (name); } bool CompilerContext::isJournalId (const std::string& name) const diff --git a/apps/openmw/mwscript/consoleextensions.cpp b/apps/openmw/mwscript/consoleextensions.cpp index 30956d429f..d2e07d3e14 100644 --- a/apps/openmw/mwscript/consoleextensions.cpp +++ b/apps/openmw/mwscript/consoleextensions.cpp @@ -1,4 +1,3 @@ - #include "consoleextensions.hpp" #include diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 70475d8e2f..4e3e010f7e 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -1,4 +1,3 @@ - #include "containerextensions.hpp" #include @@ -24,9 +23,10 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "interpretercontext.hpp" #include "ref.hpp" @@ -56,6 +56,12 @@ namespace MWScript if (count == 0) return; + if(::Misc::StringUtils::ciEqual(item, "gold_005") + || ::Misc::StringUtils::ciEqual(item, "gold_010") + || ::Misc::StringUtils::ciEqual(item, "gold_025") + || ::Misc::StringUtils::ciEqual(item, "gold_100")) + item = "gold_001"; + MWWorld::Ptr itemPtr = *ptr.getClass().getContainerStore (ptr).add (item, count, ptr); // Spawn a messagebox (only for items added to player's inventory and if player is talking to someone) @@ -91,6 +97,12 @@ namespace MWScript std::string item = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); + if(::Misc::StringUtils::ciEqual(item, "gold_005") + || ::Misc::StringUtils::ciEqual(item, "gold_010") + || ::Misc::StringUtils::ciEqual(item, "gold_025") + || ::Misc::StringUtils::ciEqual(item, "gold_100")) + item = "gold_001"; + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); runtime.push (store.count(item)); @@ -119,6 +131,12 @@ namespace MWScript if (count == 0) return; + if(::Misc::StringUtils::ciEqual(item, "gold_005") + || ::Misc::StringUtils::ciEqual(item, "gold_010") + || ::Misc::StringUtils::ciEqual(item, "gold_025") + || ::Misc::StringUtils::ciEqual(item, "gold_100")) + item = "gold_001"; + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); std::string itemName; @@ -130,7 +148,7 @@ namespace MWScript // Spawn a messagebox (only for items removed from player's inventory) if ((numRemoved > 0) - && (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr())) + && (ptr == MWMechanics::getPlayer())) { // The two GMST entries below expand to strings informing the player of what, and how many of it has been removed from their inventory std::string msgBox; @@ -172,11 +190,13 @@ namespace MWScript if (it == invStore.end()) throw std::runtime_error("Item to equip not found"); - MWWorld::ActionEquip action (*it); - action.execute(ptr); - - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr() && !ptr.getClass().getScript(ptr).empty()) - ptr.getRefData().getLocals().setVarByInt(ptr.getClass().getScript(ptr), "onpcequip", 1); + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + MWBase::Environment::get().getWindowManager()->useItem(*it); + else + { + boost::shared_ptr action = it->getClass().use(*it); + action->execute(ptr); + } } }; diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index 904e0ee850..626fafb5a4 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -1,4 +1,3 @@ - #include "controlextensions.hpp" #include diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 8b68052647..c305fb81fc 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -1,4 +1,3 @@ - #include "dialogueextensions.hpp" #include diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 93720aef62..42c204ecb0 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -58,6 +58,8 @@ op 0x20029: PCRaiseRank, explicit reference op 0x2002a: PCLowerRank, explicit reference op 0x2002b: PCJoinFaction, explicit reference op 0x2002c: MenuTest +op 0x2002d: BetaComment +op 0x2002e: BetaComment, explicit reference opcodes 0x2002d-0x3ffff unused Segment 4: @@ -396,8 +398,8 @@ op 0x2000243: GetFactionReaction op 0x2000244: Activate, explicit op 0x2000245: ClearInfoActor op 0x2000246: ClearInfoActor, explicit -op 0x2000247: BetaComment -op 0x2000248: BetaComment, explicit +op 0x2000247: (unused) +op 0x2000248: (unused) op 0x2000249: OnMurder op 0x200024a: OnMurder, explicit op 0x200024b: ToggleMenus diff --git a/apps/openmw/mwscript/extensions.cpp b/apps/openmw/mwscript/extensions.cpp index 2170ba4fb3..12bf3413a0 100644 --- a/apps/openmw/mwscript/extensions.cpp +++ b/apps/openmw/mwscript/extensions.cpp @@ -1,4 +1,3 @@ - #include "extensions.hpp" #include diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index a6ad2cc11a..6074a12d0e 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -1,10 +1,10 @@ - #include "globalscripts.hpp" #include #include #include +#include #include #include "../mwworld/esmstore.hpp" @@ -198,13 +198,12 @@ namespace MWScript if (iter==mScripts.end()) { - if (const ESM::Script *script = mStore.get().find (name)) - { - GlobalScriptDesc desc; - desc.mLocals.configure (*script); + const ESM::Script *script = mStore.get().find (name); - iter = mScripts.insert (std::make_pair (name, desc)).first; - } + GlobalScriptDesc desc; + desc.mLocals.configure (*script); + + iter = mScripts.insert (std::make_pair (name2, desc)).first; } return iter->second.mLocals; diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index 40c555f503..254da56d6e 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -1,4 +1,3 @@ - #include "guiextensions.hpp" #include @@ -15,6 +14,8 @@ #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "interpretercontext.hpp" #include "ref.hpp" @@ -54,7 +55,7 @@ namespace MWScript { MWWorld::Ptr bed = R()(runtime, false); - if (bed.isEmpty() || !MWBase::Environment::get().getMechanicsManager()->sleepInBed(MWBase::Environment::get().getWorld()->getPlayerPtr(), + if (bed.isEmpty() || !MWBase::Environment::get().getMechanicsManager()->sleepInBed(MWMechanics::getPlayer(), bed)) MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_RestBed); } @@ -115,7 +116,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { std::string cell = (runtime.getStringLiteral (runtime[0].mInteger)); - ::Misc::StringUtils::toLower(cell); + ::Misc::StringUtils::lowerCaseInPlace(cell); runtime.pop(); // "Will match complete or partial cells, so ShowMap, "Vivec" will show cells Vivec and Vivec, Fred's House as well." @@ -128,7 +129,7 @@ namespace MWScript for (; it != cells.extEnd(); ++it) { std::string name = it->mName; - ::Misc::StringUtils::toLower(name); + ::Misc::StringUtils::lowerCaseInPlace(name); if (name.find(cell) != std::string::npos) MWBase::Environment::get().getWindowManager()->addVisitedLocation ( it->mName, diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index df675aebbd..7a6afe2e0c 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -1,4 +1,3 @@ - #include "interpretercontext.hpp" #include @@ -146,7 +145,7 @@ namespace MWScript // selected), store the ID of that reference store it so it can be inherited by // targeted scripts started from this one. if (targetId.empty() && !reference.isEmpty()) - mTargetId = reference.getClass().getId (reference); + mTargetId = reference.getCellRef().getRefId(); } int InterpreterContext::getLocalShort (int index) const @@ -602,9 +601,9 @@ namespace MWScript return mTargetId; } - void InterpreterContext::updatePtr(const MWWorld::Ptr& updated) + void InterpreterContext::updatePtr(const MWWorld::Ptr& base, const MWWorld::Ptr& updated) { - if (!mReference.isEmpty()) + if (!mReference.isEmpty() && base == mReference) mReference = updated; } } diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index d3841befdf..3c43444cc7 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -167,7 +167,7 @@ namespace MWScript MWWorld::Ptr getReference(bool required=true); ///< Reference, that the script is running from (can be empty) - void updatePtr(const MWWorld::Ptr& updated); + void updatePtr(const MWWorld::Ptr& base, const MWWorld::Ptr& updated); ///< Update the Ptr stored in mReference, if there is one stored there. Should be called after the reference has been moved to a new cell. virtual std::string getTargetId() const; diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index a18d0d5ae5..ee93ea2e7b 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -9,13 +9,32 @@ #include "../mwbase/environment.hpp" #include "../mwbase/scriptmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/esmstore.hpp" #include namespace MWScript { - void Locals::configure (const ESM::Script& script) + void Locals::ensure (const std::string& scriptName) { + if (!mInitialised) + { + const ESM::Script *script = MWBase::Environment::get().getWorld()->getStore(). + get().find (scriptName); + + configure (*script); + } + } + + Locals::Locals() : mInitialised (false) {} + + bool Locals::configure (const ESM::Script& script) + { + if (mInitialised) + return false; + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals (script.mId); @@ -25,6 +44,9 @@ namespace MWScript mLongs.resize (locals.get ('l').size(), 0); mFloats.clear(); mFloats.resize (locals.get ('f').size(), 0); + + mInitialised = true; + return true; } bool Locals::isEmpty() const @@ -36,6 +58,8 @@ namespace MWScript { try { + ensure (script); + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = locals.getIndex(var); @@ -49,6 +73,8 @@ namespace MWScript int Locals::getIntVar(const std::string &script, const std::string &var) { + ensure (script); + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = locals.getIndex(var); char type = locals.getType(var); @@ -73,6 +99,8 @@ namespace MWScript bool Locals::setVarByInt(const std::string& script, const std::string& var, int val) { + ensure (script); + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); int index = locals.getIndex(var); char type = locals.getType(var); @@ -94,8 +122,11 @@ namespace MWScript return false; } - void Locals::write (ESM::Locals& locals, const std::string& script) const + bool Locals::write (ESM::Locals& locals, const std::string& script) const { + if (!mInitialised) + return false; + try { const Compiler::Locals& declarations = @@ -132,10 +163,14 @@ namespace MWScript catch (const Compiler::SourceException&) { } + + return true; } void Locals::read (const ESM::Locals& locals, const std::string& script) { + ensure (script); + try { const Compiler::Locals& declarations = diff --git a/apps/openmw/mwscript/locals.hpp b/apps/openmw/mwscript/locals.hpp index a9fcf317c8..bb4df42bf5 100644 --- a/apps/openmw/mwscript/locals.hpp +++ b/apps/openmw/mwscript/locals.hpp @@ -15,30 +15,50 @@ namespace MWScript { class Locals { + bool mInitialised; + + void ensure (const std::string& scriptName); + public: std::vector mShorts; std::vector mLongs; std::vector mFloats; + Locals(); + /// Are there any locals? + /// + /// \note Will return false, if locals have not been configured yet. bool isEmpty() const; - void configure (const ESM::Script& script); + /// \return Did the state of *this change from uninitialised to initialised? + bool configure (const ESM::Script& script); /// @note var needs to be in lowercase + /// + /// \note Locals will be automatically configured first, if necessary bool setVarByInt(const std::string& script, const std::string& var, int val); + /// \note Locals will be automatically configured first, if necessary + // + // \note If it can not be determined if the variable exists, the error will be + // ignored and false will be returned. bool hasVar(const std::string& script, const std::string& var); /// if var does not exist, returns 0 /// @note var needs to be in lowercase + /// + /// \note Locals will be automatically configured first, if necessary int getIntVar (const std::string& script, const std::string& var); - void write (ESM::Locals& locals, const std::string& script) const; + /// \note If locals have not been configured yet, no data is written. + /// + /// \return Locals written? + bool write (ESM::Locals& locals, const std::string& script) const; + /// \note Locals will be automatically configured first, if necessary void read (const ESM::Locals& locals, const std::string& script); }; } #endif - diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 38106340ae..9c63511b27 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1,4 +1,3 @@ - #include "miscextensions.hpp" #include @@ -28,6 +27,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/actorutil.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -37,7 +37,7 @@ namespace void addToLevList(ESM::LevelledListBase* list, const std::string& itemId, int level) { - for (std::vector::iterator it = list->mList.begin(); it != list->mList.end();) + for (std::vector::iterator it = list->mList.begin(); it != list->mList.end(); ++it) { if (it->mLevel == level && itemId == it->mId) return; @@ -157,7 +157,7 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); - context.executeActivation(ptr, MWBase::Environment::get().getWorld()->getPlayerPtr()); + context.executeActivation(ptr, MWMechanics::getPlayer()); } }; @@ -188,7 +188,12 @@ namespace MWScript if (ptr.getTypeName() == typeid(ESM::Door).name() && !ptr.getCellRef().getTeleport()) { MWBase::Environment::get().getWorld()->activateDoor(ptr, 0); - MWBase::Environment::get().getWorld()->localRotateObject(ptr, 0, 0, 0); + + float xr = ptr.getCellRef().getPosition().rot[0]; + float yr = ptr.getCellRef().getPosition().rot[1]; + float zr = ptr.getCellRef().getPosition().rot[2]; + + MWBase::Environment::get().getWorld()->rotateObject(ptr, xr, yr, zr); } } }; @@ -213,7 +218,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { bool enabled = - MWBase::Environment::get().getWorld()->toggleRenderMode (MWBase::World::Render_CollisionDebug); + MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_CollisionDebug); runtime.getContext().report (enabled ? "Collision Mesh Rendering -> On" : "Collision Mesh Rendering -> Off"); @@ -228,7 +233,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { bool enabled = - MWBase::Environment::get().getWorld()->toggleRenderMode (MWBase::World::Render_BoundingBoxes); + MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_BoundingBoxes); runtime.getContext().report (enabled ? "Bounding Box Rendering -> On" : "Bounding Box Rendering -> Off"); @@ -242,7 +247,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { bool enabled = - MWBase::Environment::get().getWorld()->toggleRenderMode (MWBase::World::Render_Wireframe); + MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_Wireframe); runtime.getContext().report (enabled ? "Wireframe Rendering -> On" : "Wireframe Rendering -> Off"); @@ -255,7 +260,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { bool enabled = - MWBase::Environment::get().getWorld()->toggleRenderMode (MWBase::World::Render_Pathgrid); + MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_Pathgrid); runtime.getContext().report (enabled ? "Path Grid rendering -> On" : "Path Grid Rendering -> Off"); @@ -417,9 +422,17 @@ namespace MWScript if(key < 0 || key > 32767 || *end != '\0') key = ESM::MagicEffect::effectStringToId(effect); - runtime.push(ptr.getClass().getCreatureStats(ptr).getMagicEffects().get( - MWMechanics::EffectKey(key)).getMagnitude() > 0); - } + const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); + for (MWMechanics::MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) + { + if (it->first.mId == key && it->second.getModifier() > 0) + { + runtime.push(1); + return; + } + } + runtime.push(0); + } }; template @@ -567,7 +580,8 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getClass().getNpcStats (ptr).getDrawState () == MWMechanics::DrawState_Weapon); + runtime.push((ptr.getClass().hasInventoryStore(ptr) || ptr.getClass().isBipedal(ptr)) && + ptr.getClass().getCreatureStats (ptr).getDrawState () == MWMechanics::DrawState_Weapon); } }; @@ -580,7 +594,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getClass().getNpcStats (ptr).getDrawState () == MWMechanics::DrawState_Spell); + runtime.push(ptr.getClass().getCreatureStats (ptr).getDrawState () == MWMechanics::DrawState_Spell); } }; @@ -812,10 +826,10 @@ namespace MWScript const std::string script = ptr.getClass().getScript(ptr); if(script.empty()) - str<< ptr.getCellRef().getRefId()<<" ("<getLocals(script); @@ -937,7 +951,7 @@ namespace MWScript MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false); MWMechanics::CastSpell cast(ptr, target); - cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos); + cast.mHitPosition = target.getRefData().getPosition().asVec3(); cast.mAlwaysSucceed = true; cast.cast(spell); } @@ -955,7 +969,7 @@ namespace MWScript runtime.pop(); MWMechanics::CastSpell cast(ptr, ptr); - cast.mHitPosition = Ogre::Vector3(ptr.getRefData().getPosition().pos); + cast.mHitPosition = ptr.getRefData().getPosition().asVec3(); cast.mAlwaysSucceed = true; cast.cast(spell); } @@ -976,7 +990,7 @@ namespace MWScript public: virtual void execute(Interpreter::Runtime &runtime) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).setBounty(0); MWBase::Environment::get().getWorld()->confiscateStolenItems(player); MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); @@ -988,7 +1002,7 @@ namespace MWScript public: virtual void execute(Interpreter::Runtime &runtime) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).setBounty(0); MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); } @@ -1016,10 +1030,10 @@ namespace MWScript }; template - class OpBetaComment : public Interpreter::Opcode0 + class OpBetaComment : public Interpreter::Opcode1 { public: - virtual void execute(Interpreter::Runtime &runtime) + virtual void execute(Interpreter::Runtime &runtime, unsigned int arg0) { MWWorld::Ptr ptr = R()(runtime); @@ -1037,6 +1051,11 @@ namespace MWScript msg << "RefNum: " << ptr.getCellRef().getRefNum().mIndex << std::endl; } + if (ptr.getRefData().isDeletedByContentFile()) + msg << "[Deleted by content file]" << std::endl; + if (!ptr.getRefData().getCount()) + msg << "[Deleted]" << std::endl; + msg << "RefID: " << ptr.getCellRef().getRefId() << std::endl; if (ptr.isInCell()) @@ -1045,17 +1064,21 @@ namespace MWScript msg << "Cell: " << MWBase::Environment::get().getWorld()->getCellName(cell) << std::endl; if (cell->getCell()->isExterior()) msg << "Grid: " << cell->getCell()->getGridX() << " " << cell->getCell()->getGridY() << std::endl; - Ogre::Vector3 pos (ptr.getRefData().getPosition().pos); - msg << "Coordinates: " << pos << std::endl; + osg::Vec3f pos (ptr.getRefData().getPosition().asVec3()); + msg << "Coordinates: " << pos.x() << " " << pos.y() << " " << pos.z() << std::endl; msg << "Model: " << ptr.getClass().getModel(ptr) << std::endl; if (!ptr.getClass().getScript(ptr).empty()) msg << "Script: " << ptr.getClass().getScript(ptr) << std::endl; } - std::string notes = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - if (!notes.empty()) - msg << "Notes: " << notes << std::endl; + while (arg0 > 0) + { + std::string notes = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + if (!notes.empty()) + msg << "Notes: " << notes << std::endl; + --arg0; + } runtime.getContext().report(msg.str()); } @@ -1221,8 +1244,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeExplodeSpellExplicit, new OpExplodeSpell); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcInJail, new OpGetPcInJail); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcTraveling, new OpGetPcTraveling); - interpreter.installSegment5 (Compiler::Misc::opcodeBetaComment, new OpBetaComment); - interpreter.installSegment5 (Compiler::Misc::opcodeBetaCommentExplicit, new OpBetaComment); + interpreter.installSegment3 (Compiler::Misc::opcodeBetaComment, new OpBetaComment); + interpreter.installSegment3 (Compiler::Misc::opcodeBetaCommentExplicit, new OpBetaComment); interpreter.installSegment5 (Compiler::Misc::opcodeAddToLevCreature, new OpAddToLevCreature); interpreter.installSegment5 (Compiler::Misc::opcodeRemoveFromLevCreature, new OpRemoveFromLevCreature); interpreter.installSegment5 (Compiler::Misc::opcodeAddToLevItem, new OpAddToLevItem); diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 5f755e5424..b73c9a642c 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -1,4 +1,3 @@ - #include "scriptmanagerimp.hpp" #include @@ -171,7 +170,7 @@ namespace MWScript return iter->second; } - if (const ESM::Script *script = mStore.get().find (name2)) + if (const ESM::Script *script = mStore.get().search (name2)) { if (mVerbose) std::cout diff --git a/apps/openmw/mwscript/skyextensions.cpp b/apps/openmw/mwscript/skyextensions.cpp index d28d01b638..e0fccd16d2 100644 --- a/apps/openmw/mwscript/skyextensions.cpp +++ b/apps/openmw/mwscript/skyextensions.cpp @@ -1,4 +1,3 @@ - #include "skyextensions.hpp" #include diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 606de7aa01..a9896d203b 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -1,4 +1,3 @@ - #include "extensions.hpp" #include diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 80f46e4573..cdf60d3298 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1,6 +1,6 @@ - #include "statsextensions.hpp" +#include #include #include @@ -25,13 +25,14 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "interpretercontext.hpp" #include "ref.hpp" namespace { - std::string getDialogueActorFaction(MWWorld::Ptr actor) + std::string getDialogueActorFaction(MWWorld::ConstPtr actor) { std::string factionId = actor.getClass().getPrimaryFaction(actor); if (factionId.empty()) @@ -227,7 +228,7 @@ namespace MWScript // workaround broken endgame scripts that kill dagoth ur if (!R::implicit && - Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "dagoth_ur_1")) + ::Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "dagoth_ur_1")) { runtime.push (peek); @@ -460,7 +461,7 @@ namespace MWScript MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr() && + if (ptr == MWMechanics::getPlayer() && id == wm->getSelectedSpell()) { wm->unsetSelectedSpell(); @@ -529,7 +530,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr actor = R()(runtime, false); + MWWorld::ConstPtr actor = R()(runtime, false); std::string factionID = ""; @@ -542,13 +543,13 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); if(factionID != "") { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).joinFaction(factionID); } } @@ -561,7 +562,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr actor = R()(runtime, false); + MWWorld::ConstPtr actor = R()(runtime, false); std::string factionID = ""; @@ -574,13 +575,13 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); if(factionID != "") { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); if(player.getClass().getNpcStats(player).getFactionRanks().find(factionID) == player.getClass().getNpcStats(player).getFactionRanks().end()) { player.getClass().getNpcStats(player).joinFaction(factionID); @@ -600,7 +601,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr actor = R()(runtime, false); + MWWorld::ConstPtr actor = R()(runtime, false); std::string factionID = ""; @@ -613,13 +614,13 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); if(factionID != "") { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).lowerRank(factionID); } } @@ -632,7 +633,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime, false); + MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionID = ""; if(arg0 >0) @@ -644,11 +645,11 @@ namespace MWScript { factionID = ptr.getClass().getPrimaryFaction(ptr); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); if(factionID!="") { if(player.getClass().getNpcStats(player).getFactionRanks().find(factionID) != player.getClass().getNpcStats(player).getFactionRanks().end()) @@ -738,7 +739,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime, false); + MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionId; @@ -755,9 +756,9 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - ::Misc::StringUtils::toLower (factionId); + ::Misc::StringUtils::lowerCaseInPlace (factionId); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); runtime.push ( player.getClass().getNpcStats (player).getFactionReputation (factionId)); } @@ -770,7 +771,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime, false); + MWWorld::ConstPtr ptr = R()(runtime, false); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); @@ -790,9 +791,9 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - ::Misc::StringUtils::toLower (factionId); + ::Misc::StringUtils::lowerCaseInPlace (factionId); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats (player).setFactionReputation (factionId, value); } }; @@ -804,7 +805,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime, false); + MWWorld::ConstPtr ptr = R()(runtime, false); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); @@ -824,9 +825,9 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - ::Misc::StringUtils::toLower (factionId); + ::Misc::StringUtils::lowerCaseInPlace (factionId); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats (player).setFactionReputation (factionId, player.getClass().getNpcStats (player).getFactionReputation (factionId)+ value); @@ -866,14 +867,14 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWWorld::Ptr ptr = R()(runtime); + MWWorld::ConstPtr ptr = R()(runtime); std::string race = runtime.getStringLiteral(runtime[0].mInteger); - ::Misc::StringUtils::toLower(race); + ::Misc::StringUtils::lowerCaseInPlace(race); runtime.pop(); std::string npcRace = ptr.get()->mBase->mRace; - ::Misc::StringUtils::toLower(npcRace); + ::Misc::StringUtils::lowerCaseInPlace(npcRace); runtime.push (npcRace == race); } @@ -898,7 +899,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime, false); + MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionID = ""; if(arg0 >0 ) @@ -910,8 +911,8 @@ namespace MWScript { factionID = ptr.getClass().getPrimaryFaction(ptr); } - ::Misc::StringUtils::toLower(factionID); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + ::Misc::StringUtils::lowerCaseInPlace(factionID); + MWWorld::Ptr player = MWMechanics::getPlayer(); if(factionID!="") { runtime.push(player.getClass().getNpcStats(player).getExpelled(factionID)); @@ -930,7 +931,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime, false); + MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionID = ""; if(arg0 >0 ) @@ -942,7 +943,7 @@ namespace MWScript { factionID = ptr.getClass().getPrimaryFaction(ptr); } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); if(factionID!="") { player.getClass().getNpcStats(player).expell(factionID); @@ -957,7 +958,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime, false); + MWWorld::ConstPtr ptr = R()(runtime, false); std::string factionID = ""; if(arg0 >0 ) @@ -969,7 +970,7 @@ namespace MWScript { factionID = ptr.getClass().getPrimaryFaction(ptr); } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); if(factionID!="") player.getClass().getNpcStats(player).clearExpelled(factionID); } @@ -988,7 +989,7 @@ namespace MWScript if(factionID.empty()) return; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); // no-op when executed on the player if (ptr == player) @@ -1011,7 +1012,7 @@ namespace MWScript if(factionID.empty()) return; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); // no-op when executed on the player if (ptr == player) @@ -1095,7 +1096,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); - MWBase::Environment::get().getWorld()->setWerewolf(ptr, set); + MWBase::Environment::get().getMechanicsManager()->setWerewolf(ptr, set); } }; @@ -1107,7 +1108,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); - MWBase::Environment::get().getWorld()->applyWerewolfAcrobatics(ptr); + MWBase::Environment::get().getMechanicsManager()->applyWerewolfAcrobatics(ptr); } }; @@ -1120,12 +1121,13 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == MWMechanics::getPlayer()) ptr.getClass().getCreatureStats(ptr).resurrect(); else if (ptr.getClass().getCreatureStats(ptr).isDead()) { MWBase::Environment::get().getWorld()->undeleteObject(ptr); // resets runtime state such as inventory, stats and AI. does not reset position in the world + MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(NULL); } } @@ -1159,10 +1161,10 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - float currentValue = stats.getMagicEffects().get(mPositiveEffect).getMagnitude(); + const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); + float currentValue = effects.get(mPositiveEffect).getMagnitude(); if (mNegativeEffect != -1) - currentValue -= stats.getMagicEffects().get(mNegativeEffect).getMagnitude(); + currentValue -= effects.get(mNegativeEffect).getMagnitude(); int ret = static_cast(currentValue); runtime.push(ret); @@ -1185,14 +1187,14 @@ namespace MWScript virtual void execute(Interpreter::Runtime &runtime) { MWWorld::Ptr ptr = R()(runtime); - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - float currentValue = stats.getMagicEffects().get(mPositiveEffect).getMagnitude(); + MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); + float currentValue = effects.get(mPositiveEffect).getMagnitude(); if (mNegativeEffect != -1) - currentValue -= stats.getMagicEffects().get(mNegativeEffect).getMagnitude(); + currentValue -= effects.get(mNegativeEffect).getMagnitude(); int arg = runtime[0].mInteger; runtime.pop(); - stats.getMagicEffects().modifyBase(mPositiveEffect, (arg - static_cast(currentValue))); + effects.modifyBase(mPositiveEffect, (arg - static_cast(currentValue))); } }; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 414ef7fb7d..60847e745b 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -1,4 +1,6 @@ -#include +#include + +#include #include @@ -18,6 +20,8 @@ #include "../mwworld/player.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "interpretercontext.hpp" #include "ref.hpp" @@ -81,33 +85,19 @@ namespace MWScript std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - Interpreter::Type_Float angle = runtime[0].mFloat; + Interpreter::Type_Float angle = osg::DegreesToRadians(runtime[0].mFloat); runtime.pop(); - float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); - float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); - float az = Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees(); - - MWWorld::LocalRotation localRot = ptr.getRefData().getLocalRotation(); + float ax = ptr.getRefData().getPosition().rot[0]; + float ay = ptr.getRefData().getPosition().rot[1]; + float az = ptr.getRefData().getPosition().rot[2]; if (axis == "x") - { - localRot.rot[0] = 0; - ptr.getRefData().setLocalRotation(localRot); MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az); - } else if (axis == "y") - { - localRot.rot[1] = 0; - ptr.getRefData().setLocalRotation(localRot); MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az); - } else if (axis == "z") - { - localRot.rot[2] = 0; - ptr.getRefData().setLocalRotation(localRot); MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle); - } else throw std::runtime_error ("invalid rotation axis: " + axis); } @@ -127,15 +117,15 @@ namespace MWScript if (axis == "x") { - runtime.push(Ogre::Radian(ptr.getCellRef().getPosition().rot[0]).valueDegrees()); + runtime.push(osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[0])); } else if (axis == "y") { - runtime.push(Ogre::Radian(ptr.getCellRef().getPosition().rot[1]).valueDegrees()); + runtime.push(osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[1])); } else if (axis == "z") { - runtime.push(Ogre::Radian(ptr.getCellRef().getPosition().rot[2]).valueDegrees()); + runtime.push(osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[2])); } else throw std::runtime_error ("invalid rotation axis: " + axis); @@ -156,15 +146,15 @@ namespace MWScript if (axis=="x") { - runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees()); + runtime.push(osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0])); } else if (axis=="y") { - runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees()); + runtime.push(osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1])); } else if (axis=="z") { - runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees()); + runtime.push(osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[2])); } else throw std::runtime_error ("invalid rotation axis: " + axis); @@ -196,7 +186,7 @@ namespace MWScript runtime.push(ptr.getRefData().getPosition().pos[2]); } else - throw std::runtime_error ("invalid axis: " + axis); + throw std::runtime_error ("invalid axis: " + axis); } }; @@ -212,7 +202,7 @@ namespace MWScript if (!ptr.isInCell()) return; - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == MWMechanics::getPlayer()) { MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); } @@ -242,7 +232,7 @@ namespace MWScript else throw std::runtime_error ("invalid axis: " + axis); - dynamic_cast(runtime.getContext()).updatePtr(updated); + dynamic_cast(runtime.getContext()).updatePtr(ptr,updated); } }; @@ -287,7 +277,7 @@ namespace MWScript if (ptr.getContainerStore()) return; - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == MWMechanics::getPlayer()) { MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); } @@ -310,30 +300,32 @@ namespace MWScript } catch(std::exception&) { - const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getExterior(cellID); + // cell not found, move to exterior instead (vanilla PositionCell compatibility) + const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getExterior(cellID); int cx,cy; MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); store = MWBase::Environment::get().getWorld()->getExterior(cx,cy); if(!cell) { - runtime.getContext().report ("unknown cell (" + cellID + ")"); - std::cerr << "unknown cell (" << cellID << ")\n"; + std::string error = "PositionCell: unknown interior cell (" + cellID + "), moving to exterior instead"; + runtime.getContext().report (error); + std::cerr << error << std::endl; } } if(store) { - MWBase::Environment::get().getWorld()->moveObject(ptr,store,x,y,z); - ptr = MWWorld::Ptr(ptr.getBase(), store); - dynamic_cast(runtime.getContext()).updatePtr(ptr); + MWWorld::Ptr base = ptr; + ptr = MWBase::Environment::get().getWorld()->moveObject(ptr,store,x,y,z); + dynamic_cast(runtime.getContext()).updatePtr(base,ptr); - float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); - float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); + float ax = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0]); + float ay = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1]); // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) // except for when you position the player, then degrees must be used. // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. - if(ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(ptr != MWMechanics::getPlayer()) zRot = zRot/60.0f; - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,osg::DegreesToRadians(zRot)); ptr.getClass().adjustPosition(ptr, false); } @@ -352,7 +344,7 @@ namespace MWScript if (!ptr.isInCell()) return; - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (ptr == MWMechanics::getPlayer()) { MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); } @@ -370,24 +362,24 @@ namespace MWScript // another morrowind oddity: player will be moved to the exterior cell at this location, // non-player actors will move within the cell they are in. - if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + MWWorld::Ptr base = ptr; + if (ptr == MWMechanics::getPlayer()) { MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(cx,cy); - MWBase::Environment::get().getWorld()->moveObject(ptr,cell,x,y,z); - ptr = MWWorld::Ptr(ptr.getBase(), cell); + ptr = MWBase::Environment::get().getWorld()->moveObject(ptr,cell,x,y,z); } else { ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z); } - dynamic_cast(runtime.getContext()).updatePtr(ptr); + dynamic_cast(runtime.getContext()).updatePtr(base,ptr); - float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); - float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); + float ax = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0]); + float ay = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1]); // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) // except for when you position the player, then degrees must be used. // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. - if(ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(ptr != MWMechanics::getPlayer()) zRot = zRot/60.0f; MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); ptr.getClass().adjustPosition(ptr, false); @@ -412,7 +404,7 @@ namespace MWScript runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); - Interpreter::Type_Float zRot = runtime[0].mFloat; + Interpreter::Type_Float zRotDegrees = runtime[0].mFloat; runtime.pop(); MWWorld::CellStore* store = 0; @@ -439,7 +431,7 @@ namespace MWScript pos.pos[1] = y; pos.pos[2] = z; pos.rot[0] = pos.rot[1] = 0; - pos.rot[2] = zRot; + pos.rot[2] = osg::DegreesToRadians(zRotDegrees); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().setPosition(pos); MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); @@ -464,10 +456,14 @@ namespace MWScript runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); - Interpreter::Type_Float zRot = runtime[0].mFloat; + Interpreter::Type_Float zRotDegrees = runtime[0].mFloat; runtime.pop(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::Ptr player = MWMechanics::getPlayer(); + + if (!player.isInCell()) + throw std::runtime_error("player not in a cell"); + MWWorld::CellStore* store = NULL; if (player.getCell()->isExterior()) { @@ -483,7 +479,7 @@ namespace MWScript pos.pos[1] = y; pos.pos[2] = z; pos.rot[0] = pos.rot[1] = 0; - pos.rot[2] = zRot; + pos.rot[2] = osg::DegreesToRadians(zRotDegrees); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().setPosition(pos); MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); @@ -499,7 +495,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr actor = pc - ? MWBase::Environment::get().getWorld()->getPlayerPtr() + ? MWMechanics::getPlayer() : R()(runtime); std::string itemID = runtime.getStringLiteral (runtime[0].mInteger); @@ -515,20 +511,23 @@ namespace MWScript if (count<0) throw std::runtime_error ("count must be non-negative"); + if (!actor.isInCell()) + throw std::runtime_error ("actor is not in a cell"); + for (int i=0; ilocalRotateObject(ptr,ax+rotation,ay,az); - } + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax+rotation,ay,az); else if (axis == "y") - { - MWBase::Environment::get().getWorld()->localRotateObject(ptr,ax,ay+rotation,az); - } + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay+rotation,az); else if (axis == "z") - { - MWBase::Environment::get().getWorld()->localRotateObject(ptr,ax,ay,az+rotation); - } + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,az+rotation); else throw std::runtime_error ("invalid rotation axis: " + axis); } @@ -599,14 +592,14 @@ namespace MWScript std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - Interpreter::Type_Float rotation = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); + Interpreter::Type_Float rotation = osg::DegreesToRadians(runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); const float *objRot = ptr.getRefData().getPosition().rot; - float ax = Ogre::Radian(objRot[0]).valueDegrees(); - float ay = Ogre::Radian(objRot[1]).valueDegrees(); - float az = Ogre::Radian(objRot[2]).valueDegrees(); + float ax = objRot[0]; + float ay = objRot[1]; + float az = objRot[2]; if (axis == "x") { @@ -637,15 +630,13 @@ namespace MWScript if (!ptr.isInCell()) return; - MWWorld::LocalRotation rot; - rot.rot[0] = 0; - rot.rot[1] = 0; - rot.rot[2] = 0; - ptr.getRefData().setLocalRotation(rot); + float xr = ptr.getCellRef().getPosition().rot[0]; + float yr = ptr.getCellRef().getPosition().rot[1]; + float zr = ptr.getCellRef().getPosition().rot[2]; - MWBase::Environment::get().getWorld()->rotateObject(ptr, 0,0,0,true); + MWBase::Environment::get().getWorld()->rotateObject(ptr, xr, yr, zr); - dynamic_cast(runtime.getContext()).updatePtr( + dynamic_cast(runtime.getContext()).updatePtr(ptr, MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], ptr.getCellRef().getPosition().pos[1], ptr.getCellRef().getPosition().pos[2])); @@ -669,29 +660,30 @@ namespace MWScript Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); - Ogre::Vector3 posChange; + osg::Vec3f posChange; if (axis == "x") { - posChange=Ogre::Vector3(movement, 0, 0); + posChange=osg::Vec3f(movement, 0, 0); } else if (axis == "y") { - posChange=Ogre::Vector3(0, movement, 0); + posChange=osg::Vec3f(0, movement, 0); } else if (axis == "z") { - posChange=Ogre::Vector3(0, 0, movement); + posChange=osg::Vec3f(0, 0, movement); } else throw std::runtime_error ("invalid movement axis: " + axis); + // is it correct that disabled objects can't be Move-d? if (!ptr.getRefData().getBaseNode()) return; - Ogre::Vector3 diff = ptr.getRefData().getBaseNode()->getOrientation() * posChange; - Ogre::Vector3 worldPos(ptr.getRefData().getPosition().pos); + osg::Vec3f diff = ptr.getRefData().getBaseNode()->getAttitude() * posChange; + osg::Vec3f worldPos(ptr.getRefData().getPosition().asVec3()); worldPos += diff; - MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x, worldPos.y, worldPos.z); + MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x(), worldPos.y(), worldPos.z()); } }; @@ -762,8 +754,8 @@ namespace MWScript interpreter.installSegment5(Compiler::Transformation::opcodePositionExplicit,new OpPosition); interpreter.installSegment5(Compiler::Transformation::opcodePositionCell,new OpPositionCell); interpreter.installSegment5(Compiler::Transformation::opcodePositionCellExplicit,new OpPositionCell); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell,new OpPlaceItemCell); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem,new OpPlaceItem); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell,new OpPlaceItemCell); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem,new OpPlaceItem); interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtPc,new OpPlaceAt); interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMe,new OpPlaceAt); interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMeExplicit,new OpPlaceAt); diff --git a/apps/openmw/mwscript/userextensions.cpp b/apps/openmw/mwscript/userextensions.cpp index 538133c65d..165a93062f 100644 --- a/apps/openmw/mwscript/userextensions.cpp +++ b/apps/openmw/mwscript/userextensions.cpp @@ -1,4 +1,3 @@ - #include "userextensions.hpp" #include diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 0185d3ecc1..66b7e09e3a 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -4,16 +4,9 @@ #include #include +#include -extern "C" { -#ifndef HAVE_LIBSWRESAMPLE -// FIXME: remove this section once libswresample is packaged for Debian -int swr_init(AVAudioResampleContext *avr); -void swr_free(AVAudioResampleContext **avr); -int swr_convert( AVAudioResampleContext *avr, uint8_t** output, int out_samples, const uint8_t** input, int in_samples); -AVAudioResampleContext * swr_alloc_set_opts( AVAudioResampleContext *avr, int64_t out_ch_layout, AVSampleFormat out_fmt, int out_rate, int64_t in_ch_layout, AVSampleFormat in_fmt, int in_rate, int o, void* l); -#endif -} +#include namespace MWSound { @@ -27,8 +20,10 @@ int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) { try { - Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; - return stream->read(buf, buf_size); + std::istream& stream = *static_cast(user_data)->mDataStream; + stream.clear(); + stream.read((char*)buf, buf_size); + return stream.gcount(); } catch (std::exception& ) { @@ -36,36 +31,38 @@ int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) } } -int FFmpeg_Decoder::writePacket(void *user_data, uint8_t *buf, int buf_size) +int FFmpeg_Decoder::writePacket(void *, uint8_t *, int) { - try - { - Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; - return stream->write(buf, buf_size); - } - catch (std::exception& ) - { - return 0; - } + throw std::runtime_error("can't write to read-only stream"); } int64_t FFmpeg_Decoder::seek(void *user_data, int64_t offset, int whence) { - Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; + std::istream& stream = *static_cast(user_data)->mDataStream; whence &= ~AVSEEK_FORCE; + + stream.clear(); + if(whence == AVSEEK_SIZE) - return stream->size(); + { + size_t prev = stream.tellg(); + stream.seekg(0, std::ios_base::end); + size_t size = stream.tellg(); + stream.seekg(prev, std::ios_base::beg); + return size; + } + if(whence == SEEK_SET) - stream->seek(static_cast(offset)); + stream.seekg(offset, std::ios_base::beg); else if(whence == SEEK_CUR) - stream->seek(static_cast(stream->tell()+offset)); + stream.seekg(offset, std::ios_base::cur); else if(whence == SEEK_END) - stream->seek(static_cast(stream->size()+offset)); + stream.seekg(offset, std::ios_base::end); else return -1; - return stream->tell(); + return stream.tellg(); } @@ -186,7 +183,7 @@ size_t FFmpeg_Decoder::readAVAudioData(void *data, size_t length) void FFmpeg_Decoder::open(const std::string &fname) { close(); - mDataStream = mResourceMgr.openResource(fname); + mDataStream = mResourceMgr->get(fname); if((mFormatCtx=avformat_alloc_context()) == NULL) fail("Failed to allocate context"); @@ -289,7 +286,7 @@ void FFmpeg_Decoder::close() avformat_close_input(&mFormatCtx); } - mDataStream.setNull(); + mDataStream.reset(); } std::string FFmpeg_Decoder::getName() @@ -409,8 +406,9 @@ size_t FFmpeg_Decoder::getSampleOffset() return (int)(mNextPts*(*mStream)->codec->sample_rate) - delay; } -FFmpeg_Decoder::FFmpeg_Decoder() - : mFormatCtx(NULL) +FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs) + : Sound_Decoder(vfs) + , mFormatCtx(NULL) , mStream(NULL) , mFrame(NULL) , mFrameSize(0) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 2cdbbf363d..b27e60c9f5 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -1,12 +1,6 @@ #ifndef GAME_SOUND_FFMPEG_DECODER_H #define GAME_SOUND_FFMPEG_DECODER_H -// FIXME: This can't be right? The headers refuse to build without UINT64_C, -// which only gets defined in stdint.h in either C99 mode or with this macro -// defined... -#ifndef __STDC_CONSTANT_MACROS -#define __STDC_CONSTANT_MACROS -#endif #include extern "C" { @@ -28,16 +22,13 @@ extern "C" // From version 54.56 binkaudio encoding format changed from S16 to FLTP. See: // https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d // http://ffmpeg.zeranoe.com/forum/viewtopic.php?f=15&t=872 -#ifdef HAVE_LIBSWRESAMPLE #include -#else -#include -#include -#define SwrContext AVAudioResampleContext -#endif } +#include + #include +#include #include "sound_decoder.hpp" @@ -66,7 +57,8 @@ namespace MWSound bool getNextPacket(); - Ogre::DataStreamPtr mDataStream; + Files::IStreamPtr mDataStream; + static int readPacket(void *user_data, uint8_t *buf, int buf_size); static int writePacket(void *user_data, uint8_t *buf, int buf_size); static int64_t seek(void *user_data, int64_t offset, int whence); @@ -90,7 +82,7 @@ namespace MWSound FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs); FFmpeg_Decoder(const FFmpeg_Decoder &rhs); - FFmpeg_Decoder(); + FFmpeg_Decoder(const VFS::Manager* vfs); public: virtual ~FFmpeg_Decoder(); diff --git a/apps/openmw/mwsound/libavwrapper.cpp b/apps/openmw/mwsound/libavwrapper.cpp deleted file mode 100644 index a7a3245da3..0000000000 --- a/apps/openmw/mwsound/libavwrapper.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef HAVE_LIBSWRESAMPLE -extern "C" -{ -#ifndef __STDC_CONSTANT_MACROS -#define __STDC_CONSTANT_MACROS -#endif -#include - -#include -#include -// From libavutil version 52.2.0 and onward the declaration of -// AV_CH_LAYOUT_* is removed from libavcodec/avcodec.h and moved to -// libavutil/channel_layout.h -#if AV_VERSION_INT(52, 2, 0) <= AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ - LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO) - #include -#endif -#include -#include - -/* FIXME: delete this file once libswresample is packaged for Debian */ - -int swr_init(AVAudioResampleContext *avr) { return 1; } - -void swr_free(AVAudioResampleContext **avr) { avresample_free(avr); } - -int swr_convert( - AVAudioResampleContext *avr, - uint8_t** output, - int out_samples, - const uint8_t** input, - int in_samples) -{ - // FIXME: potential performance hit - int out_plane_size = 0; - int in_plane_size = 0; - return avresample_convert(avr, output, out_plane_size, out_samples, - (uint8_t **)input, in_plane_size, in_samples); -} - -AVAudioResampleContext * swr_alloc_set_opts( - AVAudioResampleContext *avr, - int64_t out_ch_layout, - AVSampleFormat out_fmt, - int out_rate, - int64_t in_ch_layout, - AVSampleFormat in_fmt, - int in_rate, - int o, - void* l) -{ - avr = avresample_alloc_context(); - if(!avr) - return 0; - - int res; - res = av_opt_set_int(avr, "out_channel_layout", out_ch_layout, 0); - if(res < 0) - { - av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_ch_layout = %d\n", res); - return 0; - } - res = av_opt_set_int(avr, "out_sample_fmt", out_fmt, 0); - if(res < 0) - { - av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_fmt = %d\n", res); - return 0; - } - res = av_opt_set_int(avr, "out_sample_rate", out_rate, 0); - if(res < 0) - { - av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_rate = %d\n", res); - return 0; - } - res = av_opt_set_int(avr, "in_channel_layout", in_ch_layout, 0); - if(res < 0) - { - av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_ch_layout = %d\n", res); - return 0; - } - res = av_opt_set_int(avr, "in_sample_fmt", in_fmt, 0); - if(res < 0) - { - av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_fmt = %d\n", res); - return 0; - } - res = av_opt_set_int(avr, "in_sample_rate", in_rate, 0); - if(res < 0) - { - av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_rate = %d\n", res); - return 0; - } - res = av_opt_set_int(avr, "internal_sample_fmt", AV_SAMPLE_FMT_FLTP, 0); - if(res < 0) - { - av_log(avr, AV_LOG_ERROR, "av_opt_set_int: internal_sample_fmt\n"); - return 0; - } - - - if(avresample_open(avr) < 0) - { - av_log(avr, AV_LOG_ERROR, "Error opening context\n"); - return 0; - } - else - return avr; -} - -} -#endif diff --git a/apps/openmw/mwsound/loudness.cpp b/apps/openmw/mwsound/loudness.cpp index 0077919840..326c59c079 100644 --- a/apps/openmw/mwsound/loudness.cpp +++ b/apps/openmw/mwsound/loudness.cpp @@ -1,53 +1,70 @@ #include "loudness.hpp" +#include +#include + #include "soundmanagerimp.hpp" namespace MWSound { - void analyzeLoudness(const std::vector &data, int sampleRate, ChannelConfig chans, - SampleType type, std::vector &out, float valuesPerSecond) +void Sound_Loudness::analyzeLoudness(const std::vector< char >& data, int sampleRate, ChannelConfig chans, SampleType type, float valuesPerSecond) +{ + int samplesPerSegment = static_cast(sampleRate / valuesPerSecond); + int numSamples = bytesToFrames(data.size(), chans, type); + int advance = framesToBytes(1, chans, type); + + mSamplesPerSec = valuesPerSecond; + mSamples.clear(); + mSamples.reserve(numSamples/samplesPerSegment); + + int segment=0; + int sample=0; + while (segment < numSamples/samplesPerSegment) { - int samplesPerSegment = static_cast(sampleRate / valuesPerSecond); - int numSamples = bytesToFrames(data.size(), chans, type); - int advance = framesToBytes(1, chans, type); - - out.reserve(numSamples/samplesPerSegment); - - int segment=0; - int sample=0; - while (segment < numSamples/samplesPerSegment) + float sum=0; + int samplesAdded = 0; + while (sample < numSamples && sample < (segment+1)*samplesPerSegment) { - float sum=0; - int samplesAdded = 0; - while (sample < numSamples && sample < (segment+1)*samplesPerSegment) + // get sample on a scale from -1 to 1 + float value = 0; + if (type == SampleType_UInt8) + value = ((char)(data[sample*advance]^0x80))/128.f; + else if (type == SampleType_Int16) { - // get sample on a scale from -1 to 1 - float value = 0; - if (type == SampleType_UInt8) - value = ((char)(data[sample*advance]^0x80))/128.f; - else if (type == SampleType_Int16) - { - value = *reinterpret_cast(&data[sample*advance]); - value /= float(std::numeric_limits::max()); - } - else if (type == SampleType_Float32) - { - value = *reinterpret_cast(&data[sample*advance]); - value = std::max(-1.f, std::min(1.f, value)); // Float samples *should* be scaled to [-1,1] already. - } - - sum += value*value; - ++samplesAdded; - ++sample; + value = *reinterpret_cast(&data[sample*advance]); + value /= float(std::numeric_limits::max()); + } + else if (type == SampleType_Float32) + { + value = *reinterpret_cast(&data[sample*advance]); + value = std::max(-1.f, std::min(1.f, value)); // Float samples *should* be scaled to [-1,1] already. } - float rms = 0; // root mean square - if (samplesAdded > 0) - rms = std::sqrt(sum / samplesAdded); - out.push_back(rms); - ++segment; + sum += value*value; + ++samplesAdded; + ++sample; } + + float rms = 0; // root mean square + if (samplesAdded > 0) + rms = std::sqrt(sum / samplesAdded); + mSamples.push_back(rms); + ++segment; } + mReady = true; +} + + +float Sound_Loudness::getLoudnessAtTime(float sec) const +{ + if(mSamplesPerSec <= 0.0f || mSamples.empty() || sec < 0.0f) + return 0.0f; + + size_t index = static_cast(sec * mSamplesPerSec); + index = std::max(0, std::min(index, mSamples.size()-1)); + return mSamples[index]; +} + } diff --git a/apps/openmw/mwsound/loudness.hpp b/apps/openmw/mwsound/loudness.hpp index df727bd0b2..366d29de51 100644 --- a/apps/openmw/mwsound/loudness.hpp +++ b/apps/openmw/mwsound/loudness.hpp @@ -1,20 +1,40 @@ +#ifndef GAME_SOUND_LOUDNESS_H +#define GAME_SOUND_LOUDNESS_H + +#include + #include "sound_decoder.hpp" namespace MWSound { -/** - * Analyzes the energy (closely related to loudness) of a sound buffer. - * The buffer will be divided into segments according to \a valuesPerSecond, - * and for each segment a loudness value in the range of [0,1] will be computed. - * @param data the sound buffer to analyze, containing raw samples - * @param sampleRate the sample rate of the sound buffer - * @param chans channel layout of the buffer - * @param type sample type of the buffer - * @param out Will contain the output loudness values. - * @param valuesPerSecond How many loudness values per second of audio to compute. - */ -void analyzeLoudness (const std::vector& data, int sampleRate, ChannelConfig chans, SampleType type, - std::vector& out, float valuesPerSecond); +class Sound_Loudness { + // Loudness sample info + float mSamplesPerSec; + std::vector mSamples; + volatile bool mReady; + +public: + Sound_Loudness() : mSamplesPerSec(0.0f), mReady(false) { } + + /** + * Analyzes the energy (closely related to loudness) of a sound buffer. + * The buffer will be divided into segments according to \a valuesPerSecond, + * and for each segment a loudness value in the range of [0,1] will be computed. + * @param data the sound buffer to analyze, containing raw samples + * @param sampleRate the sample rate of the sound buffer + * @param chans channel layout of the buffer + * @param type sample type of the buffer + * @param valuesPerSecond How many loudness values per second of audio to compute. + */ + void analyzeLoudness(const std::vector& data, int sampleRate, + ChannelConfig chans, SampleType type, + float valuesPerSecond); + + bool isReady() { return mReady; } + float getLoudnessAtTime(float sec) const; +}; } + +#endif /* GAME_SOUND_LOUDNESS_H */ diff --git a/apps/openmw/mwsound/movieaudiofactory.cpp b/apps/openmw/mwsound/movieaudiofactory.cpp index 468f8c82c7..554b62d264 100644 --- a/apps/openmw/mwsound/movieaudiofactory.cpp +++ b/apps/openmw/mwsound/movieaudiofactory.cpp @@ -1,7 +1,7 @@ #include "movieaudiofactory.hpp" -#include -#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" @@ -17,7 +17,8 @@ namespace MWSound { public: MWSoundDecoderBridge(MWSound::MovieAudioDecoder* decoder) - : mDecoder(decoder) + : Sound_Decoder(NULL) + , mDecoder(decoder) { } @@ -51,7 +52,7 @@ namespace MWSound std::string getStreamName() { - return mVideoState->stream->getName(); + return std::string(); } private: @@ -59,7 +60,8 @@ namespace MWSound virtual double getAudioClock() { - return mAudioTrack->getTimeOffset(); + return (double)getSampleOffset()/(double)mAVStream->codec->sample_rate - + MWBase::Environment::get().getSoundManager()->getTrackTimeDelay(mAudioTrack); } virtual void adjustAudioSettings(AVSampleFormat& sampleFormat, uint64_t& channelLayout, int& sampleRate) @@ -84,11 +86,13 @@ namespace MWSound public: ~MovieAudioDecoder() { + if(mAudioTrack.get()) + MWBase::Environment::get().getSoundManager()->stopTrack(mAudioTrack); mAudioTrack.reset(); mDecoderBridge.reset(); } - MWBase::SoundPtr mAudioTrack; + MWBase::SoundStreamPtr mAudioTrack; boost::shared_ptr mDecoderBridge; }; @@ -159,7 +163,8 @@ namespace MWSound boost::shared_ptr decoder(new MWSound::MovieAudioDecoder(videoState)); decoder->setupFormat(); - MWBase::SoundPtr sound = MWBase::Environment::get().getSoundManager()->playTrack(decoder->mDecoderBridge, MWBase::SoundManager::Play_TypeMovie); + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + MWBase::SoundStreamPtr sound = sndMgr->playTrack(decoder->mDecoderBridge, MWBase::SoundManager::Play_TypeMovie); if (!sound.get()) { decoder.reset(); diff --git a/apps/openmw/mwsound/movieaudiofactory.hpp b/apps/openmw/mwsound/movieaudiofactory.hpp index a3c6021972..1391a0012d 100644 --- a/apps/openmw/mwsound/movieaudiofactory.hpp +++ b/apps/openmw/mwsound/movieaudiofactory.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MWSOUND_MOVIEAUDIOFACTORY_H #define OPENMW_MWSOUND_MOVIEAUDIOFACTORY_H -#include +#include namespace MWSound { diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 1b3dced801..d440206329 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -5,6 +5,8 @@ #include +#include + #include #include "openal_output.hpp" @@ -17,9 +19,51 @@ #define ALC_ALL_DEVICES_SPECIFIER 0x1013 #endif +#ifndef ALC_SOFT_HRTF +#define ALC_SOFT_HRTF 1 +#define ALC_HRTF_SOFT 0x1992 +#define ALC_DONT_CARE_SOFT 0x0002 +#define ALC_HRTF_STATUS_SOFT 0x1993 +#define ALC_HRTF_DISABLED_SOFT 0x0000 +#define ALC_HRTF_ENABLED_SOFT 0x0001 +#define ALC_HRTF_DENIED_SOFT 0x0002 +#define ALC_HRTF_REQUIRED_SOFT 0x0003 +#define ALC_HRTF_HEADPHONES_DETECTED_SOFT 0x0004 +#define ALC_HRTF_UNSUPPORTED_FORMAT_SOFT 0x0005 +#define ALC_NUM_HRTF_SPECIFIERS_SOFT 0x1994 +#define ALC_HRTF_SPECIFIER_SOFT 0x1995 +#define ALC_HRTF_ID_SOFT 0x1996 +typedef const ALCchar* (ALC_APIENTRY*LPALCGETSTRINGISOFT)(ALCdevice *device, ALCenum paramName, ALCsizei index); +typedef ALCboolean (ALC_APIENTRY*LPALCRESETDEVICESOFT)(ALCdevice *device, const ALCint *attribs); +#ifdef AL_ALEXT_PROTOTYPES +ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index); +ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs); +#endif +#endif + + +#define MAKE_PTRID(id) ((void*)(uintptr_t)id) +#define GET_PTRID(ptr) ((ALuint)(uintptr_t)ptr) + namespace { - const int loudnessFPS = 20; // loudness values per second of audio + +const int sLoudnessFPS = 20; // loudness values per second of audio + +// Helper to get an OpenAL extension function +template +void convertPointer(T& dest, R src) +{ + memcpy(&dest, &src, sizeof(src)); +} + +template +void getFunc(T& func, ALCdevice *device, const char *name) +{ + void* funcPtr = alcGetProcAddress(device, name); + convertPointer(func, funcPtr); +} + } namespace MWSound @@ -145,41 +189,30 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type) return AL_NONE; } -static ALint getBufferSampleCount(ALuint buf) -{ - ALint size, bits, channels; - - alGetBufferi(buf, AL_SIZE, &size); - alGetBufferi(buf, AL_BITS, &bits); - alGetBufferi(buf, AL_CHANNELS, &channels); - throwALerror(); - - return size / channels * 8 / bits; -} // // A streaming OpenAL sound. // -class OpenAL_SoundStream : public Sound +class OpenAL_SoundStream { static const ALuint sNumBuffers = 6; static const ALfloat sBufferLength; - OpenAL_Output &mOutput; - +private: ALuint mSource; + ALuint mBuffers[sNumBuffers]; + ALint mCurrentBufIdx; ALenum mFormat; ALsizei mSampleRate; ALuint mBufferSize; - - ALuint mSamplesQueued; + ALuint mFrameSize; + ALint mSilence; DecoderPtr mDecoder; volatile bool mIsFinished; - volatile bool mIsInitialBatchEnqueued; void updateAll(bool local); @@ -189,44 +222,52 @@ class OpenAL_SoundStream : public Sound friend class OpenAL_Output; public: - OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, float basevol, float pitch, int flags); - virtual ~OpenAL_SoundStream(); + OpenAL_SoundStream(ALuint src, DecoderPtr decoder); + ~OpenAL_SoundStream(); - virtual void stop(); - virtual bool isPlaying(); - virtual double getTimeOffset(); - virtual void update(); + bool isPlaying(); + double getStreamDelay() const; + double getStreamOffset() const; - void play(); bool process(); + ALint refillQueue(); }; - const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f; + // // A background streaming thread (keeps active streams processed) // struct OpenAL_Output::StreamThread { typedef std::vector StreamVec; StreamVec mStreams; - boost::recursive_mutex mMutex; + + typedef std::vector > DecoderLoudnessVec; + DecoderLoudnessVec mDecoderLoudness; + + volatile bool mQuitNow; + boost::mutex mMutex; + boost::condition_variable mCondVar; boost::thread mThread; StreamThread() - : mThread(boost::ref(*this)) + : mQuitNow(false), mThread(boost::ref(*this)) { } ~StreamThread() { - mThread.interrupt(); + mQuitNow = true; + mMutex.lock(); mMutex.unlock(); + mCondVar.notify_all(); + mThread.join(); } // boost::thread entry point void operator()() { - while(1) + boost::unique_lock lock(mMutex); + while(!mQuitNow) { - mMutex.lock(); StreamVec::iterator iter = mStreams.begin(); while(iter != mStreams.end()) { @@ -235,33 +276,68 @@ struct OpenAL_Output::StreamThread { else ++iter; } - mMutex.unlock(); - boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + + // Only do one loudness decode at a time, in case it takes particularly long we don't + // want to block up anything. + DecoderLoudnessVec::iterator dliter = mDecoderLoudness.begin(); + if(dliter != mDecoderLoudness.end()) + { + DecoderPtr decoder = dliter->first; + Sound_Loudness *loudness = dliter->second; + mDecoderLoudness.erase(dliter); + lock.unlock(); + + std::vector data; + ChannelConfig chans = ChannelConfig_Mono; + SampleType type = SampleType_Int16; + int srate = 48000; + try { + decoder->getInfo(&srate, &chans, &type); + decoder->readAll(data); + } + catch(std::exception &e) { + std::cerr<< "Failed to decode audio: "<analyzeLoudness(data, srate, chans, type, static_cast(sLoudnessFPS)); + lock.lock(); + continue; + } + mCondVar.timed_wait(lock, boost::posix_time::milliseconds(50)); } } void add(OpenAL_SoundStream *stream) { - mMutex.lock(); + boost::unique_lock lock(mMutex); if(std::find(mStreams.begin(), mStreams.end(), stream) == mStreams.end()) + { mStreams.push_back(stream); - mMutex.unlock(); + lock.unlock(); + mCondVar.notify_all(); + } } void remove(OpenAL_SoundStream *stream) { - mMutex.lock(); + boost::lock_guard lock(mMutex); StreamVec::iterator iter = std::find(mStreams.begin(), mStreams.end(), stream); - if(iter != mStreams.end()) - mStreams.erase(iter); - mMutex.unlock(); + if(iter != mStreams.end()) mStreams.erase(iter); } void removeAll() { - mMutex.lock(); + boost::lock_guard lock(mMutex); mStreams.clear(); - mMutex.unlock(); + mDecoderLoudness.clear(); + } + + void add(DecoderPtr decoder, Sound_Loudness *loudness) + { + boost::unique_lock lock(mMutex); + mDecoderLoudness.push_back(std::make_pair(decoder, loudness)); + lock.unlock(); + mCondVar.notify_all(); } private: @@ -270,12 +346,9 @@ private: }; -OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, float basevol, float pitch, int flags) - : Sound(Ogre::Vector3(0.0f), 1.0f, basevol, pitch, 1.0f, 1000.0f, flags) - , mOutput(output), mSource(src), mSamplesQueued(0), mDecoder(decoder), mIsFinished(true), mIsInitialBatchEnqueued(false) +OpenAL_SoundStream::OpenAL_SoundStream(ALuint src, DecoderPtr decoder) + : mSource(src), mCurrentBufIdx(0), mFrameSize(0), mSilence(0), mDecoder(decoder), mIsFinished(false) { - throwALerror(); - alGenBuffers(sNumBuffers, mBuffers); throwALerror(); try @@ -288,10 +361,16 @@ OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, Decode mFormat = getALFormat(chans, type); mSampleRate = srate; - mBufferSize = static_cast(sBufferLength*srate); - mBufferSize = framesToBytes(mBufferSize, chans, type); + switch(type) + { + case SampleType_UInt8: mSilence = 0x80; break; + case SampleType_Int16: mSilence = 0x00; break; + case SampleType_Float32: mSilence = 0x00; break; + } - mOutput.mActiveSounds.push_back(this); + mFrameSize = framesToBytes(1, chans, type); + mBufferSize = static_cast(sBufferLength*srate); + mBufferSize *= mFrameSize; } catch(std::exception&) { @@ -299,47 +378,14 @@ OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, Decode alGetError(); throw; } + mIsFinished = false; } OpenAL_SoundStream::~OpenAL_SoundStream() { - mOutput.mStreamThread->remove(this); - - alSourceStop(mSource); - alSourcei(mSource, AL_BUFFER, 0); - - mOutput.mFreeSources.push_back(mSource); alDeleteBuffers(sNumBuffers, mBuffers); alGetError(); mDecoder->close(); - - mOutput.mActiveSounds.erase(std::find(mOutput.mActiveSounds.begin(), - mOutput.mActiveSounds.end(), this)); -} - -void OpenAL_SoundStream::play() -{ - alSourceStop(mSource); - alSourcei(mSource, AL_BUFFER, 0); - throwALerror(); - mSamplesQueued = 0; - mIsFinished = false; - mIsInitialBatchEnqueued = false; - mOutput.mStreamThread->add(this); -} - -void OpenAL_SoundStream::stop() -{ - mOutput.mStreamThread->remove(this); - mIsFinished = true; - mIsInitialBatchEnqueued = false; - - alSourceStop(mSource); - alSourcei(mSource, AL_BUFFER, 0); - throwALerror(); - mSamplesQueued = 0; - - mDecoder->rewind(); } bool OpenAL_SoundStream::isPlaying() @@ -354,294 +400,108 @@ bool OpenAL_SoundStream::isPlaying() return !mIsFinished; } -double OpenAL_SoundStream::getTimeOffset() +double OpenAL_SoundStream::getStreamDelay() const { ALint state = AL_STOPPED; - ALfloat offset = 0.0f; - double t; + double d = 0.0; + ALint offset; - mOutput.mStreamThread->mMutex.lock(); - alGetSourcef(mSource, AL_SEC_OFFSET, &offset); + alGetSourcei(mSource, AL_SAMPLE_OFFSET, &offset); alGetSourcei(mSource, AL_SOURCE_STATE, &state); if(state == AL_PLAYING || state == AL_PAUSED) - t = (double)(mDecoder->getSampleOffset() - mSamplesQueued)/(double)mSampleRate + offset; + { + ALint queued; + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); + ALint inqueue = mBufferSize/mFrameSize*queued - offset; + d = (double)inqueue / (double)mSampleRate; + } + + throwALerror(); + return d; +} + +double OpenAL_SoundStream::getStreamOffset() const +{ + ALint state = AL_STOPPED; + ALint offset; + double t; + + alGetSourcei(mSource, AL_SAMPLE_OFFSET, &offset); + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + if(state == AL_PLAYING || state == AL_PAUSED) + { + ALint queued; + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); + ALint inqueue = mBufferSize/mFrameSize*queued - offset; + t = (double)(mDecoder->getSampleOffset() - inqueue) / (double)mSampleRate; + } else + { + /* Underrun, or not started yet. The decoder offset is where we'll play + * next. */ t = (double)mDecoder->getSampleOffset() / (double)mSampleRate; - mOutput.mStreamThread->mMutex.unlock(); + } throwALerror(); return t; } -void OpenAL_SoundStream::updateAll(bool local) -{ - alSourcef(mSource, AL_REFERENCE_DISTANCE, mMinDistance); - alSourcef(mSource, AL_MAX_DISTANCE, mMaxDistance); - if(local) - { - alSourcef(mSource, AL_ROLLOFF_FACTOR, 0.0f); - alSourcei(mSource, AL_SOURCE_RELATIVE, AL_TRUE); - } - else - { - alSourcef(mSource, AL_ROLLOFF_FACTOR, 1.0f); - alSourcei(mSource, AL_SOURCE_RELATIVE, AL_FALSE); - } - alSourcei(mSource, AL_LOOPING, AL_FALSE); - - update(); -} - -void OpenAL_SoundStream::update() -{ - ALfloat gain = mVolume*mBaseVolume; - ALfloat pitch = mPitch; - if(!(mFlags&MWBase::SoundManager::Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) - { - gain *= 0.9f; - pitch *= 0.7f; - } - - alSourcef(mSource, AL_GAIN, gain); - alSourcef(mSource, AL_PITCH, pitch); - alSource3f(mSource, AL_POSITION, mPos[0], mPos[1], mPos[2]); - alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - throwALerror(); -} - bool OpenAL_SoundStream::process() { try { - bool finished = mIsFinished; - ALint processed, state; - - alGetSourcei(mSource, AL_SOURCE_STATE, &state); - alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); - throwALerror(); - - if(processed > 0) + if(refillQueue() > 0) { - std::vector data(mBufferSize); - do { - ALuint bufid = 0; - size_t got; - - alSourceUnqueueBuffers(mSource, 1, &bufid); - mSamplesQueued -= getBufferSampleCount(bufid); - processed--; - - if(finished) - continue; - - got = mDecoder->read(&data[0], data.size()); - finished = (got < data.size()); - if(got > 0) - { - alBufferData(bufid, mFormat, &data[0], got, mSampleRate); - alSourceQueueBuffers(mSource, 1, &bufid); - mSamplesQueued += getBufferSampleCount(bufid); - } - } while(processed > 0); - throwALerror(); - } - else if (!mIsInitialBatchEnqueued) { // nothing enqueued yet - std::vector data(mBufferSize); - - for(ALuint i = 0;i < sNumBuffers && !finished;i++) + ALint state; + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + if(state != AL_PLAYING && state != AL_PAUSED) { - size_t got = mDecoder->read(&data[0], data.size()); - finished = (got < data.size()); - if(got > 0) - { - ALuint bufid = mBuffers[i]; - alBufferData(bufid, mFormat, &data[0], got, mSampleRate); - alSourceQueueBuffers(mSource, 1, &bufid); - throwALerror(); - mSamplesQueued += getBufferSampleCount(bufid); - } - } - mIsInitialBatchEnqueued = true; - } - - if(state != AL_PLAYING && state != AL_PAUSED) - { - ALint queued = 0; - - alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); - if(queued > 0) + refillQueue(); alSourcePlay(mSource); - throwALerror(); + } } - - mIsFinished = finished; } catch(std::exception&) { std::cout<< "Error updating stream \""<getName()<<"\"" < 0) { - alSourcef(mSource, AL_ROLLOFF_FACTOR, 0.0f); - alSourcei(mSource, AL_SOURCE_RELATIVE, AL_TRUE); - } - else - { - alSourcef(mSource, AL_ROLLOFF_FACTOR, 1.0f); - alSourcei(mSource, AL_SOURCE_RELATIVE, AL_FALSE); - } - alSourcei(mSource, AL_LOOPING, (mFlags&MWBase::SoundManager::Play_Loop) ? AL_TRUE : AL_FALSE); - - update(); -} - -void OpenAL_Sound::update() -{ - ALfloat gain = mVolume*mBaseVolume; - ALfloat pitch = mPitch; - - if(!(mFlags&MWBase::SoundManager::Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) - { - gain *= 0.9f; - pitch *= 0.7f; + ALuint buf; + alSourceUnqueueBuffers(mSource, 1, &buf); + --processed; } - alSourcef(mSource, AL_GAIN, gain); - alSourcef(mSource, AL_PITCH, pitch); - alSource3f(mSource, AL_POSITION, mPos[0], mPos[1], mPos[2]); - alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - throwALerror(); -} - -void OpenAL_Sound3D::update() -{ - ALfloat gain = mVolume*mBaseVolume; - ALfloat pitch = mPitch; - if(mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) - gain = 0.0f; - else if(!(mFlags&MWBase::SoundManager::Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + ALint queued; + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); + if(!mIsFinished && (ALuint)queued < sNumBuffers) { - gain *= 0.9f; - pitch *= 0.7f; + std::vector data(mBufferSize); + for(;!mIsFinished && (ALuint)queued < sNumBuffers;++queued) + { + size_t got = mDecoder->read(&data[0], data.size()); + if(got < data.size()) + { + mIsFinished = true; + memset(&data[got], mSilence, data.size()-got); + } + if(got > 0) + { + ALuint bufid = mBuffers[mCurrentBufIdx]; + alBufferData(bufid, mFormat, &data[0], data.size(), mSampleRate); + alSourceQueueBuffers(mSource, 1, &bufid); + mCurrentBufIdx = (mCurrentBufIdx+1) % sNumBuffers; + } + } } - alSourcef(mSource, AL_GAIN, gain); - alSourcef(mSource, AL_PITCH, pitch); - alSource3f(mSource, AL_POSITION, mPos[0], mPos[1], mPos[2]); - alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - throwALerror(); + return queued; } @@ -735,14 +595,6 @@ void OpenAL_Output::deinit() alDeleteSources(1, &mFreeSources[i]); mFreeSources.clear(); - mBufferRefs.clear(); - mUnusedBuffers.clear(); - while(!mBufferCache.empty()) - { - alDeleteBuffers(1, &mBufferCache.begin()->second.mALBuffer); - mBufferCache.erase(mBufferCache.begin()); - } - alcMakeContextCurrent(0); if(mContext) alcDestroyContext(mContext); @@ -755,255 +607,509 @@ void OpenAL_Output::deinit() } -const CachedSound& OpenAL_Output::getBuffer(const std::string &fname) +std::vector OpenAL_Output::enumerateHrtf() { - ALuint buf = 0; + if(!mDevice) + fail("Device not initialized"); - NameMap::iterator iditer = mBufferCache.find(fname); - if(iditer != mBufferCache.end()) + std::vector ret; + if(!alcIsExtensionPresent(mDevice, "ALC_SOFT_HRTF")) + return ret; + + LPALCGETSTRINGISOFT alcGetStringiSOFT = 0; + getFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); + + ALCint num_hrtf; + alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); + ret.reserve(num_hrtf); + for(ALCint i = 0;i < num_hrtf;++i) { - buf = iditer->second.mALBuffer; - if(mBufferRefs[buf]++ == 0) + const ALCchar *entry = alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i); + ret.push_back(entry); + } + + return ret; +} + +void OpenAL_Output::enableHrtf(const std::string &hrtfname, bool auto_enable) +{ + if(!alcIsExtensionPresent(mDevice, "ALC_SOFT_HRTF")) + { + std::cerr<< "HRTF extension not present" < attrs; + attrs.push_back(ALC_HRTF_SOFT); + attrs.push_back(auto_enable ? ALC_DONT_CARE_SOFT : ALC_TRUE); + if(!hrtfname.empty()) + { + ALCint index = -1; + ALCint num_hrtf; + alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); + for(ALCint i = 0;i < num_hrtf;++i) { - IDDq::iterator iter = std::find(mUnusedBuffers.begin(), - mUnusedBuffers.end(), buf); - if(iter != mUnusedBuffers.end()) - mUnusedBuffers.erase(iter); + const ALCchar *entry = alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i); + if(hrtfname == entry) + { + index = i; + break; + } } - return iditer->second; + if(index < 0) + std::cerr<< "Failed to find HRTF name \""< attrs; + attrs.push_back(ALC_HRTF_SOFT); + attrs.push_back(ALC_FALSE); + attrs.push_back(0); + alcResetDeviceSOFT(mDevice, &attrs[0]); + + ALCint hrtf_state; + alcGetIntegerv(mDevice, ALC_HRTF_SOFT, 1, &hrtf_state); + if(hrtf_state) + std::cerr<< "Failed to disable HRTF" <mResourceMgr->exists(fname)) + decoder->open(fname); + else + { + std::string file = fname; + std::string::size_type pos = file.rfind('.'); + if(pos != std::string::npos) + file = file.substr(0, pos)+".mp3"; + decoder->open(file); + } + std::vector data; ChannelConfig chans; SampleType type; ALenum format; int srate; - DecoderPtr decoder = mManager.getDecoder(); - try - { - decoder->open(fname); - } - catch(Ogre::FileNotFoundException&) - { - std::string::size_type pos = fname.rfind('.'); - if(pos == std::string::npos) - throw; - decoder->open(fname.substr(0, pos)+".mp3"); - } - decoder->getInfo(&srate, &chans, &type); format = getALFormat(chans, type); decoder->readAll(data); decoder->close(); - CachedSound cached; - analyzeLoudness(data, srate, chans, type, cached.mLoudnessVector, static_cast(loudnessFPS)); + ALuint buf = 0; + try { + alGenBuffers(1, &buf); + alBufferData(buf, format, &data[0], data.size(), srate); + throwALerror(); + } + catch(...) { + if(buf && alIsBuffer(buf)) + alDeleteBuffers(1, &buf); + throw; + } + return MAKE_PTRID(buf); +} - alGenBuffers(1, &buf); +void OpenAL_Output::unloadSound(Sound_Handle data) +{ + ALuint buffer = GET_PTRID(data); + // Make sure no sources are playing this buffer before unloading it. + SoundVec::const_iterator iter = mActiveSounds.begin(); + for(;iter != mActiveSounds.end();++iter) + { + if(!(*iter)->mHandle) + continue; + + ALuint source = GET_PTRID((*iter)->mHandle); + ALint srcbuf; + alGetSourcei(source, AL_BUFFER, &srcbuf); + if((ALuint)srcbuf == buffer) + { + alSourceStop(source); + alSourcei(source, AL_BUFFER, 0); + } + } + alDeleteBuffers(1, &buffer); +} + +size_t OpenAL_Output::getSoundDataSize(Sound_Handle data) const +{ + ALuint buffer = GET_PTRID(data); + ALint size = 0; + + alGetBufferi(buffer, AL_SIZE, &size); throwALerror(); - alBufferData(buf, format, &data[0], data.size(), srate); - mBufferRefs[buf] = 1; - cached.mALBuffer = buf; - mBufferCache[fname] = cached; - - ALint bufsize = 0; - alGetBufferi(buf, AL_SIZE, &bufsize); - mBufferCacheMemSize += bufsize; - - // NOTE: Max buffer cache: 15MB - while(mBufferCacheMemSize > 15*1024*1024) - { - if(mUnusedBuffers.empty()) - { - std::cout <<"No more unused buffers to clear!"<< std::endl; - break; - } - - ALuint oldbuf = mUnusedBuffers.front(); - mUnusedBuffers.pop_front(); - - NameMap::iterator nameiter = mBufferCache.begin(); - while(nameiter != mBufferCache.end()) - { - if(nameiter->second.mALBuffer == oldbuf) - mBufferCache.erase(nameiter++); - else - ++nameiter; - } - - bufsize = 0; - alGetBufferi(oldbuf, AL_SIZE, &bufsize); - alDeleteBuffers(1, &oldbuf); - mBufferCacheMemSize -= bufsize; - } - - return mBufferCache[fname]; + return (ALuint)size; } -void OpenAL_Output::bufferFinished(ALuint buf) + +void OpenAL_Output::initCommon2D(ALuint source, const osg::Vec3f &pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv) { - if(mBufferRefs.at(buf)-- == 1) + alSourcef(source, AL_REFERENCE_DISTANCE, 1.0f); + alSourcef(source, AL_MAX_DISTANCE, 1000.0f); + alSourcef(source, AL_ROLLOFF_FACTOR, 0.0f); + alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE); + + if(useenv && mListenerEnv == Env_Underwater) { - mBufferRefs.erase(mBufferRefs.find(buf)); - mUnusedBuffers.push_back(buf); + gain *= 0.9f; + pitch *= 0.7f; } + + alSourcef(source, AL_GAIN, gain); + alSourcef(source, AL_PITCH, pitch); + alSourcefv(source, AL_POSITION, pos.ptr()); + alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); } -MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float vol, float basevol, float pitch, int flags,float offset) +void OpenAL_Output::initCommon3D(ALuint source, const osg::Vec3f &pos, ALfloat mindist, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool loop, bool useenv) { - boost::shared_ptr sound; - ALuint src=0, buf=0; + alSourcef(source, AL_REFERENCE_DISTANCE, mindist); + alSourcef(source, AL_MAX_DISTANCE, maxdist); + alSourcef(source, AL_ROLLOFF_FACTOR, 1.0f); + alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE); + alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE); + + if((pos - mListenerPos).length2() > maxdist*maxdist) + gain = 0.0f; + if(useenv && mListenerEnv == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + + alSourcef(source, AL_GAIN, gain); + alSourcef(source, AL_PITCH, pitch); + alSourcefv(source, AL_POSITION, pos.ptr()); + alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); +} + +void OpenAL_Output::updateCommon(ALuint source, const osg::Vec3f& pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv, bool is3d) +{ + if(is3d) + { + if((pos - mListenerPos).length2() > maxdist*maxdist) + gain = 0.0f; + } + if(useenv && mListenerEnv == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + + alSourcef(source, AL_GAIN, gain); + alSourcef(source, AL_PITCH, pitch); + alSourcefv(source, AL_POSITION, pos.ptr()); + alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); +} + + +void OpenAL_Output::playSound(MWBase::SoundPtr sound, Sound_Handle data, float offset) +{ + ALuint source; if(mFreeSources.empty()) fail("No free sources"); - src = mFreeSources.front(); + source = mFreeSources.front(); mFreeSources.pop_front(); - try - { - buf = getBuffer(fname).mALBuffer; - sound.reset(new OpenAL_Sound(*this, src, buf, Ogre::Vector3(0.0f), vol, basevol, pitch, 1.0f, 1000.0f, flags)); + try { + initCommon2D(source, sound->getPosition(), sound->getRealVolume(), sound->getPitch(), + sound->getIsLooping(), sound->getUseEnv()); + + alSourcef(source, AL_SEC_OFFSET, offset); + throwALerror(); + + alSourcei(source, AL_BUFFER, GET_PTRID(data)); + alSourcePlay(source); + throwALerror(); + + mActiveSounds.push_back(sound); } - catch(std::exception&) - { - mFreeSources.push_back(src); - if(buf && alIsBuffer(buf)) - bufferFinished(buf); - alGetError(); + catch(std::exception&) { + mFreeSources.push_back(source); throw; } - sound->updateAll(true); - if(offset<0) - offset=0; - if(offset>1) - offset=1; - - alSourcei(src, AL_BUFFER, buf); - alSourcef(src, AL_SEC_OFFSET, static_cast(sound->getLength()*offset / pitch)); - alSourcePlay(src); - throwALerror(); - - return sound; + sound->mHandle = MAKE_PTRID(source); } -MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float vol, float basevol, float pitch, - float min, float max, int flags, float offset, bool extractLoudness) +void OpenAL_Output::playSound3D(MWBase::SoundPtr sound, Sound_Handle data, float offset) { - boost::shared_ptr sound; - ALuint src=0, buf=0; + ALuint source; if(mFreeSources.empty()) fail("No free sources"); - src = mFreeSources.front(); + source = mFreeSources.front(); mFreeSources.pop_front(); - try - { - const CachedSound& cached = getBuffer(fname); - buf = cached.mALBuffer; + try { + initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), + sound->getRealVolume(), sound->getPitch(), sound->getIsLooping(), + sound->getUseEnv()); - sound.reset(new OpenAL_Sound3D(*this, src, buf, pos, vol, basevol, pitch, min, max, flags)); - if (extractLoudness) - sound->setLoudnessVector(cached.mLoudnessVector, static_cast(loudnessFPS)); + alSourcef(source, AL_SEC_OFFSET, offset); + throwALerror(); + + alSourcei(source, AL_BUFFER, GET_PTRID(data)); + alSourcePlay(source); + throwALerror(); + + mActiveSounds.push_back(sound); } - catch(std::exception&) - { - mFreeSources.push_back(src); - if(buf && alIsBuffer(buf)) - bufferFinished(buf); - alGetError(); + catch(std::exception&) { + mFreeSources.push_back(source); throw; } - sound->updateAll(false); + sound->mHandle = MAKE_PTRID(source); +} - if(offset<0) - offset=0; - if(offset>1) - offset=1; +void OpenAL_Output::finishSound(MWBase::SoundPtr sound) +{ + if(!sound->mHandle) return; + ALuint source = GET_PTRID(sound->mHandle); + sound->mHandle = 0; - alSourcei(src, AL_BUFFER, buf); - alSourcef(src, AL_SEC_OFFSET, static_cast(sound->getLength()*offset / pitch)); + alSourceStop(source); + alSourcei(source, AL_BUFFER, 0); - alSourcePlay(src); + mFreeSources.push_back(source); + mActiveSounds.erase(std::find(mActiveSounds.begin(), mActiveSounds.end(), sound)); +} + +bool OpenAL_Output::isSoundPlaying(MWBase::SoundPtr sound) +{ + if(!sound->mHandle) return false; + ALuint source = GET_PTRID(sound->mHandle); + ALint state; + + alGetSourcei(source, AL_SOURCE_STATE, &state); throwALerror(); - return sound; + return state == AL_PLAYING || state == AL_PAUSED; +} + +void OpenAL_Output::updateSound(MWBase::SoundPtr sound) +{ + if(!sound->mHandle) return; + ALuint source = GET_PTRID(sound->mHandle); + + updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), + sound->getPitch(), sound->getUseEnv(), sound->getIs3D()); } -MWBase::SoundPtr OpenAL_Output::streamSound(DecoderPtr decoder, float volume, float pitch, int flags) +void OpenAL_Output::streamSound(DecoderPtr decoder, MWBase::SoundStreamPtr sound) { - boost::shared_ptr sound; - ALuint src; + OpenAL_SoundStream *stream = 0; + ALuint source; if(mFreeSources.empty()) fail("No free sources"); - src = mFreeSources.front(); + source = mFreeSources.front(); mFreeSources.pop_front(); - if((flags&MWBase::SoundManager::Play_Loop)) + if(sound->getIsLooping()) std::cout <<"Warning: cannot loop stream \""<getName()<<"\""<< std::endl; - try - { - sound.reset(new OpenAL_SoundStream(*this, src, decoder, volume, pitch, flags)); + try { + initCommon2D(source, sound->getPosition(), sound->getRealVolume(), sound->getPitch(), + false, sound->getUseEnv()); + throwALerror(); + + stream = new OpenAL_SoundStream(source, decoder); + mStreamThread->add(stream); + mActiveStreams.push_back(sound); } - catch(std::exception&) - { - mFreeSources.push_back(src); + catch(std::exception&) { + mStreamThread->remove(stream); + delete stream; + mFreeSources.push_back(source); throw; } - sound->updateAll(true); + sound->mHandle = stream; +} - sound->play(); - return sound; +void OpenAL_Output::streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound) +{ + OpenAL_SoundStream *stream = 0; + ALuint source; + + if(mFreeSources.empty()) + fail("No free sources"); + source = mFreeSources.front(); + mFreeSources.pop_front(); + + if(sound->getIsLooping()) + std::cout <<"Warning: cannot loop stream \""<getName()<<"\""<< std::endl; + try { + initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), + sound->getRealVolume(), sound->getPitch(), false, sound->getUseEnv()); + throwALerror(); + + stream = new OpenAL_SoundStream(source, decoder); + mStreamThread->add(stream); + mActiveStreams.push_back(sound); + } + catch(std::exception&) { + mStreamThread->remove(stream); + delete stream; + mFreeSources.push_back(source); + throw; + } + + sound->mHandle = stream; +} + +void OpenAL_Output::finishStream(MWBase::SoundStreamPtr sound) +{ + if(!sound->mHandle) return; + OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); + ALuint source = stream->mSource; + + sound->mHandle = 0; + mStreamThread->remove(stream); + + alSourceStop(source); + alSourcei(source, AL_BUFFER, 0); + + mFreeSources.push_back(source); + mActiveStreams.erase(std::find(mActiveStreams.begin(), mActiveStreams.end(), sound)); + + delete stream; +} + +double OpenAL_Output::getStreamDelay(MWBase::SoundStreamPtr sound) +{ + if(!sound->mHandle) return 0.0; + OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); + return stream->getStreamDelay(); +} + +double OpenAL_Output::getStreamOffset(MWBase::SoundStreamPtr sound) +{ + if(!sound->mHandle) return 0.0; + OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); + boost::lock_guard lock(mStreamThread->mMutex); + return stream->getStreamOffset(); +} + +bool OpenAL_Output::isStreamPlaying(MWBase::SoundStreamPtr sound) +{ + if(!sound->mHandle) return false; + OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); + boost::lock_guard lock(mStreamThread->mMutex); + return stream->isPlaying(); +} + +void OpenAL_Output::updateStream(MWBase::SoundStreamPtr sound) +{ + if(!sound->mHandle) return; + OpenAL_SoundStream *stream = reinterpret_cast(sound->mHandle); + ALuint source = stream->mSource; + + updateCommon(source, sound->getPosition(), sound->getMaxDistance(), sound->getRealVolume(), + sound->getPitch(), sound->getUseEnv(), sound->getIs3D()); } -void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) +void OpenAL_Output::startUpdate() { - mPos = pos; - mLastEnvironment = env; + alcSuspendContext(alcGetCurrentContext()); +} +void OpenAL_Output::finishUpdate() +{ + alcProcessContext(alcGetCurrentContext()); +} + + +void OpenAL_Output::updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdir, const osg::Vec3f &updir, Environment env) +{ if(mContext) { ALfloat orient[6] = { - atdir.x, atdir.y, atdir.z, - updir.x, updir.y, updir.z + atdir.x(), atdir.y(), atdir.z(), + updir.x(), updir.y(), updir.z() }; - alListener3f(AL_POSITION, mPos.x, mPos.y, mPos.z); + alListenerfv(AL_POSITION, pos.ptr()); alListenerfv(AL_ORIENTATION, orient); throwALerror(); } + + mListenerPos = pos; + mListenerEnv = env; } void OpenAL_Output::pauseSounds(int types) { std::vector sources; - SoundVec::const_iterator iter = mActiveSounds.begin(); - while(iter != mActiveSounds.end()) + SoundVec::const_iterator sound = mActiveSounds.begin(); + for(;sound != mActiveSounds.end();++sound) { - const OpenAL_SoundStream *stream = dynamic_cast(*iter); - if(stream) + if(*sound && (*sound)->mHandle && ((*sound)->getPlayType()&types)) + sources.push_back(GET_PTRID((*sound)->mHandle)); + } + StreamVec::const_iterator stream = mActiveStreams.begin(); + for(;stream != mActiveStreams.end();++stream) + { + if(*stream && (*stream)->mHandle && ((*stream)->getPlayType()&types)) { - if(stream->mSource && (stream->getPlayType()&types)) - sources.push_back(stream->mSource); + OpenAL_SoundStream *strm = reinterpret_cast((*stream)->mHandle); + sources.push_back(strm->mSource); } - else - { - const OpenAL_Sound *sound = dynamic_cast(*iter); - if(sound && sound->mSource && (sound->getPlayType()&types)) - sources.push_back(sound->mSource); - } - ++iter; } if(!sources.empty()) { @@ -1015,22 +1121,20 @@ void OpenAL_Output::pauseSounds(int types) void OpenAL_Output::resumeSounds(int types) { std::vector sources; - SoundVec::const_iterator iter = mActiveSounds.begin(); - while(iter != mActiveSounds.end()) + SoundVec::const_iterator sound = mActiveSounds.begin(); + for(;sound != mActiveSounds.end();++sound) { - const OpenAL_SoundStream *stream = dynamic_cast(*iter); - if(stream) + if(*sound && (*sound)->mHandle && ((*sound)->getPlayType()&types)) + sources.push_back(GET_PTRID((*sound)->mHandle)); + } + StreamVec::const_iterator stream = mActiveStreams.begin(); + for(;stream != mActiveStreams.end();++stream) + { + if(*stream && (*stream)->mHandle && ((*stream)->getPlayType()&types)) { - if(stream->mSource && (stream->getPlayType()&types)) - sources.push_back(stream->mSource); + OpenAL_SoundStream *strm = reinterpret_cast((*stream)->mHandle); + sources.push_back(strm->mSource); } - else - { - const OpenAL_Sound *sound = dynamic_cast(*iter); - if(sound && sound->mSource && (sound->getPlayType()&types)) - sources.push_back(sound->mSource); - } - ++iter; } if(!sources.empty()) { @@ -1040,9 +1144,16 @@ void OpenAL_Output::resumeSounds(int types) } +void OpenAL_Output::loadLoudnessAsync(DecoderPtr decoder, Sound_Loudness *loudness) +{ + mStreamThread->add(decoder, loudness); +} + + OpenAL_Output::OpenAL_Output(SoundManager &mgr) - : Sound_Output(mgr), mDevice(0), mContext(0), mBufferCacheMemSize(0), - mLastEnvironment(Env_Normal), mStreamThread(new StreamThread) + : Sound_Output(mgr), mDevice(0), mContext(0) + , mListenerPos(0.0f, 0.0f, 0.0f), mListenerEnv(Env_Normal) + , mStreamThread(new StreamThread) { } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 1a95d61505..4986cd3a03 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -16,12 +16,6 @@ namespace MWSound class SoundManager; class Sound; - struct CachedSound - { - ALuint mALBuffer; - std::vector mLoudnessVector; - }; - class OpenAL_Output : public Sound_Output { ALCdevice *mDevice; @@ -29,53 +23,65 @@ namespace MWSound typedef std::deque IDDq; IDDq mFreeSources; - IDDq mUnusedBuffers; - typedef std::map NameMap; - NameMap mBufferCache; - - typedef std::map IDRefMap; - IDRefMap mBufferRefs; - - uint64_t mBufferCacheMemSize; - - typedef std::vector SoundVec; + typedef std::vector SoundVec; SoundVec mActiveSounds; + typedef std::vector StreamVec; + StreamVec mActiveStreams; - const CachedSound& getBuffer(const std::string &fname); - void bufferFinished(ALuint buffer); - - Environment mLastEnvironment; - - virtual std::vector enumerate(); - virtual void init(const std::string &devname=""); - virtual void deinit(); - - /// @param offset Value from [0,1] meaning from which fraction the sound the playback starts. - virtual MWBase::SoundPtr playSound(const std::string &fname, float vol, float basevol, float pitch, int flags, float offset); - /// @param offset Value from [0,1] meaning from which fraction the sound the playback starts. - virtual MWBase::SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float vol, float basevol, float pitch, float min, float max, int flags, float offset, bool extractLoudness=false); - virtual MWBase::SoundPtr streamSound(DecoderPtr decoder, float volume, float pitch, int flags); - - virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env); - - virtual void pauseSounds(int types); - virtual void resumeSounds(int types); - - OpenAL_Output& operator=(const OpenAL_Output &rhs); - OpenAL_Output(const OpenAL_Output &rhs); - - OpenAL_Output(SoundManager &mgr); - virtual ~OpenAL_Output(); + osg::Vec3f mListenerPos; + Environment mListenerEnv; struct StreamThread; std::auto_ptr mStreamThread; - friend class OpenAL_Sound; - friend class OpenAL_Sound3D; - friend class OpenAL_SoundStream; - friend class SoundManager; + void initCommon2D(ALuint source, const osg::Vec3f &pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv); + void initCommon3D(ALuint source, const osg::Vec3f &pos, ALfloat mindist, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool loop, bool useenv); + + void updateCommon(ALuint source, const osg::Vec3f &pos, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool useenv, bool is3d); + + OpenAL_Output& operator=(const OpenAL_Output &rhs); + OpenAL_Output(const OpenAL_Output &rhs); + + public: + virtual std::vector enumerate(); + virtual void init(const std::string &devname=std::string()); + virtual void deinit(); + + virtual std::vector enumerateHrtf(); + virtual void enableHrtf(const std::string &hrtfname, bool auto_enable); + virtual void disableHrtf(); + + virtual Sound_Handle loadSound(const std::string &fname); + virtual void unloadSound(Sound_Handle data); + virtual size_t getSoundDataSize(Sound_Handle data) const; + + virtual void playSound(MWBase::SoundPtr sound, Sound_Handle data, float offset); + virtual void playSound3D(MWBase::SoundPtr sound, Sound_Handle data, float offset); + virtual void finishSound(MWBase::SoundPtr sound); + virtual bool isSoundPlaying(MWBase::SoundPtr sound); + virtual void updateSound(MWBase::SoundPtr sound); + + virtual void streamSound(DecoderPtr decoder, MWBase::SoundStreamPtr sound); + virtual void streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound); + virtual void finishStream(MWBase::SoundStreamPtr sound); + virtual double getStreamDelay(MWBase::SoundStreamPtr sound); + virtual double getStreamOffset(MWBase::SoundStreamPtr sound); + virtual bool isStreamPlaying(MWBase::SoundStreamPtr sound); + virtual void updateStream(MWBase::SoundStreamPtr sound); + + virtual void startUpdate(); + virtual void finishUpdate(); + + virtual void updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdir, const osg::Vec3f &updir, Environment env); + + virtual void pauseSounds(int types); + virtual void resumeSounds(int types); + + virtual void loadLoudnessAsync(DecoderPtr decoder, Sound_Loudness *loudness); + + OpenAL_Output(SoundManager &mgr); + virtual ~OpenAL_Output(); }; #ifndef DEFAULT_OUTPUT #define DEFAULT_OUTPUT(x) ::MWSound::OpenAL_Output((x)) diff --git a/apps/openmw/mwsound/sound.cpp b/apps/openmw/mwsound/sound.cpp deleted file mode 100644 index 8b6bfda822..0000000000 --- a/apps/openmw/mwsound/sound.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "sound.hpp" - -namespace MWSound -{ - - float Sound::getCurrentLoudness() - { - if (mLoudnessVector.empty()) - return 0.f; - int index = static_cast(getTimeOffset() * mLoudnessFPS); - - index = std::max(0, std::min(index, int(mLoudnessVector.size()-1))); - - return mLoudnessVector[index]; - } - - void Sound::setLoudnessVector(const std::vector &loudnessVector, float loudnessFPS) - { - mLoudnessVector = loudnessVector; - mLoudnessFPS = loudnessFPS; - } - -} diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 1b5c001966..a59027fc12 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -1,64 +1,81 @@ #ifndef GAME_SOUND_SOUND_H #define GAME_SOUND_SOUND_H -#include - -#include "soundmanagerimp.hpp" +#include "sound_output.hpp" namespace MWSound { - class Sound - { - virtual void update() = 0; - + class Sound { Sound& operator=(const Sound &rhs); Sound(const Sound &rhs); - protected: - Ogre::Vector3 mPos; + osg::Vec3f mPos; float mVolume; /* NOTE: Real volume = mVolume*mBaseVolume */ float mBaseVolume; float mPitch; float mMinDistance; float mMaxDistance; int mFlags; + float mFadeOutTime; - std::vector mLoudnessVector; - float mLoudnessFPS; + protected: + Sound_Instance mHandle; + + friend class OpenAL_Output; public: - virtual void stop() = 0; - virtual bool isPlaying() = 0; - virtual double getTimeOffset() = 0; - void setPosition(const Ogre::Vector3 &pos) { mPos = pos; } + void setPosition(const osg::Vec3f &pos) { mPos = pos; } void setVolume(float volume) { mVolume = volume; } - void setFadeout(float duration) { mFadeOutTime=duration; } - void setLoudnessVector(const std::vector& loudnessVector, float loudnessFPS); + void setBaseVolume(float volume) { mBaseVolume = volume; } + void setFadeout(float duration) { mFadeOutTime = duration; } + void updateFade(float duration) + { + if(mFadeOutTime > 0.0f) + { + float soundDuration = std::min(duration, mFadeOutTime); + mVolume *= (mFadeOutTime-soundDuration) / mFadeOutTime; + mFadeOutTime -= soundDuration; + } + } - /// Get loudness at the current time position on a [0,1] scale. - /// Requires that loudnessVector was filled in by the user. - float getCurrentLoudness(); + const osg::Vec3f &getPosition() const { return mPos; } + float getRealVolume() const { return mVolume * mBaseVolume; } + float getPitch() const { return mPitch; } + float getMinDistance() const { return mMinDistance; } + float getMaxDistance() const { return mMaxDistance; } MWBase::SoundManager::PlayType getPlayType() const { return (MWBase::SoundManager::PlayType)(mFlags&MWBase::SoundManager::Play_TypeMask); } + bool getUseEnv() const { return !(mFlags&MWBase::SoundManager::Play_NoEnv); } + bool getIsLooping() const { return mFlags&MWBase::SoundManager::Play_Loop; } + bool getDistanceCull() const { return mFlags&MWBase::SoundManager::Play_RemoveAtDistance; } + bool getIs3D() const { return mFlags&Play_3D; } - - Sound(const Ogre::Vector3& pos, float vol, float basevol, float pitch, float mindist, float maxdist, int flags) - : mPos(pos) - , mVolume(vol) - , mBaseVolume(basevol) - , mPitch(pitch) - , mMinDistance(mindist) - , mMaxDistance(maxdist) - , mFlags(flags) - , mFadeOutTime(0) - , mLoudnessFPS(20) + Sound(const osg::Vec3f& pos, float vol, float basevol, float pitch, float mindist, float maxdist, int flags) + : mPos(pos), mVolume(vol), mBaseVolume(basevol), mPitch(pitch) + , mMinDistance(mindist), mMaxDistance(maxdist), mFlags(flags) + , mFadeOutTime(0.0f), mHandle(0) { } - virtual ~Sound() { } + Sound(float vol, float basevol, float pitch, int flags) + : mPos(0.0f, 0.0f, 0.0f), mVolume(vol), mBaseVolume(basevol), mPitch(pitch) + , mMinDistance(1.0f), mMaxDistance(1000.0f), mFlags(flags) + , mFadeOutTime(0.0f), mHandle(0) + { } + }; - friend class OpenAL_Output; - friend class SoundManager; + // Same as above, but it's a different type since the output handles them differently + class Stream : public Sound { + Stream& operator=(const Stream &rhs); + Stream(const Stream &rhs); + + public: + Stream(const osg::Vec3f& pos, float vol, float basevol, float pitch, float mindist, float maxdist, int flags) + : Sound(pos, vol, basevol, pitch, mindist, maxdist, flags) + { } + Stream(float vol, float basevol, float pitch, int flags) + : Sound(vol, basevol, pitch, flags) + { } }; } diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp new file mode 100644 index 0000000000..8f8e43da4b --- /dev/null +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -0,0 +1,32 @@ +#ifndef GAME_SOUND_SOUND_BUFFER_H +#define GAME_SOUND_SOUND_BUFFER_H + +#include + +#include "soundmanagerimp.hpp" +#include "sound_output.hpp" +#include "loudness.hpp" + +#include "../mwworld/ptr.hpp" + +namespace MWSound +{ + class Sound_Buffer + { + public: + std::string mResourceName; + + float mVolume; + float mMinDist, mMaxDist; + + Sound_Handle mHandle; + + size_t mUses; + + Sound_Buffer(std::string resname, float volume, float mindist, float maxdist) + : mResourceName(resname), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist), mHandle(0), mUses(0) + { } + }; +} + +#endif /* GAME_SOUND_SOUND_BUFFER_H */ diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index 151b580360..1be9dd3749 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -2,8 +2,12 @@ #define GAME_SOUND_SOUND_DECODER_H #include +#include -#include +namespace VFS +{ + class Manager; +} namespace MWSound { @@ -28,7 +32,7 @@ namespace MWSound struct Sound_Decoder { - Ogre::ResourceGroupManager &mResourceMgr; + const VFS::Manager* mResourceMgr; virtual void open(const std::string &fname) = 0; virtual void close() = 0; @@ -41,7 +45,7 @@ namespace MWSound virtual void rewind() = 0; virtual size_t getSampleOffset() = 0; - Sound_Decoder() : mResourceMgr(Ogre::ResourceGroupManager::getSingleton()) + Sound_Decoder(const VFS::Manager* resourceMgr) : mResourceMgr(resourceMgr) { } virtual ~Sound_Decoder() { } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 4f5c210bbe..98eba8466c 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -3,50 +3,73 @@ #include #include - -#include +#include #include "soundmanagerimp.hpp" -#include "../mwworld/ptr.hpp" - namespace MWSound { class SoundManager; struct Sound_Decoder; class Sound; + class Sound_Loudness; + + // An opaque handle for the implementation's sound buffers. + typedef void *Sound_Handle; + // An opaque handle for the implementation's sound instances. + typedef void *Sound_Instance; class Sound_Output { SoundManager &mManager; virtual std::vector enumerate() = 0; - virtual void init(const std::string &devname="") = 0; + virtual void init(const std::string &devname=std::string()) = 0; virtual void deinit() = 0; - /// @param offset Value from [0,1] meaning from which fraction the sound the playback starts. - virtual MWBase::SoundPtr playSound(const std::string &fname, float vol, float basevol, float pitch, int flags, float offset) = 0; - /// @param offset Value from [0,1] meaning from which fraction the sound the playback starts. - virtual MWBase::SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float vol, float basevol, float pitch, float min, float max, int flags, float offset, bool extractLoudness=false) = 0; - virtual MWBase::SoundPtr streamSound(DecoderPtr decoder, float volume, float pitch, int flags) = 0; + virtual std::vector enumerateHrtf() = 0; + virtual void enableHrtf(const std::string &hrtfname, bool auto_enable) = 0; + virtual void disableHrtf() = 0; - virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) = 0; + virtual Sound_Handle loadSound(const std::string &fname) = 0; + virtual void unloadSound(Sound_Handle data) = 0; + virtual size_t getSoundDataSize(Sound_Handle data) const = 0; + + virtual void playSound(MWBase::SoundPtr sound, Sound_Handle data, float offset) = 0; + virtual void playSound3D(MWBase::SoundPtr sound, Sound_Handle data, float offset) = 0; + virtual void finishSound(MWBase::SoundPtr sound) = 0; + virtual bool isSoundPlaying(MWBase::SoundPtr sound) = 0; + virtual void updateSound(MWBase::SoundPtr sound) = 0; + + virtual void streamSound(DecoderPtr decoder, MWBase::SoundStreamPtr sound) = 0; + virtual void streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound) = 0; + virtual void finishStream(MWBase::SoundStreamPtr sound) = 0; + virtual double getStreamDelay(MWBase::SoundStreamPtr sound) = 0; + virtual double getStreamOffset(MWBase::SoundStreamPtr sound) = 0; + virtual bool isStreamPlaying(MWBase::SoundStreamPtr sound) = 0; + virtual void updateStream(MWBase::SoundStreamPtr sound) = 0; + + virtual void startUpdate() = 0; + virtual void finishUpdate() = 0; + + virtual void updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdir, const osg::Vec3f &updir, Environment env) = 0; virtual void pauseSounds(int types) = 0; virtual void resumeSounds(int types) = 0; + // HACK: The sound output implementation really shouldn't be handling + // asynchronous loudness data loading, but it's currently where we have + // a background processing thread. + virtual void loadLoudnessAsync(DecoderPtr decoder, Sound_Loudness *loudness) = 0; + Sound_Output& operator=(const Sound_Output &rhs); Sound_Output(const Sound_Output &rhs); protected: bool mInitialized; - Ogre::Vector3 mPos; Sound_Output(SoundManager &mgr) - : mManager(mgr) - , mInitialized(false) - , mPos(0.0f, 0.0f, 0.0f) + : mManager(mgr), mInitialized(false) { } public: virtual ~Sound_Output() { } diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index c7fb9ea506..e8ceaa40f5 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -4,7 +4,11 @@ #include #include -#include +#include + +#include + +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -13,7 +17,10 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "sound_output.hpp" +#include "sound_buffer.hpp" #include "sound_decoder.hpp" #include "sound.hpp" @@ -27,23 +34,22 @@ namespace MWSound { - SoundManager::SoundManager(bool useSound) - : mResourceMgr(Ogre::ResourceGroupManager::getSingleton()) + SoundManager::SoundManager(const VFS::Manager* vfs, bool useSound) + : mVFS(vfs) , mOutput(new DEFAULT_OUTPUT(*this)) , mMasterVolume(1.0f) , mSFXVolume(1.0f) , mMusicVolume(1.0f) , mVoiceVolume(1.0f) , mFootstepsVolume(1.0f) + , mSoundBuffers(new SoundBufferList::element_type()) + , mBufferCacheSize(0) , mListenerUnderwater(false) , mListenerPos(0,0,0) , mListenerDir(1,0,0) , mListenerUp(0,0,1) , mPausedSoundTypes(0) { - if(!useSound) - return; - mMasterVolume = Settings::Manager::getFloat("master volume", "Sound"); mMasterVolume = std::min(std::max(mMasterVolume, 0.0f), 1.0f); mSFXVolume = Settings::Manager::getFloat("sfx volume", "Sound"); @@ -55,84 +61,224 @@ namespace MWSound mFootstepsVolume = Settings::Manager::getFloat("footsteps volume", "Sound"); mFootstepsVolume = std::min(std::max(mFootstepsVolume, 0.0f), 1.0f); + mBufferCacheMin = std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1); + mBufferCacheMax = std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1); + mBufferCacheMax *= 1024*1024; + mBufferCacheMin = std::min(mBufferCacheMin*1024*1024, mBufferCacheMax); + + if(!useSound) + return; + + std::string hrtfname = Settings::Manager::getString("hrtf", "Sound"); + int hrtfstate = Settings::Manager::getInt("hrtf enable", "Sound"); + std::cout << "Sound output: " << SOUND_OUT << std::endl; std::cout << "Sound decoder: " << SOUND_IN << std::endl; - try - { + try { std::vector names = mOutput->enumerate(); std::cout <<"Enumerated output devices:"<< std::endl; for(size_t i = 0;i < names.size();i++) std::cout <<" "<init(devname); } - catch(std::exception &e) - { + catch(std::exception &e) { if(devname.empty()) throw; std::cerr <<"Failed to open device \""<init(); Settings::Manager::setString("device", "Sound", ""); } + + names = mOutput->enumerateHrtf(); + if(!names.empty()) + { + std::cout <<"Enumerated HRTF names:"<< std::endl; + for(size_t i = 0;i < names.size();i++) + std::cout <<" "<disableHrtf(); + else if(!hrtfname.empty()) + mOutput->enableHrtf(hrtfname, hrtfstate<0); } - catch(std::exception &e) - { + catch(std::exception &e) { std::cout <<"Sound init failed: "<begin(); + for(;sfxiter != mSoundBuffers->end();++sfxiter) + { + if(sfxiter->mHandle) + mOutput->unloadSound(sfxiter->mHandle); + sfxiter->mHandle = 0; + } + mUnusedBuffers.clear(); mOutput.reset(); } // Return a new decoder instance, used as needed by the output implementations DecoderPtr SoundManager::getDecoder() { - return DecoderPtr(new DEFAULT_DECODER); + return DecoderPtr(new DEFAULT_DECODER (mVFS)); } - // Convert a soundId to file name, and modify the volume - // according to the sounds local volume setting, minRange and - // maxRange. - std::string SoundManager::lookup(const std::string &soundId, - float &volume, float &min, float &max) + Sound_Buffer *SoundManager::insertSound(const std::string &soundId, const ESM::Sound *sound) { MWBase::World* world = MWBase::Environment::get().getWorld(); - const ESM::Sound *snd = world->getStore().get().find(soundId); + static const float fAudioDefaultMinDistance = world->getStore().get().find("fAudioDefaultMinDistance")->getFloat(); + static const float fAudioDefaultMaxDistance = world->getStore().get().find("fAudioDefaultMaxDistance")->getFloat(); + static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->getFloat(); + static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->getFloat(); + float volume, min, max; - volume *= static_cast(pow(10.0, (snd->mData.mVolume / 255.0*3348.0 - 3348.0) / 2000.0)); - - if(snd->mData.mMinRange == 0 && snd->mData.mMaxRange == 0) + volume = static_cast(pow(10.0, (sound->mData.mVolume / 255.0*3348.0 - 3348.0) / 2000.0)); + if(sound->mData.mMinRange == 0 && sound->mData.mMaxRange == 0) { - static const float fAudioDefaultMinDistance = world->getStore().get().find("fAudioDefaultMinDistance")->getFloat(); - static const float fAudioDefaultMaxDistance = world->getStore().get().find("fAudioDefaultMaxDistance")->getFloat(); min = fAudioDefaultMinDistance; max = fAudioDefaultMaxDistance; } else { - min = snd->mData.mMinRange; - max = snd->mData.mMaxRange; + min = sound->mData.mMinRange; + max = sound->mData.mMaxRange; } - static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->getFloat(); - static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->getFloat(); min *= fAudioMinDistanceMult; max *= fAudioMaxDistanceMult; min = std::max(min, 1.0f); max = std::max(min, max); - return "Sound/"+snd->mSound; + Sound_Buffer *sfx = &*mSoundBuffers->insert(mSoundBuffers->end(), + Sound_Buffer("Sound/"+sound->mSound, volume, min, max) + ); + mVFS->normalizeFilename(sfx->mResourceName); + + mBufferNameMap.insert(std::make_pair(soundId, sfx)); + + return sfx; } + // Lookup a soundId for its sound data (resource name, local volume, + // minRange, and maxRange) + Sound_Buffer *SoundManager::lookupSound(const std::string &soundId) const + { + NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId); + if(snd != mBufferNameMap.end()) return snd->second; + return 0; + } + + // Lookup a soundId for its sound data (resource name, local volume, + // minRange, and maxRange), and ensure it's ready for use. + Sound_Buffer *SoundManager::loadSound(const std::string &soundId) + { + Sound_Buffer *sfx; + NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId); + if(snd != mBufferNameMap.end()) + sfx = snd->second; + else + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + const ESM::Sound *sound = world->getStore().get().find(soundId); + sfx = insertSound(soundId, sound); + } + + if(!sfx->mHandle) + { + sfx->mHandle = mOutput->loadSound(sfx->mResourceName); + mBufferCacheSize += mOutput->getSoundDataSize(sfx->mHandle); + + if(mBufferCacheSize > mBufferCacheMax) + { + do { + if(mUnusedBuffers.empty()) + { + std::cerr<< "No unused sound buffers to free, using "<getSoundDataSize(unused->mHandle); + mOutput->unloadSound(unused->mHandle); + unused->mHandle = 0; + + mUnusedBuffers.pop_back(); + } while(mBufferCacheSize > mBufferCacheMin); + } + mUnusedBuffers.push_front(sfx); + } + + return sfx; + } + + DecoderPtr SoundManager::loadVoice(const std::string &voicefile, Sound_Loudness **lipdata) + { + DecoderPtr decoder = getDecoder(); + // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. + if(mVFS->exists(voicefile)) + decoder->open(voicefile); + else + { + std::string file = voicefile; + std::string::size_type pos = file.rfind('.'); + if(pos != std::string::npos) + file = file.substr(0, pos)+".mp3"; + decoder->open(file); + } + + NameLoudnessRefMap::iterator lipiter = mVoiceLipNameMap.find(voicefile); + if(lipiter != mVoiceLipNameMap.end()) + { + *lipdata = lipiter->second; + return decoder; + } + + mVoiceLipBuffers.insert(mVoiceLipBuffers.end(), Sound_Loudness()); + lipiter = mVoiceLipNameMap.insert( + std::make_pair(voicefile, &mVoiceLipBuffers.back()) + ).first; + + mOutput->loadLoudnessAsync(decoder, lipiter->second); + + *lipdata = lipiter->second; + return decoder; + } + + MWBase::SoundStreamPtr SoundManager::playVoice(DecoderPtr decoder, const osg::Vec3f &pos, bool playlocal) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->getFloat(); + static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->getFloat(); + static const float fAudioVoiceDefaultMinDistance = world->getStore().get().find("fAudioVoiceDefaultMinDistance")->getFloat(); + static const float fAudioVoiceDefaultMaxDistance = world->getStore().get().find("fAudioVoiceDefaultMaxDistance")->getFloat(); + static float minDistance = std::max(fAudioVoiceDefaultMinDistance * fAudioMinDistanceMult, 1.0f); + static float maxDistance = std::max(fAudioVoiceDefaultMaxDistance * fAudioMaxDistanceMult, minDistance); + + MWBase::SoundStreamPtr sound; + float basevol = volumeFromType(Play_TypeVoice); + if(playlocal) + { + sound.reset(new Stream(1.0f, basevol, 1.0f, Play_Normal|Play_TypeVoice|Play_2D)); + mOutput->streamSound(decoder, sound); + } + else + { + sound.reset(new Stream(pos, 1.0f, basevol, 1.0f, minDistance, maxDistance, + Play_Normal|Play_TypeVoice|Play_3D)); + mOutput->streamSound3D(decoder, sound); + } + return sound; + } + + // Gets the combined volume settings for the given sound type float SoundManager::volumeFromType(PlayType type) const { @@ -159,23 +305,11 @@ namespace MWSound return volume; } - bool SoundManager::isPlaying(const MWWorld::Ptr &ptr, const std::string &id) const - { - SoundMap::const_iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) - { - if(snditer->second.first == ptr && snditer->second.second == id) - return snditer->first->isPlaying(); - ++snditer; - } - return false; - } - void SoundManager::stopMusic() { if(mMusic) - mMusic->stop(); + mOutput->finishStream(mMusic); mMusic.reset(); } @@ -185,19 +319,19 @@ namespace MWSound return; std::cout <<"Playing "<open(filename); - mMusic = mOutput->streamSound(decoder, volumeFromType(Play_TypeMusic), - 1.0f, Play_NoEnv|Play_TypeMusic); + mMusic.reset(new Stream(1.0f, volumeFromType(Play_TypeMusic), 1.0f, + Play_NoEnv|Play_TypeMusic|Play_2D)); + mOutput->streamSound(decoder, mMusic); } - catch(std::exception &e) - { + catch(std::exception &e) { std::cout << "Music Error: " << e.what() << "\n"; + mMusic.reset(); } } @@ -208,17 +342,26 @@ namespace MWSound void SoundManager::startRandomTitle() { - Ogre::StringVector filelist; + std::vector filelist; if (mMusicFiles.find(mCurrentPlaylist) == mMusicFiles.end()) { - Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups (); - for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it) + const std::map& index = mVFS->getIndex(); + + std::string pattern = "Music/" + mCurrentPlaylist; + mVFS->normalizeFilename(pattern); + + std::map::const_iterator found = index.lower_bound(pattern); + while (found != index.end()) { - Ogre::StringVectorPtr resourcesInThisGroup = mResourceMgr.findResourceNames(*it, - "Music/"+mCurrentPlaylist+"/*"); - filelist.insert(filelist.end(), resourcesInThisGroup->begin(), resourcesInThisGroup->end()); + if (found->first.size() >= pattern.size() && found->first.substr(0, pattern.size()) == pattern) + filelist.push_back(found->first); + else + break; + ++found; } + mMusicFiles[mCurrentPlaylist] = filelist; + } else filelist = mMusicFiles[mCurrentPlaylist]; @@ -226,7 +369,7 @@ namespace MWSound if(!filelist.size()) return; - int i = OEngine::Misc::Rng::rollDice(filelist.size()); + int i = Misc::Rng::rollDice(filelist.size()); // Don't play the same music track twice in a row if (filelist[i] == mLastPlayedMusic) @@ -239,7 +382,7 @@ namespace MWSound bool SoundManager::isMusicPlaying() { - return mMusic && mMusic->isPlaying(); + return mMusic && mOutput->isStreamPlaying(mMusic); } void SoundManager::playPlaylist(const std::string &playlist) @@ -248,31 +391,37 @@ namespace MWSound startRandomTitle(); } - void SoundManager::say(const MWWorld::Ptr &ptr, const std::string& filename) + + void SoundManager::say(const MWWorld::ConstPtr &ptr, const std::string &filename) { if(!mOutput->isInitialized()) return; try { - float basevol = volumeFromType(Play_TypeVoice); - std::string filePath = "Sound/"+filename; - const ESM::Position &pos = ptr.getRefData().getPosition(); - const Ogre::Vector3 objpos(pos.pos); + std::string voicefile = "Sound/"+filename; - MWBase::World* world = MWBase::Environment::get().getWorld(); - static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->getFloat(); - static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->getFloat(); - static const float fAudioVoiceDefaultMinDistance = world->getStore().get().find("fAudioVoiceDefaultMinDistance")->getFloat(); - static const float fAudioVoiceDefaultMaxDistance = world->getStore().get().find("fAudioVoiceDefaultMaxDistance")->getFloat(); + Sound_Loudness *loudness; + mVFS->normalizeFilename(voicefile); + DecoderPtr decoder = loadVoice(voicefile, &loudness); - float minDistance = fAudioVoiceDefaultMinDistance * fAudioMinDistanceMult; - float maxDistance = fAudioVoiceDefaultMaxDistance * fAudioMaxDistanceMult; - minDistance = std::max(minDistance, 1.f); - maxDistance = std::max(minDistance, maxDistance); + if(!loudness->isReady()) + mPendingSaySounds[ptr] = std::make_pair(decoder, loudness); + else + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); - MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f, - minDistance, maxDistance, Play_Normal|Play_TypeVoice, 0, true); - mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); + SaySoundMap::iterator oldIt = mActiveSaySounds.find(ptr); + if (oldIt != mActiveSaySounds.end()) + { + mOutput->finishStream(oldIt->second.first); + mActiveSaySounds.erase(oldIt); + } + + MWBase::SoundStreamPtr sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer())); + + mActiveSaySounds.insert(std::make_pair(ptr, std::make_pair(sound, loudness))); + } } catch(std::exception &e) { @@ -280,19 +429,18 @@ namespace MWSound } } - float SoundManager::getSaySoundLoudness(const MWWorld::Ptr &ptr) const + float SoundManager::getSaySoundLoudness(const MWWorld::ConstPtr &ptr) const { - SoundMap::const_iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr); + if(snditer != mActiveSaySounds.end()) { - if(snditer->second.first == ptr && snditer->second.second == "_say_sound") - break; - ++snditer; + MWBase::SoundStreamPtr sound = snditer->second.first; + Sound_Loudness *loudness = snditer->second.second; + float sec = mOutput->getStreamOffset(sound); + return loudness->getLoudnessAtTime(sec); } - if (snditer == mActiveSounds.end()) - return 0.f; - return snditer->first->getCurrentLoudness(); + return 0.0f; } void SoundManager::say(const std::string& filename) @@ -301,11 +449,26 @@ namespace MWSound return; try { - float basevol = volumeFromType(Play_TypeVoice); - std::string filePath = "Sound/"+filename; + std::string voicefile = "Sound/"+filename; - MWBase::SoundPtr sound = mOutput->playSound(filePath, 1.0f, basevol, 1.0f, Play_Normal|Play_TypeVoice, 0); - mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), std::string("_say_sound")); + Sound_Loudness *loudness; + mVFS->normalizeFilename(voicefile); + DecoderPtr decoder = loadVoice(voicefile, &loudness); + + if(!loudness->isReady()) + mPendingSaySounds[MWWorld::ConstPtr()] = std::make_pair(decoder, loudness); + else + { + SaySoundMap::iterator oldIt = mActiveSaySounds.find(MWWorld::ConstPtr()); + if (oldIt != mActiveSaySounds.end()) + { + mOutput->finishStream(oldIt->second.first); + mActiveSaySounds.erase(oldIt); + } + + mActiveSaySounds.insert(std::make_pair(MWWorld::ConstPtr(), + std::make_pair(playVoice(decoder, osg::Vec3f(), true), loudness))); + } } catch(std::exception &e) { @@ -313,35 +476,42 @@ namespace MWSound } } - bool SoundManager::sayDone(const MWWorld::Ptr &ptr) const + bool SoundManager::sayDone(const MWWorld::ConstPtr &ptr) const { - return !isPlaying(ptr, "_say_sound"); - } - - void SoundManager::stopSay(const MWWorld::Ptr &ptr) - { - SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr); + if(snditer != mActiveSaySounds.end()) { - if(snditer->second.first == ptr && snditer->second.second == "_say_sound") - { - snditer->first->stop(); - mActiveSounds.erase(snditer++); - } - else - ++snditer; + if(mOutput->isStreamPlaying(snditer->second.first)) + return false; + return true; } + return mPendingSaySounds.find(ptr) == mPendingSaySounds.end(); + } + + void SoundManager::stopSay(const MWWorld::ConstPtr &ptr) + { + SaySoundMap::iterator snditer = mActiveSaySounds.find(ptr); + if(snditer != mActiveSaySounds.end()) + { + mOutput->finishStream(snditer->second.first); + mActiveSaySounds.erase(snditer); + } + mPendingSaySounds.erase(ptr); } - MWBase::SoundPtr SoundManager::playTrack(const DecoderPtr& decoder, PlayType type) + MWBase::SoundStreamPtr SoundManager::playTrack(const DecoderPtr& decoder, PlayType type) { - MWBase::SoundPtr track; + MWBase::SoundStreamPtr track; if(!mOutput->isInitialized()) return track; try { - track = mOutput->streamSound(decoder, volumeFromType(type), 1.0f, Play_NoEnv|type); + track.reset(new Stream(1.0f, volumeFromType(type), 1.0f, Play_NoEnv|type|Play_2D)); + mOutput->streamSound(decoder, track); + + TrackList::iterator iter = std::lower_bound(mActiveTracks.begin(), mActiveTracks.end(), track); + mActiveTracks.insert(iter, track); } catch(std::exception &e) { @@ -350,6 +520,19 @@ namespace MWSound return track; } + void SoundManager::stopTrack(MWBase::SoundStreamPtr stream) + { + mOutput->finishStream(stream); + TrackList::iterator iter = std::lower_bound(mActiveTracks.begin(), mActiveTracks.end(), stream); + if(iter != mActiveTracks.end() && *iter == stream) + mActiveTracks.erase(iter); + } + + double SoundManager::getTrackTimeDelay(MWBase::SoundStreamPtr stream) + { + return mOutput->getStreamDelay(stream); + } + MWBase::SoundPtr SoundManager::playSound(const std::string& soundId, float volume, float pitch, PlayType type, PlayMode mode, float offset) { @@ -358,21 +541,28 @@ namespace MWSound return sound; try { + Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); float basevol = volumeFromType(type); - float min, max; - std::string file = lookup(soundId, volume, min, max); - sound = mOutput->playSound(file, volume, basevol, pitch, mode|type, offset); - mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); + sound.reset(new Sound(volume * sfx->mVolume, basevol, pitch, mode|type|Play_2D)); + mOutput->playSound(sound, sfx->mHandle, offset); + if(sfx->mUses++ == 0) + { + SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); + if(iter != mUnusedBuffers.end()) + mUnusedBuffers.erase(iter); + } + mActiveSounds[MWWorld::ConstPtr()].push_back(std::make_pair(sound, sfx)); } catch(std::exception&) { //std::cout <<"Sound Error: "< 2000*2000) - { + if((mode&Play_RemoveAtDistance) && (mListenerPos-objpos).length2() > 2000*2000) return MWBase::SoundPtr(); - } - sound = mOutput->playSound3D(file, objpos, volume, basevol, pitch, min, max, mode|type, offset); - if((mode&Play_NoTrack)) - mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); + if(!(mode&Play_NoPlayerLocal) && ptr == MWMechanics::getPlayer()) + { + sound.reset(new Sound(volume * sfx->mVolume, basevol, pitch, mode|type|Play_2D)); + mOutput->playSound(sound, sfx->mHandle, offset); + } else - mActiveSounds[sound] = std::make_pair(ptr, soundId); + { + sound.reset(new Sound(objpos, volume * sfx->mVolume, basevol, pitch, + sfx->mMinDist, sfx->mMaxDist, mode|type|Play_3D)); + mOutput->playSound3D(sound, sfx->mHandle, offset); + } + if(sfx->mUses++ == 0) + { + SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); + if(iter != mUnusedBuffers.end()) + mUnusedBuffers.erase(iter); + } + mActiveSounds[ptr].push_back(std::make_pair(sound, sfx)); } catch(std::exception&) { //std::cout <<"Sound Error: "<isInitialized()) @@ -414,62 +615,57 @@ namespace MWSound try { // Look up the sound in the ESM data + Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); float basevol = volumeFromType(type); - float min, max; - std::string file = lookup(soundId, volume, min, max); - sound = mOutput->playSound3D(file, initialPos, volume, basevol, pitch, min, max, mode|type, offset); - mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); + sound.reset(new Sound(initialPos, volume * sfx->mVolume, basevol, pitch, + sfx->mMinDist, sfx->mMaxDist, mode|type|Play_3D)); + mOutput->playSound3D(sound, sfx->mHandle, offset); + if(sfx->mUses++ == 0) + { + SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx); + if(iter != mUnusedBuffers.end()) + mUnusedBuffers.erase(iter); + } + mActiveSounds[MWWorld::ConstPtr()].push_back(std::make_pair(sound, sfx)); } catch(std::exception &) { //std::cout <<"Sound Error: "<finishSound(sound); + } + + void SoundManager::stopSound3D(const MWWorld::ConstPtr &ptr, const std::string& soundId) + { + SoundMap::iterator snditer = mActiveSounds.find(ptr); + if(snditer != mActiveSounds.end()) { - if(snditer->first == sound) + Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) { - snditer->first->stop(); - mActiveSounds.erase(snditer++); + if(sndidx->second == sfx) + mOutput->finishSound(sndidx->first); } - else - ++snditer; } } - void SoundManager::stopSound3D(const MWWorld::Ptr &ptr, const std::string& soundId) + void SoundManager::stopSound3D(const MWWorld::ConstPtr &ptr) { - SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + SoundMap::iterator snditer = mActiveSounds.find(ptr); + if(snditer != mActiveSounds.end()) { - if(snditer->second.first == ptr && snditer->second.second == soundId) - { - snditer->first->stop(); - mActiveSounds.erase(snditer++); - } - else - ++snditer; - } - } - - void SoundManager::stopSound3D(const MWWorld::Ptr &ptr) - { - SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) - { - if(snditer->second.first == ptr) - { - snditer->first->stop(); - mActiveSounds.erase(snditer++); - } - else - ++snditer; + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) + mOutput->finishSound(sndidx->first); } } @@ -478,51 +674,74 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - if(snditer->second.first != MWWorld::Ptr() && - snditer->second.first != MWBase::Environment::get().getWorld()->getPlayerPtr() && - snditer->second.first.getCell() == cell) + if(snditer->first != MWWorld::ConstPtr() && + snditer->first != MWMechanics::getPlayer() && + snditer->first.getCell() == cell) { - snditer->first->stop(); - mActiveSounds.erase(snditer++); + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) + mOutput->finishSound(sndidx->first); } - else - ++snditer; + ++snditer; + } + SaySoundMap::iterator sayiter = mActiveSaySounds.begin(); + while(sayiter != mActiveSaySounds.end()) + { + if(sayiter->first != MWWorld::ConstPtr() && + sayiter->first != MWMechanics::getPlayer() && + sayiter->first.getCell() == cell) + { + mOutput->finishStream(sayiter->second.first); + } + ++sayiter; } } void SoundManager::stopSound(const std::string& soundId) { - SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + SoundMap::iterator snditer = mActiveSounds.find(MWWorld::ConstPtr()); + if(snditer != mActiveSounds.end()) { - if(snditer->second.first == MWWorld::Ptr() && - snditer->second.second == soundId) + Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) { - snditer->first->stop(); - mActiveSounds.erase(snditer++); + if(sndidx->second == sfx) + mOutput->finishSound(sndidx->first); } - else - ++snditer; } } - void SoundManager::fadeOutSound3D(const MWWorld::Ptr &ptr, + void SoundManager::fadeOutSound3D(const MWWorld::ConstPtr &ptr, const std::string& soundId, float duration) { - SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + SoundMap::iterator snditer = mActiveSounds.find(ptr); + if(snditer != mActiveSounds.end()) { - if(snditer->second.first == ptr && snditer->second.second == soundId) + Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId)); + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) { - snditer->first->setFadeout(duration); + if(sndidx->second == sfx) + sndidx->first->setFadeout(duration); } - ++snditer; } } - bool SoundManager::getSoundPlaying(const MWWorld::Ptr &ptr, const std::string& soundId) const + bool SoundManager::getSoundPlaying(const MWWorld::ConstPtr &ptr, const std::string& soundId) const { - return isPlaying(ptr, soundId); + SoundMap::const_iterator snditer = mActiveSounds.find(ptr); + if(snditer != mActiveSounds.end()) + { + Sound_Buffer *sfx = lookupSound(Misc::StringUtils::lowerCase(soundId)); + SoundBufferRefPairList::const_iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) + { + if(sndidx->second == sfx && mOutput->isSoundPlaying(sndidx->first)) + return true; + } + } + return false; } @@ -554,14 +773,14 @@ namespace MWSound static std::string regionName = ""; static float sTimePassed = 0.0; MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Ptr player = world->getPlayerPtr(); + const MWWorld::ConstPtr player = world->getPlayerPtr(); const ESM::Cell *cell = player.getCell()->getCell(); sTimePassed += duration; if(!cell->isExterior() || sTimePassed < sTimeToNextEnvSound) return; - float a = OEngine::Misc::Rng::rollClosedProbability(); + float a = Misc::Rng::rollClosedProbability(); // NOTE: We should use the "Minimum Time Between Environmental Sounds" and // "Maximum Time Between Environmental Sounds" fallback settings here. sTimeToNextEnvSound = 5.0f*a + 15.0f*(1.0f-a); @@ -590,7 +809,7 @@ namespace MWSound return; } - int r = OEngine::Misc::Rng::rollDice(total); + int r = Misc::Rng::rollDice(total); int pos = 0; soundIter = regn->mSoundList.begin(); @@ -623,18 +842,14 @@ namespace MWSound Environment env = Env_Normal; if (mListenerUnderwater) - { env = Env_Underwater; - //play underwater sound - if(!(mUnderwaterSound && mUnderwaterSound->isPlaying())) - mUnderwaterSound = playSound("Underwater", 1.0f, 1.0f, Play_TypeSfx, Play_LoopNoEnv); - } else if(mUnderwaterSound) { - mUnderwaterSound->stop(); + mOutput->finishSound(mUnderwaterSound); mUnderwaterSound.reset(); } + mOutput->startUpdate(); mOutput->updateListener( mListenerPos, mListenerDir, @@ -643,44 +858,149 @@ namespace MWSound ); // Check if any sounds are finished playing, and trash them - // Lower volume on fading out sounds SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - if(!snditer->first->isPlaying()) - mActiveSounds.erase(snditer++); - else + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + while(sndidx != snditer->second.end()) { - const MWWorld::Ptr &ptr = snditer->second.first; - if(!ptr.isEmpty()) + MWWorld::ConstPtr ptr = snditer->first; + MWBase::SoundPtr sound = sndidx->first; + if(!ptr.isEmpty() && sound->getIs3D()) { const ESM::Position &pos = ptr.getRefData().getPosition(); - const Ogre::Vector3 objpos(pos.pos); - snditer->first->setPosition(objpos); + const osg::Vec3f objpos(pos.asVec3()); + sound->setPosition(objpos); - if ((snditer->first->mFlags & Play_RemoveAtDistance) - && mListenerPos.squaredDistance(Ogre::Vector3(ptr.getRefData().getPosition().pos)) > 2000*2000) + if(sound->getDistanceCull()) { - mActiveSounds.erase(snditer++); - continue; + if((mListenerPos - objpos).length2() > 2000*2000) + mOutput->finishSound(sound); } } - //update fade out - if(snditer->first->mFadeOutTime>0) + + if(!mOutput->isSoundPlaying(sound)) { - float soundDuration=duration; - if(soundDuration>snditer->first->mFadeOutTime) - soundDuration=snditer->first->mFadeOutTime; - snditer->first->setVolume(snditer->first->mVolume - - soundDuration / snditer->first->mFadeOutTime * snditer->first->mVolume); - snditer->first->mFadeOutTime -= soundDuration; + mOutput->finishSound(sound); + Sound_Buffer *sfx = sndidx->second; + if(sfx->mUses-- == 1) + mUnusedBuffers.push_front(sfx); + sndidx = snditer->second.erase(sndidx); } - snditer->first->update(); + else + { + sound->updateFade(duration); + + mOutput->updateSound(sound); + ++sndidx; + } + } + if(snditer->second.empty()) + mActiveSounds.erase(snditer++); + else ++snditer; + } + + SayDecoderMap::iterator penditer = mPendingSaySounds.begin(); + while(penditer != mPendingSaySounds.end()) + { + Sound_Loudness *loudness = penditer->second.second; + if(loudness->isReady()) + { + try { + DecoderPtr decoder = penditer->second.first; + decoder->rewind(); + + MWBase::SoundStreamPtr sound; + MWWorld::ConstPtr ptr = penditer->first; + + SaySoundMap::iterator old = mActiveSaySounds.find(ptr); + if (old != mActiveSaySounds.end()) + { + mOutput->finishStream(old->second.first); + mActiveSaySounds.erase(old); + } + + if(ptr == MWWorld::ConstPtr()) + sound = playVoice(decoder, osg::Vec3f(), true); + else + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); + sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer())); + } + mActiveSaySounds.insert(std::make_pair(ptr, std::make_pair(sound, loudness))); + } + catch(std::exception &e) { + std::cerr<< "Sound Error: "<first; + MWBase::SoundStreamPtr sound = sayiter->second.first; + if(!ptr.isEmpty() && sound->getIs3D()) + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); + sound->setPosition(pos); + + if(sound->getDistanceCull()) + { + if((mListenerPos - pos).length2() > 2000*2000) + mOutput->finishStream(sound); + } + } + + if(!mOutput->isStreamPlaying(sound)) + { + mOutput->finishStream(sound); + mActiveSaySounds.erase(sayiter++); + } + else + { + sound->updateFade(duration); + + mOutput->updateStream(sound); + ++sayiter; } } + + TrackList::iterator trkiter = mActiveTracks.begin(); + for(;trkiter != mActiveTracks.end();++trkiter) + { + MWBase::SoundStreamPtr sound = *trkiter; + if(!mOutput->isStreamPlaying(sound)) + { + mOutput->finishStream(sound); + trkiter = mActiveTracks.erase(trkiter); + } + else + { + sound->updateFade(duration); + + mOutput->updateStream(sound); + ++trkiter; + } + } + + if(mListenerUnderwater) + { + // Play underwater sound (after updating sounds) + if(!(mUnderwaterSound && mOutput->isSoundPlaying(mUnderwaterSound))) + mUnderwaterSound = playSound("Underwater", 1.0f, 1.0f, Play_TypeSfx, Play_LoopNoEnv); + } + mOutput->finishUpdate(); } + void SoundManager::update(float duration) { if(!mOutput->isInitialized()) @@ -703,39 +1023,73 @@ namespace MWSound mFootstepsVolume = Settings::Manager::getFloat("footsteps volume", "Sound"); mVoiceVolume = Settings::Manager::getFloat("voice volume", "Sound"); + if(!mOutput->isInitialized()) + return; + mOutput->startUpdate(); SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + for(;snditer != mActiveSounds.end();++snditer) { - snditer->first->mBaseVolume = volumeFromType(snditer->first->getPlayType()); - snditer->first->update(); - ++snditer; + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) + { + MWBase::SoundPtr sound = sndidx->first; + sound->setBaseVolume(volumeFromType(sound->getPlayType())); + mOutput->updateSound(sound); + } + } + SaySoundMap::iterator sayiter = mActiveSaySounds.begin(); + for(;sayiter != mActiveSaySounds.end();++sayiter) + { + MWBase::SoundStreamPtr sound = sayiter->second.first; + sound->setBaseVolume(volumeFromType(sound->getPlayType())); + mOutput->updateStream(sound); + } + TrackList::iterator trkiter = mActiveTracks.begin(); + for(;trkiter != mActiveTracks.end();++trkiter) + { + MWBase::SoundStreamPtr sound = *trkiter; + sound->setBaseVolume(volumeFromType(sound->getPlayType())); + mOutput->updateStream(sound); } if(mMusic) { - mMusic->mBaseVolume = volumeFromType(mMusic->getPlayType()); - mMusic->update(); + mMusic->setBaseVolume(volumeFromType(mMusic->getPlayType())); + mOutput->updateStream(mMusic); } + mOutput->finishUpdate(); } - void SoundManager::setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up) + void SoundManager::setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up, bool underwater) { mListenerPos = pos; mListenerDir = dir; mListenerUp = up; - MWWorld::Ptr player = - MWBase::Environment::get().getWorld()->getPlayerPtr(); - const MWWorld::CellStore *cell = player.getCell(); - - mListenerUnderwater = ((cell->getCell()->mData.mFlags&ESM::Cell::HasWater) && mListenerPos.z < cell->getWaterLevel()); + mListenerUnderwater = underwater; } - void SoundManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) + void SoundManager::updatePtr(const MWWorld::ConstPtr &old, const MWWorld::ConstPtr &updated) { - for (SoundMap::iterator snditer = mActiveSounds.begin(); snditer != mActiveSounds.end(); ++snditer) + SoundMap::iterator snditer = mActiveSounds.find(old); + if(snditer != mActiveSounds.end()) { - if (snditer->second.first == old) - snditer->second.first = updated; + SoundBufferRefPairList sndlist = snditer->second; + mActiveSounds.erase(snditer); + mActiveSounds[updated] = sndlist; + } + SaySoundMap::iterator sayiter = mActiveSaySounds.find(old); + if(sayiter != mActiveSaySounds.end()) + { + SoundLoudnessPair sndlist = sayiter->second; + mActiveSaySounds.erase(sayiter); + mActiveSaySounds[updated] = sndlist; + } + SayDecoderMap::iterator penditer = mPendingSaySounds.find(old); + if(penditer != mPendingSaySounds.end()) + { + DecoderLoudnessPair dl = penditer->second; + mPendingSaySounds.erase(penditer); + mPendingSaySounds[updated] = dl; } } @@ -806,10 +1160,29 @@ namespace MWSound void SoundManager::clear() { - for (SoundMap::iterator iter (mActiveSounds.begin()); iter!=mActiveSounds.end(); ++iter) - iter->first->stop(); - + SoundMap::iterator snditer = mActiveSounds.begin(); + for(;snditer != mActiveSounds.end();++snditer) + { + SoundBufferRefPairList::iterator sndidx = snditer->second.begin(); + for(;sndidx != snditer->second.end();++sndidx) + { + mOutput->finishSound(sndidx->first); + Sound_Buffer *sfx = sndidx->second; + if(sfx->mUses-- == 1) + mUnusedBuffers.push_front(sfx); + } + } mActiveSounds.clear(); + SaySoundMap::iterator sayiter = mActiveSaySounds.begin(); + for(;sayiter != mActiveSaySounds.end();++sayiter) + mOutput->finishStream(sayiter->second.first); + mActiveSaySounds.clear(); + TrackList::iterator trkiter = mActiveTracks.begin(); + for(;trkiter != mActiveTracks.end();++trkiter) + mOutput->finishStream(*trkiter); + mActiveTracks.clear(); + mPendingSaySounds.clear(); + mUnderwaterSound.reset(); stopMusic(); } } diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 250cb0d51c..695ca92a2a 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -1,38 +1,54 @@ #ifndef GAME_SOUND_SOUNDMANAGER_H #define GAME_SOUND_SOUNDMANAGER_H +#include #include #include +#include #include #include -#include -#include - #include +#include "loudness.hpp" #include "../mwbase/soundmanager.hpp" +namespace VFS +{ + class Manager; +} + +namespace ESM +{ + struct Sound; +} + namespace MWSound { class Sound_Output; struct Sound_Decoder; class Sound; + class Sound_Buffer; enum Environment { Env_Normal, Env_Underwater }; + // Extra play flags, not intended for caller use + enum PlayModeEx { + Play_2D = 0, + Play_3D = 1<<31 + }; class SoundManager : public MWBase::SoundManager { - Ogre::ResourceGroupManager& mResourceMgr; + const VFS::Manager* mVFS; std::auto_ptr mOutput; // Caches available music tracks by - std::map mMusicFiles; + std::map > mMusicFiles; std::string mLastPlayedMusic; // The music file that was last played float mMasterVolume; @@ -41,26 +57,68 @@ namespace MWSound float mVoiceVolume; float mFootstepsVolume; - boost::shared_ptr mMusic; - std::string mCurrentPlaylist; + typedef std::auto_ptr > SoundBufferList; + // List of sound buffers, grown as needed. New enties are added to the + // back, allowing existing Sound_Buffer references/pointers to remain + // valid. + SoundBufferList mSoundBuffers; + size_t mBufferCacheMin; + size_t mBufferCacheMax; + size_t mBufferCacheSize; - typedef std::pair PtrIDPair; - typedef std::map SoundMap; + typedef std::map NameBufferMap; + NameBufferMap mBufferNameMap; + + typedef std::deque LoudnessList; + LoudnessList mVoiceLipBuffers; + + typedef std::map NameLoudnessRefMap; + NameLoudnessRefMap mVoiceLipNameMap; + + // NOTE: unused buffers are stored in front-newest order. + typedef std::deque SoundList; + SoundList mUnusedBuffers; + + typedef std::pair SoundBufferRefPair; + typedef std::vector SoundBufferRefPairList; + typedef std::map SoundMap; SoundMap mActiveSounds; - MWBase::SoundPtr mUnderwaterSound; + typedef std::pair SoundLoudnessPair; + typedef std::map SaySoundMap; + SaySoundMap mActiveSaySounds; + + typedef std::pair DecoderLoudnessPair; + typedef std::map SayDecoderMap; + SayDecoderMap mPendingSaySounds; + + typedef std::vector TrackList; + TrackList mActiveTracks; + + MWBase::SoundStreamPtr mMusic; + std::string mCurrentPlaylist; bool mListenerUnderwater; - Ogre::Vector3 mListenerPos; - Ogre::Vector3 mListenerDir; - Ogre::Vector3 mListenerUp; + osg::Vec3f mListenerPos; + osg::Vec3f mListenerDir; + osg::Vec3f mListenerUp; int mPausedSoundTypes; - std::string lookup(const std::string &soundId, - float &volume, float &min, float &max); + MWBase::SoundPtr mUnderwaterSound; + + Sound_Buffer *insertSound(const std::string &soundId, const ESM::Sound *sound); + + Sound_Buffer *lookupSound(const std::string &soundId) const; + Sound_Buffer *loadSound(const std::string &soundId); + + // Ensures the loudness/"lip" data gets loaded, and returns a decoder + // to start streaming + DecoderPtr loadVoice(const std::string &voicefile, Sound_Loudness **lipdata); + + MWBase::SoundStreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f &pos, bool playlocal); + void streamMusicFull(const std::string& filename); - bool isPlaying(const MWWorld::Ptr &ptr, const std::string &id) const; void updateSounds(float duration); void updateRegionSound(float duration); @@ -74,7 +132,7 @@ namespace MWSound friend class OpenAL_Output; public: - SoundManager(bool useSound); + SoundManager(const VFS::Manager* vfs, bool useSound); virtual ~SoundManager(); virtual void processChangedSettings(const Settings::CategorySettingVector& settings); @@ -96,7 +154,7 @@ namespace MWSound ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist - virtual void say(const MWWorld::Ptr &reference, const std::string& filename); + virtual void say(const MWWorld::ConstPtr &reference, const std::string& filename); ///< Make an actor say some text. /// \param filename name of a sound file in "Sound/" in the data directory. @@ -104,45 +162,52 @@ namespace MWSound ///< Say some text, without an actor ref /// \param filename name of a sound file in "Sound/" in the data directory. - virtual bool sayDone(const MWWorld::Ptr &reference=MWWorld::Ptr()) const; + virtual bool sayDone(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()) const; ///< Is actor not speaking? - virtual void stopSay(const MWWorld::Ptr &reference=MWWorld::Ptr()); + virtual void stopSay(const MWWorld::ConstPtr &reference=MWWorld::ConstPtr()); ///< Stop an actor speaking - virtual float getSaySoundLoudness(const MWWorld::Ptr& reference) const; + virtual float getSaySoundLoudness(const MWWorld::ConstPtr& reference) const; ///< Check the currently playing say sound for this actor /// and get an average loudness value (scale [0,1]) at the current time position. /// If the actor is not saying anything, returns 0. - virtual MWBase::SoundPtr playTrack(const DecoderPtr& decoder, PlayType type); + virtual MWBase::SoundStreamPtr playTrack(const DecoderPtr& decoder, PlayType type); ///< Play a 2D audio track, using a custom decoder + virtual void stopTrack(MWBase::SoundStreamPtr stream); + ///< Stop the given audio track from playing + + virtual double getTrackTimeDelay(MWBase::SoundStreamPtr stream); + ///< Retives the time delay, in seconds, of the audio track (must be a sound + /// returned by \ref playTrack). Only intended to be called by the track + /// decoder's read method. + virtual MWBase::SoundPtr playSound(const std::string& soundId, float volume, float pitch, PlayType type=Play_TypeSfx, PlayMode mode=Play_Normal, float offset=0); ///< Play a sound, independently of 3D-position - ///< @param offset value from [0,1], when to start playback. 0 is beginning, 1 is end. + ///< @param offset Number of seconds into the sound to start playback. - virtual MWBase::SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId, + virtual MWBase::SoundPtr playSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, float volume, float pitch, PlayType type=Play_TypeSfx, PlayMode mode=Play_Normal, float offset=0); ///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless Play_NoTrack is specified. - ///< @param offset Value from [0,1] meaning from which fraction the sound the playback starts. + ///< @param offset Number of seconds into the sound to start playback. - virtual MWBase::SoundPtr playManualSound3D(const Ogre::Vector3& initialPos, const std::string& soundId, - float volume, float pitch, PlayType type, PlayMode mode, float offset=0); - ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated manually using Sound::setPosition. - - ///< Play a sound from an object - ///< @param offset value from [0,1], when to start playback. 0 is beginning, 1 is end. - - virtual void stopSound3D(const MWWorld::Ptr &reference, const std::string& soundId); - ///< Stop the given object from playing the given sound, - - virtual void stopSound3D(const MWWorld::Ptr &reference); - ///< Stop the given object from playing all sounds. + virtual MWBase::SoundPtr playSound3D(const osg::Vec3f& initialPos, const std::string& soundId, + float volume, float pitch, PlayType type, PlayMode mode, float offset=0); + ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated using Sound::setPosition. + ///< @param offset Number of seconds into the sound to start playback. virtual void stopSound(MWBase::SoundPtr sound); - ///< Stop the given sound handle + ///< Stop the given sound from playing + /// @note no-op if \a sound is null + + virtual void stopSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId); + ///< Stop the given object from playing the given sound, + + virtual void stopSound3D(const MWWorld::ConstPtr &reference); + ///< Stop the given object from playing all sounds. virtual void stopSound(const MWWorld::CellStore *cell); ///< Stop all sounds for the given cell. @@ -150,13 +215,13 @@ namespace MWSound virtual void stopSound(const std::string& soundId); ///< Stop a non-3d looping sound - virtual void fadeOutSound3D(const MWWorld::Ptr &reference, const std::string& soundId, float duration); + virtual void fadeOutSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId, float duration); ///< Fade out given sound (that is already playing) of given object ///< @param reference Reference to object, whose sound is faded out ///< @param soundId ID of the sound to fade out. ///< @param duration Time until volume reaches 0. - virtual bool getSoundPlaying(const MWWorld::Ptr &reference, const std::string& soundId) const; + virtual bool getSoundPlaying(const MWWorld::ConstPtr &reference, const std::string& soundId) const; ///< Is the given sound currently playing on the given object? virtual void pauseSounds(int types=Play_TypeMask); @@ -167,9 +232,9 @@ namespace MWSound virtual void update(float duration); - virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up); + virtual void setListenerPosDir(const osg::Vec3f &pos, const osg::Vec3f &dir, const osg::Vec3f &up, bool underwater); - virtual void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); + virtual void updatePtr (const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated); virtual void clear(); }; diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index f190565daf..733ac1171c 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -1,4 +1,3 @@ - #include "character.hpp" #include @@ -29,9 +28,6 @@ void MWState::Character::addSlot (const boost::filesystem::path& path, const std ESM::ESMReader reader; reader.open (slot.mPath.string()); - if (reader.getFormat()>ESM::Header::CurrentFormat) - return; // format is too new -> ignore - if (reader.getRecName()!=ESM::REC_SAVE) return; // invalid save file -> ignore diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index 70e9f09258..22192c355d 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -1,4 +1,3 @@ - #include "charactermanager.hpp" #include diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 0883bc63b3..89d31c4853 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -1,4 +1,3 @@ - #include "statemanagerimp.hpp" #include @@ -6,11 +5,15 @@ #include #include +#include + #include #include -#include +#include + +#include #include #include @@ -33,6 +36,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwscript/globalscripts.hpp" @@ -137,11 +141,28 @@ void MWState::StateManager::newGame (bool bypass) if (!bypass) MWBase::Environment::get().getWindowManager()->setNewGame (true); - MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); + try + { + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); - MWBase::Environment::get().getWorld()->startNewGame (bypass); + MWBase::Environment::get().getWorld()->startNewGame (bypass); - mState = State_Running; + mState = State_Running; + } + catch (std::exception& e) + { + std::stringstream error; + error << "Failed to start new game: " << e.what(); + + std::cerr << error.str() << std::endl; + cleanup (true); + + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + + std::vector buttons; + buttons.push_back("#{sOk}"); + MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error.str(), buttons); + } } void MWState::StateManager::endGame() @@ -179,19 +200,16 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot profile.mTimePlayed = mTimePlayed; profile.mDescription = description; - int screenshotW = 259*2, screenshotH = 133*2; // *2 to get some nice antialiasing - Ogre::Image screenshot; - world.screenshot(screenshot, screenshotW, screenshotH); - Ogre::DataStreamPtr encoded = screenshot.encode("jpg"); - profile.mScreenshot.resize(encoded->size()); - encoded->read(&profile.mScreenshot[0], encoded->size()); + writeScreenshot(profile.mScreenshot); if (!slot) slot = getCurrentCharacter()->createSlot (profile); else slot = getCurrentCharacter()->updateSlot (slot, profile); - boost::filesystem::ofstream stream (slot->mPath, std::ios::binary); + // Write to a memory stream first. If there is an exception during the save process, we don't want to trash the + // existing save file we are overwriting. + std::stringstream stream; ESM::ESMWriter writer; @@ -202,7 +220,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot ++iter) writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0 - writer.setFormat (ESM::Header::CurrentFormat); + writer.setFormat (ESM::SavedGame::sCurrentFormat); // all unused writer.setVersion(0); @@ -224,7 +242,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); // Using only Cells for progress information, since they typically have the largest records by far listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells()); - listener.setLabel("#{sNotifyMessage4}"); + listener.setLabel("#{sNotifyMessage4}", true); Loading::ScopedLoad load(&listener); @@ -246,7 +264,14 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.close(); if (stream.fail()) - throw std::runtime_error("Write operation failed"); + throw std::runtime_error("Write operation failed (memory stream)"); + + // All good, write to file + boost::filesystem::ofstream filestream (slot->mPath, std::ios::binary); + filestream << stream.rdbuf(); + + if (filestream.fail()) + throw std::runtime_error("Write operation failed (file stream)"); Settings::Manager::setString ("character", "Saves", slot->mPath.parent_path().filename().string()); @@ -311,8 +336,6 @@ void MWState::StateManager::loadGame(const std::string& filepath) // have to peek into the save file to get the player name ESM::ESMReader reader; reader.open (filepath); - if (reader.getFormat()>ESM::Header::CurrentFormat) - return; // format is too new -> ignore if (reader.getRecName()!=ESM::REC_SAVE) return; // invalid save file -> ignore reader.getRecHeader(); @@ -334,6 +357,9 @@ void MWState::StateManager::loadGame (const Character *character, const std::str ESM::ESMReader reader; reader.open (filepath); + if (reader.getFormat() > ESM::SavedGame::sCurrentFormat) + throw std::runtime_error("This save file was created using a newer version of OpenMW and is thus not supported. Please upgrade to the newest OpenMW version to load this file."); + std::map contentFileMap = buildContentFileIndexMap (reader); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); @@ -456,9 +482,9 @@ void MWState::StateManager::loadGame (const Character *character, const std::str if (firstPersonCam != MWBase::Environment::get().getWorld()->isFirstPerson()) MWBase::Environment::get().getWorld()->togglePOV(); - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::ConstPtr ptr = MWMechanics::getPlayer(); - ESM::CellId cellId = ptr.getCell()->getCell()->getCellId(); + const ESM::CellId& cellId = ptr.getCell()->getCell()->getCellId(); // Use detectWorldSpaceChange=false, otherwise some of the data we just loaded would be cleared again MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition(), false); @@ -503,7 +529,7 @@ void MWState::StateManager::deleteGame(const MWState::Character *character, cons MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::ConstPtr player = MWMechanics::getPlayer(); std::string name = player.get()->mBase->mName; return mCharacterManager.getCurrentCharacter (create, name); @@ -570,3 +596,31 @@ bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const } return true; } + +void MWState::StateManager::writeScreenshot(std::vector &imageData) const +{ + int screenshotW = 259*2, screenshotH = 133*2; // *2 to get some nice antialiasing + + osg::ref_ptr screenshot (new osg::Image); + + MWBase::Environment::get().getWorld()->screenshot(screenshot.get(), screenshotW, screenshotH); + + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg"); + if (!readerwriter) + { + std::cerr << "Unable to write screenshot, can't find a jpg ReaderWriter" << std::endl; + return; + } + + std::ostringstream ostream; + osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*screenshot, ostream); + if (!result.success()) + { + std::cerr << "Unable to write screenshot: " << result.message() << " code " << result.status() << std::endl; + return; + } + + std::string data = ostream.str(); + imageData = std::vector(data.begin(), data.end()); + +} diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index 37f38f8df7..59dc919d14 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -25,6 +25,8 @@ namespace MWState bool verifyProfile (const ESM::SavedGame& profile) const; + void writeScreenshot (std::vector& imageData) const; + std::map buildContentFileIndexMap (const ESM::ESMReader& reader) const; public: diff --git a/apps/openmw/mwworld/action.cpp b/apps/openmw/mwworld/action.cpp index 5e1fb41a6c..c29377ecb3 100644 --- a/apps/openmw/mwworld/action.cpp +++ b/apps/openmw/mwworld/action.cpp @@ -1,4 +1,3 @@ - #include "action.hpp" #include "../mwbase/environment.hpp" @@ -6,6 +5,8 @@ #include "../mwbase/soundmanager.hpp" +#include "../mwmechanics/actorutil.hpp" + const MWWorld::Ptr& MWWorld::Action::getTarget() const { return mTarget; @@ -18,19 +19,26 @@ MWWorld::Action::~Action() {} void MWWorld::Action::execute (const Ptr& actor) { - if (!mSoundId.empty()) + if(!mSoundId.empty()) { - if (mKeepSound && actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if(mKeepSound && actor == MWMechanics::getPlayer()) MWBase::Environment::get().getSoundManager()->playSound(mSoundId, 1.0, 1.0, - MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Normal,mSoundOffset); + MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Normal, mSoundOffset + ); else { bool local = mTarget.isEmpty() || !mTarget.isInCell(); // no usable target - - MWBase::Environment::get().getSoundManager()->playSound3D(local ? actor : mTarget, - mSoundId, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, - mKeepSound ? MWBase::SoundManager::Play_NoTrack : MWBase::SoundManager::Play_Normal, - mSoundOffset); + if(mKeepSound) + MWBase::Environment::get().getSoundManager()->playSound3D( + (local ? actor : mTarget).getRefData().getPosition().asVec3(), + mSoundId, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, + MWBase::SoundManager::Play_Normal, mSoundOffset + ); + else + MWBase::Environment::get().getSoundManager()->playSound3D(local ? actor : mTarget, + mSoundId, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, + MWBase::SoundManager::Play_Normal, mSoundOffset + ); } } diff --git a/apps/openmw/mwworld/actionalchemy.cpp b/apps/openmw/mwworld/actionalchemy.cpp index bbba1081c3..53ed1ad848 100644 --- a/apps/openmw/mwworld/actionalchemy.cpp +++ b/apps/openmw/mwworld/actionalchemy.cpp @@ -5,12 +5,16 @@ #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" +#include "../mwmechanics/actorutil.hpp" namespace MWWorld { void ActionAlchemy::executeImp (const Ptr& actor) { - if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { //Ensure we're not in combat + if (actor != MWMechanics::getPlayer()) + return; + + if(MWMechanics::isPlayerInCombat()) { //Ensure we're not in combat MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage3}"); return; } diff --git a/apps/openmw/mwworld/actionapply.cpp b/apps/openmw/mwworld/actionapply.cpp index bfd64c85d8..e3699a6ac3 100644 --- a/apps/openmw/mwworld/actionapply.cpp +++ b/apps/openmw/mwworld/actionapply.cpp @@ -1,4 +1,3 @@ - #include "actionapply.hpp" #include "class.hpp" @@ -8,6 +7,8 @@ #include "../mwworld/containerstore.hpp" +#include "../mwmechanics/actorutil.hpp" + namespace MWWorld { ActionApply::ActionApply (const Ptr& object, const std::string& id) @@ -33,7 +34,7 @@ namespace MWWorld { MWBase::Environment::get().getWorld()->breakInvisibility(actor); - if (actor.getClass().apply (actor, mId, actor) && mUsageType!=-1) + if (actor.getClass().apply (actor, mId, actor) && mUsageType!=-1 && actor == MWMechanics::getPlayer()) actor.getClass().skillUsageSucceeded (actor, mSkillIndex, mUsageType); actor.getClass().getContainerStore(actor).remove(getTarget(), 1, actor); diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index 6609155236..84805c70e1 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -1,8 +1,5 @@ - #include "actioneat.hpp" -#include - #include #include "../mwbase/environment.hpp" @@ -10,6 +7,8 @@ #include "../mwworld/containerstore.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "class.hpp" namespace MWWorld @@ -20,9 +19,9 @@ namespace MWWorld getTarget().getContainerStore()->remove(getTarget(), 1, actor); // apply to actor - std::string id = getTarget().getClass().getId (getTarget()); + std::string id = getTarget().getCellRef().getRefId(); - if (actor.getClass().apply (actor, id, actor)) + if (actor.getClass().apply (actor, id, actor) && actor == MWMechanics::getPlayer()) actor.getClass().skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1); } diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index 87a4c63084..a6d6eef2b1 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -4,6 +4,8 @@ #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/actorutil.hpp" + #include #include "inventorystore.hpp" @@ -24,7 +26,7 @@ namespace MWWorld std::pair result = object.getClass().canBeEquipped (object, actor); // display error message if the player tried to equip something - if (!result.second.empty() && actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (!result.second.empty() && actor == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox(result.second); switch(result.first) @@ -50,7 +52,12 @@ namespace MWWorld } } - assert(it != invStore.end()); + if (it == invStore.end()) + { + std::stringstream error; + error << "ActionEquip can't find item " << object.getCellRef().getRefId(); + throw std::runtime_error(error.str()); + } // equip the item in the first free slot std::vector::const_iterator slot=slots_.first.begin(); diff --git a/apps/openmw/mwworld/actionopen.hpp b/apps/openmw/mwworld/actionopen.hpp index 8578995aee..454cc09f11 100644 --- a/apps/openmw/mwworld/actionopen.hpp +++ b/apps/openmw/mwworld/actionopen.hpp @@ -1,4 +1,3 @@ - #ifndef GAME_MWWORLD_ACTIONOPEN_H #define GAME_MWWORLD_ACTIONOPEN_H diff --git a/apps/openmw/mwworld/actionread.cpp b/apps/openmw/mwworld/actionread.cpp index cd0471a0ea..90e9a375b3 100644 --- a/apps/openmw/mwworld/actionread.cpp +++ b/apps/openmw/mwworld/actionread.cpp @@ -5,6 +5,7 @@ #include "../mwbase/world.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actorutil.hpp" #include "player.hpp" #include "class.hpp" @@ -18,8 +19,11 @@ namespace MWWorld void ActionRead::executeImp (const MWWorld::Ptr& actor) { + if (actor != MWMechanics::getPlayer()) + return; + //Ensure we're not in combat - if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat() + if(MWMechanics::isPlayerInCombat() // Reading in combat is still allowed if the scroll/book is not in the player inventory yet // (since otherwise, there would be no way to pick it up) && getTarget().getContainerStore() == &actor.getClass().getContainerStore(actor) diff --git a/apps/openmw/mwworld/actionrepair.cpp b/apps/openmw/mwworld/actionrepair.cpp index 699440a01c..8e19927b80 100644 --- a/apps/openmw/mwworld/actionrepair.cpp +++ b/apps/openmw/mwworld/actionrepair.cpp @@ -4,6 +4,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" +#include "../mwmechanics/actorutil.hpp" namespace MWWorld { @@ -14,7 +15,10 @@ namespace MWWorld void ActionRepair::executeImp (const Ptr& actor) { - if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { + if (actor != MWMechanics::getPlayer()) + return; + + if(MWMechanics::isPlayerInCombat()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage2}"); return; } diff --git a/apps/openmw/mwworld/actionsoulgem.cpp b/apps/openmw/mwworld/actionsoulgem.cpp index 7237fd3342..98fe8ee34d 100644 --- a/apps/openmw/mwworld/actionsoulgem.cpp +++ b/apps/openmw/mwworld/actionsoulgem.cpp @@ -4,6 +4,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" +#include "../mwmechanics/actorutil.hpp" namespace MWWorld { @@ -16,7 +17,10 @@ namespace MWWorld void ActionSoulgem::executeImp(const Ptr &actor) { - if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { //Ensure we're not in combat + if (actor != MWMechanics::getPlayer()) + return; + + if(MWMechanics::isPlayerInCombat()) { //Ensure we're not in combat MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage5}"); return; } diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp index 269d941dc6..4e61357642 100644 --- a/apps/openmw/mwworld/actiontake.cpp +++ b/apps/openmw/mwworld/actiontake.cpp @@ -1,4 +1,3 @@ - #include "actiontake.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwworld/actiontalk.cpp b/apps/openmw/mwworld/actiontalk.cpp index 905497f85b..051380ff52 100644 --- a/apps/openmw/mwworld/actiontalk.cpp +++ b/apps/openmw/mwworld/actiontalk.cpp @@ -1,4 +1,3 @@ - #include "actiontalk.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index fccd176a85..031f07258c 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -3,6 +3,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" + +#include "../mwworld/class.hpp" + #include "player.hpp" namespace @@ -40,8 +43,12 @@ namespace MWWorld for(std::set::iterator it = followers.begin();it != followers.end();++it) { MWWorld::Ptr follower = *it; - if (Ogre::Vector3(follower.getRefData().getPosition().pos).squaredDistance( - Ogre::Vector3( actor.getRefData().getPosition().pos)) + + std::string script = follower.getClass().getScript(follower); + if (!script.empty() && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1) + continue; + + if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() <= 800*800) teleport(*it); } diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp index d153b7e618..68d7c69e9f 100644 --- a/apps/openmw/mwworld/actiontrap.cpp +++ b/apps/openmw/mwworld/actiontrap.cpp @@ -9,8 +9,8 @@ namespace MWWorld void ActionTrap::executeImp(const Ptr &actor) { - Ogre::Vector3 actorPosition(actor.getRefData().getPosition().pos); - Ogre::Vector3 trapPosition(mTrapSource.getRefData().getPosition().pos); + osg::Vec3f actorPosition(actor.getRefData().getPosition().asVec3()); + osg::Vec3f trapPosition(mTrapSource.getRefData().getPosition().asVec3()); float activationDistance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); // GUI calcs if object in activation distance include object and player geometry @@ -20,7 +20,7 @@ namespace MWWorld // to open door/container. // Note, can't just detonate the trap at the trapped object's location and use the blast // radius, because for most trap spells this is 1 foot, much less than the activation distance. - if (trapPosition.distance(actorPosition) < (activationDistance * fudgeFactor)) + if ((trapPosition - actorPosition).length() < (activationDistance * fudgeFactor)) { // assume actor touched trap MWMechanics::CastSpell cast(mTrapSource, actor); diff --git a/apps/openmw/mwworld/cellfunctors.hpp b/apps/openmw/mwworld/cellfunctors.hpp deleted file mode 100644 index 5115fa02db..0000000000 --- a/apps/openmw/mwworld/cellfunctors.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef GAME_MWWORLD_CELLFUNCTORS_H -#define GAME_MWWORLD_CELLFUNCTORS_H - -#include -#include - -#include "ptr.hpp" - -namespace ESM -{ - class CellRef; -} - -namespace MWWorld -{ - /// List all (Ogre-)handles, then reset RefData::mBaseNode to 0. - struct ListAndResetHandles - { - std::vector mHandles; - - bool operator() (MWWorld::Ptr ptr) - { - Ogre::SceneNode* handle = ptr.getRefData().getBaseNode(); - if (handle) - mHandles.push_back (handle); - - ptr.getRefData().setBaseNode(0); - return true; - } - }; -} - -#endif diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 2c5e01aaa3..4d503dcc8d 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -24,32 +24,11 @@ namespace MWWorld /// all methods are known. void load (ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); - LiveRef *find (const std::string& name) - { - for (typename List::iterator iter (mList.begin()); iter!=mList.end(); ++iter) - if (!iter->mData.isDeletedByContentFile() - && (iter->mRef.hasContentFile() || iter->mData.getCount() > 0) - && iter->mRef.getRefId() == name) - return &*iter; - - return 0; - } - LiveRef &insert (const LiveRef &item) { mList.push_back(item); return mList.back(); } - - LiveCellRef *searchViaHandle (const std::string& handle) - { - for (typename List::iterator iter (mList.begin()); iter!=mList.end(); ++iter) - if (iter->mData.getBaseNode() && - iter->mData.getHandle()==handle) - return &*iter; - - return 0; - } }; } diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 2aa817fa50..75d908db83 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -1,9 +1,12 @@ #include "cells.hpp" +#include + #include #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -22,7 +25,7 @@ MWWorld::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell) if (result==mInteriors.end()) { - result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell))).first; + result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell, mStore, mReader))).first; } return &result->second; @@ -35,7 +38,7 @@ MWWorld::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell) if (result==mExteriors.end()) { result = mExteriors.insert (std::make_pair ( - std::make_pair (cell->getGridX(), cell->getGridY()), CellStore (cell))).first; + std::make_pair (cell->getGridX(), cell->getGridY()), CellStore (cell, mStore, mReader))).first; } @@ -69,7 +72,7 @@ MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, CellStore& void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, CellStore& cell) const { if (cell.getState()!=CellStore::State_Loaded) - cell.load (mStore, mReader); + cell.load (); ESM::CellState cellState; @@ -113,13 +116,12 @@ MWWorld::CellStore *MWWorld::Cells::getExterior (int x, int y) } result = mExteriors.insert (std::make_pair ( - std::make_pair (x, y), CellStore (cell))).first; + std::make_pair (x, y), CellStore (cell, mStore, mReader))).first; } if (result->second.getState()!=CellStore::State_Loaded) { - // Multiple plugin support for landscape data is much easier than for references. The last plugin wins. - result->second.load (mStore, mReader); + result->second.load (); } return &result->second; @@ -134,12 +136,12 @@ MWWorld::CellStore *MWWorld::Cells::getInterior (const std::string& name) { const ESM::Cell *cell = mStore.get().find(lowerName); - result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell))).first; + result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell, mStore, mReader))).first; } if (result->second.getState()!=CellStore::State_Loaded) { - result->second.load (mStore, mReader); + result->second.load (); } return &result->second; @@ -157,13 +159,13 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, CellStore& cell, bool searchInContainers) { if (cell.getState()==CellStore::State_Unloaded) - cell.preload (mStore, mReader); + cell.preload (); if (cell.getState()==CellStore::State_Preloaded) { if (cell.hasId (name)) { - cell.load (mStore, mReader); + cell.load (); } else return Ptr(); @@ -171,7 +173,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, CellStore& cell, Ptr ptr = cell.search (name); - if (!ptr.isEmpty()) + if (!ptr.isEmpty() && MWWorld::CellStore::isAccessible(ptr.getRefData(), ptr.getCellRef())) return ptr; if (searchInContainers) @@ -303,6 +305,29 @@ void MWWorld::Cells::write (ESM::ESMWriter& writer, Loading::Listener& progress) } } +struct GetCellStoreCallback : public MWWorld::CellStore::GetCellStoreCallback +{ +public: + GetCellStoreCallback(MWWorld::Cells& cells) + : mCells(cells) + { + } + + MWWorld::Cells& mCells; + + virtual MWWorld::CellStore* getCellStore(const ESM::CellId& cellId) + { + try + { + return mCells.getCell(cellId); + } + catch (...) + { + return NULL; + } + } +}; + bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap) { @@ -320,9 +345,9 @@ bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, uint32_t type, catch (...) { // silently drop cells that don't exist anymore + std::cerr << "Dropping state for cell " << state.mId.mWorldspace << " (cell no longer exists)" << std::endl; reader.skipRecord(); return true; - /// \todo log } state.load (reader); @@ -332,9 +357,11 @@ bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, uint32_t type, cellStore->readFog(reader); if (cellStore->getState()!=CellStore::State_Loaded) - cellStore->load (mStore, mReader); + cellStore->load (); - cellStore->readReferences (reader, contentFileMap); + GetCellStoreCallback callback(*this); + + cellStore->readReferences (reader, contentFileMap, &callback); return true; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 7da7c187d9..08f272ea2a 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -46,13 +47,16 @@ namespace template MWWorld::Ptr searchViaActorId (MWWorld::CellRefList& actorList, int actorId, - MWWorld::CellStore *cell) + MWWorld::CellStore *cell, const std::map& toIgnore) { for (typename MWWorld::CellRefList::List::iterator iter (actorList.mList.begin()); iter!=actorList.mList.end(); ++iter) { MWWorld::Ptr actor (&*iter, cell); + if (toIgnore.find(&*iter) != toIgnore.end()) + continue; + if (actor.getClass().getCreatureStats (actor).matchesActorId (actorId) && actor.getRefData().getCount() > 0) return actor; } @@ -140,6 +144,28 @@ namespace ref.load (state); collection.mList.push_back (ref); } + + struct SearchByRefNumVisitor + { + MWWorld::LiveCellRefBase* mFound; + ESM::RefNum mRefNumToFind; + + SearchByRefNumVisitor(const ESM::RefNum& toFind) + : mFound(NULL) + , mRefNumToFind(toFind) + { + } + + bool operator()(const MWWorld::Ptr& ptr) + { + if (ptr.getCellRef().getRefNum() == mRefNumToFind) + { + mFound = ptr.getBase(); + return false; + } + return true; + } + }; } namespace MWWorld @@ -158,7 +184,7 @@ namespace MWWorld LiveRef liveCellRef (ref, ptr); if (deleted) - liveCellRef.mData.setDeleted(true); + liveCellRef.mData.setDeletedByContentFile(true); if (iter != mList.end()) *iter = liveCellRef; @@ -178,8 +204,121 @@ namespace MWWorld return (ref.mRef.mRefnum == pRefnum); } - CellStore::CellStore (const ESM::Cell *cell) - : mCell (cell), mState (State_Unloaded), mHasState (false), mLastRespawn(0,0) + void CellStore::moveFrom(const Ptr &object, CellStore *from) + { + if (mState != State_Loaded) + load(); + + mHasState = true; + MovedRefTracker::iterator found = mMovedToAnotherCell.find(object.getBase()); + if (found != mMovedToAnotherCell.end()) + { + // A cell we had previously moved an object to is returning it to us. + assert (found->second == from); + mMovedToAnotherCell.erase(found); + } + else + { + mMovedHere.insert(std::make_pair(object.getBase(), from)); + } + updateMergedRefs(); + } + + MWWorld::Ptr CellStore::moveTo(const Ptr &object, CellStore *cellToMoveTo) + { + if (cellToMoveTo == this) + throw std::runtime_error("moveTo: object is already in this cell"); + + // We assume that *this is in State_Loaded since we could hardly have reference to a live object otherwise. + if (mState != State_Loaded) + throw std::runtime_error("moveTo: can't move object from a non-loaded cell (how did you get this object anyway?)"); + + // Ensure that the object actually exists in the cell + SearchByRefNumVisitor searchVisitor(object.getCellRef().getRefNum()); + forEach(searchVisitor); + if (!searchVisitor.mFound) + throw std::runtime_error("moveTo: object is not in this cell"); + + + // Objects with no refnum can't be handled correctly in the merging process that happens + // on a save/load, so do a simple copy & delete for these objects. + if (!object.getCellRef().getRefNum().hasContentFile()) + { + MWWorld::Ptr copied = object.getClass().copyToCell(object, *cellToMoveTo, object.getRefData().getCount()); + object.getRefData().setCount(0); + object.getRefData().setBaseNode(NULL); + return copied; + } + + MovedRefTracker::iterator found = mMovedHere.find(object.getBase()); + if (found != mMovedHere.end()) + { + // Special case - object didn't originate in this cell + // Move it back to its original cell first + CellStore* originalCell = found->second; + assert (originalCell != this); + originalCell->moveFrom(object, this); + + mMovedHere.erase(found); + + // Now that object is back to its rightful owner, we can move it + if (cellToMoveTo != originalCell) + { + originalCell->moveTo(object, cellToMoveTo); + } + + updateMergedRefs(); + return MWWorld::Ptr(object.getBase(), cellToMoveTo); + } + + cellToMoveTo->moveFrom(object, this); + mMovedToAnotherCell.insert(std::make_pair(object.getBase(), cellToMoveTo)); + + updateMergedRefs(); + return MWWorld::Ptr(object.getBase(), cellToMoveTo); + } + + struct MergeVisitor + { + MergeVisitor(std::vector& mergeTo, const std::map& movedHere, + const std::map& movedToAnotherCell) + : mMergeTo(mergeTo) + , mMovedHere(movedHere) + , mMovedToAnotherCell(movedToAnotherCell) + { + } + + bool operator() (const MWWorld::Ptr& ptr) + { + if (mMovedToAnotherCell.find(ptr.getBase()) != mMovedToAnotherCell.end()) + return true; + mMergeTo.push_back(ptr.getBase()); + return true; + } + + void merge() + { + for (std::map::const_iterator it = mMovedHere.begin(); it != mMovedHere.end(); ++it) + mMergeTo.push_back(it->first); + } + + private: + std::vector& mMergeTo; + + const std::map& mMovedHere; + const std::map& mMovedToAnotherCell; + }; + + void CellStore::updateMergedRefs() + { + mMergedRefs.clear(); + MergeVisitor visitor(mMergedRefs, mMovedHere, mMovedToAnotherCell); + forEachInternal(visitor); + visitor.merge(); + } + + CellStore::CellStore (const ESM::Cell *cell, const MWWorld::ESMStore& esmStore, std::vector& readerList) + : mStore(esmStore), mReader(readerList), mCell (cell), mState (State_Unloaded), mHasState (false), mLastRespawn(0,0) { mWaterLevel = cell->mWater; } @@ -207,165 +346,65 @@ namespace MWWorld if (mState==State_Preloaded) return std::binary_search (mIds.begin(), mIds.end(), id); - /// \todo address const-issues - return const_cast (this)->search (id).isEmpty(); + return searchConst (id).isEmpty(); } + template + struct SearchVisitor + { + PtrType mFound; + std::string mIdToFind; + bool operator()(const PtrType& ptr) + { + if (ptr.getCellRef().getRefId() == mIdToFind) + { + mFound = ptr; + return false; + } + return true; + } + }; + Ptr CellStore::search (const std::string& id) { - bool oldState = mHasState; - - mHasState = true; - - if (LiveCellRef *ref = mActivators.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mPotions.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mAppas.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mArmors.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mBooks.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mClothes.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mContainers.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mCreatures.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mDoors.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mIngreds.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mCreatureLists.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mItemLists.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mLights.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mLockpicks.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mMiscItems.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mNpcs.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mProbes.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mRepairs.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mStatics.find (id)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mWeapons.find (id)) - return Ptr (ref, this); - - mHasState = oldState; - - return Ptr(); + SearchVisitor searchVisitor; + searchVisitor.mIdToFind = id; + forEach(searchVisitor); + return searchVisitor.mFound; } - Ptr CellStore::searchViaHandle (const std::string& handle) + ConstPtr CellStore::searchConst (const std::string& id) const { - bool oldState = mHasState; - - mHasState = true; - - if (LiveCellRef *ref = mActivators.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mPotions.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mAppas.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mArmors.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mBooks.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mClothes.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mContainers.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mCreatures.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mDoors.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mIngreds.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mCreatureLists.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mItemLists.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mLights.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mLockpicks.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mMiscItems.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mNpcs.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mProbes.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mRepairs.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mStatics.searchViaHandle (handle)) - return Ptr (ref, this); - - if (LiveCellRef *ref = mWeapons.searchViaHandle (handle)) - return Ptr (ref, this); - - mHasState = oldState; - - return Ptr(); + SearchVisitor searchVisitor; + searchVisitor.mIdToFind = id; + forEachConst(searchVisitor); + return searchVisitor.mFound; } Ptr CellStore::searchViaActorId (int id) { - if (Ptr ptr = ::searchViaActorId (mNpcs, id, this)) + if (Ptr ptr = ::searchViaActorId (mNpcs, id, this, mMovedToAnotherCell)) return ptr; - if (Ptr ptr = ::searchViaActorId (mCreatures, id, this)) + if (Ptr ptr = ::searchViaActorId (mCreatures, id, this, mMovedToAnotherCell)) return ptr; + for (MovedRefTracker::const_iterator it = mMovedHere.begin(); it != mMovedHere.end(); ++it) + { + MWWorld::Ptr actor (it->first, this); + if (!actor.getClass().isActor()) + continue; + if (actor.getClass().getCreatureStats (actor).matchesActorId (id) && actor.getRefData().getCount() > 0) + return actor; + } + return Ptr(); } float CellStore::getWaterLevel() const { + if (isExterior()) + return -1; return mWaterLevel; } @@ -377,37 +416,17 @@ namespace MWWorld int CellStore::count() const { - return - mActivators.mList.size() - + mPotions.mList.size() - + mAppas.mList.size() - + mArmors.mList.size() - + mBooks.mList.size() - + mClothes.mList.size() - + mContainers.mList.size() - + mDoors.mList.size() - + mIngreds.mList.size() - + mCreatureLists.mList.size() - + mItemLists.mList.size() - + mLights.mList.size() - + mLockpicks.mList.size() - + mMiscItems.mList.size() - + mProbes.mList.size() - + mRepairs.mList.size() - + mStatics.mList.size() - + mWeapons.mList.size() - + mCreatures.mList.size() - + mNpcs.mList.size(); + return mMergedRefs.size(); } - void CellStore::load (const MWWorld::ESMStore &store, std::vector &esm) + void CellStore::load () { if (mState!=State_Loaded) { if (mState==State_Preloaded) mIds.clear(); - loadRefs (store, esm); + loadRefs (); mState = State_Loaded; @@ -417,18 +436,20 @@ namespace MWWorld } } - void CellStore::preload (const MWWorld::ESMStore &store, std::vector &esm) + void CellStore::preload () { if (mState==State_Unloaded) { - listRefs (store, esm); + listRefs (); mState = State_Preloaded; } } - void CellStore::listRefs(const MWWorld::ESMStore &store, std::vector &esm) + void CellStore::listRefs() { + std::vector& esm = mReader; + assert (mCell); if (mCell->mContextList.empty()) @@ -472,8 +493,10 @@ namespace MWWorld std::sort (mIds.begin(), mIds.end()); } - void CellStore::loadRefs(const MWWorld::ESMStore &store, std::vector &esm) + void CellStore::loadRefs() { + std::vector& esm = mReader; + assert (mCell); if (mCell->mContextList.empty()) @@ -500,7 +523,7 @@ namespace MWWorld continue; } - loadRef (ref, deleted, store); + loadRef (ref, deleted); } } @@ -509,8 +532,10 @@ namespace MWWorld { ESM::CellRef &ref = const_cast(*it); - loadRef (ref, false, store); + loadRef (ref, false); } + + updateMergedRefs(); } bool CellStore::isExterior() const @@ -538,14 +563,16 @@ namespace MWWorld return Ptr(); } - void CellStore::loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store) + void CellStore::loadRef (ESM::CellRef& ref, bool deleted) { - Misc::StringUtils::toLower (ref.mRefID); + Misc::StringUtils::lowerCaseInPlace (ref.mRefID); + + const MWWorld::ESMStore& store = mStore; switch (store.find (ref.mRefID)) { case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break; - case ESM::REC_ALCH: mPotions.load(ref, deleted, store); break; + case ESM::REC_ALCH: mPotions.load(ref, deleted,store); break; case ESM::REC_APPA: mAppas.load(ref, deleted, store); break; case ESM::REC_ARMO: mArmors.load(ref, deleted, store); break; case ESM::REC_BOOK: mBooks.load(ref, deleted, store); break; @@ -632,10 +659,19 @@ namespace MWWorld writeReferenceCollection (writer, mRepairs); writeReferenceCollection (writer, mStatics); writeReferenceCollection (writer, mWeapons); + + for (MovedRefTracker::const_iterator it = mMovedToAnotherCell.begin(); it != mMovedToAnotherCell.end(); ++it) + { + LiveCellRefBase* base = it->first; + ESM::RefNum refNum = base->mRef.getRefNum(); + ESM::CellId movedTo = it->second->getCell()->getCellId(); + + refNum.save(writer, true, "MVRF"); + movedTo.save(writer); + } } - void CellStore::readReferences (ESM::ESMReader& reader, - const std::map& contentFileMap) + void CellStore::readReferences (ESM::ESMReader& reader, const std::map& contentFileMap, GetCellStoreCallback* callback) { mHasState = true; @@ -763,6 +799,50 @@ namespace MWWorld throw std::runtime_error ("unknown type in cell reference section"); } } + + while (reader.isNextSub("MVRF")) + { + reader.cacheSubName(); + ESM::RefNum refnum; + ESM::CellId movedTo; + refnum.load(reader, true, "MVRF"); + movedTo.load(reader); + + // Search for the reference. It might no longer exist if its content file was removed. + SearchByRefNumVisitor visitor(refnum); + forEachInternal(visitor); + + if (!visitor.mFound) + { + std::cerr << "Dropping moved ref tag for " << refnum.mIndex << " (moved object no longer exists)" << std::endl; + continue; + } + + MWWorld::LiveCellRefBase* movedRef = visitor.mFound; + + CellStore* otherCell = callback->getCellStore(movedTo); + + if (otherCell == NULL) + { + std::cerr << "Dropping moved ref tag for " << movedRef->mRef.getRefId() + << " (target cell " << movedTo.mWorldspace << " no longer exists). Reference moved back to its original location." << std::endl; + // Note by dropping tag the object will automatically re-appear in its original cell, though potentially at inapproriate coordinates. + // Restore original coordinates: + movedRef->mData.setPosition(movedRef->mRef.getPosition()); + continue; + } + + if (otherCell == this) + { + // Should never happen unless someone's tampering with files. + std::cerr << "Found invalid moved ref, ignoring" << std::endl; + continue; + } + + moveTo(MWWorld::Ptr(movedRef, this), otherCell); + } + + updateMergedRefs(); } bool operator== (const CellStore& left, const CellStore& right) diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 672b6046b6..27fe9ec036 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -5,30 +5,49 @@ #include #include #include +#include + #include #include "livecellref.hpp" #include "cellreflist.hpp" -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld #include "timestamp.hpp" +#include "ptr.hpp" namespace ESM { struct CellState; struct FogState; + struct CellId; } namespace MWWorld { - class Ptr; class ESMStore; - /// \brief Mutable state of a cell class CellStore { @@ -41,6 +60,9 @@ namespace MWWorld private: + const MWWorld::ESMStore& mStore; + std::vector& mReader; + // Even though fog actually belongs to the player and not cells, // it makes sense to store it here since we need it once for each cell. // Note this is NULL until the cell is explored to save some memory @@ -54,6 +76,7 @@ namespace MWWorld MWWorld::TimeStamp mLastRespawn; + // List of refs owned by this cell CellRefList mActivators; CellRefList mPotions; CellRefList mAppas; @@ -75,9 +98,104 @@ namespace MWWorld CellRefList mStatics; CellRefList mWeapons; + typedef std::map MovedRefTracker; + // References owned by a different cell that have been moved here. + // + MovedRefTracker mMovedHere; + // References owned by this cell that have been moved to another cell. + // + MovedRefTracker mMovedToAnotherCell; + + // Merged list of ref's currently in this cell - i.e. with added refs from mMovedHere, removed refs from mMovedToAnotherCell + std::vector mMergedRefs; + + /// Moves object from the given cell to this cell. + void moveFrom(const MWWorld::Ptr& object, MWWorld::CellStore* from); + + /// Repopulate mMergedRefs. + void updateMergedRefs(); + + // helper function for forEachInternal + template + bool forEachImp (Visitor& visitor, List& list) + { + for (typename List::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); + ++iter) + { + if (!isAccessible(iter->mData, iter->mRef)) + continue; + if (!visitor (MWWorld::Ptr(&*iter, this))) + return false; + } + return true; + } + + // listing only objects owned by this cell. Internal use only, you probably want to use forEach() so that moved objects are accounted for. + template + bool forEachInternal (Visitor& visitor) + { + return + forEachImp (visitor, mActivators) && + forEachImp (visitor, mPotions) && + forEachImp (visitor, mAppas) && + forEachImp (visitor, mArmors) && + forEachImp (visitor, mBooks) && + forEachImp (visitor, mClothes) && + forEachImp (visitor, mContainers) && + forEachImp (visitor, mDoors) && + forEachImp (visitor, mIngreds) && + forEachImp (visitor, mItemLists) && + forEachImp (visitor, mLights) && + forEachImp (visitor, mLockpicks) && + forEachImp (visitor, mMiscItems) && + forEachImp (visitor, mProbes) && + forEachImp (visitor, mRepairs) && + forEachImp (visitor, mStatics) && + forEachImp (visitor, mWeapons) && + forEachImp (visitor, mCreatures) && + forEachImp (visitor, mNpcs) && + forEachImp (visitor, mCreatureLists); + } + + /// @note If you get a linker error here, this means the given type can not be stored in a cell. The supported types are + /// defined at the bottom of this file. + template + CellRefList& get(); + public: - CellStore (const ESM::Cell *cell_); + /// Should this reference be accessible to the outside world (i.e. to scripts / game logic)? + /// Determined based on the deletion flags. By default, objects deleted by content files are never accessible; + /// objects deleted by setCount(0) are still accessible *if* they came from a content file (needed for vanilla + /// scripting compatibility, and the fact that objects may be "un-deleted" in the original game). + static bool isAccessible(const MWWorld::RefData& refdata, const MWWorld::CellRef& cref) + { + return !refdata.isDeletedByContentFile() && (cref.hasContentFile() || refdata.getCount() > 0); + } + + /// Moves object from this cell to the given cell. + /// @note automatically updates given cell by calling cellToMoveTo->moveFrom(...) + /// @note throws exception if cellToMoveTo == this + /// @return updated MWWorld::Ptr with the new CellStore pointer set. + MWWorld::Ptr moveTo(const MWWorld::Ptr& object, MWWorld::CellStore* cellToMoveTo); + + /// Make a copy of the given object and insert it into this cell. + /// @note If you get a linker error here, this means the given type can not be inserted into a cell. + /// The supported types are defined at the bottom of this file. + template + LiveCellRefBase* insert(const LiveCellRef* ref) + { + mHasState = true; + CellRefList& list = get(); + LiveCellRefBase* ret = &list.insert(*ref); + updateMergedRefs(); + return ret; + } + + /// @param readerList The readers to use for loading of the cell on-demand. + CellStore (const ESM::Cell *cell_, + const MWWorld::ESMStore& store, + std::vector& readerList); const ESM::Cell *getCell() const; @@ -89,13 +207,17 @@ namespace MWWorld bool hasId (const std::string& id) const; ///< May return true for deleted IDs when in preload state. Will return false, if cell is /// unloaded. + /// @note Will not account for moved references which may exist in Loaded state. Use search() instead if the cell is loaded. Ptr search (const std::string& id); ///< Will return an empty Ptr if cell is not loaded. Does not check references in /// containers. + /// @note Triggers CellStore hasState flag. - Ptr searchViaHandle (const std::string& handle); - ///< Will return an empty Ptr if cell is not loaded. + ConstPtr searchConst (const std::string& id) const; + ///< Will return an empty Ptr if cell is not loaded. Does not check references in + /// containers. + /// @note Does not trigger CellStore hasState flag. Ptr searchViaActorId (int id); ///< Will return an empty Ptr if cell is not loaded. @@ -112,55 +234,102 @@ namespace MWWorld int count() const; ///< Return total number of references, including deleted ones. - void load (const MWWorld::ESMStore &store, std::vector &esm); + void load (); ///< Load references from content file. - void preload (const MWWorld::ESMStore &store, std::vector &esm); + void preload (); ///< Build ID list from content file. - /// Call functor (ref) for each reference. functor must return a bool. Returning + /// Call visitor (MWWorld::Ptr) for each reference. visitor must return a bool. Returning + /// false will abort the iteration. + /// \note Prefer using forEachConst when possible. + /// \attention This function also lists deleted (count 0) objects! + /// \return Iteration completed? + template + bool forEach (Visitor& visitor) + { + if (mState != State_Loaded) + return false; + + mHasState = true; + + for (unsigned int i=0; imData, mMergedRefs[i]->mRef)) + continue; + + if (!visitor(MWWorld::Ptr(mMergedRefs[i], this))) + return false; + } + return true; + } + + /// Call visitor (MWWorld::ConstPtr) for each reference. visitor must return a bool. Returning /// false will abort the iteration. /// \attention This function also lists deleted (count 0) objects! /// \return Iteration completed? - /// - /// \note Creatures and NPCs are handled last. - template - bool forEach (Functor& functor) + template + bool forEachConst (Visitor& visitor) const { - mHasState = true; + if (mState != State_Loaded) + return false; - return - forEachImp (functor, mActivators) && - forEachImp (functor, mPotions) && - forEachImp (functor, mAppas) && - forEachImp (functor, mArmors) && - forEachImp (functor, mBooks) && - forEachImp (functor, mClothes) && - forEachImp (functor, mContainers) && - forEachImp (functor, mDoors) && - forEachImp (functor, mIngreds) && - forEachImp (functor, mItemLists) && - forEachImp (functor, mLights) && - forEachImp (functor, mLockpicks) && - forEachImp (functor, mMiscItems) && - forEachImp (functor, mProbes) && - forEachImp (functor, mRepairs) && - forEachImp (functor, mStatics) && - forEachImp (functor, mWeapons) && - forEachImp (functor, mCreatures) && - forEachImp (functor, mNpcs) && - forEachImp (functor, mCreatureLists); + for (unsigned int i=0; imData, mMergedRefs[i]->mRef)) + continue; + + if (!visitor(MWWorld::ConstPtr(mMergedRefs[i], this))) + return false; + } + return true; } - template - bool forEachContainer (Functor& functor) + + /// Call visitor (ref) for each reference of given type. visitor must return a bool. Returning + /// false will abort the iteration. + /// \attention This function also lists deleted (count 0) objects! + /// \return Iteration completed? + template + bool forEachType(Visitor& visitor) { + if (mState != State_Loaded) + return false; + mHasState = true; - return - forEachImp (functor, mContainers) && - forEachImp (functor, mCreatures) && - forEachImp (functor, mNpcs); + CellRefList& list = get(); + + for (typename CellRefList::List::iterator it (list.mList.begin()); it!=list.mList.end(); ++it) + { + LiveCellRefBase* base = &*it; + if (mMovedToAnotherCell.find(base) != mMovedToAnotherCell.end()) + continue; + if (!isAccessible(base->mData, base->mRef)) + continue; + if (!visitor(MWWorld::Ptr(base, this))) + return false; + } + + for (MovedRefTracker::const_iterator it = mMovedHere.begin(); it != mMovedHere.end(); ++it) + { + LiveCellRefBase* base = it->first; + if (dynamic_cast*>(base)) + if (!visitor(MWWorld::Ptr(base, this))) + return false; + } + return true; + } + + // NOTE: does not account for moved references + // Should be phased out when we have const version of forEach + inline const CellRefList& getReadOnlyDoors() const + { + return mDoors; + } + inline const CellRefList& getReadOnlyStatics() const + { + return mStatics; } bool isExterior() const; @@ -177,47 +346,31 @@ namespace MWWorld void writeReferences (ESM::ESMWriter& writer) const; - void readReferences (ESM::ESMReader& reader, const std::map& contentFileMap); + struct GetCellStoreCallback + { + public: + ///@note must return NULL if the cell is not found + virtual CellStore* getCellStore(const ESM::CellId& cellId) = 0; + }; + + /// @param callback to use for retrieving of additional CellStore objects by ID (required for resolving moved references) + void readReferences (ESM::ESMReader& reader, const std::map& contentFileMap, GetCellStoreCallback* callback); void respawn (); ///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded. - template - CellRefList& get() { - throw std::runtime_error ("Storage for type " + std::string(typeid(T).name())+ " does not exist in cells"); - } - - template - const CellRefList& getReadOnly() { - throw std::runtime_error ("Read Only CellRefList access not available for type " + std::string(typeid(T).name()) ); - } - bool isPointConnected(const int start, const int end) const; std::list aStarSearch(const int start, const int end) const; private: - template - bool forEachImp (Functor& functor, List& list) - { - for (typename List::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); - ++iter) - { - if (iter->mData.isDeletedByContentFile()) - continue; - if (!functor (MWWorld::Ptr(&*iter, this))) - return false; - } - return true; - } - /// Run through references and store IDs - void listRefs(const MWWorld::ESMStore &store, std::vector &esm); + void listRefs(); - void loadRefs(const MWWorld::ESMStore &store, std::vector &esm); + void loadRefs(); - void loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store); + void loadRef (ESM::CellRef& ref, bool deleted); ///< Make case-adjustments to \a ref and insert it into the respective container. /// /// Invalid \a ref objects are silently dropped. @@ -365,12 +518,6 @@ namespace MWWorld return mWeapons; } - template<> - inline const CellRefList& CellStore::getReadOnly() - { - return mDoors; - } - bool operator== (const CellStore& left, const CellStore& right); bool operator!= (const CellStore& left, const CellStore& right); } diff --git a/apps/openmw/mwworld/cellvisitors.hpp b/apps/openmw/mwworld/cellvisitors.hpp new file mode 100644 index 0000000000..cfb07f7492 --- /dev/null +++ b/apps/openmw/mwworld/cellvisitors.hpp @@ -0,0 +1,29 @@ +#ifndef GAME_MWWORLD_CELLVISITORS_H +#define GAME_MWWORLD_CELLVISITORS_H + +#include +#include + +#include "ptr.hpp" + + +namespace MWWorld +{ + struct ListAndResetObjectsVisitor + { + std::vector mObjects; + + bool operator() (MWWorld::Ptr ptr) + { + if (ptr.getRefData().getBaseNode()) + { + ptr.getRefData().setBaseNode(NULL); + mObjects.push_back (ptr); + } + + return true; + } + }; +} + +#endif diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 6fa9ba9b69..7f2d759b95 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -1,10 +1,7 @@ - #include "class.hpp" #include -#include - #include #include "../mwbase/environment.hpp" @@ -33,17 +30,12 @@ namespace MWWorld Class::~Class() {} - std::string Class::getId (const Ptr& ptr) const - { - throw std::runtime_error ("class does not support ID retrieval"); - } - void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const { } - void Class::insertObject(const Ptr& ptr, const std::string& mesh, MWWorld::PhysicsSystem& physics) const + void Class::insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const { } @@ -58,12 +50,12 @@ namespace MWWorld throw std::runtime_error ("class does not represent an actor"); } - bool Class::canSell (const MWWorld::Ptr& item, int npcServices) const + bool Class::canSell (const MWWorld::ConstPtr& item, int npcServices) const { return false; } - int Class::getServices(const Ptr &actor) const + int Class::getServices(const ConstPtr &actor) const { throw std::runtime_error ("class does not have services"); } @@ -78,12 +70,12 @@ namespace MWWorld throw std::runtime_error ("class does not have NPC stats"); } - bool Class::hasItemHealth (const Ptr& ptr) const + bool Class::hasItemHealth (const ConstPtr& ptr) const { return false; } - int Class::getItemHealth(const Ptr &ptr) const + int Class::getItemHealth(const ConstPtr &ptr) const { if (ptr.getCellRef().getCharge() == -1) return getItemMaxHealth(ptr); @@ -91,12 +83,12 @@ namespace MWWorld return ptr.getCellRef().getCharge(); } - int Class::getItemMaxHealth (const Ptr& ptr) const + int Class::getItemMaxHealth (const ConstPtr& ptr) const { throw std::runtime_error ("class does not have item health"); } - void Class::hit(const Ptr& ptr, int type) const + void Class::hit(const Ptr& ptr, float attackStrength, int type) const { throw std::runtime_error("class cannot hit"); } @@ -111,11 +103,6 @@ namespace MWWorld throw std::runtime_error("class cannot be hit"); } - void Class::setActorHealth(const Ptr& ptr, float health, const Ptr& attacker) const - { - throw std::runtime_error("class does not have actor health"); - } - boost::shared_ptr Class::activate (const Ptr& ptr, const Ptr& actor) const { return boost::shared_ptr (new NullAction); @@ -151,17 +138,22 @@ namespace MWWorld throw std::runtime_error ("class does not support unlocking"); } + bool Class::canLock(const ConstPtr &ptr) const + { + return false; + } + void Class::setRemainingUsageTime (const Ptr& ptr, float duration) const { throw std::runtime_error ("class does not support time-based uses"); } - float Class::getRemainingUsageTime (const Ptr& ptr) const + float Class::getRemainingUsageTime (const ConstPtr& ptr) const { return -1; } - std::string Class::getScript (const Ptr& ptr) const + std::string Class::getScript (const ConstPtr& ptr) const { return ""; } @@ -176,7 +168,7 @@ namespace MWWorld return 0; } - int Class::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Class::getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const { throw std::runtime_error ("class does not support enchanting"); } @@ -186,27 +178,22 @@ namespace MWWorld throw std::runtime_error ("movement settings not supported by class"); } - Ogre::Vector3 Class::getMovementVector (const Ptr& ptr) const + osg::Vec3f Class::getRotationVector (const Ptr& ptr) const { - return Ogre::Vector3 (0, 0, 0); + return osg::Vec3f (0, 0, 0); } - Ogre::Vector3 Class::getRotationVector (const Ptr& ptr) const - { - return Ogre::Vector3 (0, 0, 0); - } - - std::pair, bool> Class::getEquipmentSlots (const Ptr& ptr) const + std::pair, bool> Class::getEquipmentSlots (const ConstPtr& ptr) const { return std::make_pair (std::vector(), false); } - int Class::getEquipmentSkill (const Ptr& ptr) const + int Class::getEquipmentSkill (const ConstPtr& ptr) const { return -1; } - int Class::getValue (const Ptr& ptr) const + int Class::getValue (const ConstPtr& ptr) const { throw std::logic_error ("value not supported by this class"); } @@ -216,7 +203,7 @@ namespace MWWorld throw std::runtime_error ("capacity not supported by this class"); } - float Class::getWeight(const Ptr &ptr) const + float Class::getWeight(const ConstPtr &ptr) const { throw std::runtime_error ("weight not supported by this class"); } @@ -226,7 +213,7 @@ namespace MWWorld throw std::runtime_error ("encumbrance not supported by class"); } - bool Class::isEssential (const MWWorld::Ptr& ptr) const + bool Class::isEssential (const MWWorld::ConstPtr& ptr) const { return false; } @@ -249,7 +236,7 @@ namespace MWWorld return *iter->second; } - bool Class::isPersistent(const Ptr &ptr) const + bool Class::isPersistent(const ConstPtr &ptr) const { throw std::runtime_error ("class does not support persistence"); } @@ -260,12 +247,12 @@ namespace MWWorld sClasses.insert(std::make_pair(key, instance)); } - std::string Class::getUpSoundId (const Ptr& ptr) const + std::string Class::getUpSoundId (const ConstPtr& ptr) const { throw std::runtime_error ("class does not have an up sound"); } - std::string Class::getDownSoundId (const Ptr& ptr) const + std::string Class::getDownSoundId (const ConstPtr& ptr) const { throw std::runtime_error ("class does not have an down sound"); } @@ -275,41 +262,41 @@ namespace MWWorld throw std::runtime_error("class does not support soundgen look up"); } - std::string Class::getInventoryIcon (const MWWorld::Ptr& ptr) const + std::string Class::getInventoryIcon (const MWWorld::ConstPtr& ptr) const { throw std::runtime_error ("class does not have any inventory icon"); } - MWGui::ToolTipInfo Class::getToolTipInfo (const Ptr& ptr) const + MWGui::ToolTipInfo Class::getToolTipInfo (const ConstPtr& ptr, int count) const { throw std::runtime_error ("class does not have a tool tip"); } - bool Class::hasToolTip (const Ptr& ptr) const + bool Class::hasToolTip (const ConstPtr& ptr) const { return false; } - std::string Class::getEnchantment (const Ptr& ptr) const + std::string Class::getEnchantment (const ConstPtr& ptr) const { return ""; } - void Class::adjustScale(const MWWorld::Ptr& ptr,float& scale) const + void Class::adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const { } - std::string Class::getModel(const MWWorld::Ptr &ptr) const + std::string Class::getModel(const MWWorld::ConstPtr &ptr) const { return ""; } - std::string Class::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + std::string Class::applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { throw std::runtime_error ("class can't be enchanted"); } - std::pair Class::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const + std::pair Class::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { return std::make_pair (1, ""); } @@ -341,44 +328,45 @@ namespace MWWorld } MWWorld::Ptr - Class::copyToCellImpl(const Ptr &ptr, CellStore &cell) const + Class::copyToCellImpl(const ConstPtr &ptr, CellStore &cell) const { - throw std::runtime_error("unable to move class to cell"); + throw std::runtime_error("unable to copy class to cell"); } MWWorld::Ptr - Class::copyToCell(const Ptr &ptr, CellStore &cell) const + Class::copyToCell(const ConstPtr &ptr, CellStore &cell, int count) const { Ptr newPtr = copyToCellImpl(ptr, cell); newPtr.getCellRef().unsetRefNum(); // This RefNum is only valid within the original cell of the reference + newPtr.getRefData().setCount(count); return newPtr; } MWWorld::Ptr - Class::copyToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos) const + Class::copyToCell(const ConstPtr &ptr, CellStore &cell, const ESM::Position &pos, int count) const { - Ptr newPtr = copyToCell(ptr, cell); + Ptr newPtr = copyToCell(ptr, cell, count); newPtr.getRefData().setPosition(pos); return newPtr; } - bool Class::isBipedal(const Ptr &ptr) const + bool Class::isBipedal(const ConstPtr &ptr) const { return false; } - bool Class::canFly(const Ptr &ptr) const + bool Class::canFly(const ConstPtr &ptr) const { return false; } - bool Class::canSwim(const Ptr &ptr) const + bool Class::canSwim(const ConstPtr &ptr) const { return false; } - bool Class::canWalk(const Ptr &ptr) const + bool Class::canWalk(const ConstPtr &ptr) const { return false; } @@ -398,26 +386,26 @@ namespace MWWorld throw std::runtime_error("class does not support skills"); } - int Class::getBloodTexture (const MWWorld::Ptr& ptr) const + int Class::getBloodTexture (const MWWorld::ConstPtr& ptr) const { throw std::runtime_error("class does not support gore"); } void Class::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const {} - void Class::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const {} + void Class::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const {} - int Class::getBaseGold(const MWWorld::Ptr& ptr) const + int Class::getBaseGold(const MWWorld::ConstPtr& ptr) const { throw std::runtime_error("class does not support base gold"); } - bool Class::isClass(const MWWorld::Ptr& ptr, const std::string &className) const + bool Class::isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const { return false; } - int Class::getDoorState (const MWWorld::Ptr &ptr) const + int Class::getDoorState (const MWWorld::ConstPtr &ptr) const { throw std::runtime_error("this is not a door"); } @@ -436,26 +424,26 @@ namespace MWWorld return getEncumbrance(ptr) / capacity; } - std::string Class::getSound(const MWWorld::Ptr&) const + std::string Class::getSound(const MWWorld::ConstPtr&) const { return std::string(); } - int Class::getBaseFightRating(const Ptr &ptr) const + int Class::getBaseFightRating(const ConstPtr &ptr) const { throw std::runtime_error("class does not support fight rating"); } - std::string Class::getPrimaryFaction (const MWWorld::Ptr& ptr) const + std::string Class::getPrimaryFaction (const MWWorld::ConstPtr& ptr) const { return std::string(); } - int Class::getPrimaryFactionRank (const MWWorld::Ptr& ptr) const + int Class::getPrimaryFactionRank (const MWWorld::ConstPtr& ptr) const { return -1; } - int Class::getEffectiveArmorRating(const Ptr &ptr, const Ptr &actor) const + int Class::getEffectiveArmorRating(const ConstPtr &armor, const Ptr &actor) const { throw std::runtime_error("class does not support armor ratings"); } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 782aa78151..85cd9ff7c5 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -14,16 +14,16 @@ namespace ESM struct ObjectState; } -namespace Ogre -{ - class Vector3; -} - namespace MWRender { class RenderingInterface; } +namespace MWPhysics +{ + class PhysicsSystem; +} + namespace MWMechanics { class CreatureStats; @@ -45,7 +45,6 @@ namespace MWWorld { class ContainerStore; class InventoryStore; - class PhysicsSystem; class CellStore; class Action; @@ -67,7 +66,7 @@ namespace MWWorld boost::shared_ptr defaultItemActivate(const Ptr &ptr, const Ptr &actor) const; ///< Generate default action for activating inventory items - virtual Ptr copyToCellImpl(const Ptr &ptr, CellStore &cell) const; + virtual Ptr copyToCellImpl(const ConstPtr &ptr, CellStore &cell) const; public: @@ -77,17 +76,11 @@ namespace MWWorld return mTypeName; } - virtual std::string getId (const Ptr& ptr) const; - ///< Return ID of \a ptr or throw an exception, if class does not support ID retrieval - /// (default implementation: throw an exception) - /// @note This function is currently redundant; the same ID can be retrieved by CellRef::getRefId. - /// Leaving it here for now in case we want to optimize later. - virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; - virtual void insertObject(const Ptr& ptr, const std::string& mesh, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). - virtual std::string getName (const Ptr& ptr) const = 0; + virtual std::string getName (const ConstPtr& ptr) const = 0; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -99,29 +92,30 @@ namespace MWWorld ///< Return creature stats or throw an exception, if class does not have creature stats /// (default implementation: throw an exception) - virtual bool hasToolTip (const Ptr& ptr) const; + virtual bool hasToolTip (const ConstPtr& ptr) const; ///< @return true if this object has a tooltip when focused (default implementation: false) - virtual MWGui::ToolTipInfo getToolTipInfo (const Ptr& ptr) const; + virtual MWGui::ToolTipInfo getToolTipInfo (const ConstPtr& ptr, int count) const; ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. virtual MWMechanics::NpcStats& getNpcStats (const Ptr& ptr) const; ///< Return NPC stats or throw an exception, if class does not have NPC stats /// (default implementation: throw an exception) - virtual bool hasItemHealth (const Ptr& ptr) const; + virtual bool hasItemHealth (const ConstPtr& ptr) const; ///< \return Item health data available? (default implementation: false) - virtual int getItemHealth (const Ptr& ptr) const; + virtual int getItemHealth (const ConstPtr& ptr) const; ///< Return current item health or throw an exception if class does not have item health - virtual int getItemMaxHealth (const Ptr& ptr) const; + virtual int getItemMaxHealth (const ConstPtr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health /// (default implementation: throw an exception) - virtual void hit(const Ptr& ptr, int type=-1) const; + virtual void hit(const Ptr& ptr, float attackStrength, int type=-1) const; ///< Execute a melee hit, using the current weapon. This will check the relevant skills /// of the given attacker, and whoever is hit. + /// \param attackStrength how long the attack was charged for, a value in 0-1 range. /// \param type - type of attack, one of the MWMechanics::CreatureStats::AttackType /// enums. ignored for creature attacks. /// (default implementation: throw an exception) @@ -136,12 +130,6 @@ namespace MWWorld ///< Play the appropriate sound for a blocked attack, depending on the currently equipped shield /// (default implementation: throw an exception) - virtual void setActorHealth(const Ptr& ptr, float health, const Ptr& attacker=Ptr()) const; - ///< Sets a new current health value for the actor, optionally specifying the object causing - /// the change. Use this instead of using CreatureStats directly as this will make sure the - /// correct dialog and actor states are properly handled when being hurt or healed. - /// (default implementation: throw an exception) - virtual boost::shared_ptr activate (const Ptr& ptr, const Ptr& actor) const; ///< Generate action for activation (default implementation: return a null action). @@ -167,15 +155,17 @@ namespace MWWorld virtual void unlock (const Ptr& ptr) const; ///< Unlock object (default implementation: throw an exception) + virtual bool canLock (const ConstPtr& ptr) const; + virtual void setRemainingUsageTime (const Ptr& ptr, float duration) const; ///< Sets the remaining duration of the object, such as an equippable light /// source. (default implementation: throw an exception) - virtual float getRemainingUsageTime (const Ptr& ptr) const; + virtual float getRemainingUsageTime (const ConstPtr& ptr) const; ///< Returns the remaining duration of the object, such as an equippable light /// source. (default implementation: -1, i.e. infinite) - virtual std::string getScript (const Ptr& ptr) const; + virtual std::string getScript (const ConstPtr& ptr) const; ///< Return name of the script attached to ptr (default implementation: return an empty /// string). @@ -188,26 +178,22 @@ namespace MWWorld virtual MWMechanics::Movement& getMovementSettings (const Ptr& ptr) const; ///< Return desired movement. - virtual Ogre::Vector3 getMovementVector (const Ptr& ptr) const; - ///< Return desired movement vector (determined based on movement settings, - /// stance and stats). - - virtual Ogre::Vector3 getRotationVector (const Ptr& ptr) const; + virtual osg::Vec3f getRotationVector (const Ptr& ptr) const; ///< Return desired rotations, as euler angles. - virtual std::pair, bool> getEquipmentSlots (const Ptr& ptr) const; + virtual std::pair, bool> getEquipmentSlots (const ConstPtr& 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 getEquipmentSkill (const Ptr& ptr) + virtual int getEquipmentSkill (const ConstPtr& ptr) const; - /// Return the index of the skill this item corresponds to when equiopped or -1, if there is + /// Return the index of the skill this item corresponds to when equipped or -1, if there is /// no such skill. /// (default implementation: return -1) - virtual int getValue (const Ptr& ptr) const; + virtual int getValue (const ConstPtr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. /// (default implementation: throws an exception) @@ -237,16 +223,16 @@ namespace MWWorld /// /// (default implementations: throws an exception) - virtual bool isEssential (const MWWorld::Ptr& ptr) const; + virtual bool isEssential (const MWWorld::ConstPtr& ptr) const; ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) /// /// (default implementation: return false) - virtual std::string getUpSoundId (const Ptr& ptr) const; + virtual std::string getUpSoundId (const ConstPtr& ptr) const; ///< Return the up sound ID of \a ptr or throw an exception, if class does not support ID retrieval /// (default implementation: throw an exception) - virtual std::string getDownSoundId (const Ptr& ptr) const; + virtual std::string getDownSoundId (const ConstPtr& ptr) const; ///< Return the down sound ID of \a ptr or throw an exception, if class does not support ID retrieval /// (default implementation: throw an exception) @@ -256,46 +242,47 @@ namespace MWWorld virtual float getArmorRating (const MWWorld::Ptr& ptr) const; ///< @return combined armor rating of this actor - virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const; ///< Return name of inventory icon. - virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; + virtual std::string getEnchantment (const MWWorld::ConstPtr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string /// (default implementation: return empty string) - virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::ConstPtr& ptr) const; ///< @return the number of enchantment points available for possible enchanting - virtual void adjustScale(const MWWorld::Ptr& ptr,float& scale) const; + virtual void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const; + /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh - virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual bool canSell (const MWWorld::ConstPtr& item, int npcServices) const; ///< Determine whether or not \a item can be sold to an npc with the given \a npcServices - virtual int getServices (const MWWorld::Ptr& actor) const; + virtual int getServices (const MWWorld::ConstPtr& actor) const; - virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; - virtual std::string applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + virtual std::string applyEnchantment(const MWWorld::ConstPtr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. - virtual std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; + virtual std::pair canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. /// Second item in the pair specifies the error message - virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::ConstPtr& ptr) const; - virtual bool isPersistent (const MWWorld::Ptr& ptr) const; + virtual bool isPersistent (const MWWorld::ConstPtr& ptr) const; - virtual bool isKey (const MWWorld::Ptr& ptr) const { return false; } + virtual bool isKey (const MWWorld::ConstPtr& ptr) const { return false; } + virtual bool isGold(const MWWorld::ConstPtr& ptr) const { return false; } + /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) - virtual int getBloodTexture (const MWWorld::Ptr& ptr) const; + virtual int getBloodTexture (const MWWorld::ConstPtr& ptr) const; - virtual Ptr - copyToCell(const Ptr &ptr, CellStore &cell) const; + virtual Ptr copyToCell(const ConstPtr &ptr, CellStore &cell, int count) const; - virtual Ptr - copyToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos) const; + virtual Ptr copyToCell(const ConstPtr &ptr, CellStore &cell, const ESM::Position &pos, int count) const; virtual bool isActor() const { return false; @@ -305,10 +292,10 @@ namespace MWWorld return false; } - virtual bool isBipedal(const MWWorld::Ptr& ptr) const; - virtual bool canFly(const MWWorld::Ptr& ptr) const; - virtual bool canSwim(const MWWorld::Ptr& ptr) const; - virtual bool canWalk(const MWWorld::Ptr& ptr) const; + virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const; + virtual bool canFly(const MWWorld::ConstPtr& ptr) const; + virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; + virtual bool canWalk(const MWWorld::ConstPtr& ptr) const; bool isPureWaterCreature(const MWWorld::Ptr& ptr) const; bool isMobile(const MWWorld::Ptr& ptr) const; @@ -318,7 +305,7 @@ namespace MWWorld const; ///< Read additional state from \a state into \a ptr. - virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + virtual void writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. @@ -327,12 +314,12 @@ namespace MWWorld static void registerClass (const std::string& key, boost::shared_ptr instance); - virtual int getBaseGold(const MWWorld::Ptr& ptr) const; + virtual int getBaseGold(const MWWorld::ConstPtr& ptr) const; - virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const; + virtual bool isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const; /// 0 = nothing, 1 = opening, 2 = closing - virtual int getDoorState (const MWWorld::Ptr &ptr) const; + virtual int getDoorState (const MWWorld::ConstPtr &ptr) const; /// This does not actually cause the door to move. Use World::activateDoor instead. virtual void setDoorState (const MWWorld::Ptr &ptr, int state) const; @@ -341,15 +328,15 @@ namespace MWWorld virtual void restock (const MWWorld::Ptr& ptr) const {} /// Returns sound id - virtual std::string getSound(const MWWorld::Ptr& ptr) const; + virtual std::string getSound(const MWWorld::ConstPtr& ptr) const; - virtual int getBaseFightRating (const MWWorld::Ptr& ptr) const; + virtual int getBaseFightRating (const MWWorld::ConstPtr& ptr) const; - virtual std::string getPrimaryFaction (const MWWorld::Ptr& ptr) const; - virtual int getPrimaryFactionRank (const MWWorld::Ptr& ptr) const; + virtual std::string getPrimaryFaction (const MWWorld::ConstPtr& ptr) const; + virtual int getPrimaryFactionRank (const MWWorld::ConstPtr& ptr) const; /// Get the effective armor rating, factoring in the actor's skills, for the given armor. - virtual int getEffectiveArmorRating(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; + virtual int getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const; }; } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index d4aadc6c7a..ab9fa46110 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -1,4 +1,3 @@ - #include "containerstore.hpp" #include @@ -12,6 +11,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/levelledlist.hpp" +#include "../mwmechanics/actorutil.hpp" #include "manualref.hpp" #include "refdata.hpp" @@ -94,7 +94,7 @@ void MWWorld::ContainerStore::storeState (const LiveCellRef& ref, ESM::Object } template -void MWWorld::ContainerStore::storeStates (CellRefList& collection, +void MWWorld::ContainerStore::storeStates (const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable) const { for (typename CellRefList::List::const_iterator iter (collection.mList.begin()); @@ -140,7 +140,11 @@ void MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container) { if (ptr.getRefData().getCount() <= 1) return; - addNewStack(ptr, ptr.getRefData().getCount()-1); + MWWorld::ContainerStoreIterator it = addNewStack(ptr, ptr.getRefData().getCount()-1); + const std::string script = it->getClass().getScript(*it); + if (!script.empty()) + MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it); + remove(ptr, ptr.getRefData().getCount()-1, container); } @@ -172,7 +176,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld:: return retval; } -bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) +bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) { const MWWorld::Class& cls1 = ptr1.getClass(); const MWWorld::Class& cls2 = ptr2.getClass(); @@ -210,7 +214,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const std::string & { MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id, count); // a bit pointless to set owner for the player - if (actorPtr != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (actorPtr != MWMechanics::getPlayer()) return add(ref.getPtr(), count, actorPtr, true); else return add(ref.getPtr(), count, actorPtr, false); @@ -225,7 +229,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr // HACK: Set owner on the original item, then reset it after we have copied it // If we set the owner on the copied item, it would not stack correctly... std::string oldOwner = itemPtr.getCellRef().getOwner(); - if (!setOwner || actorPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) // No point in setting owner to the player - NPCs will not respect this anyway + if (!setOwner || actorPtr == MWMechanics::getPlayer()) // No point in setting owner to the player - NPCs will not respect this anyway { itemPtr.getCellRef().setOwner(""); } @@ -298,11 +302,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, // gold needs special handling: when it is inserted into a container, the base object automatically becomes Gold_001 // this ensures that gold piles of different sizes stack with each other (also, several scripts rely on Gold_001 for detecting player gold) - if (Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_001") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_005") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_010") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_025") - || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_100")) + if(ptr.getClass().isGold(ptr)) { int realCount = count * ptr.getClass().getValue(ptr); @@ -336,7 +336,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, return addNewStack(ptr, count); } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const Ptr& ptr, int count) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const ConstPtr& ptr, int count) { ContainerStoreIterator it = begin(); @@ -415,69 +415,133 @@ void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std:: void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, int count, bool topLevel, const std::string& levItem) { - ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count); + if (count == 0) return; //Don't restock with nothing. + try { + ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count); - if (ref.getPtr().getTypeName()==typeid (ESM::ItemLevList).name()) - { - const ESM::ItemLevList* levItem = ref.getPtr().get()->mBase; - - if (topLevel && std::abs(count) > 1 && levItem->mFlags & ESM::ItemLevList::Each) + if (ref.getPtr().getTypeName()==typeid (ESM::ItemLevList).name()) { - for (int i=0; i 0 ? 1 : -1, true, levItem->mId); - return; + const ESM::ItemLevList* levItem = ref.getPtr().get()->mBase; + + if (topLevel && std::abs(count) > 1 && levItem->mFlags & ESM::ItemLevList::Each) + { + for (int i=0; i 0 ? 1 : -1, true, levItem->mId); + return; + } + else + { + std::string id = MWMechanics::getLevelledItem(ref.getPtr().get()->mBase, false); + if (id.empty()) + return; + addInitialItem(id, owner, count, false, levItem->mId); + } } else { - std::string id = MWMechanics::getLevelledItem(ref.getPtr().get()->mBase, false); - if (id.empty()) - return; - addInitialItem(id, owner, count, false, levItem->mId); - } - } - else - { - // A negative count indicates restocking items - // For a restocking levelled item, remember what we spawned so we can delete it later when the merchant restocks - if (!levItem.empty() && count < 0) - { - if (mLevelledItemMap.find(id) == mLevelledItemMap.end()) - mLevelledItemMap[id] = 0; - mLevelledItemMap[id] += std::abs(count); - } - count = std::abs(count); + // A negative count indicates restocking items + // For a restocking levelled item, remember what we spawned so we can delete it later when the merchant restocks + if (!levItem.empty() && count < 0) + { + //If there is no item in map, insert it + std::map, int>::iterator itemInMap = + mLevelledItemMap.insert(std::make_pair(std::make_pair(id, levItem), 0)).first; + //Update spawned count + itemInMap->second += std::abs(count); + } + count = std::abs(count); - ref.getPtr().getCellRef().setOwner(owner); - addImp (ref.getPtr(), count); + ref.getPtr().getCellRef().setOwner(owner); + addImp (ref.getPtr(), count); + } } + catch (const std::exception& e) + { + std::cerr << "Error in MWWorld::ContainerStore::addInitialItem: " << e.what() << std::endl; + } + } void MWWorld::ContainerStore::restock (const ESM::InventoryList& items, const MWWorld::Ptr& ptr, const std::string& owner) { - // Remove the items already spawned by levelled items that will restock - for (std::map::iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end(); ++it) - { - if (count(it->first) >= it->second) - remove(it->first, it->second, ptr); - } - mLevelledItemMap.clear(); + //allowedForReplace - Holds information about how many items from the list were not sold; + // Hence, tells us how many items we don't need to restock. + //allowedForReplace[list] <- How many items we should generate(how many of these were sold) + std::map allowedForReplace; + //Check which lists need restocking: + for (std::map, int>::iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end();) + { + int spawnedCount = it->second; //How many items should be in shop originally + int itemCount = count(it->first.first); //How many items are there in shop now + //If something was not sold + if(itemCount >= spawnedCount) + { + const std::string& parent = it->first.second; + // Security check for old saves: + //If item is imported from old save(doesn't have an parent) and wasn't sold + if(parent == "") + { + //Remove it, from shop, + remove(it->first.first, itemCount, ptr);//ptr is the NPC + //And remove it from map, so that when we restock, the new item will have proper parent. + mLevelledItemMap.erase(it++); + continue; + } + //Create the entry if it does not exist yet + std::map::iterator listInMap = allowedForReplace.insert( + std::make_pair(it->first.second, 0)).first; + //And signal that we don't need to restock item from this list + listInMap->second += std::abs(itemCount); + } + //If every of the item was sold + else if (itemCount == 0) + { + mLevelledItemMap.erase(it++); + continue; + } + //If some was sold, but some remain + else + { + //Create entry if it does not exist yet + std::map::iterator listInMap = allowedForReplace.insert( + std::make_pair(it->first.second, 0)).first; + //And signal that we don't need to restock all items from this list + listInMap->second += std::abs(itemCount); + //And update itemCount so we don't mistake it next time. + it->second = itemCount; + } + ++it; + } + + //Restock: + //For every item that NPC could have for (std::vector::const_iterator it = items.mList.begin(); it != items.mList.end(); ++it) { + //If he shouldn't have it restocked, don't restock it. if (it->mCount >= 0) continue; - std::string item = Misc::StringUtils::lowerCase(it->mItem.toString()); + std::string itemOrList = Misc::StringUtils::lowerCase(it->mItem.toString()); + //If it's levelled list, restock if there's need to do so. if (MWBase::Environment::get().getWorld()->getStore().get().search(it->mItem.toString())) { - addInitialItem(item, owner, it->mCount, true); + std::map::iterator listInMap = allowedForReplace.find(itemOrList); + + int restockNum = it->mCount; + //If we know we must restock less, take it into account + if(listInMap != allowedForReplace.end()) + restockNum += listInMap->second;//We add, because list items have negative count + //restock + addInitialItem(itemOrList, owner, restockNum, true); } else { - int currentCount = count(item); + //Restocking static item - just restock to the max count + int currentCount = count(itemOrList); if (currentCount < std::abs(it->mCount)) - addInitialItem(item, owner, std::abs(it->mCount) - currentCount, true); + addInitialItem(itemOrList, owner, std::abs(it->mCount) - currentCount, true); } } flagAsModified(); @@ -521,7 +585,7 @@ float MWWorld::ContainerStore::getWeight() const return mCachedWeight; } -int MWWorld::ContainerStore::getType (const Ptr& ptr) +int MWWorld::ContainerStore::getType (const ConstPtr& ptr) { if (ptr.isEmpty()) throw std::runtime_error ("can't put a non-existent object into a container"); @@ -643,7 +707,7 @@ MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) return Ptr(); } -void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) +void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const { state.mItems.clear(); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index e9750a6228..b7ec4bb74e 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -68,9 +69,9 @@ namespace MWWorld MWWorld::CellRefList repairs; MWWorld::CellRefList weapons; - std::map mLevelledItemMap; - ///< Stores result of levelled item spawns. - /// This is used to remove the spawned item(s) if the levelled item is restocked. + std::map, int> mLevelledItemMap; + ///< Stores result of levelled item spawns. <(refId, spawningGroup), count> + /// This is used to restock levelled items(s) if the old item was sold. mutable float mCachedWeight; mutable bool mWeightUpToDate; @@ -85,7 +86,7 @@ namespace MWWorld void storeState (const LiveCellRef& ref, ESM::ObjectState& state) const; template - void storeStates (CellRefList& collection, + void storeStates (const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable = false) const; @@ -141,14 +142,14 @@ namespace MWWorld int count (const std::string& id); protected: - ContainerStoreIterator addNewStack (const Ptr& ptr, int count); + ContainerStoreIterator addNewStack (const ConstPtr& ptr, int count); ///< Add the item to this container (do not try to stack it onto existing items) virtual void flagAsModified(); public: - virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2); + virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2); ///< @return true if the two specified objects can stack with each other void fill (const ESM::InventoryList& items, const std::string& owner); @@ -162,14 +163,13 @@ namespace MWWorld float getWeight() const; ///< Return total weight of the items contained in *this. - static int getType (const Ptr& ptr); + static int getType (const ConstPtr& ptr); ///< This function throws an exception, if ptr does not point to an object, that can be /// put into a container. Ptr search (const std::string& id); - /// \todo make this method const once const-correct ContainerStoreIterators are available - virtual void writeState (ESM::InventoryState& state); + virtual void writeState (ESM::InventoryState& state) const; virtual void readState (const ESM::InventoryState& state); diff --git a/apps/openmw/mwworld/customdata.cpp b/apps/openmw/mwworld/customdata.cpp new file mode 100644 index 0000000000..a63123bcf5 --- /dev/null +++ b/apps/openmw/mwworld/customdata.cpp @@ -0,0 +1,74 @@ +#include "customdata.hpp" + +#include +#include +#include + +namespace MWWorld +{ + +MWClass::CreatureCustomData &CustomData::asCreatureCustomData() +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureCustomData"; + throw std::logic_error(error.str()); +} + +const MWClass::CreatureCustomData &CustomData::asCreatureCustomData() const +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureCustomData"; + throw std::logic_error(error.str()); +} + +MWClass::NpcCustomData &CustomData::asNpcCustomData() +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to NpcCustomData"; + throw std::logic_error(error.str()); +} + +const MWClass::NpcCustomData &CustomData::asNpcCustomData() const +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to NpcCustomData"; + throw std::logic_error(error.str()); +} + +MWClass::ContainerCustomData &CustomData::asContainerCustomData() +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to ContainerCustomData"; + throw std::logic_error(error.str()); +} + +MWClass::DoorCustomData &CustomData::asDoorCustomData() +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to DoorCustomData"; + throw std::logic_error(error.str()); +} + +const MWClass::DoorCustomData &CustomData::asDoorCustomData() const +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to DoorCustomData"; + throw std::logic_error(error.str()); +} + +MWClass::CreatureLevListCustomData &CustomData::asCreatureLevListCustomData() +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureLevListCustomData"; + throw std::logic_error(error.str()); +} + +const MWClass::CreatureLevListCustomData &CustomData::asCreatureLevListCustomData() const +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to CreatureLevListCustomData"; + throw std::logic_error(error.str()); +} + + +} diff --git a/apps/openmw/mwworld/customdata.hpp b/apps/openmw/mwworld/customdata.hpp index 588991fe40..11932e690f 100644 --- a/apps/openmw/mwworld/customdata.hpp +++ b/apps/openmw/mwworld/customdata.hpp @@ -1,6 +1,15 @@ #ifndef GAME_MWWORLD_CUSTOMDATA_H #define GAME_MWWORLD_CUSTOMDATA_H +namespace MWClass +{ + class CreatureCustomData; + class NpcCustomData; + class ContainerCustomData; + class DoorCustomData; + class CreatureLevListCustomData; +} + namespace MWWorld { /// \brief Base class for the MW-class-specific part of RefData @@ -11,6 +20,22 @@ namespace MWWorld virtual ~CustomData() {} virtual CustomData *clone() const = 0; + + // Fast version of dynamic_cast. Needs to be overridden in the respective class. + + virtual MWClass::CreatureCustomData& asCreatureCustomData(); + virtual const MWClass::CreatureCustomData& asCreatureCustomData() const; + + virtual MWClass::NpcCustomData& asNpcCustomData(); + virtual const MWClass::NpcCustomData& asNpcCustomData() const; + + virtual MWClass::ContainerCustomData& asContainerCustomData(); + + virtual MWClass::DoorCustomData& asDoorCustomData(); + virtual const MWClass::DoorCustomData& asDoorCustomData() const; + + virtual MWClass::CreatureLevListCustomData& asCreatureLevListCustomData(); + virtual const MWClass::CreatureLevListCustomData& asCreatureLevListCustomData() const; }; } diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 5d9beecb65..1882c6e1aa 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace MWWorld { @@ -31,6 +32,12 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) ESM::Dialogue *dialogue = 0; + // Land texture loading needs to use a separate internal store for each plugin. + // We set the number of plugins here to avoid continual resizes during loading, + // and so we can properly verify if valid plugin indices are being passed to the + // LandTexture Store retrieval methods. + mLandTextures.resize(esm.getGlobalReaderList()->size()); + /// \todo Move this to somewhere else. ESMReader? // Cache parent esX files by tracking their indices in the global list of // all files/readers used by the engine. This will greaty accelerate @@ -95,34 +102,18 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) throw std::runtime_error(error.str()); } } else { - // Load it - std::string id = esm.getHNOString("NAME"); - // ... unless it got deleted! This means that the following record - // has been deleted, and trying to load it using standard assumptions - // on the structure will (probably) fail. - if (esm.isNextSub("DELE")) { - esm.skipRecord(); - it->second->eraseStatic(id); - continue; - } - it->second->load(esm, id); - - // DELE can also occur after the usual subrecords - if (esm.isNextSub("DELE")) { - esm.skipRecord(); - it->second->eraseStatic(id); - continue; + RecordId id = it->second->load(esm); + if (id.mIsDeleted) + { + it->second->eraseStatic(id.mId); + continue; } if (n.val==ESM::REC_DIAL) { - dialogue = const_cast(mDialogs.find(id)); + dialogue = const_cast(mDialogs.find(id.mId)); } else { dialogue = 0; } - // Insert the reference into the global lookup - if (!id.empty() && isCacheableRecord(n.val)) { - mIds[Misc::StringUtils::lowerCase (id)] = n.val; - } } listener->setProgress(static_cast(esm.getFileOffset() / (float)esm.getFileSize() * 1000)); } @@ -130,9 +121,20 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) void ESMStore::setUp() { - std::map::iterator it = mStores.begin(); - for (; it != mStores.end(); ++it) { - it->second->setUp(); + mIds.clear(); + + std::map::iterator storeIt = mStores.begin(); + for (; storeIt != mStores.end(); ++storeIt) { + storeIt->second->setUp(); + + if (isCacheableRecord(storeIt->first)) + { + std::vector identifiers; + storeIt->second->listIdentifier(identifiers); + + for (std::vector::const_iterator record = identifiers.begin(); record != identifiers.end(); ++record) + mIds[*record] = storeIt->first; + } } mSkills.setUp(); mMagicEffects.setUp(); @@ -194,19 +196,13 @@ void ESMStore::setUp() case ESM::REC_LEVC: { - std::string id = reader.getHNString ("NAME"); - mStores[type]->read (reader, id); - - // FIXME: there might be stale dynamic IDs in mIds from an earlier savegame - // that really should be cleared instead of just overwritten - - mIds[id] = type; + mStores[type]->read (reader); } if (type==ESM::REC_NPC_) { // NPC record will always be last and we know that there can be only one - // dynamic NPC record (player) -> We are done here with dynamic record laoding + // dynamic NPC record (player) -> We are done here with dynamic record loading setUp(); const ESM::NPC *player = mNpcs.find ("player"); diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 05b6339566..a14f6368e4 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_MWWORLD_ESMSTORE_H #define OPENMW_MWWORLD_ESMSTORE_H +#include #include #include diff --git a/apps/openmw/mwworld/failedaction.cpp b/apps/openmw/mwworld/failedaction.cpp index cf5a2a5c2f..49ca9dae07 100644 --- a/apps/openmw/mwworld/failedaction.cpp +++ b/apps/openmw/mwworld/failedaction.cpp @@ -4,6 +4,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwmechanics/actorutil.hpp" namespace MWWorld { @@ -13,7 +14,7 @@ namespace MWWorld void FailedAction::executeImp(const Ptr &actor) { - if(actor == MWBase::Environment::get().getWorld()->getPlayerPtr() && !mMessage.empty()) + if(actor == MWMechanics::getPlayer() && !mMessage.empty()) MWBase::Environment::get().getWindowManager()->messageBox(mMessage); } } diff --git a/apps/openmw/mwworld/fallback.cpp b/apps/openmw/mwworld/fallback.cpp index 3a8154ca7c..e810f8241b 100644 --- a/apps/openmw/mwworld/fallback.cpp +++ b/apps/openmw/mwworld/fallback.cpp @@ -1,5 +1,7 @@ #include "fallback.hpp" -#include "boost/lexical_cast.hpp" + +#include + namespace MWWorld { Fallback::Fallback(const std::map& fallback):mFallbackMap(fallback) @@ -39,11 +41,11 @@ namespace MWWorld else return boost::lexical_cast(fallback); } - Ogre::ColourValue Fallback::getFallbackColour(const std::string& fall) const + osg::Vec4f Fallback::getFallbackColour(const std::string& fall) const { std::string sum=getFallbackString(fall); if(sum.empty()) - return Ogre::ColourValue(0,0,0); + return osg::Vec4f(0.f,0.f,0.f,1.f); else { std::string ret[3]; @@ -53,7 +55,8 @@ namespace MWWorld else if (sum[i] != ' ') ret[j]+=sum[i]; } - return Ogre::ColourValue(boost::lexical_cast(ret[0])/255.f,boost::lexical_cast(ret[1])/255.f,boost::lexical_cast(ret[2])/255.f); + return osg::Vec4f(boost::lexical_cast(ret[0])/255.f,boost::lexical_cast(ret[1])/255.f,boost::lexical_cast(ret[2])/255.f, 1.f); } } + } diff --git a/apps/openmw/mwworld/fallback.hpp b/apps/openmw/mwworld/fallback.hpp index f69a5e57bb..af47063ee8 100644 --- a/apps/openmw/mwworld/fallback.hpp +++ b/apps/openmw/mwworld/fallback.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include namespace MWWorld { @@ -17,7 +17,7 @@ namespace MWWorld float getFallbackFloat(const std::string& fall) const; int getFallbackInt(const std::string& fall) const; bool getFallbackBool(const std::string& fall) const; - Ogre::ColourValue getFallbackColour(const std::string& fall) const; + osg::Vec4f getFallbackColour(const std::string& fall) const; }; } #endif diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index e7cb04590b..acc06b28a9 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -1,4 +1,3 @@ - #include "globals.hpp" #include @@ -14,7 +13,7 @@ namespace MWWorld { Globals::Collection::const_iterator Globals::find (const std::string& name) const { - Collection::const_iterator iter = mVariables.find (name); + Collection::const_iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) throw std::runtime_error ("unknown global variable: " + name); @@ -24,7 +23,7 @@ namespace MWWorld Globals::Collection::iterator Globals::find (const std::string& name) { - Collection::iterator iter = mVariables.find (name); + Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) throw std::runtime_error ("unknown global variable: " + name); @@ -41,28 +40,28 @@ namespace MWWorld for (MWWorld::Store::iterator iter = globals.begin(); iter!=globals.end(); ++iter) { - mVariables.insert (std::make_pair (iter->mId, iter->mValue)); + mVariables.insert (std::make_pair (Misc::StringUtils::lowerCase (iter->mId), *iter)); } } const ESM::Variant& Globals::operator[] (const std::string& name) const { - return find (name)->second; + return find (Misc::StringUtils::lowerCase (name))->second.mValue; } ESM::Variant& Globals::operator[] (const std::string& name) { - return find (name)->second; + return find (Misc::StringUtils::lowerCase (name))->second.mValue; } char Globals::getType (const std::string& name) const { - Collection::const_iterator iter = mVariables.find (name); + Collection::const_iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) return ' '; - switch (iter->second.getType()) + switch (iter->second.mValue.getType()) { case ESM::VT_Short: return 's'; case ESM::VT_Long: return 'l'; @@ -82,8 +81,7 @@ namespace MWWorld for (Collection::const_iterator iter (mVariables.begin()); iter!=mVariables.end(); ++iter) { writer.startRecord (ESM::REC_GLOB); - writer.writeHNString ("NAME", iter->first); - iter->second.write (writer, ESM::Variant::Format_Global); + iter->second.save (writer); writer.endRecord (ESM::REC_GLOB); } } @@ -92,14 +90,17 @@ namespace MWWorld { if (type==ESM::REC_GLOB) { - std::string id = reader.getHNString ("NAME"); + ESM::Global global; + bool isDeleted = false; - Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (id)); + // This readRecord() method is used when reading a saved game. + // Deleted globals can't appear there, so isDeleted will be ignored here. + global.load(reader, isDeleted); + Misc::StringUtils::lowerCaseInPlace(global.mId); + Collection::iterator iter = mVariables.find (global.mId); if (iter!=mVariables.end()) - iter->second.read (reader, ESM::Variant::Format_Global); - else - reader.skipRecord(); + iter->second = global; return true; } diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index bb4ab13d92..3468c2e719 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include namespace ESM { @@ -29,7 +29,7 @@ namespace MWWorld { private: - typedef std::map Collection; + typedef std::map Collection; Collection mVariables; // type, value diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index a2e445d581..b82b798d11 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -1,4 +1,3 @@ - #include "inventorystore.hpp" #include @@ -6,6 +5,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -16,6 +16,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/actorutil.hpp" #include "esmstore.hpp" @@ -136,7 +137,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, count, actorPtr, setOwner); // Auto-equip items if an armor/clothing or weapon item is added, but not for the player nor werewolves - if (actorPtr != MWBase::Environment::get().getWorld()->getPlayerPtr() + if (actorPtr != MWMechanics::getPlayer() && !(actorPtr.getClass().isNpc() && actorPtr.getClass().getNpcStats(actorPtr).isWerewolf())) { std::string type = itemPtr.getTypeName(); @@ -367,7 +368,7 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) // Roll some dice, one for each effect params.resize(enchantment.mEffects.mList.size()); for (unsigned int i=0; ipermanentEffectAdded(magicEffect, !mFirstAutoEquip, !mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin()); - - // Apply instant effects - MWMechanics::CastSpell cast(actor, actor); - if (magnitude) - cast.applyInstantEffect(actor, actor, effectIt->mEffectID, magnitude); } if (magnitude) @@ -454,7 +450,7 @@ void MWWorld::InventoryStore::flagAsModified() mRechargingItemsUpToDate = false; } -bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2) +bool MWWorld::InventoryStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) { bool canStack = MWWorld::ContainerStore::stacks(ptr1, ptr2); if (!canStack) @@ -489,6 +485,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor { int retCount = ContainerStore::remove(item, count, actor); + bool wasEquipped = false; if (!item.getRefData().getCount()) { for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) @@ -499,6 +496,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor if (*mSlots[slot] == item) { unequipSlot(slot, actor); + wasEquipped = true; break; } } @@ -506,7 +504,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor // If an armor/clothing item is removed, try to find a replacement, // but not for the player nor werewolves. - if ((actor != MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (wasEquipped && (actor != MWMechanics::getPlayer()) && !(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())) { std::string type = item.getTypeName(); @@ -538,7 +536,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c { retval = restack(*it); - if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + if (actor == MWMechanics::getPlayer()) { // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared const std::string& script = it->getClass().getScript(*it); @@ -573,6 +571,11 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWor throw std::runtime_error ("attempt to unequip an item that is not currently equipped"); } +MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getListener() +{ + return mListener; +} + void MWWorld::InventoryStore::setListener(InventoryStoreListener *listener, const Ptr& actor) { mListener = listener; @@ -587,10 +590,12 @@ void MWWorld::InventoryStore::fireEquipmentChangedEvent(const Ptr& actor) mListener->equipmentChanged(); // if player, update inventory window - if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + /* + if (actor == MWMechanics::getPlayer()) { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); } + */ } void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisitor &visitor) @@ -635,8 +640,15 @@ void MWWorld::InventoryStore::updateRechargingItems() { if (it->getClass().getEnchantment(*it) != "") { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( - it->getClass().getEnchantment(*it)); + std::string enchantmentId = it->getClass().getEnchantment(*it); + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().search( + enchantmentId); + if (!enchantment) + { + std::cerr << "Can't find enchantment '" << enchantmentId << "' on item " << it->getCellRef().getRefId() << std::endl; + continue; + } + if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) mRechargingItems.push_back(std::make_pair(it, static_cast(enchantment->mData.mCharge))); @@ -688,7 +700,7 @@ void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sou if (*iter==end()) continue; - if ((*iter)->getClass().getId(**iter) != sourceId) + if ((*iter)->getCellRef().getRefId() != sourceId) continue; std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); @@ -730,7 +742,7 @@ void MWWorld::InventoryStore::clear() ContainerStore::clear(); } -bool MWWorld::InventoryStore::isEquipped(const MWWorld::Ptr &item) +bool MWWorld::InventoryStore::isEquipped(const MWWorld::ConstPtr &item) { for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) { @@ -740,7 +752,7 @@ bool MWWorld::InventoryStore::isEquipped(const MWWorld::Ptr &item) return false; } -void MWWorld::InventoryStore::writeState(ESM::InventoryState &state) +void MWWorld::InventoryStore::writeState(ESM::InventoryState &state) const { MWWorld::ContainerStore::writeState(state); diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 6b906207e1..28c44bcec1 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -87,7 +87,6 @@ namespace MWWorld float mMultiplier; }; - // TODO: store in savegame typedef std::map > TEffectMagnitudes; TEffectMagnitudes mPermanentMagicEffectMagnitudes; @@ -142,7 +141,7 @@ namespace MWWorld void equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor); ///< \warning \a iterator can not be an end()-iterator, use unequip function instead - bool isEquipped(const MWWorld::Ptr& item); + bool isEquipped(const MWWorld::ConstPtr& item); ///< Utility function, returns true if the given item is equipped in any slot void setSelectedEnchantItem(const ContainerStoreIterator& iterator); @@ -168,7 +167,7 @@ namespace MWWorld ///< \attention This function is internal to the world model and should not be called from /// outside. - virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2); + virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2); ///< @return true if the two specified objects can stack with each other virtual int remove(const Ptr& item, int count, const Ptr& actor); @@ -192,6 +191,8 @@ namespace MWWorld void setListener (InventoryStoreListener* listener, const Ptr& actor); ///< Set a listener for various events, see \a InventoryStoreListener + InventoryStoreListener* getListener(); + void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor); void rechargeItems (float duration); @@ -206,7 +207,7 @@ namespace MWWorld virtual void clear(); ///< Empty container. - virtual void writeState (ESM::InventoryState& state); + virtual void writeState (ESM::InventoryState& state) const; virtual void readState (const ESM::InventoryState& state); }; diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index bfc708185d..32830b5fb9 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -11,7 +11,7 @@ #include "class.hpp" #include "esmstore.hpp" -MWWorld::LiveCellRefBase::LiveCellRefBase(std::string type, const ESM::CellRef &cref) +MWWorld::LiveCellRefBase::LiveCellRefBase(const std::string& type, const ESM::CellRef &cref) : mClass(&Class::get(type)), mRef(cref), mData(cref) { } @@ -19,7 +19,7 @@ MWWorld::LiveCellRefBase::LiveCellRefBase(std::string type, const ESM::CellRef & void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state) { mRef = state.mRef; - mData = RefData (state); + mData = RefData (state, mData.isDeletedByContentFile()); Ptr ptr (this); @@ -54,8 +54,7 @@ void MWWorld::LiveCellRefBase::saveImp (ESM::ObjectState& state) const { mRef.writeState(state); - /// \todo get rid of this cast once const-correct Ptr are available - Ptr ptr (const_cast (this)); + ConstPtr ptr (this); mData.write (state, mClass->getScript (ptr)); diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index 3994d8a249..2631f513fb 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -31,7 +31,7 @@ namespace MWWorld /** runtime-data */ RefData mData; - LiveCellRefBase(std::string type, const ESM::CellRef &cref=ESM::CellRef()); + LiveCellRefBase(const std::string& type, const ESM::CellRef &cref=ESM::CellRef()); /* Need this for the class to be recognized as polymorphic */ virtual ~LiveCellRefBase() { } diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index e30246f7cb..46d0b3cc22 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -11,51 +11,59 @@ namespace { - template - void listCellScripts (MWWorld::LocalScripts& localScripts, - MWWorld::CellRefList& cellRefList, MWWorld::CellStore *cell) + + struct AddScriptsVisitor { - for (typename MWWorld::CellRefList::List::iterator iter ( - cellRefList.mList.begin()); - iter!=cellRefList.mList.end(); ++iter) + AddScriptsVisitor(MWWorld::LocalScripts& scripts) + : mScripts(scripts) { - if (!iter->mBase->mScript.empty() && !iter->mData.isDeleted()) - { - localScripts.add (iter->mBase->mScript, MWWorld::Ptr (&*iter, cell)); - } } - } + MWWorld::LocalScripts& mScripts; - // Adds scripts for items in containers (containers/npcs/creatures) - template - void listCellScriptsCont (MWWorld::LocalScripts& localScripts, - MWWorld::CellRefList& cellRefList, MWWorld::CellStore *cell) - { - for (typename MWWorld::CellRefList::List::iterator iter ( - cellRefList.mList.begin()); - iter!=cellRefList.mList.end(); ++iter) + bool operator()(const MWWorld::Ptr& ptr) { + if (ptr.getRefData().isDeleted()) + return true; - MWWorld::Ptr containerPtr (&*iter, cell); + std::string script = ptr.getClass().getScript(ptr); + if (!script.empty()) + mScripts.add(script, ptr); + + return true; + } + }; + + struct AddContainerItemScriptsVisitor + { + AddContainerItemScriptsVisitor(MWWorld::LocalScripts& scripts) + : mScripts(scripts) + { + } + MWWorld::LocalScripts& mScripts; + + bool operator()(const MWWorld::Ptr& containerPtr) + { MWWorld::ContainerStore& container = containerPtr.getClass().getContainerStore(containerPtr); - for(MWWorld::ContainerStoreIterator it3 = container.begin(); it3 != container.end(); ++it3) + for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it) { - std::string script = it3->getClass().getScript(*it3); + std::string script = it->getClass().getScript(*it); if(script != "") { - MWWorld::Ptr item = *it3; - item.mCell = cell; - localScripts.add (script, item); + MWWorld::Ptr item = *it; + item.mCell = containerPtr.getCell(); + mScripts.add (script, item); } } + return true; } - } + }; + } MWWorld::LocalScripts::LocalScripts (const MWWorld::ESMStore& store) : mStore (store) {} -void MWWorld::LocalScripts::setIgnore (const Ptr& ptr) +void MWWorld::LocalScripts::setIgnore (const ConstPtr& ptr) { mIgnore = ptr; } @@ -116,26 +124,13 @@ void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr) void MWWorld::LocalScripts::addCell (CellStore *cell) { - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScriptsCont (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScriptsCont (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScriptsCont (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); - listCellScripts (*this, cell->get(), cell); + AddScriptsVisitor addScriptsVisitor(*this); + cell->forEach(addScriptsVisitor); + + AddContainerItemScriptsVisitor addContainerItemScriptsVisitor(*this); + cell->forEachType(addContainerItemScriptsVisitor); + cell->forEachType(addContainerItemScriptsVisitor); + cell->forEachType(addContainerItemScriptsVisitor); } void MWWorld::LocalScripts::clear() diff --git a/apps/openmw/mwworld/localscripts.hpp b/apps/openmw/mwworld/localscripts.hpp index 9d612cb33c..6ef4633a1d 100644 --- a/apps/openmw/mwworld/localscripts.hpp +++ b/apps/openmw/mwworld/localscripts.hpp @@ -17,14 +17,14 @@ namespace MWWorld { std::list > mScripts; std::list >::iterator mIter; - MWWorld::Ptr mIgnore; + MWWorld::ConstPtr mIgnore; const MWWorld::ESMStore& mStore; public: LocalScripts (const MWWorld::ESMStore& store); - void setIgnore (const Ptr& ptr); + void setIgnore (const ConstPtr& ptr); ///< Mark a single reference for ignoring during iteration over local scripts (will revoke /// previous ignores). @@ -52,7 +52,7 @@ namespace MWWorld void remove (RefData *ref); void remove (const Ptr& ptr); - ///< Remove script for given reference (ignored if reference does not have a scirpt listed). + ///< Remove script for given reference (ignored if reference does not have a script listed). }; } diff --git a/apps/openmw/mwworld/manualref.cpp b/apps/openmw/mwworld/manualref.cpp index b926c57992..d6e40ad09e 100644 --- a/apps/openmw/mwworld/manualref.cpp +++ b/apps/openmw/mwworld/manualref.cpp @@ -1,7 +1,6 @@ #include "manualref.hpp" #include "esmstore.hpp" -#include "cellstore.hpp" namespace { diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp deleted file mode 100644 index bec4c6db32..0000000000 --- a/apps/openmw/mwworld/physicssystem.cpp +++ /dev/null @@ -1,1011 +0,0 @@ -#include "physicssystem.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include "../mwbase/world.hpp" // FIXME -#include "../mwbase/environment.hpp" - -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/movement.hpp" - -#include "../mwworld/esmstore.hpp" -#include "../mwworld/cellstore.hpp" - -#include "../apps/openmw/mwrender/animation.hpp" -#include "../apps/openmw/mwbase/world.hpp" -#include "../apps/openmw/mwbase/environment.hpp" - -#include "ptr.hpp" -#include "class.hpp" - -using namespace Ogre; - -namespace -{ - -void animateCollisionShapes (std::map& map, btDynamicsWorld* dynamicsWorld) -{ - for (std::map::iterator it = map.begin(); - it != map.end(); ++it) - { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaHandle(it->first->mName); - if (ptr.isEmpty()) // Shouldn't happen - throw std::runtime_error("can't find Ptr"); - - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); - if (!animation) - continue; - - OEngine::Physic::AnimatedShapeInstance& instance = it->second; - - std::map& shapes = instance.mAnimatedShapes; - for (std::map::iterator shapeIt = shapes.begin(); - shapeIt != shapes.end(); ++shapeIt) - { - - const std::string& mesh = animation->getObjectRootName(); - int boneHandle = NifOgre::NIFSkeletonLoader::lookupOgreBoneHandle(mesh, shapeIt->first); - Ogre::Node* bone = animation->getNode(boneHandle); - - if (bone == NULL) - continue; - - btCompoundShape* compound = static_cast(instance.mCompound); - - btTransform trans; - trans.setOrigin(BtOgre::Convert::toBullet(bone->_getDerivedPosition()) * compound->getLocalScaling()); - trans.setRotation(BtOgre::Convert::toBullet(bone->_getDerivedOrientation())); - - compound->getChildShape(shapeIt->second)->setLocalScaling( - compound->getLocalScaling() * - BtOgre::Convert::toBullet(bone->_getDerivedScale())); - compound->updateChildTransform(shapeIt->second, trans); - } - - // needed because we used btDynamicsWorld::setForceUpdateAllAabbs(false) - dynamicsWorld->updateSingleAabb(it->first); - } -} - -} - - -namespace MWWorld -{ - - static const float sMaxSlope = 49.0f; - static const float sStepSizeUp = 34.0f; - static const float sStepSizeDown = 62.0f; - - // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. - static const int sMaxIterations = 8; - - class MovementSolver - { - private: - static float getSlope(const Ogre::Vector3 &normal) - { - return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees(); - } - - static bool stepMove(btCollisionObject *colobj, Ogre::Vector3 &position, - const Ogre::Vector3 &toMove, float &remainingTime, - OEngine::Physic::PhysicEngine *engine) - { - /* - * Slide up an incline or set of stairs. Should be called only after a - * collision detection otherwise unnecessary tracing will be performed. - * - * NOTE: with a small change this method can be used to step over an obstacle - * of height sStepSize. - * - * If successful return 'true' and update 'position' to the new possible - * location and adjust 'remainingTime'. - * - * If not successful return 'false'. May fail for these reasons: - * - can't move directly up from current position - * - having moved up by between epsilon() and sStepSize, can't move forward - * - having moved forward by between epsilon() and toMove, - * = moved down between 0 and just under sStepSize but slope was too steep, or - * = moved the full sStepSize down (FIXME: this could be a bug) - * - * - * - * Starting position. Obstacle or stairs with height upto sStepSize in front. - * - * +--+ +--+ |XX - * | | -------> toMove | | +--+XX - * | | | | |XXXXX - * | | +--+ | | +--+XXXXX - * | | |XX| | | |XXXXXXXX - * +--+ +--+ +--+ +-------- - * ============================================== - */ - - /* - * Try moving up sStepSize using stepper. - * FIXME: does not work in case there is no front obstacle but there is one above - * - * +--+ +--+ - * | | | | - * | | | | |XX - * | | | | +--+XX - * | | | | |XXXXX - * +--+ +--+ +--+ +--+XXXXX - * |XX| |XXXXXXXX - * +--+ +-------- - * ============================================== - */ - OEngine::Physic::ActorTracer tracer, stepper; - - stepper.doTrace(colobj, position, position+Ogre::Vector3(0.0f,0.0f,sStepSizeUp), engine); - if(stepper.mFraction < std::numeric_limits::epsilon()) - return false; // didn't even move the smallest representable amount - // (TODO: shouldn't this be larger? Why bother with such a small amount?) - - /* - * Try moving from the elevated position using tracer. - * - * +--+ +--+ - * | | |YY| FIXME: collision with object YY - * | | +--+ - * | | - * <------------------->| | - * +--+ +--+ - * |XX| the moved amount is toMove*tracer.mFraction - * +--+ - * ============================================== - */ - tracer.doTrace(colobj, stepper.mEndPos, stepper.mEndPos + toMove, engine); - if(tracer.mFraction < std::numeric_limits::epsilon()) - return false; // didn't even move the smallest representable amount - - /* - * Try moving back down sStepSizeDown using stepper. - * NOTE: if there is an obstacle below (e.g. stairs), we'll be "stepping up". - * Below diagram is the case where we "stepped over" an obstacle in front. - * - * +--+ - * |YY| - * +--+ +--+ - * | | - * | | - * +--+ | | - * |XX| | | - * +--+ +--+ - * ============================================== - */ - stepper.doTrace(colobj, tracer.mEndPos, tracer.mEndPos-Ogre::Vector3(0.0f,0.0f,sStepSizeDown), engine); - if(stepper.mFraction < 1.0f && getSlope(stepper.mPlaneNormal) <= sMaxSlope) - { - // don't allow stepping up other actors - if (stepper.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == OEngine::Physic::CollisionType_Actor) - return false; - // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall. - // TODO: stepper.mPlaneNormal does not appear to be reliable - needs more testing - // NOTE: caller's variables 'position' & 'remainingTime' are modified here - position = stepper.mEndPos; - remainingTime *= (1.0f-tracer.mFraction); // remaining time is proportional to remaining distance - return true; - } - - // moved between 0 and just under sStepSize distance but slope was too great, - // or moved full sStepSize distance (FIXME: is this a bug?) - return false; - } - - - ///Project a vector u on another vector v - static inline Ogre::Vector3 project(const Ogre::Vector3 u, const Ogre::Vector3 &v) - { - return v * u.dotProduct(v); - } - - ///Helper for computing the character sliding - static inline Ogre::Vector3 slide(Ogre::Vector3 direction, const Ogre::Vector3 &planeNormal) - { - return direction - project(direction, planeNormal); - } - - - public: - static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, OEngine::Physic::PhysicEngine *engine, float maxHeight) - { - const ESM::Position &refpos = ptr.getRefData().getPosition(); - Ogre::Vector3 position(refpos.pos); - - OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle()); - if (!physicActor) - return position; - - OEngine::Physic::ActorTracer tracer; - tracer.findGround(physicActor, position, position-Ogre::Vector3(0,0,maxHeight), engine); - if(tracer.mFraction >= 1.0f) - { - physicActor->setOnGround(false); - return position; - } - else - { - // Check if we actually found a valid spawn point (use an infinitely thin ray this time). - // Required for some broken door destinations in Morrowind.esm, where the spawn point - // intersects with other geometry if the actor's base is taken into account - btVector3 from = BtOgre::Convert::toBullet(position); - btVector3 to = from - btVector3(0,0,maxHeight); - - btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); - resultCallback1.m_collisionFilterGroup = 0xff; - resultCallback1.m_collisionFilterMask = OEngine::Physic::CollisionType_World|OEngine::Physic::CollisionType_HeightMap; - - engine->mDynamicsWorld->rayTest(from, to, resultCallback1); - if (resultCallback1.hasHit() && - (BtOgre::Convert::toOgre(resultCallback1.m_hitPointWorld).distance(tracer.mEndPos) > 30 - || getSlope(tracer.mPlaneNormal) > sMaxSlope)) - { - physicActor->setOnGround(getSlope(BtOgre::Convert::toOgre(resultCallback1.m_hitNormalWorld)) <= sMaxSlope); - return BtOgre::Convert::toOgre(resultCallback1.m_hitPointWorld) + Ogre::Vector3(0,0,1.f); - } - - physicActor->setOnGround(getSlope(tracer.mPlaneNormal) <= sMaxSlope); - - return tracer.mEndPos; - } - } - - static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time, - bool isFlying, float waterlevel, float slowFall, OEngine::Physic::PhysicEngine *engine - , std::map& collisionTracker - , std::map& standingCollisionTracker) - { - const ESM::Position &refpos = ptr.getRefData().getPosition(); - Ogre::Vector3 position(refpos.pos); - - // Early-out for totally static creatures - // (Not sure if gravity should still apply?) - if (!ptr.getClass().isMobile(ptr)) - return position; - - OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle()); - if (!physicActor) - return position; - - // Reset per-frame data - physicActor->setWalkingOnWater(false); - // Anything to collide with? - if(!physicActor->getCollisionMode()) - { - return position + (Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * - Ogre::Quaternion(Ogre::Radian(refpos.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)) - * movement * time; - } - - btCollisionObject *colobj = physicActor->getCollisionBody(); - Ogre::Vector3 halfExtents = physicActor->getHalfExtents(); - position.z += halfExtents.z; - - static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get() - .find("fSwimHeightScale")->getFloat(); - float swimlevel = waterlevel + halfExtents.z - (halfExtents.z * 2 * fSwimHeightScale); - - OEngine::Physic::ActorTracer tracer; - Ogre::Vector3 inertia = physicActor->getInertialForce(); - Ogre::Vector3 velocity; - - if(position.z < swimlevel || isFlying) - { - velocity = (Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z)* - Ogre::Quaternion(Ogre::Radian(refpos.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)) * movement; - } - else - { - velocity = Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * movement; - - if (velocity.z > 0.f) - inertia = velocity; - if(!physicActor->getOnGround()) - { - velocity = velocity + physicActor->getInertialForce(); - } - } - ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; - - // Now that we have the effective movement vector, apply wind forces to it - if (MWBase::Environment::get().getWorld()->isInStorm()) - { - Ogre::Vector3 stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); - Ogre::Degree angle = stormDirection.angleBetween(velocity); - static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get() - .find("fStromWalkMult")->getFloat(); - velocity *= 1.f-(fStromWalkMult * (angle.valueDegrees()/180.f)); - } - - Ogre::Vector3 origVelocity = velocity; - - Ogre::Vector3 newPosition = position; - /* - * A loop to find newPosition using tracer, if successful different from the starting position. - * nextpos is the local variable used to find potential newPosition, using velocity and remainingTime - * The initial velocity was set earlier (see above). - */ - float remainingTime = time; - for(int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations) - { - Ogre::Vector3 nextpos = newPosition + velocity * remainingTime; - - // If not able to fly, don't allow to swim up into the air - if(newPosition.z < swimlevel && - !isFlying && // can't fly - nextpos.z > swimlevel && // but about to go above water - newPosition.z <= swimlevel) - { - const Ogre::Vector3 down(0,0,-1); - Ogre::Real movelen = velocity.normalise(); - Ogre::Vector3 reflectdir = velocity.reflect(down); - reflectdir.normalise(); - velocity = slide(reflectdir, down)*movelen; - // NOTE: remainingTime is unchanged before the loop continues - continue; // velocity updated, calculate nextpos again - } - - if(newPosition.squaredDistance(nextpos) > 0.0001) - { - // trace to where character would go if there were no obstructions - tracer.doTrace(colobj, newPosition, nextpos, engine); - - // check for obstructions - if(tracer.mFraction >= 1.0f) - { - newPosition = tracer.mEndPos; // ok to move, so set newPosition - break; - } - else - { - const btCollisionObject* standingOn = tracer.mHitObject; - if (const OEngine::Physic::RigidBody* body = dynamic_cast(standingOn)) - { - collisionTracker[ptr.getRefData().getHandle()] = body->mName; - } - } - } - else - { - // The current position and next position are nearly the same, so just exit. - // Note: Bullet can trigger an assert in debug modes if the positions - // are the same, since that causes it to attempt to normalize a zero - // length vector (which can also happen with nearly identical vectors, since - // precision can be lost due to any math Bullet does internally). Since we - // aren't performing any collision detection, we want to reject the next - // position, so that we don't slowly move inside another object. - break; - } - - - Ogre::Vector3 oldPosition = newPosition; - // We hit something. Try to step up onto it. (NOTE: stepMove does not allow stepping over) - // NOTE: stepMove modifies newPosition if successful - bool result = stepMove(colobj, newPosition, velocity*remainingTime, remainingTime, engine); - if (!result) // to make sure the maximum stepping distance isn't framerate-dependent or movement-speed dependent - result = stepMove(colobj, newPosition, velocity.normalisedCopy()*10.f, remainingTime, engine); - if(result) - { - // don't let pure water creatures move out of water after stepMove - if (ptr.getClass().isPureWaterCreature(ptr) - && newPosition.z + halfExtents.z > waterlevel) - newPosition = oldPosition; - } - else - { - // Can't move this way, try to find another spot along the plane - Ogre::Vector3 direction = velocity; - Ogre::Real movelen = direction.normalise(); - Ogre::Vector3 reflectdir = velocity.reflect(tracer.mPlaneNormal); - reflectdir.normalise(); - - Ogre::Vector3 newVelocity = slide(reflectdir, tracer.mPlaneNormal)*movelen; - if ((newVelocity-velocity).squaredLength() < 0.01) - break; - if (velocity.dotProduct(origVelocity) <= 0.f) - break; - - velocity = newVelocity; - - // Do not allow sliding upward if there is gravity. Stepping will have taken - // care of that. - if(!(newPosition.z < swimlevel || isFlying)) - velocity.z = std::min(velocity.z, 0.0f); - } - } - - bool isOnGround = false; - if (!(inertia.z > 0.f) && !(newPosition.z < swimlevel)) - { - Ogre::Vector3 from = newPosition; - Ogre::Vector3 to = newPosition - (physicActor->getOnGround() ? - Ogre::Vector3(0,0,sStepSizeDown+2.f) : Ogre::Vector3(0,0,2.f)); - tracer.doTrace(colobj, from, to, engine); - if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope - && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != OEngine::Physic::CollisionType_Actor) - { - const btCollisionObject* standingOn = tracer.mHitObject; - if (const OEngine::Physic::RigidBody* body = dynamic_cast(standingOn)) - { - standingCollisionTracker[ptr.getRefData().getHandle()] = body->mName; - } - if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == OEngine::Physic::CollisionType_Water) - physicActor->setWalkingOnWater(true); - - if (!isFlying) - newPosition.z = tracer.mEndPos.z + 1.0f; - - isOnGround = true; - } - else - { - // standing on actors is not allowed (see above). - // in addition to that, apply a sliding effect away from the center of the actor, - // so that we do not stay suspended in air indefinitely. - if (tracer.mFraction < 1.0f && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == OEngine::Physic::CollisionType_Actor) - { - if (Ogre::Vector3(velocity.x, velocity.y, 0).squaredLength() < 100.f*100.f) - { - btVector3 aabbMin, aabbMax; - tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax); - btVector3 center = (aabbMin + aabbMax) / 2.f; - inertia = Ogre::Vector3(position.x - center.x(), position.y - center.y(), 0); - inertia.normalise(); - inertia *= 100; - } - } - - isOnGround = false; - } - } - - if(isOnGround || newPosition.z < swimlevel || isFlying) - physicActor->setInertialForce(Ogre::Vector3(0.0f)); - else - { - inertia.z += time * -627.2f; - if (inertia.z < 0) - inertia.z *= slowFall; - physicActor->setInertialForce(inertia); - } - physicActor->setOnGround(isOnGround); - - newPosition.z -= halfExtents.z; // remove what was added at the beginning - return newPosition; - } - }; - - - PhysicsSystem::PhysicsSystem(OEngine::Render::OgreRenderer &_rend) : - mRender(_rend), mEngine(0), mTimeAccum(0.0f), mWaterHeight(0), mWaterEnabled(false) - { - // Create physics. shapeLoader is deleted by the physic engine - NifBullet::ManualBulletShapeLoader* shapeLoader = new NifBullet::ManualBulletShapeLoader(); - mEngine = new OEngine::Physic::PhysicEngine(shapeLoader); - } - - PhysicsSystem::~PhysicsSystem() - { - if (mWaterCollisionObject.get()) - mEngine->mDynamicsWorld->removeCollisionObject(mWaterCollisionObject.get()); - delete mEngine; - delete OEngine::Physic::BulletShapeManager::getSingletonPtr(); - } - - OEngine::Physic::PhysicEngine* PhysicsSystem::getEngine() - { - return mEngine; - } - - std::pair PhysicsSystem::getFacedHandle(float queryDistance) - { - Ray ray = mRender.getCamera()->getCameraToViewportRay(0.5, 0.5); - - Ogre::Vector3 origin_ = ray.getOrigin(); - btVector3 origin(origin_.x, origin_.y, origin_.z); - Ogre::Vector3 dir_ = ray.getDirection().normalisedCopy(); - btVector3 dir(dir_.x, dir_.y, dir_.z); - - btVector3 dest = origin + dir * queryDistance; - std::pair result = mEngine->rayTest(origin, dest); - result.second *= queryDistance; - - return std::make_pair (result.second, result.first); - } - - std::vector < std::pair > PhysicsSystem::getFacedHandles (float queryDistance) - { - Ray ray = mRender.getCamera()->getCameraToViewportRay(0.5, 0.5); - - Ogre::Vector3 origin_ = ray.getOrigin(); - btVector3 origin(origin_.x, origin_.y, origin_.z); - Ogre::Vector3 dir_ = ray.getDirection().normalisedCopy(); - btVector3 dir(dir_.x, dir_.y, dir_.z); - - btVector3 dest = origin + dir * queryDistance; - std::vector < std::pair > results; - /* auto */ results = mEngine->rayTest2(origin, dest); - std::vector < std::pair >::iterator i; - for (/* auto */ i = results.begin (); i != results.end (); ++i) - i->first *= queryDistance; - return results; - } - - std::vector < std::pair > PhysicsSystem::getFacedHandles (float mouseX, float mouseY, float queryDistance) - { - Ray ray = mRender.getCamera()->getCameraToViewportRay(mouseX, mouseY); - Ogre::Vector3 from = ray.getOrigin(); - Ogre::Vector3 to = ray.getPoint(queryDistance); - - btVector3 _from, _to; - _from = btVector3(from.x, from.y, from.z); - _to = btVector3(to.x, to.y, to.z); - - std::vector < std::pair > results; - /* auto */ results = mEngine->rayTest2(_from,_to); - std::vector < std::pair >::iterator i; - for (/* auto */ i = results.begin (); i != results.end (); ++i) - i->first *= queryDistance; - return results; - } - - std::pair PhysicsSystem::getHitContact(const std::string &name, - const Ogre::Vector3 &origin, - const Ogre::Quaternion &orient, - float queryDistance) - { - const MWWorld::Store &store = MWBase::Environment::get().getWorld()->getStore().get(); - - btConeShape shape(Ogre::Degree(store.find("fCombatAngleXY")->getFloat()/2.0f).valueRadians(), - queryDistance); - shape.setLocalScaling(btVector3(1, 1, Ogre::Degree(store.find("fCombatAngleZ")->getFloat()/2.0f).valueRadians() / - shape.getRadius())); - - // The shape origin is its center, so we have to move it forward by half the length. The - // real origin will be provided to getFilteredContact to find the closest. - Ogre::Vector3 center = origin + (orient * Ogre::Vector3(0.0f, queryDistance*0.5f, 0.0f)); - - btCollisionObject object; - object.setCollisionShape(&shape); - object.setWorldTransform(btTransform(btQuaternion(orient.x, orient.y, orient.z, orient.w), - btVector3(center.x, center.y, center.z))); - - std::pair result = mEngine->getFilteredContact( - name, btVector3(origin.x, origin.y, origin.z), &object); - if(!result.first) - return std::make_pair(std::string(), Ogre::Vector3(&result.second[0])); - return std::make_pair(result.first->mName, Ogre::Vector3(&result.second[0])); - } - - - bool PhysicsSystem::castRay(const Vector3& from, const Vector3& to, bool raycastingObjectOnly,bool ignoreHeightMap) - { - btVector3 _from, _to; - _from = btVector3(from.x, from.y, from.z); - _to = btVector3(to.x, to.y, to.z); - - std::pair result = mEngine->rayTest(_from, _to, raycastingObjectOnly,ignoreHeightMap); - return !(result.first == ""); - } - - std::pair - PhysicsSystem::castRay(const Ogre::Vector3 &orig, const Ogre::Vector3 &dir, float len) - { - Ogre::Ray ray = Ogre::Ray(orig, dir); - Ogre::Vector3 to = ray.getPoint(len); - - btVector3 btFrom = btVector3(orig.x, orig.y, orig.z); - btVector3 btTo = btVector3(to.x, to.y, to.z); - - std::pair test = mEngine->rayTest(btFrom, btTo); - if (test.second == -1) { - return std::make_pair(false, Ogre::Vector3()); - } - return std::make_pair(true, ray.getPoint(len * test.second)); - } - - std::pair PhysicsSystem::castRay(float mouseX, float mouseY, Ogre::Vector3* normal, std::string* hit) - { - Ogre::Ray ray = mRender.getCamera()->getCameraToViewportRay( - mouseX, - mouseY); - Ogre::Vector3 from = ray.getOrigin(); - Ogre::Vector3 to = ray.getPoint(200); /// \todo make this distance (ray length) configurable - - btVector3 _from, _to; - _from = btVector3(from.x, from.y, from.z); - _to = btVector3(to.x, to.y, to.z); - - std::pair result = mEngine->rayTest(_from, _to, true, false, normal); - - if (result.first == "") - return std::make_pair(false, Ogre::Vector3()); - else - { - if (hit != NULL) - *hit = result.first; - return std::make_pair(true, ray.getPoint(200*result.second)); /// \todo make this distance (ray length) configurable - } - } - - std::vector PhysicsSystem::getCollisions(const Ptr &ptr, int collisionGroup, int collisionMask) - { - return mEngine->getCollisions(ptr.getRefData().getBaseNode()->getName(), collisionGroup, collisionMask); - } - - Ogre::Vector3 PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, float maxHeight) - { - return MovementSolver::traceDown(ptr, mEngine, maxHeight); - } - - 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 Ptr& ptr, const std::string& mesh, bool placeable) - { - Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); - handleToMesh[node->getName()] = mesh; - mEngine->createAndAdjustRigidBody( - mesh, node->getName(), ptr.getCellRef().getScale(), node->getPosition(), node->getOrientation(), 0, 0, false, placeable); - mEngine->createAndAdjustRigidBody( - mesh, node->getName(), ptr.getCellRef().getScale(), node->getPosition(), node->getOrientation(), 0, 0, true, placeable); - } - - void PhysicsSystem::addActor (const Ptr& ptr, const std::string& mesh) - { - Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); - //TODO:optimize this. Searching the std::map isn't very efficient i think. - mEngine->addCharacter(node->getName(), mesh, node->getPosition(), node->getScale().x, node->getOrientation()); - } - - void PhysicsSystem::removeObject (const std::string& handle) - { - mEngine->removeCharacter(handle); - mEngine->removeRigidBody(handle); - mEngine->deleteRigidBody(handle); - } - - void PhysicsSystem::moveObject (const Ptr& ptr) - { - Ogre::SceneNode *node = ptr.getRefData().getBaseNode(); - const std::string &handle = node->getName(); - const Ogre::Vector3 &position = node->getPosition(); - - if(OEngine::Physic::RigidBody *body = mEngine->getRigidBody(handle)) - { - body->getWorldTransform().setOrigin(btVector3(position.x,position.y,position.z)); - mEngine->mDynamicsWorld->updateSingleAabb(body); - } - - if(OEngine::Physic::RigidBody *body = mEngine->getRigidBody(handle, true)) - { - body->getWorldTransform().setOrigin(btVector3(position.x,position.y,position.z)); - mEngine->mDynamicsWorld->updateSingleAabb(body); - } - - // Actors update their AABBs every frame (DISABLE_DEACTIVATION), so no need to do it manually - if(OEngine::Physic::PhysicActor *physact = mEngine->getCharacter(handle)) - physact->setPosition(position); - } - - void PhysicsSystem::rotateObject (const Ptr& ptr) - { - Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); - const std::string &handle = node->getName(); - const Ogre::Quaternion &rotation = node->getOrientation(); - - // TODO: map to MWWorld::Ptr for faster access - if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle)) - { - act->setRotation(rotation); - } - if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle)) - { - if(dynamic_cast(body->getCollisionShape()) == NULL) - body->getWorldTransform().setRotation(btQuaternion(rotation.x, rotation.y, rotation.z, rotation.w)); - else - mEngine->boxAdjustExternal(handleToMesh[handle], body, node->getScale().x, node->getPosition(), rotation); - mEngine->mDynamicsWorld->updateSingleAabb(body); - } - if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle, true)) - { - if(dynamic_cast(body->getCollisionShape()) == NULL) - body->getWorldTransform().setRotation(btQuaternion(rotation.x, rotation.y, rotation.z, rotation.w)); - else - mEngine->boxAdjustExternal(handleToMesh[handle], body, node->getScale().x, node->getPosition(), rotation); - mEngine->mDynamicsWorld->updateSingleAabb(body); - } - } - - void PhysicsSystem::scaleObject (const Ptr& ptr) - { - Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); - const std::string &handle = node->getName(); - if(handleToMesh.find(handle) != handleToMesh.end()) - { - std::string model = ptr.getClass().getModel(ptr); - model = Misc::ResourceHelpers::correctActorModelPath(model); // FIXME: scaling shouldn't require model - - bool placeable = false; - if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle,true)) - placeable = body->mPlaceable; - else if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle,false)) - placeable = body->mPlaceable; - removeObject(handle); - addObject(ptr, model, placeable); - } - - if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle)) - { - float scale = ptr.getCellRef().getScale(); - if (!ptr.getClass().isNpc()) - // NOTE: Ignoring Npc::adjustScale (race height) on purpose. This is a bug in MW and must be replicated for compatibility reasons - ptr.getClass().adjustScale(ptr, scale); - act->setScale(scale); - } - } - - bool PhysicsSystem::toggleCollisionMode() - { - for(std::map::iterator it = mEngine->mActorMap.begin(); it != mEngine->mActorMap.end();++it) - { - if (it->first=="player") - { - OEngine::Physic::PhysicActor* act = it->second; - - bool cmode = act->getCollisionMode(); - if(cmode) - { - act->enableCollisionMode(false); - return false; - } - else - { - act->enableCollisionMode(true); - return true; - } - } - } - - throw std::logic_error ("can't find player"); - } - - bool PhysicsSystem::getObjectAABB(const MWWorld::Ptr &ptr, Ogre::Vector3 &min, Ogre::Vector3 &max) - { - std::string model = ptr.getClass().getModel(ptr); - model = Misc::ResourceHelpers::correctActorModelPath(model); - if (model.empty()) { - return false; - } - btVector3 btMin, btMax; - float scale = ptr.getCellRef().getScale(); - mEngine->getObjectAABB(model, scale, btMin, btMax); - - min.x = btMin.x(); - min.y = btMin.y(); - min.z = btMin.z(); - - max.x = btMax.x(); - max.y = btMax.y(); - max.z = btMax.z(); - - return true; - } - - - void PhysicsSystem::queueObjectMovement(const Ptr &ptr, const Ogre::Vector3 &movement) - { - PtrVelocityList::iterator iter = mMovementQueue.begin(); - for(;iter != mMovementQueue.end();++iter) - { - if(iter->first == ptr) - { - iter->second = movement; - return; - } - } - - mMovementQueue.push_back(std::make_pair(ptr, movement)); - } - - void PhysicsSystem::clearQueuedMovement() - { - mMovementQueue.clear(); - mCollisions.clear(); - mStandingCollisions.clear(); - } - - const PtrVelocityList& PhysicsSystem::applyQueuedMovement(float dt) - { - mMovementResults.clear(); - - mTimeAccum += dt; - if(mTimeAccum >= 1.0f/60.0f) - { - // Collision events should be available on every frame - mCollisions.clear(); - mStandingCollisions.clear(); - - const MWBase::World *world = MWBase::Environment::get().getWorld(); - PtrVelocityList::iterator iter = mMovementQueue.begin(); - for(;iter != mMovementQueue.end();++iter) - { - float waterlevel = -std::numeric_limits::max(); - const MWWorld::CellStore *cell = iter->first.getCell(); - if(cell->getCell()->hasWater()) - waterlevel = cell->getWaterLevel(); - - float oldHeight = iter->first.getRefData().getPosition().pos[2]; - - const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects(); - - bool waterCollision = false; - if (effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() - && cell->getCell()->hasWater() - && !world->isUnderwater(iter->first.getCell(), - Ogre::Vector3(iter->first.getRefData().getPosition().pos))) - waterCollision = true; - - OEngine::Physic::PhysicActor *physicActor = mEngine->getCharacter(iter->first.getRefData().getHandle()); - if (!physicActor) // actor was already removed from the scene - continue; - physicActor->setCanWaterWalk(waterCollision); - - // Slow fall reduces fall speed by a factor of (effect magnitude / 200) - float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); - - Ogre::Vector3 newpos = MovementSolver::move(iter->first, iter->second, mTimeAccum, - world->isFlying(iter->first), - waterlevel, slowFall, mEngine, mCollisions, mStandingCollisions); - - float heightDiff = newpos.z - oldHeight; - - if (heightDiff < 0) - iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff); - - mMovementResults.push_back(std::make_pair(iter->first, newpos)); - } - - mTimeAccum = 0.0f; - } - mMovementQueue.clear(); - - return mMovementResults; - } - - void PhysicsSystem::stepSimulation(float dt) - { - animateCollisionShapes(mEngine->mAnimatedShapes, mEngine->mDynamicsWorld); - animateCollisionShapes(mEngine->mAnimatedRaycastingShapes, mEngine->mDynamicsWorld); - - mEngine->stepSimulation(dt); - } - - bool PhysicsSystem::isActorStandingOn(const Ptr &actor, const Ptr &object) const - { - const std::string& actorHandle = actor.getRefData().getHandle(); - const std::string& objectHandle = object.getRefData().getHandle(); - - for (std::map::const_iterator it = mStandingCollisions.begin(); - it != mStandingCollisions.end(); ++it) - { - if (it->first == actorHandle && it->second == objectHandle) - return true; - } - return false; - } - - void PhysicsSystem::getActorsStandingOn(const Ptr &object, std::vector &out) const - { - const std::string& objectHandle = object.getRefData().getHandle(); - - for (std::map::const_iterator it = mStandingCollisions.begin(); - it != mStandingCollisions.end(); ++it) - { - if (it->second == objectHandle) - out.push_back(it->first); - } - } - - bool PhysicsSystem::isActorCollidingWith(const Ptr &actor, const Ptr &object) const - { - const std::string& actorHandle = actor.getRefData().getHandle(); - const std::string& objectHandle = object.getRefData().getHandle(); - - for (std::map::const_iterator it = mCollisions.begin(); - it != mCollisions.end(); ++it) - { - if (it->first == actorHandle && it->second == objectHandle) - return true; - } - return false; - } - - void PhysicsSystem::getActorsCollidingWith(const Ptr &object, std::vector &out) const - { - const std::string& objectHandle = object.getRefData().getHandle(); - - for (std::map::const_iterator it = mCollisions.begin(); - it != mCollisions.end(); ++it) - { - if (it->second == objectHandle) - out.push_back(it->first); - } - } - - void PhysicsSystem::disableWater() - { - if (mWaterEnabled) - { - mWaterEnabled = false; - updateWater(); - } - } - - void PhysicsSystem::enableWater(float height) - { - if (!mWaterEnabled || mWaterHeight != height) - { - mWaterEnabled = true; - mWaterHeight = height; - updateWater(); - } - } - - void PhysicsSystem::setWaterHeight(float height) - { - if (mWaterHeight != height) - { - mWaterHeight = height; - updateWater(); - } - } - - void PhysicsSystem::updateWater() - { - if (mWaterCollisionObject.get()) - { - mEngine->mDynamicsWorld->removeCollisionObject(mWaterCollisionObject.get()); - } - - if (!mWaterEnabled) - return; - - mWaterCollisionObject.reset(new btCollisionObject()); - mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight)); - mWaterCollisionObject->setCollisionShape(mWaterCollisionShape.get()); - mEngine->mDynamicsWorld->addCollisionObject(mWaterCollisionObject.get(), OEngine::Physic::CollisionType_Water, - OEngine::Physic::CollisionType_Actor); - } -} diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp deleted file mode 100644 index c1046aacb4..0000000000 --- a/apps/openmw/mwworld/physicssystem.hpp +++ /dev/null @@ -1,146 +0,0 @@ -#ifndef GAME_MWWORLD_PHYSICSSYSTEM_H -#define GAME_MWWORLD_PHYSICSSYSTEM_H - -#include - -#include - -#include - -#include "ptr.hpp" - - -namespace OEngine -{ - namespace Render - { - class OgreRenderer; - } - namespace Physic - { - class PhysicEngine; - } -} - -namespace MWWorld -{ - class World; - - typedef std::vector > PtrVelocityList; - - class PhysicsSystem - { - public: - PhysicsSystem (OEngine::Render::OgreRenderer &_rend); - ~PhysicsSystem (); - - void enableWater(float height); - void setWaterHeight(float height); - void disableWater(); - - void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, bool placeable=false); - - void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); - - void addHeightField (float* heights, - int x, int y, float yoffset, - float triSize, float sqrtVerts); - - void removeHeightField (int x, int y); - - // have to keep this as handle for now as unloadcell only knows scenenode names - void removeObject (const std::string& handle); - - void moveObject (const MWWorld::Ptr& ptr); - - void rotateObject (const MWWorld::Ptr& ptr); - - void scaleObject (const MWWorld::Ptr& ptr); - - bool toggleCollisionMode(); - - void stepSimulation(float dt); - - std::vector getCollisions(const MWWorld::Ptr &ptr, int collisionGroup, int collisionMask); ///< get handles this object collides with - Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, float maxHeight); - - std::pair getFacedHandle(float queryDistance); - std::pair getHitContact(const std::string &name, - const Ogre::Vector3 &origin, - const Ogre::Quaternion &orientation, - float queryDistance); - std::vector < std::pair > getFacedHandles (float queryDistance); - std::vector < std::pair > getFacedHandles (float mouseX, float mouseY, float queryDistance); - - // cast ray, return true if it hit something. if raycasringObjectOnlt is set to false, it ignores NPCs and objects with no collisions. - bool castRay(const Ogre::Vector3& from, const Ogre::Vector3& to, bool raycastingObjectOnly = true,bool ignoreHeightMap = false); - - std::pair - castRay(const Ogre::Vector3 &orig, const Ogre::Vector3 &dir, float len); - - std::pair castRay(float mouseX, float mouseY, Ogre::Vector3* normal = NULL, std::string* hit = NULL); - ///< cast ray from the mouse, return true if it hit something and the first result - /// @param normal if non-NULL, the hit normal will be written there (if there is a hit) - /// @param hit if non-NULL, the string handle of the hit object will be written there (if there is a hit) - - OEngine::Physic::PhysicEngine* getEngine(); - - bool getObjectAABB(const MWWorld::Ptr &ptr, Ogre::Vector3 &min, Ogre::Vector3 &max); - - /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will - /// be overwritten. Valid until the next call to applyQueuedMovement. - void queueObjectMovement(const Ptr &ptr, const Ogre::Vector3 &velocity); - - /// Apply all queued movements, then clear the list. - const PtrVelocityList& applyQueuedMovement(float dt); - - /// Clear the queued movements list without applying. - void clearQueuedMovement(); - - /// Return true if \a actor has been standing on \a object in this frame - /// This will trigger whenever the object is directly below the actor. - /// It doesn't matter if the actor is stationary or moving. - bool isActorStandingOn(const MWWorld::Ptr& actor, const MWWorld::Ptr& object) const; - - /// Get the handle of all actors standing on \a object in this frame. - void getActorsStandingOn(const MWWorld::Ptr& object, std::vector& out) const; - - /// Return true if \a actor has collided with \a object in this frame. - /// This will detect running into objects, but will not detect climbing stairs, stepping up a small object, etc. - bool isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::Ptr& object) const; - - /// Get the handle of all actors colliding with \a object in this frame. - void getActorsCollidingWith(const MWWorld::Ptr& object, std::vector& out) const; - - private: - - void updateWater(); - - OEngine::Render::OgreRenderer &mRender; - OEngine::Physic::PhysicEngine* mEngine; - std::map handleToMesh; - - // Tracks all movement collisions happening during a single frame. - // This will detect e.g. running against a vertical wall. It will not detect climbing up stairs, - // stepping up small objects, etc. - std::map mCollisions; - - std::map mStandingCollisions; - - PtrVelocityList mMovementQueue; - PtrVelocityList mMovementResults; - - float mTimeAccum; - - float mWaterHeight; - float mWaterEnabled; - - std::auto_ptr mWaterCollisionObject; - std::auto_ptr mWaterCollisionShape; - - PhysicsSystem (const PhysicsSystem&); - PhysicsSystem& operator= (const PhysicsSystem&); - }; -} - -#endif diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 0b81532e18..19b66c3c93 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -1,7 +1,7 @@ - #include "player.hpp" #include +#include #include #include @@ -19,17 +19,14 @@ #include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/actors.hpp" -#include "../mwmechanics/mechanicsmanagerimp.hpp" #include "class.hpp" #include "ptr.hpp" -#include "inventorystore.hpp" #include "cellstore.hpp" namespace MWWorld { - Player::Player (const ESM::NPC *player, const MWBase::World& world) + Player::Player (const ESM::NPC *player) : mCellStore(0), mLastKnownExteriorPosition(0,0,0), mMarkedCell(NULL), @@ -37,7 +34,8 @@ namespace MWWorld mForwardBackward(0), mTeleported(false), mCurrentCrimeId(-1), - mPaidCrimeId(-1) + mPaidCrimeId(-1), + mAttackingOrSpell(false) { ESM::CellRef cellRef; cellRef.blank(); @@ -49,6 +47,55 @@ namespace MWWorld mPlayer.mData.setPosition(playerPos); } + void Player::saveSkillsAttributes() + { + MWMechanics::NpcStats& stats = getPlayer().getClass().getNpcStats(getPlayer()); + for (int i=0; i& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + MWMechanics::NpcStats& stats = getPlayer().getClass().getNpcStats(getPlayer()); + for(size_t i = 0;i < ESM::Attribute::Length;++i) + { + // Oh, Bethesda. It's "Intelligence". + std::string name = "fWerewolf"+((i==ESM::Attribute::Intelligence) ? std::string("Intellegence") : + ESM::Attribute::sAttributeNames[i]); + + MWMechanics::AttributeValue value = stats.getAttribute(i); + value.setBase(int(gmst.find(name)->getFloat())); + stats.setAttribute(i, value); + } + + for(size_t i = 0;i < ESM::Skill::Length;i++) + { + // Acrobatics is set separately for some reason. + if(i == ESM::Skill::Acrobatics) + continue; + + // "Mercantile"! >_< + std::string name = "fWerewolf"+((i==ESM::Skill::Mercantile) ? std::string("Merchantile") : + ESM::Skill::sSkillNames[i]); + + MWMechanics::SkillValue value = stats.getSkill(i); + value.setBase(int(gmst.find(name)->getFloat())); + stats.setSkill(i, value); + } + } + void Player::set(const ESM::NPC *player) { mPlayer.mBase = player; @@ -158,6 +205,35 @@ namespace MWWorld return ptr.getClass().getNpcStats(ptr).getDrawState(); } + void Player::activate() + { + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) + return; + + MWWorld::Ptr player = getPlayer(); + const MWMechanics::NpcStats &playerStats = player.getClass().getNpcStats(player); + if (playerStats.isParalyzed() || playerStats.getKnockedDown()) + return; + + MWWorld::Ptr toActivate = MWBase::Environment::get().getWorld()->getFacedObject(); + + if (toActivate.isEmpty()) + return; + + if (toActivate.getClass().getName(toActivate) == "") // objects without name presented to user can never be activated + return; + + if (toActivate.getClass().isActor()) + { + MWMechanics::CreatureStats &stats = toActivate.getClass().getCreatureStats(toActivate); + + if (stats.getAiSequence().isInCombat() && !stats.isDead()) + return; + } + + MWBase::Environment::get().getWorld()->activate(toActivate, player); + } + bool Player::wasTeleported() const { return mTeleported; @@ -168,6 +244,16 @@ namespace MWWorld mTeleported = teleported; } + void Player::setAttackingOrSpell(bool attackingOrSpell) + { + mAttackingOrSpell = attackingOrSpell; + } + + bool Player::getAttackingOrSpell() const + { + return mAttackingOrSpell; + } + bool Player::isInCombat() { return MWBase::Environment::get().getMechanicsManager()->getActorsFighting(getPlayer()).size() != 0; } @@ -207,9 +293,9 @@ namespace MWWorld player.mBirthsign = mSign; - player.mLastKnownExteriorPosition[0] = mLastKnownExteriorPosition.x; - player.mLastKnownExteriorPosition[1] = mLastKnownExteriorPosition.y; - player.mLastKnownExteriorPosition[2] = mLastKnownExteriorPosition.z; + player.mLastKnownExteriorPosition[0] = mLastKnownExteriorPosition.x(); + player.mLastKnownExteriorPosition[1] = mLastKnownExteriorPosition.y(); + player.mLastKnownExteriorPosition[2] = mLastKnownExteriorPosition.z(); if (mMarkedCell) { @@ -222,6 +308,11 @@ namespace MWWorld player.mAutoMove = mAutoMove ? 1 : 0; + for (int i=0; iindexToPosition(0, 0, pos.pos[0], pos.pos[1], true); @@ -280,9 +383,9 @@ namespace MWWorld mSign = player.mBirthsign; - mLastKnownExteriorPosition.x = player.mLastKnownExteriorPosition[0]; - mLastKnownExteriorPosition.y = player.mLastKnownExteriorPosition[1]; - mLastKnownExteriorPosition.z = player.mLastKnownExteriorPosition[2]; + mLastKnownExteriorPosition.x() = player.mLastKnownExteriorPosition[0]; + mLastKnownExteriorPosition.y() = player.mLastKnownExteriorPosition[1]; + mLastKnownExteriorPosition.z() = player.mLastKnownExteriorPosition[2]; if (player.mHasMark && !player.mMarkedCell.mPaged) { diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 25d8981cde..595dd89fc6 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -5,8 +5,10 @@ #include "../mwworld/livecellref.hpp" #include "../mwmechanics/drawstate.hpp" +#include "../mwmechanics/stat.hpp" -#include +#include +#include namespace ESM { @@ -15,12 +17,6 @@ namespace ESM class ESMReader; } -namespace MWBase -{ - class World; - class Ptr; -} - namespace Loading { class Listener; @@ -37,7 +33,7 @@ namespace MWWorld MWWorld::CellStore *mCellStore; std::string mSign; - Ogre::Vector3 mLastKnownExteriorPosition; + osg::Vec3f mLastKnownExteriorPosition; ESM::Position mMarkedPosition; // If no position was marked, this is NULL @@ -50,9 +46,19 @@ namespace MWWorld int mCurrentCrimeId; // the id assigned witnesses int mPaidCrimeId; // the last id paid off (0 bounty) + // Saved skills and attributes prior to becoming a werewolf + MWMechanics::SkillValue mSaveSkills[ESM::Skill::Length]; + MWMechanics::AttributeValue mSaveAttributes[ESM::Attribute::Length]; + + bool mAttackingOrSpell; + public: - Player(const ESM::NPC *player, const MWBase::World& world); + Player(const ESM::NPC *player); + + void saveSkillsAttributes(); + void restoreSkillsAttributes(); + void setWerewolfSkillsAttributes(); // For mark/recall magic effects void markPosition (CellStore* markedCell, ESM::Position markedPosition); @@ -61,9 +67,8 @@ namespace MWWorld /// Interiors can not always be mapped to a world position. However /// world position is still required for divine / almsivi magic effects /// and the player arrow on the global map. - /// TODO: This should be stored in the savegame, too. - void setLastKnownExteriorPosition (const Ogre::Vector3& position) { mLastKnownExteriorPosition = position; } - Ogre::Vector3 getLastKnownExteriorPosition() const { return mLastKnownExteriorPosition; } + void setLastKnownExteriorPosition (const osg::Vec3f& position) { mLastKnownExteriorPosition = position; } + osg::Vec3f getLastKnownExteriorPosition() const { return mLastKnownExteriorPosition; } void set (const ESM::NPC *player); @@ -77,6 +82,9 @@ namespace MWWorld void setDrawState (MWMechanics::DrawState_ state); MWMechanics::DrawState_ getDrawState(); /// \todo constness + /// Activate the object under the crosshair, if any + void activate(); + bool getAutoMove() const; void setAutoMove (bool enable); @@ -95,6 +103,9 @@ namespace MWWorld bool wasTeleported() const; void setTeleported(bool teleported); + void setAttackingOrSpell(bool attackingOrSpell); + bool getAttackingOrSpell() const; + ///Checks all nearby actors to see if anyone has an aipackage against you bool isInCombat(); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index acbe819f1e..439ac19ec9 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -1,11 +1,14 @@ #include "projectilemanager.hpp" -#include -#include - -#include +#include +#include #include +#include +#include +#include +#include +#include #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" @@ -19,75 +22,118 @@ #include "../mwmechanics/combat.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwrender/effectmanager.hpp" #include "../mwrender/animation.hpp" -#include "../mwrender/renderconst.hpp" +#include "../mwrender/vismask.hpp" +#include "../mwrender/renderingmanager.hpp" #include "../mwsound/sound.hpp" +#include "../mwphysics/physicssystem.hpp" + + namespace MWWorld { - ProjectileManager::ProjectileManager(Ogre::SceneManager* sceneMgr, OEngine::Physic::PhysicEngine &engine) - : mPhysEngine(engine) - , mSceneMgr(sceneMgr) + ProjectileManager::ProjectileManager(osg::Group* parent, Resource::ResourceSystem* resourceSystem, + MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics) + : mParent(parent) + , mResourceSystem(resourceSystem) + , mRendering(rendering) + , mPhysics(physics) { } - void ProjectileManager::createModel(State &state, const std::string &model) + /// Rotates an osg::PositionAttitudeTransform over time. + class RotateCallback : public osg::NodeCallback { - state.mObject = NifOgre::Loader::createObjects(state.mNode, model); - for(size_t i = 0;i < state.mObject->mControllers.size();i++) + public: + RotateCallback(const osg::Vec3f& axis = osg::Vec3f(0,-1,0), float rotateSpeed = osg::PI*2) + : mAxis(axis) + , mRotateSpeed(rotateSpeed) { - if(state.mObject->mControllers[i].getSource().isNull()) - state.mObject->mControllers[i].setSource(Ogre::SharedPtr (new MWRender::EffectAnimationTime())); } - MWRender::Animation::setRenderProperties(state.mObject, MWRender::RV_Effects, - MWRender::RQG_Main, MWRender::RQG_Alpha, 0.f, false, NULL); + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osg::PositionAttitudeTransform* transform = static_cast(node); + + double time = nv->getFrameStamp()->getSimulationTime(); + + osg::Quat orient = osg::Quat(time * mRotateSpeed, mAxis); + transform->setAttitude(orient); + + traverse(node, nv); + } + + private: + osg::Vec3f mAxis; + float mRotateSpeed; + }; + + + void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate) + { + state.mNode = new osg::PositionAttitudeTransform; + state.mNode->setNodeMask(MWRender::Mask_Effect); + state.mNode->setPosition(pos); + state.mNode->setAttitude(orient); + + osg::Group* attachTo = state.mNode; + + if (rotate) + { + osg::ref_ptr rotateNode (new osg::PositionAttitudeTransform); + rotateNode->addUpdateCallback(new RotateCallback()); + state.mNode->addChild(rotateNode); + attachTo = rotateNode; + } + + mResourceSystem->getSceneManager()->createInstance(model, attachTo); + + SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; + state.mNode->accept(disableFreezeOnCullVisitor); + + state.mNode->addCullCallback(new SceneUtil::LightListCallback); + + mParent->addChild(state.mNode); + + state.mEffectAnimationTime.reset(new MWRender::EffectAnimationTime); + + SceneUtil::AssignControllerSourcesVisitor assignVisitor (state.mEffectAnimationTime); + state.mNode->accept(assignVisitor); } - void ProjectileManager::update(NifOgre::ObjectScenePtr object, float duration) + void ProjectileManager::update(State& state, float duration) { - for(size_t i = 0; i < object->mControllers.size() ;i++) - { - MWRender::EffectAnimationTime* value = dynamic_cast(object->mControllers[i].getSource().get()); - if (value) - value->addTime(duration); - - object->mControllers[i].update(); - } + state.mEffectAnimationTime->addTime(duration); } void ProjectileManager::launchMagicBolt(const std::string &model, const std::string &sound, const std::string &spellId, float speed, bool stack, const ESM::EffectList &effects, const Ptr &caster, const std::string &sourceName, - const Ogre::Vector3& fallbackDirection) + const osg::Vec3f& fallbackDirection) { - float height = 0; - if (OEngine::Physic::PhysicActor* actor = mPhysEngine.getCharacter(caster.getRefData().getHandle())) - height = actor->getHalfExtents().z * 2 * 0.75f; // Spawn at 0.75 * ActorHeight - - Ogre::Vector3 pos(caster.getRefData().getPosition().pos); - pos.z += height; + osg::Vec3f pos = mPhysics->getPosition(caster) + osg::Vec3f(0,0,mPhysics->getHalfExtents(caster).z() * 0.5); // Spawn at 0.75 * ActorHeight if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible return; - Ogre::Quaternion orient; + osg::Quat orient; if (caster.getClass().isActor()) - orient = Ogre::Quaternion(Ogre::Radian(caster.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * - Ogre::Quaternion(Ogre::Radian(caster.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + orient = osg::Quat(caster.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) + * osg::Quat(caster.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); else - orient = Ogre::Vector3::UNIT_Y.getRotationTo(fallbackDirection); + orient.makeRotate(osg::Vec3f(0,1,0), osg::Vec3f(fallbackDirection)); MagicBoltState state; state.mSourceName = sourceName; state.mId = model; state.mSpellId = spellId; - state.mCasterHandle = caster.getRefData().getHandle(); + state.mCasterHandle = caster; if (caster.getClass().isActor()) state.mActorId = caster.getClass().getCreatureStats(caster).getActorId(); else @@ -107,29 +153,28 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), model); MWWorld::Ptr ptr = ref.getPtr(); - state.mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(pos, orient); - createModel(state, ptr.getClass().getModel(ptr)); + createModel(state, ptr.getClass().getModel(ptr), pos, orient, true); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - state.mSound = sndMgr->playManualSound3D(pos, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + state.mSound = sndMgr->playSound3D(pos, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); mMagicBolts.push_back(state); } - void ProjectileManager::launchProjectile(Ptr actor, Ptr projectile, const Ogre::Vector3 &pos, - const Ogre::Quaternion &orient, Ptr bow, float speed) + void ProjectileManager::launchProjectile(Ptr actor, ConstPtr projectile, const osg::Vec3f &pos, const osg::Quat &orient, Ptr bow, float speed, float attackStrength) { ProjectileState state; state.mActorId = actor.getClass().getCreatureStats(actor).getActorId(); state.mBowId = bow.getCellRef().getRefId(); - state.mVelocity = orient.yAxis() * speed; + state.mVelocity = orient * osg::Vec3f(0,1,0) * speed; state.mId = projectile.getCellRef().getRefId(); + state.mCasterHandle = actor; + state.mAttackStrength = attackStrength; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId()); MWWorld::Ptr ptr = ref.getPtr(); - state.mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(pos, orient); - createModel(state, ptr.getClass().getModel(ptr)); + createModel(state, ptr.getClass().getModel(ptr), pos, orient, false); mProjectiles.push_back(state); } @@ -144,74 +189,58 @@ namespace MWWorld { for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) { - Ogre::Quaternion orient = it->mNode->getOrientation(); + osg::Quat orient = it->mNode->getAttitude(); static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get() .find("fTargetSpellMaxSpeed")->getFloat(); float speed = fTargetSpellMaxSpeed * it->mSpeed; - Ogre::Vector3 direction = orient.yAxis(); - direction.normalise(); - Ogre::Vector3 pos(it->mNode->getPosition()); - Ogre::Vector3 newPos = pos + direction * duration * speed; + osg::Vec3f direction = orient * osg::Vec3f(0,1,0); + direction.normalize(); + osg::Vec3f pos(it->mNode->getPosition()); + osg::Vec3f newPos = pos + direction * duration * speed; if (it->mSound.get()) it->mSound->setPosition(newPos); it->mNode->setPosition(newPos); - update(it->mObject, duration); + update(*it, duration); + + MWWorld::Ptr caster = it->getCaster(); // Check for impact // TODO: use a proper btRigidBody / btGhostObject? - btVector3 from(pos.x, pos.y, pos.z); - btVector3 to(newPos.x, newPos.y, newPos.z); + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, 0xff, MWPhysics::CollisionType_Projectile); - std::vector > collisions = mPhysEngine.rayTest2(from, to, OEngine::Physic::CollisionType_Projectile); - bool hit=false; - - for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end() && !hit; ++cIt) + bool hit = false; + if (result.mHit) { - MWWorld::Ptr obstacle = MWBase::Environment::get().getWorld()->searchPtrViaHandle(cIt->second); - - MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaHandle(it->mCasterHandle); - if (caster.isEmpty()) - caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId); - - if (!obstacle.isEmpty() && obstacle == caster) - continue; - - if (caster.isEmpty()) - caster = obstacle; - - if (obstacle.isEmpty()) + hit = true; + if (result.mHitObject.isEmpty()) { - // Terrain + // terrain } else { - MWMechanics::CastSpell cast(caster, obstacle); + MWMechanics::CastSpell cast(caster, result.mHitObject); cast.mHitPosition = pos; cast.mId = it->mSpellId; cast.mSourceName = it->mSourceName; cast.mStack = it->mStack; - cast.inflict(obstacle, caster, it->mEffects, ESM::RT_Target, false, true); + cast.inflict(result.mHitObject, caster, it->mEffects, ESM::RT_Target, false, true); } - - hit = true; } // Explodes when hitting water - if (MWBase::Environment::get().getWorld()->isUnderwater(MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), newPos)) + if (MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos)) hit = true; if (hit) { - MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId); MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, ESM::RT_Target, it->mSpellId, it->mSourceName); MWBase::Environment::get().getSoundManager()->stopSound(it->mSound); - - mSceneMgr->destroySceneNode(it->mNode); + mParent->removeChild(it->mNode); it = mMagicBolts.erase(it); continue; @@ -227,62 +256,56 @@ namespace MWWorld { // gravity constant - must be way lower than the gravity affecting actors, since we're not // simulating aerodynamics at all - it->mVelocity -= Ogre::Vector3(0, 0, 627.2f * 0.1f) * duration; + it->mVelocity -= osg::Vec3f(0, 0, 627.2f * 0.1f) * duration; - Ogre::Vector3 pos(it->mNode->getPosition()); - Ogre::Vector3 newPos = pos + it->mVelocity * duration; + osg::Vec3f pos(it->mNode->getPosition()); + osg::Vec3f newPos = pos + it->mVelocity * duration; - Ogre::Quaternion orient = Ogre::Vector3::UNIT_Y.getRotationTo(it->mVelocity); - it->mNode->setOrientation(orient); + osg::Quat orient; + orient.makeRotate(osg::Vec3f(0,1,0), it->mVelocity); + it->mNode->setAttitude(orient); it->mNode->setPosition(newPos); - update(it->mObject, duration); + update(*it, duration); + + MWWorld::Ptr caster = it->getCaster(); // Check for impact // TODO: use a proper btRigidBody / btGhostObject? - btVector3 from(pos.x, pos.y, pos.z); - btVector3 to(newPos.x, newPos.y, newPos.z); - std::vector > collisions = mPhysEngine.rayTest2(from, to, OEngine::Physic::CollisionType_Projectile); - bool hit=false; + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, 0xff, MWPhysics::CollisionType_Projectile); - for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end() && !hit; ++cIt) + bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos); + if (result.mHit || underwater) { - MWWorld::Ptr obstacle = MWBase::Environment::get().getWorld()->searchPtrViaHandle(cIt->second); - - MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId); - - // Arrow intersects with player immediately after shooting :/ - if (obstacle == caster) - continue; - - MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mId); - - // Try to get a Ptr to the bow that was used. It might no longer exist. - MWWorld::Ptr bow = projectileRef.getPtr(); - if (!caster.isEmpty()) + if (result.mHit) { - MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); - MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) - bow = *invIt; + MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mId); + + // Try to get a Ptr to the bow that was used. It might no longer exist. + MWWorld::Ptr bow = projectileRef.getPtr(); + if (!caster.isEmpty()) + { + MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); + MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) + bow = *invIt; + } + + if (caster.isEmpty()) + caster = result.mHitObject; + + MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHitPos, it->mAttackStrength); } - if (caster.isEmpty()) - caster = obstacle; - - MWMechanics::projectileHit(caster, obstacle, bow, projectileRef.getPtr(), pos + (newPos - pos) * cIt->first); - - hit = true; - } - if (hit) - { - mSceneMgr->destroySceneNode(it->mNode); + if (underwater) + mRendering->emitWaterRipple(newPos); + mParent->removeChild(it->mNode); it = mProjectiles.erase(it); continue; } - else - ++it; + + ++it; } } @@ -290,13 +313,13 @@ namespace MWWorld { for (std::vector::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) { - mSceneMgr->destroySceneNode(it->mNode); + mParent->removeChild(it->mNode); } mProjectiles.clear(); for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) { + mParent->removeChild(it->mNode); MWBase::Environment::get().getSoundManager()->stopSound(it->mSound); - mSceneMgr->destroySceneNode(it->mNode); } mMagicBolts.clear(); } @@ -309,12 +332,13 @@ namespace MWWorld ESM::ProjectileState state; state.mId = it->mId; - state.mPosition = it->mNode->getPosition(); - state.mOrientation = it->mNode->getOrientation(); + state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition())); + state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude())); state.mActorId = it->mActorId; state.mBowId = it->mBowId; state.mVelocity = it->mVelocity; + state.mAttackStrength = it->mAttackStrength; state.save(writer); @@ -327,8 +351,8 @@ namespace MWWorld ESM::MagicBoltState state; state.mId = it->mId; - state.mPosition = it->mNode->getPosition(); - state.mOrientation = it->mNode->getOrientation(); + state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition())); + state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude())); state.mActorId = it->mActorId; state.mSpellId = it->mSpellId; @@ -356,6 +380,7 @@ namespace MWWorld state.mBowId = esm.mBowId; state.mVelocity = esm.mVelocity; state.mId = esm.mId; + state.mAttackStrength = esm.mAttackStrength; std::string model; try @@ -369,8 +394,7 @@ namespace MWWorld return true; } - state.mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(esm.mPosition, esm.mOrientation); - createModel(state, model); + createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), false); mProjectiles.push_back(state); return true; @@ -401,12 +425,11 @@ namespace MWWorld return true; } - state.mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(esm.mPosition, esm.mOrientation); - createModel(state, model); + createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - state.mSound = sndMgr->playManualSound3D(esm.mPosition, esm.mSound, 1.0f, 1.0f, - MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + state.mSound = sndMgr->playSound3D(esm.mPosition, esm.mSound, 1.0f, 1.0f, + MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); state.mSoundId = esm.mSound; mMagicBolts.push_back(state); @@ -421,4 +444,12 @@ namespace MWWorld return mMagicBolts.size() + mProjectiles.size(); } + MWWorld::Ptr ProjectileManager::State::getCaster() + { + if (!mCasterHandle.isEmpty()) + return mCasterHandle; + + return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mActorId); + } + } diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 93f54c0086..74d4c1dc53 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -3,21 +3,18 @@ #include -#include +#include +#include #include -#include #include "../mwbase/soundmanager.hpp" #include "ptr.hpp" -namespace OEngine +namespace MWPhysics { -namespace Physic -{ - class PhysicEngine; -} + class PhysicsSystem; } namespace Loading @@ -25,9 +22,21 @@ namespace Loading class Listener; } -namespace Ogre +namespace osg { - class SceneManager; + class Group; + class Quat; +} + +namespace Resource +{ + class ResourceSystem; +} + +namespace MWRender +{ + class EffectAnimationTime; + class RenderingManager; } namespace MWWorld @@ -36,16 +45,16 @@ namespace MWWorld class ProjectileManager { public: - ProjectileManager (Ogre::SceneManager* sceneMgr, - OEngine::Physic::PhysicEngine& engine); + ProjectileManager (osg::Group* parent, Resource::ResourceSystem* resourceSystem, + MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics); /// If caster is an actor, the actor's facing orientation is used. Otherwise fallbackDirection is used. void launchMagicBolt (const std::string& model, const std::string &sound, const std::string &spellId, float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, const std::string& sourceName, const Ogre::Vector3& fallbackDirection); + const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection); - void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, - const Ogre::Vector3& pos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed); + void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, + const osg::Vec3f& pos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength); void update(float dt); @@ -57,22 +66,23 @@ namespace MWWorld int countSavedGameRecords() const; private: - OEngine::Physic::PhysicEngine& mPhysEngine; - Ogre::SceneManager* mSceneMgr; + osg::ref_ptr mParent; + Resource::ResourceSystem* mResourceSystem; + MWRender::RenderingManager* mRendering; + MWPhysics::PhysicsSystem* mPhysics; struct State { - NifOgre::ObjectScenePtr mObject; - Ogre::SceneNode* mNode; + osg::ref_ptr mNode; + boost::shared_ptr mEffectAnimationTime; int mActorId; - // actorId doesn't work for non-actors, so we also keep track of the Ogre-handle. - // For non-actors, the caster ptr is mainly needed to prevent the projectile - // from colliding with its caster. // TODO: this will break when the game is saved and reloaded, since there is currently // no way to write identifiers for non-actors to a savegame. - std::string mCasterHandle; + MWWorld::Ptr mCasterHandle; + + MWWorld::Ptr getCaster(); // MW-id of this projectile std::string mId; @@ -100,7 +110,8 @@ namespace MWWorld // RefID of the bow or crossbow the actor was using when this projectile was fired (may be empty) std::string mBowId; - Ogre::Vector3 mVelocity; + osg::Vec3f mVelocity; + float mAttackStrength; }; std::vector mMagicBolts; @@ -109,8 +120,11 @@ namespace MWWorld void moveProjectiles(float dt); void moveMagicBolts(float dt); - void createModel (State& state, const std::string& model); - void update (NifOgre::ObjectScenePtr object, float duration); + void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate); + void update (State& state, float duration); + + void operator=(const ProjectileManager&); + ProjectileManager(const ProjectileManager&); }; } diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index 1cf22744a8..01e7be1d1d 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -1,4 +1,3 @@ - #include "ptr.hpp" #include @@ -53,3 +52,30 @@ MWWorld::Ptr::operator const void *() { return mRef; } + +// ------------------------------------------------------------------------------- + +const std::string &MWWorld::ConstPtr::getTypeName() const +{ + if(mRef != 0) + return mRef->mClass->getTypeName(); + throw std::runtime_error("Can't get type name from an empty object."); +} + +const MWWorld::LiveCellRefBase *MWWorld::ConstPtr::getBase() const +{ + if (!mRef) + throw std::runtime_error ("Can't access cell ref pointed to by null Ptr"); + + return mRef; +} + +const MWWorld::ContainerStore *MWWorld::ConstPtr::getContainerStore() const +{ + return mContainerStore; +} + +MWWorld::ConstPtr::operator const void *() +{ + return mRef; +} diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index c97d2e6d1e..d34f516a79 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -85,6 +85,87 @@ namespace MWWorld ///< Return a 0-pointer, if Ptr is empty; return a non-0-pointer, if Ptr is not empty }; + /// \brief Pointer to a const LiveCellRef + /// @note a Ptr can be implicitely converted to a ConstPtr, but you can not convert a ConstPtr to a Ptr. + class ConstPtr + { + public: + + const MWWorld::LiveCellRefBase *mRef; + const CellStore *mCell; + const ContainerStore *mContainerStore; + + public: + ConstPtr(const MWWorld::LiveCellRefBase *liveCellRef=0, const CellStore *cell=0) + : mRef(liveCellRef), mCell(cell), mContainerStore(0) + { + } + + ConstPtr(const MWWorld::Ptr& ptr) + : mRef(ptr.mRef), mCell(ptr.mCell), mContainerStore(ptr.mContainerStore) + { + } + + bool isEmpty() const + { + return mRef == 0; + } + + const std::string& getTypeName() const; + + const Class& getClass() const + { + if(mRef != 0) + return *(mRef->mClass); + throw std::runtime_error("Cannot get class of an empty object"); + } + + template + const MWWorld::LiveCellRef *get() const + { + const MWWorld::LiveCellRef *ref = dynamic_cast*>(mRef); + if(ref) return ref; + + std::stringstream str; + str<< "Bad LiveCellRef cast to "<mRef; + } + + const RefData& getRefData() const + { + assert(mRef); + return mRef->mData; + } + + const CellStore *getCell() const + { + assert(mCell); + return mCell; + } + + bool isInCell() const + { + return (mContainerStore == 0) && (mCell != 0); + } + + const ContainerStore *getContainerStore() const; + ///< May return a 0-pointer, if reference is not in a container. + + operator const void *(); + ///< Return a 0-pointer, if Ptr is empty; return a non-0-pointer, if Ptr is not empty + }; + inline bool operator== (const Ptr& left, const Ptr& right) { return left.mRef==right.mRef; @@ -114,6 +195,36 @@ namespace MWWorld { return !(left>right); } + + inline bool operator== (const ConstPtr& left, const ConstPtr& right) + { + return left.mRef==right.mRef; + } + + inline bool operator!= (const ConstPtr& left, const ConstPtr& right) + { + return !(left==right); + } + + inline bool operator< (const ConstPtr& left, const ConstPtr& right) + { + return left.mRef= (const ConstPtr& left, const ConstPtr& right) + { + return !(left (const ConstPtr& left, const ConstPtr& right) + { + return rightright); + } } #endif diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index ae985f8574..1da6b53fa4 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -1,8 +1,5 @@ - #include "refdata.hpp" -#include - #include #include "customdata.hpp" @@ -17,13 +14,11 @@ namespace MWWorld { mBaseNode = refData.mBaseNode; mLocals = refData.mLocals; - mHasLocals = refData.mHasLocals; mEnabled = refData.mEnabled; mCount = refData.mCount; mPosition = refData.mPosition; - mLocalRotation = refData.mLocalRotation; mChanged = refData.mChanged; - mDeleted = refData.mDeleted; + mDeletedByContentFile = refData.mDeletedByContentFile; mCustomData = refData.mCustomData ? refData.mCustomData->clone() : 0; } @@ -37,37 +32,31 @@ namespace MWWorld } RefData::RefData() - : mBaseNode(0), mDeleted(false), mHasLocals (false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false) + : mBaseNode(0), mDeletedByContentFile(false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false) { for (int i=0; i<3; ++i) { - mLocalRotation.rot[i] = 0; mPosition.pos[i] = 0; mPosition.rot[i] = 0; } } RefData::RefData (const ESM::CellRef& cellRef) - : mBaseNode(0), mDeleted(false), mHasLocals (false), mEnabled (true), + : mBaseNode(0), mDeletedByContentFile(false), mEnabled (true), mCount (1), mPosition (cellRef.mPos), mCustomData (0), mChanged(false) // Loading from ESM/ESP files -> assume unchanged { - mLocalRotation.rot[0]=0; - mLocalRotation.rot[1]=0; - mLocalRotation.rot[2]=0; } - RefData::RefData (const ESM::ObjectState& objectState) - : mBaseNode (0), mDeleted(false), mHasLocals (false), + RefData::RefData (const ESM::ObjectState& objectState, bool deletedByContentFile) + : mBaseNode(0), mDeletedByContentFile(deletedByContentFile), mEnabled (objectState.mEnabled != 0), mCount (objectState.mCount), mPosition (objectState.mPosition), mCustomData (0), mChanged(true) // Loading from a savegame -> assume changed - { - for (int i=0; i<3; ++i) - mLocalRotation.rot[i] = objectState.mLocalRotation[i]; + { } RefData::RefData (const RefData& refData) @@ -86,17 +75,11 @@ namespace MWWorld void RefData::write (ESM::ObjectState& objectState, const std::string& scriptId) const { - objectState.mHasLocals = mHasLocals; - - if (mHasLocals) - mLocals.write (objectState.mLocals, scriptId); + objectState.mHasLocals = mLocals.write (objectState.mLocals, scriptId); objectState.mEnabled = mEnabled; objectState.mCount = mCount; objectState.mPosition = mPosition; - - for (int i=0; i<3; ++i) - objectState.mLocalRotation[i] = mLocalRotation.rot[i]; } RefData& RefData::operator= (const RefData& refData) @@ -125,25 +108,19 @@ namespace MWWorld {} } - const std::string &RefData::getHandle() + void RefData::setBaseNode(SceneUtil::PositionAttitudeTransform *base) { - if(!mBaseNode) - { - static const std::string empty; - return empty; - } - - return mBaseNode->getName(); + mBaseNode = base; } - Ogre::SceneNode* RefData::getBaseNode() + SceneUtil::PositionAttitudeTransform* RefData::getBaseNode() { return mBaseNode; } - void RefData::setBaseNode(Ogre::SceneNode* base) + const SceneUtil::PositionAttitudeTransform* RefData::getBaseNode() const { - mBaseNode = base; + return mBaseNode; } int RefData::getCount() const @@ -153,13 +130,8 @@ namespace MWWorld void RefData::setLocals (const ESM::Script& script) { - if (!mHasLocals) - { - mLocals.configure (script); - mHasLocals = true; - if (!mLocals.isEmpty()) - mChanged = true; - } + if (mLocals.configure (script) && !mLocals.isEmpty()) + mChanged = true; } void RefData::setCount (int count) @@ -172,19 +144,19 @@ namespace MWWorld mCount = count; } - void RefData::setDeleted(bool deleted) + void RefData::setDeletedByContentFile(bool deleted) { - mDeleted = deleted; + mDeletedByContentFile = deleted; } bool RefData::isDeleted() const { - return mDeleted || mCount == 0; + return mDeletedByContentFile || mCount == 0; } bool RefData::isDeletedByContentFile() const { - return mDeleted; + return mDeletedByContentFile; } MWScript::Locals& RefData::getLocals() @@ -199,14 +171,20 @@ namespace MWWorld void RefData::enable() { - mChanged = !mEnabled; - mEnabled = true; + if (!mEnabled) + { + mChanged = true; + mEnabled = true; + } } void RefData::disable() { - mChanged = mEnabled; - mEnabled = false; + if (mEnabled) + { + mChanged = true; + mEnabled = false; + } } void RefData::setPosition(const ESM::Position& pos) @@ -215,22 +193,11 @@ namespace MWWorld mPosition = pos; } - const ESM::Position& RefData::getPosition() + const ESM::Position& RefData::getPosition() const { return mPosition; } - void RefData::setLocalRotation(const LocalRotation& rot) - { - mChanged = true; - mLocalRotation = rot; - } - - const LocalRotation& RefData::getLocalRotation() - { - return mLocalRotation; - } - void RefData::setCustomData (CustomData *data) { mChanged = true; // We do not currently track CustomData, so assume anything with a CustomData is changed @@ -243,6 +210,11 @@ namespace MWWorld return mCustomData; } + const CustomData *RefData::getCustomData() const + { + return mCustomData; + } + bool RefData::hasChanged() const { return mChanged; diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index e90b44f9c9..28d2dad4c9 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -5,9 +5,11 @@ #include "../mwscript/locals.hpp" -namespace Ogre +#include + +namespace SceneUtil { - class SceneNode; + class PositionAttitudeTransform; } namespace ESM @@ -19,30 +21,26 @@ namespace ESM namespace MWWorld { - struct LocalRotation{ - float rot[3]; - }; class CustomData; class RefData { - Ogre::SceneNode* mBaseNode; + SceneUtil::PositionAttitudeTransform* mBaseNode; + MWScript::Locals mLocals; + + /// separate delete flag used for deletion by a content file + /// @note not stored in the save game file. + bool mDeletedByContentFile; - MWScript::Locals mLocals; // if we find the overhead of heaving a locals - // object in the refdata of refs without a script, - // we can make this a pointer later. - bool mDeleted; // separate delete flag used for deletion by a content file - bool mHasLocals; bool mEnabled; - int mCount; // 0: deleted + /// 0: deleted + int mCount; ESM::Position mPosition; - LocalRotation mLocalRotation; - CustomData *mCustomData; void copy (const RefData& refData); @@ -57,10 +55,10 @@ namespace MWWorld /// @param cellRef Used to copy constant data such as position into this class where it can /// be altered without affecting the original data. This makes it possible - /// to reset the position as the orignal data is still held in the CellRef + /// to reset the position as the original data is still held in the CellRef RefData (const ESM::CellRef& cellRef); - RefData (const ESM::ObjectState& objectState); + RefData (const ESM::ObjectState& objectState, bool deletedByContentFile); ///< Ignores local variables and custom data (not enough context available here to /// perform these operations). @@ -74,14 +72,14 @@ namespace MWWorld RefData& operator= (const RefData& refData); - /// Return OGRE handle (may be empty). - const std::string &getHandle(); + /// Return base node (can be a null pointer). + SceneUtil::PositionAttitudeTransform* getBaseNode(); - /// Return OGRE base node (can be a null pointer). - Ogre::SceneNode* getBaseNode(); + /// Return base node (can be a null pointer). + const SceneUtil::PositionAttitudeTransform* getBaseNode() const; - /// Set OGRE base node (can be a null pointer). - void setBaseNode (Ogre::SceneNode* base); + /// Set base node (can be a null pointer). + void setBaseNode (SceneUtil::PositionAttitudeTransform* base); int getCount() const; @@ -96,7 +94,7 @@ namespace MWWorld /// This flag is only used for content stack loading and will not be stored in the savegame. /// If the object was deleted by gameplay, then use setCount(0) instead. - void setDeleted(bool deleted); + void setDeletedByContentFile(bool deleted); /// Returns true if the object was either deleted by the content file or by gameplay. bool isDeleted() const; @@ -112,10 +110,7 @@ namespace MWWorld void disable(); void setPosition (const ESM::Position& pos); - const ESM::Position& getPosition(); - - void setLocalRotation (const LocalRotation& rotation); - const LocalRotation& getLocalRotation(); + const ESM::Position& getPosition() const; void setCustomData (CustomData *data); ///< Set custom data (potentially replacing old custom data). The ownership of \a data is @@ -124,6 +119,8 @@ namespace MWWorld CustomData *getCustomData(); ///< May return a 0-pointer. The ownership of the return data object is not transferred. + const CustomData *getCustomData() const; + bool hasChanged() const; ///< Has this RefData changed since it was originally loaded? }; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index f5a9b8960d..15b3c057bc 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1,9 +1,12 @@ #include "scene.hpp" -#include +#include +#include -#include +#include #include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -11,74 +14,93 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "physicssystem.hpp" +#include "../mwrender/renderingmanager.hpp" + +#include "../mwphysics/physicssystem.hpp" + #include "player.hpp" #include "localscripts.hpp" #include "esmstore.hpp" #include "class.hpp" -#include "cellfunctors.hpp" +#include "cellvisitors.hpp" #include "cellstore.hpp" namespace { - void addObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics, + void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering) { - std::string model = Misc::ResourceHelpers::correctActorModelPath(ptr.getClass().getModel(ptr)); - std::string id = ptr.getClass().getId(ptr); + std::string model = Misc::ResourceHelpers::correctActorModelPath(ptr.getClass().getModel(ptr), rendering.getResourceSystem()->getVFS()); + std::string id = ptr.getCellRef().getRefId(); if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker") model = ""; // marker objects that have a hardcoded function in the game logic, should be hidden from the player - rendering.addObject(ptr, model); + ptr.getClass().insertObjectRendering(ptr, model, rendering); ptr.getClass().insertObject (ptr, model, physics); + + if (ptr.getClass().isActor()) + rendering.addWaterRippleEmitter(ptr); } - void updateObjectLocalRotation (const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics, - MWRender::RenderingManager& rendering) + void updateObjectRotation (const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, + MWRender::RenderingManager& rendering, bool inverseRotationOrder) { if (ptr.getRefData().getBaseNode() != NULL) { - Ogre::Quaternion worldRotQuat(Ogre::Radian(ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z); + osg::Quat worldRotQuat(ptr.getRefData().getPosition().rot[2], osg::Vec3(0,0,-1)); if (!ptr.getClass().isActor()) - worldRotQuat = Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)* - Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[1]), Ogre::Vector3::NEGATIVE_UNIT_Y)* worldRotQuat; + { + float xr = ptr.getRefData().getPosition().rot[0]; + float yr = ptr.getRefData().getPosition().rot[1]; + if (!inverseRotationOrder) + worldRotQuat = worldRotQuat * osg::Quat(yr, osg::Vec3(0,-1,0)) * + osg::Quat(xr, osg::Vec3(-1,0,0)); + else + worldRotQuat = osg::Quat(xr, osg::Vec3(-1,0,0)) * osg::Quat(yr, osg::Vec3(0,-1,0)) * worldRotQuat; + } - float x = ptr.getRefData().getLocalRotation().rot[0]; - float y = ptr.getRefData().getLocalRotation().rot[1]; - float z = ptr.getRefData().getLocalRotation().rot[2]; - - Ogre::Quaternion rot(Ogre::Radian(z), Ogre::Vector3::NEGATIVE_UNIT_Z); - if (!ptr.getClass().isActor()) - rot = Ogre::Quaternion(Ogre::Radian(x), Ogre::Vector3::NEGATIVE_UNIT_X)* - Ogre::Quaternion(Ogre::Radian(y), Ogre::Vector3::NEGATIVE_UNIT_Y)*rot; - - ptr.getRefData().getBaseNode()->setOrientation(worldRotQuat*rot); - physics.rotateObject(ptr); + rendering.rotateObject(ptr, worldRotQuat); + physics.updateRotation(ptr); } } - struct InsertFunctor + void updateObjectScale(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, + MWRender::RenderingManager& rendering) + { + if (ptr.getRefData().getBaseNode() != NULL) + { + float scale = ptr.getCellRef().getScale(); + osg::Vec3f scaleVec (scale, scale, scale); + ptr.getClass().adjustScale(ptr, scaleVec, true); + rendering.scaleObject(ptr, scaleVec); + + physics.updateScale(ptr); + } + } + + struct InsertVisitor { MWWorld::CellStore& mCell; bool mRescale; Loading::Listener& mLoadingListener; - MWWorld::PhysicsSystem& mPhysics; + MWPhysics::PhysicsSystem& mPhysics; MWRender::RenderingManager& mRendering; - InsertFunctor (MWWorld::CellStore& cell, bool rescale, Loading::Listener& loadingListener, - MWWorld::PhysicsSystem& physics, MWRender::RenderingManager& rendering); + InsertVisitor (MWWorld::CellStore& cell, bool rescale, Loading::Listener& loadingListener, + MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering); bool operator() (const MWWorld::Ptr& ptr); }; - InsertFunctor::InsertFunctor (MWWorld::CellStore& cell, bool rescale, - Loading::Listener& loadingListener, MWWorld::PhysicsSystem& physics, + InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, bool rescale, + Loading::Listener& loadingListener, MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering) : mCell (cell), mRescale (rescale), mLoadingListener (loadingListener), - mPhysics (physics), mRendering (rendering) + mPhysics (physics), + mRendering (rendering) {} - bool InsertFunctor::operator() (const MWWorld::Ptr& ptr) + bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) { if (mRescale) { @@ -93,18 +115,11 @@ namespace try { addObject(ptr, mPhysics, mRendering); - updateObjectLocalRotation(ptr, mPhysics, mRendering); - if (ptr.getRefData().getBaseNode()) - { - float scale = ptr.getCellRef().getScale(); - ptr.getClass().adjustScale(ptr, scale); - mRendering.scaleObject(ptr, Ogre::Vector3(scale)); - } - ptr.getClass().adjustPosition (ptr, false); + updateObjectRotation(ptr, mPhysics, mRendering, false); } catch (const std::exception& e) { - std::string error ("error during rendering: "); + std::string error ("error during rendering '" + ptr.getCellRef().getRefId() + "': "); std::cerr << error + e.what() << std::endl; } } @@ -113,24 +128,31 @@ namespace return true; } + + struct AdjustPositionVisitor + { + bool operator() (const MWWorld::Ptr& ptr) + { + if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) + ptr.getClass().adjustPosition (ptr, false); + return true; + } + }; + } namespace MWWorld { - void Scene::updateObjectLocalRotation (const Ptr& ptr) + void Scene::updateObjectRotation (const Ptr& ptr, bool inverseRotationOrder) { - ::updateObjectLocalRotation(ptr, *mPhysics, mRendering); + ::updateObjectRotation(ptr, *mPhysics, mRendering, inverseRotationOrder); } - void Scene::updateObjectRotation (const Ptr& ptr) + void Scene::updateObjectScale(const Ptr &ptr) { - if(ptr.getRefData().getBaseNode() != 0) - { - mRendering.rotateObject(ptr); - mPhysics->rotateObject(ptr); - } + ::updateObjectScale(ptr, *mPhysics, mRendering); } void Scene::getGridCenter(int &cellX, int &cellY) @@ -161,8 +183,13 @@ namespace MWWorld { // Note: exterior cell maps must be updated, even if they were visited before, because the set of surrounding cells might be different // (and objects in a different cell can "bleed" into another cells map if they cross the border) + std::set cellsToUpdate; for (CellStoreCollection::iterator active = mActiveCells.begin(); active!=mActiveCells.end(); ++active) - mRendering.requestMap(*active); + { + cellsToUpdate.insert(*active); + } + MWBase::Environment::get().getWindowManager()->requestMap(cellsToUpdate); + mNeedMapUpdate = false; if (mCurrentCell->isExterior()) @@ -179,17 +206,13 @@ namespace MWWorld void Scene::unloadCell (CellStoreCollection::iterator iter) { std::cout << "Unloading cell\n"; - ListAndResetHandles functor; + ListAndResetObjectsVisitor visitor; - (*iter)->forEach(functor); + (*iter)->forEach(visitor); + for (std::vector::const_iterator iter2 (visitor.mObjects.begin()); + iter2!=visitor.mObjects.end(); ++iter2) { - // silence annoying g++ warning - for (std::vector::const_iterator iter2 (functor.mHandles.begin()); - iter2!=functor.mHandles.end(); ++iter2) - { - Ogre::SceneNode* node = *iter2; - mPhysics->removeObject (node->getName()); - } + mPhysics->remove(*iter2); } if ((*iter)->getCell()->isExterior()) @@ -203,12 +226,13 @@ namespace MWWorld mPhysics->removeHeightField ((*iter)->getCell()->getGridX(), (*iter)->getCell()->getGridY()); } + MWBase::Environment::get().getMechanicsManager()->drop (*iter); + mRendering.removeCell(*iter); + MWBase::Environment::get().getWindowManager()->removeCell(*iter); MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter); - MWBase::Environment::get().getMechanicsManager()->drop (*iter); - MWBase::Environment::get().getSoundManager()->stopSound (*iter); mActiveCells.erase(*iter); } @@ -219,7 +243,7 @@ namespace MWWorld if(result.second) { - std::cout << "loading cell " << cell->getCell()->getDescription() << std::endl; + std::cout << "Loading cell " << cell->getCell()->getDescription() << std::endl; float verts = ESM::Land::LAND_SIZE; float worldsize = ESM::Land::REAL_SIZE; @@ -236,16 +260,10 @@ namespace MWWorld // Actually only VHGT is needed here, but we'll need the rest for rendering anyway. // Load everything now to reduce IO overhead. const int flags = ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX; - if (!land->isDataLoaded(flags)) - land->loadData(flags); - mPhysics->addHeightField ( - land->mLandData->mHeights, - cell->getCell()->getGridX(), - cell->getCell()->getGridY(), - 0, - worldsize / (verts-1), - verts) - ; + + const ESM::Land::LandData *data = land->getLandData (flags); + mPhysics->addHeightField (data->mHeights, cell->getCell()->getGridX(), cell->getCell()->getGridY(), + worldsize / (verts-1), verts); } } @@ -255,10 +273,10 @@ namespace MWWorld /// \todo rescale depending on the state of a new GMST insertCell (*cell, true, loadingListener); - mRendering.cellAdded (cell); + mRendering.addCell(cell); bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); + float waterLevel = cell->getWaterLevel(); mRendering.setWaterEnabled(waterEnabled); - float waterLevel = cell->isExterior() ? -1.f : cell->getWaterLevel(); if (waterEnabled) { mPhysics->enableWater(waterLevel); @@ -267,7 +285,8 @@ namespace MWWorld else mPhysics->disableWater(); - mRendering.configureAmbient(*cell); + if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) + mRendering.configureAmbient(cell->getCell()); } // register local scripts @@ -284,7 +303,7 @@ namespace MWWorld mCurrentCell = NULL; } - void Scene::playerMoved(const Ogre::Vector3 &pos) + void Scene::playerMoved(const osg::Vec3f &pos) { if (!mCurrentCell || !mCurrentCell->isExterior()) return; @@ -295,13 +314,13 @@ namespace MWWorld float centerX, centerY; MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true); const float maxDistance = 8192/2 + 1024; // 1/2 cell size + threshold - float distance = std::max(std::abs(centerX-pos.x), std::abs(centerY-pos.y)); + float distance = std::max(std::abs(centerX-pos.x()), std::abs(centerY-pos.y())); if (distance > maxDistance) { int newX, newY; - MWBase::Environment::get().getWorld()->positionToIndex(pos.x, pos.y, newX, newY); + MWBase::Environment::get().getWorld()->positionToIndex(pos.x(), pos.y(), newX, newY); changeCellGrid(newX, newY); - mRendering.updateTerrain(); + //mRendering.updateTerrain(); } } @@ -310,12 +329,12 @@ namespace MWWorld Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); - mRendering.enableTerrain(true); + //mRendering.enableTerrain(true); std::string loadingExteriorText = "#{sLoadingMessage3}"; loadingListener->setLabel(loadingExteriorText); - const int halfGridSize = Settings::Manager::getInt("exterior grid size", "Cells")/2; + const int halfGridSize = Settings::Manager::getInt("exterior cell load distance", "Cells"); CellStoreCollection::iterator active = mActiveCells.begin(); while (active!=mActiveCells.end()) @@ -394,6 +413,8 @@ namespace MWWorld // Delay the map update until scripts have been given a chance to run. // If we don't do this, objects that should be disabled will still appear on the map. mNeedMapUpdate = true; + + mRendering.getResourceSystem()->clearCache(); } void Scene::changePlayerCell(CellStore *cell, const ESM::Position &pos, bool adjustPlayerPos) @@ -410,9 +431,9 @@ namespace MWWorld if (adjustPlayerPos) { world->moveObject(player, pos.pos[0], pos.pos[1], pos.pos[2]); - float x = Ogre::Radian(pos.rot[0]).valueDegrees(); - float y = Ogre::Radian(pos.rot[1]).valueDegrees(); - float z = Ogre::Radian(pos.rot[2]).valueDegrees(); + float x = pos.rot[0]; + float y = pos.rot[1]; + float z = pos.rot[2]; world->rotateObject(player, x, y, z); player.getClass().adjustPosition(player, true); @@ -427,8 +448,7 @@ namespace MWWorld MWBase::Environment::get().getWorld()->adjustSky(); } - //We need the ogre renderer and a scene node. - Scene::Scene (MWRender::RenderingManager& rendering, PhysicsSystem *physics) + Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics) : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering), mNeedMapUpdate(false) { } @@ -461,16 +481,16 @@ namespace MWWorld loadingListener->setLabel(loadingInteriorText); Loading::ScopedLoad load(loadingListener); - mRendering.enableTerrain(false); + //mRendering.enableTerrain(false); if(!loadcell) { MWBase::World *world = MWBase::Environment::get().getWorld(); world->moveObject(world->getPlayerPtr(), position.pos[0], position.pos[1], position.pos[2]); - float x = Ogre::Radian(position.rot[0]).valueDegrees(); - float y = Ogre::Radian(position.rot[1]).valueDegrees(); - float z = Ogre::Radian(position.rot[2]).valueDegrees(); + float x = position.rot[0]; + float y = position.rot[1]; + float z = position.rot[2]; world->rotateObject(world->getPlayerPtr(), x, y, z); world->getPlayerPtr().getClass().adjustPosition(world->getPlayerPtr(), true); @@ -498,7 +518,7 @@ namespace MWWorld changePlayerCell(cell, position, true); // adjust fog - mRendering.configureFog(*mCurrentCell); + mRendering.configureFog(mCurrentCell->getCell()); // Sky system MWBase::Environment::get().getWorld()->adjustSky(); @@ -510,6 +530,8 @@ namespace MWWorld // Delay the map update until scripts have been given a chance to run. // If we don't do this, objects that should be disabled will still appear on the map. mNeedMapUpdate = true; + + mRendering.getResourceSystem()->clearCache(); } void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos) @@ -524,7 +546,7 @@ namespace MWWorld CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); changePlayerCell(current, position, adjustPlayerPos); - mRendering.updateTerrain(); + //mRendering.updateTerrain(); } CellStore* Scene::getCurrentCell () @@ -539,8 +561,12 @@ namespace MWWorld void Scene::insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener) { - InsertFunctor functor (cell, rescale, *loadingListener, *mPhysics, mRendering); - cell.forEach (functor); + InsertVisitor insertVisitor (cell, rescale, *loadingListener, *mPhysics, mRendering); + cell.forEach (insertVisitor); + + // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order + AdjustPositionVisitor adjustPosVisitor; + cell.forEach (adjustPosVisitor); } void Scene::addObjectToScene (const Ptr& ptr) @@ -548,12 +574,12 @@ namespace MWWorld try { addObject(ptr, *mPhysics, mRendering); - MWBase::Environment::get().getWorld()->rotateObject(ptr, 0, 0, 0, true); + updateObjectRotation(ptr, false); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); } catch (std::exception& e) { - std::cerr << "error during rendering: " << e.what() << std::endl; + std::cerr << "error during rendering '" << ptr.getCellRef().getRefId() << "': " << e.what() << std::endl; } } @@ -561,8 +587,10 @@ namespace MWWorld { MWBase::Environment::get().getMechanicsManager()->remove (ptr); MWBase::Environment::get().getSoundManager()->stopSound3D (ptr); - mPhysics->removeObject (ptr.getRefData().getHandle()); + mPhysics->remove(ptr); mRendering.removeObject (ptr); + if (ptr.getClass().isActor()) + mRendering.removeWaterRippleEmitter(ptr); } bool Scene::isCellActive(const CellStore &cell) @@ -577,16 +605,6 @@ namespace MWWorld return false; } - Ptr Scene::searchPtrViaHandle (const std::string& handle) - { - for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); - iter!=mActiveCells.end(); ++iter) - if (Ptr ptr = (*iter)->searchViaHandle (handle)) - return ptr; - - return Ptr(); - } - Ptr Scene::searchPtrViaActorId (int actorId) { for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index a9d80bf178..439c8d72c3 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -1,14 +1,16 @@ #ifndef GAME_MWWORLD_SCENE_H #define GAME_MWWORLD_SCENE_H -#include "../mwrender/renderingmanager.hpp" +//#include "../mwrender/renderingmanager.hpp" #include "ptr.hpp" #include "globals.hpp" -namespace Ogre +#include + +namespace osg { - class Vector3; + class Vec3f; } namespace ESM @@ -26,20 +28,19 @@ namespace Loading class Listener; } -namespace Render -{ - class OgreRenderer; -} - namespace MWRender { class SkyManager; - class CellRender; + class RenderingManager; +} + +namespace MWPhysics +{ + class PhysicsSystem; } namespace MWWorld { - class PhysicsSystem; class Player; class CellStore; @@ -51,11 +52,10 @@ namespace MWWorld private: - //OEngine::Render::OgreRenderer& mRenderer; CellStore* mCurrentCell; // the cell the player is in CellStoreCollection mActiveCells; bool mCellChanged; - PhysicsSystem *mPhysics; + MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; bool mNeedMapUpdate; @@ -69,7 +69,7 @@ namespace MWWorld public: - Scene (MWRender::RenderingManager& rendering, PhysicsSystem *physics); + Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics); ~Scene(); @@ -77,7 +77,7 @@ namespace MWWorld void loadCell (CellStore *cell, Loading::Listener* loadingListener); - void playerMoved (const Ogre::Vector3& pos); + void playerMoved (const osg::Vec3f& pos); void changePlayerCell (CellStore* newCell, const ESM::Position& position, bool adjustPlayerPos); @@ -107,14 +107,11 @@ namespace MWWorld void removeObjectFromScene (const Ptr& ptr); ///< Remove an object from the scene, but not from the world model. - void updateObjectLocalRotation (const Ptr& ptr); - - void updateObjectRotation (const Ptr& ptr); + void updateObjectRotation (const Ptr& ptr, bool inverseRotationOrder); + void updateObjectScale(const Ptr& ptr); bool isCellActive(const CellStore &cell); - Ptr searchPtrViaHandle (const std::string& handle); - Ptr searchPtrViaActorId (int actorId); }; } diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index cdcc00b4d3..d1ba0ef0b5 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1,127 +1,1111 @@ #include "store.hpp" -#include "esmstore.hpp" #include +#include -namespace MWWorld { +#include +#include -void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) +#include +#include + +namespace { - //Handling MovedCellRefs, there is no way to do it inside loadcell - while (esm.isNextSub("MVRF")) { - ESM::CellRef ref; - ESM::MovedCellRef cMRef; - cell->getNextMVRF(esm, cMRef); - - ESM::Cell *cellAlt = const_cast(searchOrCreate(cMRef.mTarget[0], cMRef.mTarget[1])); - - // Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following - // implementation when the oher implementation works as well. - bool deleted = false; - cell->getNextRef(esm, ref, deleted); - - // Add data required to make reference appear in the correct cell. - // We should not need to test for duplicates, as this part of the code is pre-cell merge. - cell->mMovedRefs.push_back(cMRef); - // But there may be duplicates here! - if (!deleted) - { - ESM::CellRefTracker::iterator iter = std::find(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ref.mRefNum); - if (iter == cellAlt->mLeasedRefs.end()) - cellAlt->mLeasedRefs.push_back(ref); - else - *iter = ref; - } - } -} - -void Store::load(ESM::ESMReader &esm, const std::string &id) -{ - // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, - // and we merge all this data into one Cell object. However, we can't simply search for the cell id, - // as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they - // are not available until both cells have been loaded at least partially! - - // All cells have a name record, even nameless exterior cells. - std::string idLower = Misc::StringUtils::lowerCase(id); - ESM::Cell cell; - cell.mName = id; - - // Load the (x,y) coordinates of the cell, if it is an exterior cell, - // so we can find the cell we need to merge with - cell.loadData(esm); - - if(cell.mData.mFlags & ESM::Cell::Interior) + template + class GetRecords { - // Store interior cell by name, try to merge with existing parent data. - ESM::Cell *oldcell = const_cast(search(idLower)); - if (oldcell) { - // merge new cell into old cell - // push the new references on the list of references to manage (saveContext = true) - oldcell->mData = cell.mData; - oldcell->mName = cell.mName; // merge name just to be sure (ID will be the same, but case could have been changed) - oldcell->loadCell(esm, true); - } else + const std::string mFind; + std::vector *mRecords; + + public: + GetRecords(const std::string &str, std::vector *records) + : mFind(Misc::StringUtils::lowerCase(str)), mRecords(records) + { } + + void operator()(const T *item) { - // spawn a new cell - cell.loadCell(esm, true); - - mInt[idLower] = cell; + if(Misc::StringUtils::ciCompareLen(mFind, item->mId, mFind.size()) == 0) + mRecords->push_back(item); } - } - else + }; + + struct Compare { - // Store exterior cells by grid position, try to merge with existing parent data. - ESM::Cell *oldcell = const_cast(search(cell.getGridX(), cell.getGridY())); - if (oldcell) { - // merge new cell into old cell - oldcell->mData = cell.mData; - oldcell->mName = cell.mName; - oldcell->loadCell(esm, false); - - // handle moved ref (MVRF) subrecords - handleMovedCellRefs (esm, &cell); - - // push the new references on the list of references to manage - oldcell->postLoad(esm); - - // merge lists of leased references, use newer data in case of conflict - for (ESM::MovedCellRefTracker::const_iterator it = cell.mMovedRefs.begin(); it != cell.mMovedRefs.end(); ++it) { - // remove reference from current leased ref tracker and add it to new cell - ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefNum); - if (itold != oldcell->mMovedRefs.end()) { - ESM::MovedCellRef target0 = *itold; - ESM::Cell *wipecell = const_cast(search(target0.mTarget[0], target0.mTarget[1])); - ESM::CellRefTracker::iterator it_lease = std::find(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), it->mRefNum); - wipecell->mLeasedRefs.erase(it_lease); - *itold = *it; - } - else - oldcell->mMovedRefs.push_back(*it); + bool operator()(const ESM::Land *x, const ESM::Land *y) { + if (x->mX == y->mX) { + return x->mY < y->mY; } + return x->mX < y->mX; + } + }; +} - // We don't need to merge mLeasedRefs of cell / oldcell. This list is filled when another cell moves a - // reference to this cell, so the list for the new cell should be empty. The list for oldcell, - // however, could have leased refs in it and so should be kept. - } else +namespace MWWorld +{ + RecordId::RecordId(const std::string &id, bool isDeleted) + : mId(id), mIsDeleted(isDeleted) + {} + + template + IndexedStore::IndexedStore() + { + } + template + typename IndexedStore::iterator IndexedStore::begin() const + { + return mStatic.begin(); + } + template + typename IndexedStore::iterator IndexedStore::end() const + { + return mStatic.end(); + } + template + void IndexedStore::load(ESM::ESMReader &esm) + { + T record; + bool isDeleted = false; + + record.load(esm, isDeleted); + + // Try to overwrite existing record + std::pair ret = mStatic.insert(std::make_pair(record.mIndex, record)); + if (!ret.second) + ret.first->second = record; + } + template + int IndexedStore::getSize() const + { + return mStatic.size(); + } + template + void IndexedStore::setUp() + { + } + template + const T *IndexedStore::search(int index) const + { + typename Static::const_iterator it = mStatic.find(index); + if (it != mStatic.end()) + return &(it->second); + return NULL; + } + template + const T *IndexedStore::find(int index) const + { + const T *ptr = search(index); + if (ptr == 0) { + std::ostringstream msg; + msg << T::getRecordType() << " with index " << index << " not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + + // Need to instantiate these before they're used + template class IndexedStore; + template class IndexedStore; + + template + Store::Store() + { + } + + template + Store::Store(const Store& orig) + : mStatic(orig.mStatic) + { + } + + template + void Store::clearDynamic() + { + // remove the dynamic part of mShared + assert(mShared.size() >= mStatic.size()); + mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); + mDynamic.clear(); + } + + template + const T *Store::search(const std::string &id) const + { + T item; + item.mId = Misc::StringUtils::lowerCase(id); + + typename Dynamic::const_iterator dit = mDynamic.find(item.mId); + if (dit != mDynamic.end()) { + return &dit->second; + } + + typename std::map::const_iterator it = mStatic.find(item.mId); + + if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { + return &(it->second); + } + + return 0; + } + template + bool Store::isDynamic(const std::string &id) const + { + typename Dynamic::const_iterator dit = mDynamic.find(id); + return (dit != mDynamic.end()); + } + template + const T *Store::searchRandom(const std::string &id) const + { + std::vector results; + std::for_each(mShared.begin(), mShared.end(), GetRecords(id, &results)); + if(!results.empty()) + return results[Misc::Rng::rollDice(results.size())]; + return NULL; + } + template + const T *Store::find(const std::string &id) const + { + const T *ptr = search(id); + if (ptr == 0) { + std::ostringstream msg; + msg << T::getRecordType() << " '" << id << "' not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + template + const T *Store::findRandom(const std::string &id) const + { + const T *ptr = searchRandom(id); + if(ptr == 0) { - // spawn a new cell - cell.loadCell(esm, false); + std::ostringstream msg; + msg << T::getRecordType() << " starting with '"< + RecordId Store::load(ESM::ESMReader &esm) + { + T record; + bool isDeleted = false; - // handle moved ref (MVRF) subrecords - handleMovedCellRefs (esm, &cell); + record.load(esm, isDeleted); + Misc::StringUtils::lowerCaseInPlace(record.mId); - // push the new references on the list of references to manage - cell.postLoad(esm); + std::pair inserted = mStatic.insert(std::make_pair(record.mId, record)); + if (inserted.second) + mShared.push_back(&inserted.first->second); + else + inserted.first->second = record; - mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell; + return RecordId(record.mId, isDeleted); + } + template + void Store::setUp() + { + } + + template + typename Store::iterator Store::begin() const + { + return mShared.begin(); + } + template + typename Store::iterator Store::end() const + { + return mShared.end(); + } + + template + size_t Store::getSize() const + { + return mShared.size(); + } + + template + int Store::getDynamicSize() const + { + return mDynamic.size(); + } + template + void Store::listIdentifier(std::vector &list) const + { + list.reserve(list.size() + getSize()); + typename std::vector::const_iterator it = mShared.begin(); + for (; it != mShared.end(); ++it) { + list.push_back((*it)->mId); } } -} + template + T *Store::insert(const T &item) + { + std::string id = Misc::StringUtils::lowerCase(item.mId); + std::pair result = + mDynamic.insert(std::pair(id, item)); + T *ptr = &result.first->second; + if (result.second) { + mShared.push_back(ptr); + } else { + *ptr = item; + } + return ptr; + } + template + T *Store::insertStatic(const T &item) + { + std::string id = Misc::StringUtils::lowerCase(item.mId); + std::pair result = + mStatic.insert(std::pair(id, item)); + T *ptr = &result.first->second; + if (result.second) { + mShared.push_back(ptr); + } else { + *ptr = item; + } + return ptr; + } + template + bool Store::eraseStatic(const std::string &id) + { + T item; + item.mId = Misc::StringUtils::lowerCase(id); -void Store::load(ESM::ESMReader &esm, const std::string &id) -{ - load(esm, id, esm.getIndex()); -} + typename std::map::iterator it = mStatic.find(item.mId); + + if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { + // delete from the static part of mShared + typename std::vector::iterator sharedIter = mShared.begin(); + typename std::vector::iterator end = sharedIter + mStatic.size(); + + while (sharedIter != mShared.end() && sharedIter != end) { + if((*sharedIter)->mId == item.mId) { + mShared.erase(sharedIter); + break; + } + ++sharedIter; + } + mStatic.erase(it); + } + + return true; + } + + template + bool Store::erase(const std::string &id) + { + std::string key = Misc::StringUtils::lowerCase(id); + typename Dynamic::iterator it = mDynamic.find(key); + if (it == mDynamic.end()) { + return false; + } + mDynamic.erase(it); + + // have to reinit the whole shared part + assert(mShared.size() >= mStatic.size()); + mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); + for (it = mDynamic.begin(); it != mDynamic.end(); ++it) { + mShared.push_back(&it->second); + } + return true; + } + template + bool Store::erase(const T &item) + { + return erase(item.mId); + } + template + void Store::write (ESM::ESMWriter& writer, Loading::Listener& progress) const + { + for (typename Dynamic::const_iterator iter (mDynamic.begin()); iter!=mDynamic.end(); + ++iter) + { + writer.startRecord (T::sRecordId); + iter->second.save (writer); + writer.endRecord (T::sRecordId); + } + } + template + RecordId Store::read(ESM::ESMReader& reader) + { + T record; + bool isDeleted = false; + + record.load (reader, isDeleted); + insert (record); + + return RecordId(record.mId, isDeleted); + } + + // LandTexture + //========================================================================= + Store::Store() + { + mStatic.push_back(LandTextureList()); + LandTextureList <exl = mStatic[0]; + // More than enough to hold Morrowind.esm. Extra lists for plugins will we + // added on-the-fly in a different method. + ltexl.reserve(128); + } + const ESM::LandTexture *Store::search(size_t index, size_t plugin) const + { + assert(plugin < mStatic.size()); + const LandTextureList <exl = mStatic[plugin]; + + if (index >= ltexl.size()) + return NULL; + return <exl[index]; + } + const ESM::LandTexture *Store::find(size_t index, size_t plugin) const + { + const ESM::LandTexture *ptr = search(index, plugin); + if (ptr == 0) { + std::ostringstream msg; + msg << "Land texture with index " << index << " not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + size_t Store::getSize() const + { + return mStatic.size(); + } + size_t Store::getSize(size_t plugin) const + { + assert(plugin < mStatic.size()); + return mStatic[plugin].size(); + } + RecordId Store::load(ESM::ESMReader &esm, size_t plugin) + { + ESM::LandTexture lt; + bool isDeleted = false; + + lt.load(esm, isDeleted); + + assert(plugin < mStatic.size()); + + LandTextureList <exl = mStatic[plugin]; + if(lt.mIndex + 1 > (int)ltexl.size()) + ltexl.resize(lt.mIndex+1); + + // Store it + ltexl[lt.mIndex] = lt; + + return RecordId(lt.mId, isDeleted); + } + RecordId Store::load(ESM::ESMReader &esm) + { + return load(esm, esm.getIndex()); + } + Store::iterator Store::begin(size_t plugin) const + { + assert(plugin < mStatic.size()); + return mStatic[plugin].begin(); + } + Store::iterator Store::end(size_t plugin) const + { + assert(plugin < mStatic.size()); + return mStatic[plugin].end(); + } + void Store::resize(size_t num) + { + if (mStatic.size() < num) + mStatic.resize(num); + } + + // Land + //========================================================================= + Store::~Store() + { + for (std::vector::const_iterator it = + mStatic.begin(); it != mStatic.end(); ++it) + { + delete *it; + } + + } + size_t Store::getSize() const + { + return mStatic.size(); + } + Store::iterator Store::begin() const + { + return iterator(mStatic.begin()); + } + Store::iterator Store::end() const + { + return iterator(mStatic.end()); + } + ESM::Land *Store::search(int x, int y) const + { + ESM::Land land; + land.mX = x, land.mY = y; + + std::vector::const_iterator it = + std::lower_bound(mStatic.begin(), mStatic.end(), &land, Compare()); + + if (it != mStatic.end() && (*it)->mX == x && (*it)->mY == y) { + return const_cast(*it); + } + return 0; + } + ESM::Land *Store::find(int x, int y) const + { + ESM::Land *ptr = search(x, y); + if (ptr == 0) { + std::ostringstream msg; + msg << "Land at (" << x << ", " << y << ") not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + RecordId Store::load(ESM::ESMReader &esm) + { + ESM::Land *ptr = new ESM::Land(); + bool isDeleted = false; + + ptr->load(esm, isDeleted); + + // Same area defined in multiple plugins? -> last plugin wins + // Can't use search() because we aren't sorted yet - is there any other way to speed this up? + for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) + { + if ((*it)->mX == ptr->mX && (*it)->mY == ptr->mY) + { + delete *it; + mStatic.erase(it); + break; + } + } + + mStatic.push_back(ptr); + + return RecordId("", isDeleted); + } + void Store::setUp() + { + std::sort(mStatic.begin(), mStatic.end(), Compare()); + } + + + // Cell + //========================================================================= + + const ESM::Cell *Store::search(const ESM::Cell &cell) const + { + if (cell.isExterior()) { + return search(cell.getGridX(), cell.getGridY()); + } + return search(cell.mName); + } + void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) + { + //Handling MovedCellRefs, there is no way to do it inside loadcell + while (esm.isNextSub("MVRF")) { + ESM::CellRef ref; + ESM::MovedCellRef cMRef; + cell->getNextMVRF(esm, cMRef); + + ESM::Cell *cellAlt = const_cast(searchOrCreate(cMRef.mTarget[0], cMRef.mTarget[1])); + + // Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following + // implementation when the oher implementation works as well. + bool deleted = false; + cell->getNextRef(esm, ref, deleted); + + // Add data required to make reference appear in the correct cell. + // We should not need to test for duplicates, as this part of the code is pre-cell merge. + cell->mMovedRefs.push_back(cMRef); + // But there may be duplicates here! + if (!deleted) + { + ESM::CellRefTracker::iterator iter = std::find(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ref.mRefNum); + if (iter == cellAlt->mLeasedRefs.end()) + cellAlt->mLeasedRefs.push_back(ref); + else + *iter = ref; + } + } + } + const ESM::Cell *Store::search(const std::string &id) const + { + ESM::Cell cell; + cell.mName = Misc::StringUtils::lowerCase(id); + + std::map::const_iterator it = mInt.find(cell.mName); + + if (it != mInt.end() && Misc::StringUtils::ciEqual(it->second.mName, id)) { + return &(it->second); + } + + DynamicInt::const_iterator dit = mDynamicInt.find(cell.mName); + if (dit != mDynamicInt.end()) { + return &dit->second; + } + + return 0; + } + const ESM::Cell *Store::search(int x, int y) const + { + ESM::Cell cell; + cell.mData.mX = x, cell.mData.mY = y; + + std::pair key(x, y); + DynamicExt::const_iterator it = mExt.find(key); + if (it != mExt.end()) { + return &(it->second); + } + + DynamicExt::const_iterator dit = mDynamicExt.find(key); + if (dit != mDynamicExt.end()) { + return &dit->second; + } + + return 0; + } + const ESM::Cell *Store::searchOrCreate(int x, int y) + { + std::pair key(x, y); + DynamicExt::const_iterator it = mExt.find(key); + if (it != mExt.end()) { + return &(it->second); + } + + DynamicExt::const_iterator dit = mDynamicExt.find(key); + if (dit != mDynamicExt.end()) { + return &dit->second; + } + + ESM::Cell newCell; + newCell.mData.mX = x; + newCell.mData.mY = y; + newCell.mData.mFlags = ESM::Cell::HasWater; + newCell.mAmbi.mAmbient = 0; + newCell.mAmbi.mSunlight = 0; + newCell.mAmbi.mFog = 0; + newCell.mAmbi.mFogDensity = 0; + return &mExt.insert(std::make_pair(key, newCell)).first->second; + } + const ESM::Cell *Store::find(const std::string &id) const + { + const ESM::Cell *ptr = search(id); + if (ptr == 0) { + std::ostringstream msg; + msg << "Interior cell '" << id << "' not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + const ESM::Cell *Store::find(int x, int y) const + { + const ESM::Cell *ptr = search(x, y); + if (ptr == 0) { + std::ostringstream msg; + msg << "Exterior at (" << x << ", " << y << ") not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + void Store::setUp() + { + typedef DynamicExt::iterator ExtIterator; + typedef std::map::iterator IntIterator; + + mSharedInt.clear(); + mSharedInt.reserve(mInt.size()); + for (IntIterator it = mInt.begin(); it != mInt.end(); ++it) { + mSharedInt.push_back(&(it->second)); + } + + mSharedExt.clear(); + mSharedExt.reserve(mExt.size()); + for (ExtIterator it = mExt.begin(); it != mExt.end(); ++it) { + mSharedExt.push_back(&(it->second)); + } + } + RecordId Store::load(ESM::ESMReader &esm) + { + // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, + // and we merge all this data into one Cell object. However, we can't simply search for the cell id, + // as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they + // are not available until both cells have been loaded at least partially! + + // All cells have a name record, even nameless exterior cells. + ESM::Cell cell; + bool isDeleted = false; + + // Load the (x,y) coordinates of the cell, if it is an exterior cell, + // so we can find the cell we need to merge with + cell.loadNameAndData(esm, isDeleted); + std::string idLower = Misc::StringUtils::lowerCase(cell.mName); + + if(cell.mData.mFlags & ESM::Cell::Interior) + { + // Store interior cell by name, try to merge with existing parent data. + ESM::Cell *oldcell = const_cast(search(idLower)); + if (oldcell) { + // merge new cell into old cell + // push the new references on the list of references to manage (saveContext = true) + oldcell->mData = cell.mData; + oldcell->mName = cell.mName; // merge name just to be sure (ID will be the same, but case could have been changed) + oldcell->loadCell(esm, true); + } else + { + // spawn a new cell + cell.loadCell(esm, true); + + mInt[idLower] = cell; + } + } + else + { + // Store exterior cells by grid position, try to merge with existing parent data. + ESM::Cell *oldcell = const_cast(search(cell.getGridX(), cell.getGridY())); + if (oldcell) { + // merge new cell into old cell + oldcell->mData = cell.mData; + oldcell->mName = cell.mName; + oldcell->loadCell(esm, false); + + // handle moved ref (MVRF) subrecords + handleMovedCellRefs (esm, &cell); + + // push the new references on the list of references to manage + oldcell->postLoad(esm); + + // merge lists of leased references, use newer data in case of conflict + for (ESM::MovedCellRefTracker::const_iterator it = cell.mMovedRefs.begin(); it != cell.mMovedRefs.end(); ++it) { + // remove reference from current leased ref tracker and add it to new cell + ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefNum); + if (itold != oldcell->mMovedRefs.end()) { + ESM::MovedCellRef target0 = *itold; + ESM::Cell *wipecell = const_cast(search(target0.mTarget[0], target0.mTarget[1])); + ESM::CellRefTracker::iterator it_lease = std::find(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), it->mRefNum); + wipecell->mLeasedRefs.erase(it_lease); + *itold = *it; + } + else + oldcell->mMovedRefs.push_back(*it); + } + + // We don't need to merge mLeasedRefs of cell / oldcell. This list is filled when another cell moves a + // reference to this cell, so the list for the new cell should be empty. The list for oldcell, + // however, could have leased refs in it and so should be kept. + } else + { + // spawn a new cell + cell.loadCell(esm, false); + + // handle moved ref (MVRF) subrecords + handleMovedCellRefs (esm, &cell); + + // push the new references on the list of references to manage + cell.postLoad(esm); + + mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell; + } + } + + return RecordId(cell.mName, isDeleted); + } + Store::iterator Store::intBegin() const + { + return iterator(mSharedInt.begin()); + } + Store::iterator Store::intEnd() const + { + return iterator(mSharedInt.end()); + } + Store::iterator Store::extBegin() const + { + return iterator(mSharedExt.begin()); + } + Store::iterator Store::extEnd() const + { + return iterator(mSharedExt.end()); + } + const ESM::Cell *Store::searchExtByName(const std::string &id) const + { + ESM::Cell *cell = 0; + std::vector::const_iterator it = mSharedExt.begin(); + for (; it != mSharedExt.end(); ++it) { + if (Misc::StringUtils::ciEqual((*it)->mName, id)) { + if ( cell == 0 || + ( (*it)->mData.mX > cell->mData.mX ) || + ( (*it)->mData.mX == cell->mData.mX && (*it)->mData.mY > cell->mData.mY ) ) + { + cell = *it; + } + } + } + return cell; + } + const ESM::Cell *Store::searchExtByRegion(const std::string &id) const + { + ESM::Cell *cell = 0; + std::vector::const_iterator it = mSharedExt.begin(); + for (; it != mSharedExt.end(); ++it) { + if (Misc::StringUtils::ciEqual((*it)->mRegion, id)) { + if ( cell == 0 || + ( (*it)->mData.mX > cell->mData.mX ) || + ( (*it)->mData.mX == cell->mData.mX && (*it)->mData.mY > cell->mData.mY ) ) + { + cell = *it; + } + } + } + return cell; + } + size_t Store::getSize() const + { + return mSharedInt.size() + mSharedExt.size(); + } + void Store::listIdentifier(std::vector &list) const + { + list.reserve(list.size() + mSharedInt.size()); + + std::vector::const_iterator it = mSharedInt.begin(); + for (; it != mSharedInt.end(); ++it) { + list.push_back((*it)->mName); + } + } + ESM::Cell *Store::insert(const ESM::Cell &cell) + { + if (search(cell) != 0) { + std::ostringstream msg; + msg << "Failed to create "; + msg << ((cell.isExterior()) ? "exterior" : "interior"); + msg << " cell"; + + throw std::runtime_error(msg.str()); + } + ESM::Cell *ptr; + if (cell.isExterior()) { + std::pair key(cell.getGridX(), cell.getGridY()); + + // duplicate insertions are avoided by search(ESM::Cell &) + std::pair result = + mDynamicExt.insert(std::make_pair(key, cell)); + + ptr = &result.first->second; + mSharedExt.push_back(ptr); + } else { + std::string key = Misc::StringUtils::lowerCase(cell.mName); + + // duplicate insertions are avoided by search(ESM::Cell &) + std::pair result = + mDynamicInt.insert(std::make_pair(key, cell)); + + ptr = &result.first->second; + mSharedInt.push_back(ptr); + } + return ptr; + } + bool Store::erase(const ESM::Cell &cell) + { + if (cell.isExterior()) { + return erase(cell.getGridX(), cell.getGridY()); + } + return erase(cell.mName); + } + bool Store::erase(const std::string &id) + { + std::string key = Misc::StringUtils::lowerCase(id); + DynamicInt::iterator it = mDynamicInt.find(key); + + if (it == mDynamicInt.end()) { + return false; + } + mDynamicInt.erase(it); + mSharedInt.erase( + mSharedInt.begin() + mSharedInt.size(), + mSharedInt.end() + ); + + for (it = mDynamicInt.begin(); it != mDynamicInt.end(); ++it) { + mSharedInt.push_back(&it->second); + } + + return true; + } + bool Store::erase(int x, int y) + { + std::pair key(x, y); + DynamicExt::iterator it = mDynamicExt.find(key); + + if (it == mDynamicExt.end()) { + return false; + } + mDynamicExt.erase(it); + mSharedExt.erase( + mSharedExt.begin() + mSharedExt.size(), + mSharedExt.end() + ); + + for (it = mDynamicExt.begin(); it != mDynamicExt.end(); ++it) { + mSharedExt.push_back(&it->second); + } + + return true; + } + + + // Pathgrid + //========================================================================= + + Store::Store() + : mCells(NULL) + { + } + + void Store::setCells(Store& cells) + { + mCells = &cells; + } + RecordId Store::load(ESM::ESMReader &esm) + { + ESM::Pathgrid pathgrid; + bool isDeleted = false; + + pathgrid.load(esm, isDeleted); + + // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell. + // For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name. + // mX and mY will be (0,0) for interior cells, but there is also an exterior cell with the coordinates of (0,0), so that doesn't help. + // Check whether mCell is an interior cell. This isn't perfect, will break if a Region with the same name as an interior cell is created. + // A proper fix should be made for future versions of the file format. + bool interior = mCells->search(pathgrid.mCell) != NULL; + + // Try to overwrite existing record + if (interior) + { + std::pair ret = mInt.insert(std::make_pair(pathgrid.mCell, pathgrid)); + if (!ret.second) + ret.first->second = pathgrid; + } + else + { + std::pair ret = mExt.insert(std::make_pair(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY), pathgrid)); + if (!ret.second) + ret.first->second = pathgrid; + } + + return RecordId("", isDeleted); + } + size_t Store::getSize() const + { + return mInt.size() + mExt.size(); + } + void Store::setUp() + { + } + const ESM::Pathgrid *Store::search(int x, int y) const + { + Exterior::const_iterator it = mExt.find(std::make_pair(x,y)); + if (it != mExt.end()) + return &(it->second); + return NULL; + } + const ESM::Pathgrid *Store::search(const std::string& name) const + { + Interior::const_iterator it = mInt.find(name); + if (it != mInt.end()) + return &(it->second); + return NULL; + } + const ESM::Pathgrid *Store::find(int x, int y) const + { + const ESM::Pathgrid* pathgrid = search(x,y); + if (!pathgrid) + { + std::ostringstream msg; + msg << "Pathgrid in cell '" << x << " " << y << "' not found"; + throw std::runtime_error(msg.str()); + } + return pathgrid; + } + const ESM::Pathgrid* Store::find(const std::string& name) const + { + const ESM::Pathgrid* pathgrid = search(name); + if (!pathgrid) + { + std::ostringstream msg; + msg << "Pathgrid in cell '" << name << "' not found"; + throw std::runtime_error(msg.str()); + } + return pathgrid; + } + const ESM::Pathgrid *Store::search(const ESM::Cell &cell) const + { + if (!(cell.mData.mFlags & ESM::Cell::Interior)) + return search(cell.mData.mX, cell.mData.mY); + else + return search(cell.mName); + } + const ESM::Pathgrid *Store::find(const ESM::Cell &cell) const + { + if (!(cell.mData.mFlags & ESM::Cell::Interior)) + return find(cell.mData.mX, cell.mData.mY); + else + return find(cell.mName); + } + + + // Skill + //========================================================================= + + Store::Store() + { + } + + + // Magic effect + //========================================================================= + + Store::Store() + { + } + + + // Attribute + //========================================================================= + + Store::Store() + { + mStatic.reserve(ESM::Attribute::Length); + } + const ESM::Attribute *Store::search(size_t index) const + { + if (index >= mStatic.size()) { + return 0; + } + return &mStatic.at(index); + } + + const ESM::Attribute *Store::find(size_t index) const + { + const ESM::Attribute *ptr = search(index); + if (ptr == 0) { + std::ostringstream msg; + msg << "Attribute with index " << index << " not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + void Store::setUp() + { + for (int i = 0; i < ESM::Attribute::Length; ++i) { + mStatic.push_back( + ESM::Attribute( + ESM::Attribute::sAttributeIds[i], + ESM::Attribute::sGmstAttributeIds[i], + ESM::Attribute::sGmstAttributeDescIds[i] + ) + ); + } + } + size_t Store::getSize() const + { + return mStatic.size(); + } + Store::iterator Store::begin() const + { + return mStatic.begin(); + } + Store::iterator Store::end() const + { + return mStatic.end(); + } + + + // Dialogue + //========================================================================= + + + template<> + void Store::setUp() + { + // DialInfos marked as deleted are kept during the loading phase, so that the linked list + // structure is kept intact for inserting further INFOs. Delete them now that loading is done. + for (Static::iterator it = mStatic.begin(); it != mStatic.end(); ++it) + { + ESM::Dialogue& dial = it->second; + dial.clearDeletedInfos(); + } + + mShared.clear(); + mShared.reserve(mStatic.size()); + std::map::iterator it = mStatic.begin(); + for (; it != mStatic.end(); ++it) { + mShared.push_back(&(it->second)); + } + } + + template <> + inline RecordId Store::load(ESM::ESMReader &esm) { + // The original letter case of a dialogue ID is saved, because it's printed + ESM::Dialogue dialogue; + bool isDeleted = false; + + dialogue.loadId(esm); + + std::string idLower = Misc::StringUtils::lowerCase(dialogue.mId); + std::map::iterator found = mStatic.find(idLower); + if (found == mStatic.end()) + { + dialogue.loadData(esm, isDeleted); + mStatic.insert(std::make_pair(idLower, dialogue)); + } + else + { + found->second.loadData(esm, isDeleted); + dialogue = found->second; + } + + return RecordId(dialogue.mId, isDeleted); + } } + +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; + diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index ab09782b14..617c3552af 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -4,39 +4,76 @@ #include #include #include -#include -#include - -#include - -#include - -#include #include "recordcmp.hpp" +namespace ESM +{ + struct Land; +} + +namespace Loading +{ + class Listener; +} + namespace MWWorld { - struct StoreBase + struct RecordId { + std::string mId; + bool mIsDeleted; + + RecordId(const std::string &id = "", bool isDeleted = false); + }; + + class StoreBase + { + public: virtual ~StoreBase() {} virtual void setUp() {} + + /// List identifiers of records contained in this Store (case-smashed). No-op for Stores that don't use string IDs. virtual void listIdentifier(std::vector &list) const {} virtual size_t getSize() const = 0; virtual int getDynamicSize() const { return 0; } - virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; + virtual RecordId load(ESM::ESMReader &esm) = 0; virtual bool eraseStatic(const std::string &id) {return false;} virtual void clearDynamic() {} virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} - virtual void read (ESM::ESMReader& reader, const std::string& id) {} + virtual RecordId read (ESM::ESMReader& reader) { return RecordId(); } ///< Read into dynamic storage }; + template + class IndexedStore + { + protected: + typedef typename std::map Static; + Static mStatic; + + public: + typedef typename std::map::const_iterator iterator; + + IndexedStore(); + + iterator begin() const; + iterator end() const; + + void load(ESM::ESMReader &esm); + + int getSize() const; + void setUp(); + + const T *search(int index) const; + const T *find(int index) const; + }; + template class SharedIterator { @@ -110,275 +147,55 @@ namespace MWWorld typedef std::map Dynamic; typedef std::map Static; - class GetRecords { - const std::string mFind; - std::vector *mRecords; - - public: - GetRecords(const std::string &str, std::vector *records) - : mFind(Misc::StringUtils::lowerCase(str)), mRecords(records) - { } - - void operator()(const T *item) - { - if(Misc::StringUtils::ciCompareLen(mFind, item->mId, mFind.size()) == 0) - mRecords->push_back(item); - } - }; - - friend class ESMStore; public: - Store() - {} - - Store(const Store &orig) - : mStatic(orig.mData) - {} + Store(); + Store(const Store &orig); typedef SharedIterator iterator; // setUp needs to be called again after - virtual void clearDynamic() - { - // remove the dynamic part of mShared - assert(mShared.size() >= mStatic.size()); - mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); - mDynamic.clear(); - } + virtual void clearDynamic(); + void setUp(); - const T *search(const std::string &id) const { - T item; - item.mId = Misc::StringUtils::lowerCase(id); - - typename Dynamic::const_iterator dit = mDynamic.find(item.mId); - if (dit != mDynamic.end()) { - return &dit->second; - } - - typename std::map::const_iterator it = mStatic.find(item.mId); - - if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { - return &(it->second); - } - - return 0; - } + const T *search(const std::string &id) const; /** * Does the record with this ID come from the dynamic store? */ - bool isDynamic(const std::string &id) const { - typename Dynamic::const_iterator dit = mDynamic.find(id); - return (dit != mDynamic.end()); - } + bool isDynamic(const std::string &id) const; /** Returns a random record that starts with the named ID, or NULL if not found. */ - const T *searchRandom(const std::string &id) const - { - std::vector results; - std::for_each(mShared.begin(), mShared.end(), GetRecords(id, &results)); - if(!results.empty()) - return results[OEngine::Misc::Rng::rollDice(results.size())]; - return NULL; - } + const T *searchRandom(const std::string &id) const; - const T *find(const std::string &id) const { - const T *ptr = search(id); - if (ptr == 0) { - std::ostringstream msg; - msg << T::getRecordType() << " '" << id << "' not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } + const T *find(const std::string &id) const; /** Returns a random record that starts with the named ID. An exception is thrown if none * are found. */ - const T *findRandom(const std::string &id) const - { - const T *ptr = searchRandom(id); - if(ptr == 0) - { - std::ostringstream msg; - msg << T::getRecordType() << " starting with '"< inserted = mStatic.insert(std::make_pair(idLower, T())); - if (inserted.second) - mShared.push_back(&inserted.first->second); + size_t getSize() const; + int getDynamicSize() const; - inserted.first->second.mId = idLower; - inserted.first->second.load(esm); - } + /// @note The record identifiers are listed in the order that the records were defined by the content files. + void listIdentifier(std::vector &list) const; - void setUp() { - } + T *insert(const T &item); + T *insertStatic(const T &item); - iterator begin() const { - return mShared.begin(); - } + bool eraseStatic(const std::string &id); + bool erase(const std::string &id); + bool erase(const T &item); - iterator end() const { - return mShared.end(); - } - - size_t getSize() const { - return mShared.size(); - } - - int getDynamicSize() const - { - return static_cast (mDynamic.size()); // truncated from unsigned __int64 if _MSC_VER && _WIN64 - } - - void listIdentifier(std::vector &list) const { - list.reserve(list.size() + getSize()); - typename std::vector::const_iterator it = mShared.begin(); - for (; it != mShared.end(); ++it) { - list.push_back((*it)->mId); - } - } - - T *insert(const T &item) { - std::string id = Misc::StringUtils::lowerCase(item.mId); - std::pair result = - mDynamic.insert(std::pair(id, item)); - T *ptr = &result.first->second; - if (result.second) { - mShared.push_back(ptr); - } else { - *ptr = item; - } - return ptr; - } - - T *insertStatic(const T &item) { - std::string id = Misc::StringUtils::lowerCase(item.mId); - std::pair result = - mStatic.insert(std::pair(id, item)); - T *ptr = &result.first->second; - if (result.second) { - mShared.push_back(ptr); - } else { - *ptr = item; - } - return ptr; - } - - - bool eraseStatic(const std::string &id) { - T item; - item.mId = Misc::StringUtils::lowerCase(id); - - typename std::map::iterator it = mStatic.find(item.mId); - - if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { - // delete from the static part of mShared - typename std::vector::iterator sharedIter = mShared.begin(); - typename std::vector::iterator end = sharedIter + mStatic.size(); - - while (sharedIter != mShared.end() && sharedIter != end) { - if((*sharedIter)->mId == item.mId) { - mShared.erase(sharedIter); - break; - } - ++sharedIter; - } - mStatic.erase(it); - } - - return true; - } - - bool erase(const std::string &id) { - std::string key = Misc::StringUtils::lowerCase(id); - typename Dynamic::iterator it = mDynamic.find(key); - if (it == mDynamic.end()) { - return false; - } - mDynamic.erase(it); - - // have to reinit the whole shared part - assert(mShared.size() >= mStatic.size()); - mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); - for (it = mDynamic.begin(); it != mDynamic.end(); ++it) { - mShared.push_back(&it->second); - } - return true; - } - - bool erase(const T &item) { - return erase(item.mId); - } - - void write (ESM::ESMWriter& writer, Loading::Listener& progress) const - { - for (typename Dynamic::const_iterator iter (mDynamic.begin()); iter!=mDynamic.end(); - ++iter) - { - writer.startRecord (T::sRecordId); - writer.writeHNString ("NAME", iter->second.mId); - iter->second.save (writer); - writer.endRecord (T::sRecordId); - } - } - - void read (ESM::ESMReader& reader, const std::string& id) - { - T record; - record.mId = id; - record.load (reader); - insert (record); - } + RecordId load(ESM::ESMReader &esm); + void write(ESM::ESMWriter& writer, Loading::Listener& progress) const; + RecordId read(ESM::ESMReader& reader); }; - template <> - inline void Store::load(ESM::ESMReader &esm, const std::string &id) { - std::string idLower = Misc::StringUtils::lowerCase(id); - - std::map::iterator it = mStatic.find(idLower); - if (it == mStatic.end()) { - it = mStatic.insert( std::make_pair( idLower, ESM::Dialogue() ) ).first; - it->second.mId = id; // don't smash case here, as this line is printed - } - - it->second.load(esm); - } - - template <> - inline void Store::load(ESM::ESMReader &esm, const std::string &id) { - ESM::Script scpt; - scpt.load(esm); - Misc::StringUtils::toLower(scpt.mId); - - std::pair inserted = mStatic.insert(std::make_pair(scpt.mId, scpt)); - if (inserted.second) - mShared.push_back(&inserted.first->second); - else - inserted.first->second = scpt; - } - - template <> - inline void Store::load(ESM::ESMReader &esm, const std::string &id) - { - ESM::StartScript s; - s.load(esm); - s.mId = Misc::StringUtils::toLower(s.mId); - std::pair inserted = mStatic.insert(std::make_pair(s.mId, s)); - if (inserted.second) - mShared.push_back(&inserted.first->second); - else - inserted.first->second = s; - } - template <> class Store : public StoreBase { @@ -387,73 +204,26 @@ namespace MWWorld std::vector mStatic; public: - Store() { - mStatic.push_back(LandTextureList()); - LandTextureList <exl = mStatic[0]; - // More than enough to hold Morrowind.esm. Extra lists for plugins will we - // added on-the-fly in a different method. - ltexl.reserve(128); - } + Store(); typedef std::vector::const_iterator iterator; // Must be threadsafe! Called from terrain background loading threads. // Not a big deal here, since ESM::LandTexture can never be modified or inserted/erased - const ESM::LandTexture *search(size_t index, size_t plugin) const { - assert(plugin < mStatic.size()); - const LandTextureList <exl = mStatic[plugin]; + const ESM::LandTexture *search(size_t index, size_t plugin) const; + const ESM::LandTexture *find(size_t index, size_t plugin) const; - assert(index < ltexl.size()); - return <exl.at(index); - } + /// Resize the internal store to hold at least \a num plugins. + void resize(size_t num); - const ESM::LandTexture *find(size_t index, size_t plugin) const { - const ESM::LandTexture *ptr = search(index, plugin); - if (ptr == 0) { - std::ostringstream msg; - msg << "Land texture with index " << index << " not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } + size_t getSize() const; + size_t getSize(size_t plugin) const; - size_t getSize() const { - return mStatic.size(); - } + RecordId load(ESM::ESMReader &esm, size_t plugin); + RecordId load(ESM::ESMReader &esm); - size_t getSize(size_t plugin) const { - assert(plugin < mStatic.size()); - return mStatic[plugin].size(); - } - - void load(ESM::ESMReader &esm, const std::string &id, size_t plugin) { - ESM::LandTexture lt; - lt.load(esm); - lt.mId = id; - - // Make sure we have room for the structure - if (plugin >= mStatic.size()) { - mStatic.resize(plugin+1); - } - LandTextureList <exl = mStatic[plugin]; - if(lt.mIndex + 1 > (int)ltexl.size()) - ltexl.resize(lt.mIndex+1); - - // Store it - ltexl[lt.mIndex] = lt; - } - - void load(ESM::ESMReader &esm, const std::string &id); - - iterator begin(size_t plugin) const { - assert(plugin < mStatic.size()); - return mStatic[plugin].begin(); - } - - iterator end(size_t plugin) const { - assert(plugin < mStatic.size()); - return mStatic[plugin].end(); - } + iterator begin(size_t plugin) const; + iterator end(size_t plugin) const; }; template <> @@ -461,88 +231,22 @@ namespace MWWorld { std::vector mStatic; - struct Compare - { - bool operator()(const ESM::Land *x, const ESM::Land *y) { - if (x->mX == y->mX) { - return x->mY < y->mY; - } - return x->mX < y->mX; - } - }; - public: typedef SharedIterator iterator; - virtual ~Store() - { - for (std::vector::const_iterator it = - mStatic.begin(); it != mStatic.end(); ++it) - { - delete *it; - } + virtual ~Store(); - } - - size_t getSize() const { - return mStatic.size(); - } - - iterator begin() const { - return iterator(mStatic.begin()); - } - - iterator end() const { - return iterator(mStatic.end()); - } + size_t getSize() const; + iterator begin() const; + iterator end() const; // Must be threadsafe! Called from terrain background loading threads. // Not a big deal here, since ESM::Land can never be modified or inserted/erased - ESM::Land *search(int x, int y) const { - ESM::Land land; - land.mX = x, land.mY = y; + ESM::Land *search(int x, int y) const; + ESM::Land *find(int x, int y) const; - std::vector::const_iterator it = - std::lower_bound(mStatic.begin(), mStatic.end(), &land, Compare()); - - if (it != mStatic.end() && (*it)->mX == x && (*it)->mY == y) { - return const_cast(*it); - } - return 0; - } - - ESM::Land *find(int x, int y) const{ - ESM::Land *ptr = search(x, y); - if (ptr == 0) { - std::ostringstream msg; - msg << "Land at (" << x << ", " << y << ") not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } - - void load(ESM::ESMReader &esm, const std::string &id) { - ESM::Land *ptr = new ESM::Land(); - ptr->load(esm); - - // Same area defined in multiple plugins? -> last plugin wins - // Can't use search() because we aren't sorted yet - is there any other way to speed this up? - for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) - { - if ((*it)->mX == ptr->mX && (*it)->mY == ptr->mY) - { - delete *it; - mStatic.erase(it); - break; - } - } - - mStatic.push_back(ptr); - } - - void setUp() { - std::sort(mStatic.begin(), mStatic.end(), Compare()); - } + RecordId load(ESM::ESMReader &esm); + void setUp(); }; template <> @@ -576,261 +280,44 @@ namespace MWWorld DynamicInt mDynamicInt; DynamicExt mDynamicExt; - const ESM::Cell *search(const ESM::Cell &cell) const { - if (cell.isExterior()) { - return search(cell.getGridX(), cell.getGridY()); - } - return search(cell.mName); - } - + const ESM::Cell *search(const ESM::Cell &cell) const; void handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell); public: typedef SharedIterator iterator; - const ESM::Cell *search(const std::string &id) const { - ESM::Cell cell; - cell.mName = Misc::StringUtils::lowerCase(id); + const ESM::Cell *search(const std::string &id) const; + const ESM::Cell *search(int x, int y) const; + const ESM::Cell *searchOrCreate(int x, int y); - std::map::const_iterator it = mInt.find(cell.mName); + const ESM::Cell *find(const std::string &id) const; + const ESM::Cell *find(int x, int y) const; - if (it != mInt.end() && Misc::StringUtils::ciEqual(it->second.mName, id)) { - return &(it->second); - } + void setUp(); - DynamicInt::const_iterator dit = mDynamicInt.find(cell.mName); - if (dit != mDynamicInt.end()) { - return &dit->second; - } + RecordId load(ESM::ESMReader &esm); - return 0; - } - - const ESM::Cell *search(int x, int y) const { - ESM::Cell cell; - cell.mData.mX = x, cell.mData.mY = y; - - std::pair key(x, y); - DynamicExt::const_iterator it = mExt.find(key); - if (it != mExt.end()) { - return &(it->second); - } - - DynamicExt::const_iterator dit = mDynamicExt.find(key); - if (dit != mDynamicExt.end()) { - return &dit->second; - } - - return 0; - } - - const ESM::Cell *searchOrCreate(int x, int y) { - std::pair key(x, y); - DynamicExt::const_iterator it = mExt.find(key); - if (it != mExt.end()) { - return &(it->second); - } - - DynamicExt::const_iterator dit = mDynamicExt.find(key); - if (dit != mDynamicExt.end()) { - return &dit->second; - } - - ESM::Cell newCell; - newCell.mData.mX = x; - newCell.mData.mY = y; - newCell.mData.mFlags = ESM::Cell::HasWater; - newCell.mAmbi.mAmbient = 0; - newCell.mAmbi.mSunlight = 0; - newCell.mAmbi.mFog = 0; - newCell.mAmbi.mFogDensity = 0; - return &mExt.insert(std::make_pair(key, newCell)).first->second; - } - - const ESM::Cell *find(const std::string &id) const { - const ESM::Cell *ptr = search(id); - if (ptr == 0) { - std::ostringstream msg; - msg << "Interior cell '" << id << "' not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } - - const ESM::Cell *find(int x, int y) const { - const ESM::Cell *ptr = search(x, y); - if (ptr == 0) { - std::ostringstream msg; - msg << "Exterior at (" << x << ", " << y << ") not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } - - void setUp() { - typedef DynamicExt::iterator ExtIterator; - typedef std::map::iterator IntIterator; - - mSharedInt.clear(); - mSharedInt.reserve(mInt.size()); - for (IntIterator it = mInt.begin(); it != mInt.end(); ++it) { - mSharedInt.push_back(&(it->second)); - } - - mSharedExt.clear(); - mSharedExt.reserve(mExt.size()); - for (ExtIterator it = mExt.begin(); it != mExt.end(); ++it) { - mSharedExt.push_back(&(it->second)); - } - } - - // HACK: Method implementation had to be moved to a separate cpp file, as we would otherwise get - // errors related to the compare operator used in std::find for ESM::MovedCellRefTracker::find. - // There some nasty three-way cyclic header dependency involved, which I could only fix by moving - // this method. - void load(ESM::ESMReader &esm, const std::string &id); - - iterator intBegin() const { - return iterator(mSharedInt.begin()); - } - - iterator intEnd() const { - return iterator(mSharedInt.end()); - } - - iterator extBegin() const { - return iterator(mSharedExt.begin()); - } - - iterator extEnd() const { - return iterator(mSharedExt.end()); - } + iterator intBegin() const; + iterator intEnd() const; + iterator extBegin() const; + iterator extEnd() const; // Return the northernmost cell in the easternmost column. - const ESM::Cell *searchExtByName(const std::string &id) const { - ESM::Cell *cell = 0; - std::vector::const_iterator it = mSharedExt.begin(); - for (; it != mSharedExt.end(); ++it) { - if (Misc::StringUtils::ciEqual((*it)->mName, id)) { - if ( cell == 0 || - ( (*it)->mData.mX > cell->mData.mX ) || - ( (*it)->mData.mX == cell->mData.mX && (*it)->mData.mY > cell->mData.mY ) ) - { - cell = *it; - } - } - } - return cell; - } + const ESM::Cell *searchExtByName(const std::string &id) const; // Return the northernmost cell in the easternmost column. - const ESM::Cell *searchExtByRegion(const std::string &id) const { - ESM::Cell *cell = 0; - std::vector::const_iterator it = mSharedExt.begin(); - for (; it != mSharedExt.end(); ++it) { - if (Misc::StringUtils::ciEqual((*it)->mRegion, id)) { - if ( cell == 0 || - ( (*it)->mData.mX > cell->mData.mX ) || - ( (*it)->mData.mX == cell->mData.mX && (*it)->mData.mY > cell->mData.mY ) ) - { - cell = *it; - } - } - } - return cell; - } + const ESM::Cell *searchExtByRegion(const std::string &id) const; - size_t getSize() const { - return mSharedInt.size() + mSharedExt.size(); - } + size_t getSize() const; - void listIdentifier(std::vector &list) const { - list.reserve(list.size() + mSharedInt.size()); + void listIdentifier(std::vector &list) const; - std::vector::const_iterator it = mSharedInt.begin(); - for (; it != mSharedInt.end(); ++it) { - list.push_back((*it)->mName); - } - } + ESM::Cell *insert(const ESM::Cell &cell); - ESM::Cell *insert(const ESM::Cell &cell) { - if (search(cell) != 0) { - std::ostringstream msg; - msg << "Failed to create "; - msg << ((cell.isExterior()) ? "exterior" : "interior"); - msg << " cell"; + bool erase(const ESM::Cell &cell); + bool erase(const std::string &id); - throw std::runtime_error(msg.str()); - } - ESM::Cell *ptr; - if (cell.isExterior()) { - std::pair key(cell.getGridX(), cell.getGridY()); - - // duplicate insertions are avoided by search(ESM::Cell &) - std::pair result = - mDynamicExt.insert(std::make_pair(key, cell)); - - ptr = &result.first->second; - mSharedExt.push_back(ptr); - } else { - std::string key = Misc::StringUtils::lowerCase(cell.mName); - - // duplicate insertions are avoided by search(ESM::Cell &) - std::pair result = - mDynamicInt.insert(std::make_pair(key, cell)); - - ptr = &result.first->second; - mSharedInt.push_back(ptr); - } - return ptr; - } - - bool erase(const ESM::Cell &cell) { - if (cell.isExterior()) { - return erase(cell.getGridX(), cell.getGridY()); - } - return erase(cell.mName); - } - - bool erase(const std::string &id) { - std::string key = Misc::StringUtils::lowerCase(id); - DynamicInt::iterator it = mDynamicInt.find(key); - - if (it == mDynamicInt.end()) { - return false; - } - mDynamicInt.erase(it); - mSharedInt.erase( - mSharedInt.begin() + mSharedInt.size(), - mSharedInt.end() - ); - - for (it = mDynamicInt.begin(); it != mDynamicInt.end(); ++it) { - mSharedInt.push_back(&it->second); - } - - return true; - } - - bool erase(int x, int y) { - std::pair key(x, y); - DynamicExt::iterator it = mDynamicExt.find(key); - - if (it == mDynamicExt.end()) { - return false; - } - mDynamicExt.erase(it); - mSharedExt.erase( - mSharedExt.begin() + mSharedExt.size(), - mSharedExt.end() - ); - - for (it = mDynamicExt.begin(); it != mDynamicExt.end(); ++it) { - mSharedExt.push_back(&it->second); - } - - return true; - } + bool erase(int x, int y); }; template <> @@ -847,165 +334,35 @@ namespace MWWorld public: - Store() - : mCells(NULL) - { - } + Store(); - void setCells(Store& cells) - { - mCells = &cells; - } + void setCells(Store& cells); + RecordId load(ESM::ESMReader &esm); + size_t getSize() const; - void load(ESM::ESMReader &esm, const std::string &id) { - ESM::Pathgrid pathgrid; - pathgrid.load(esm); + void setUp(); - // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell. - // For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name. - // mX and mY will be (0,0) for interior cells, but there is also an exterior cell with the coordinates of (0,0), so that doesn't help. - // Check whether mCell is an interior cell. This isn't perfect, will break if a Region with the same name as an interior cell is created. - // A proper fix should be made for future versions of the file format. - bool interior = mCells->search(pathgrid.mCell) != NULL; - - // Try to overwrite existing record - if (interior) - { - std::pair ret = mInt.insert(std::make_pair(pathgrid.mCell, pathgrid)); - if (!ret.second) - ret.first->second = pathgrid; - } - else - { - std::pair ret = mExt.insert(std::make_pair(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY), pathgrid)); - if (!ret.second) - ret.first->second = pathgrid; - } - } - - size_t getSize() const { - return mInt.size() + mExt.size(); - } - - void setUp() { - } - - const ESM::Pathgrid *search(int x, int y) const { - Exterior::const_iterator it = mExt.find(std::make_pair(x,y)); - if (it != mExt.end()) - return &(it->second); - return NULL; - } - - const ESM::Pathgrid *search(const std::string& name) const { - Interior::const_iterator it = mInt.find(name); - if (it != mInt.end()) - return &(it->second); - return NULL; - } - - const ESM::Pathgrid *find(int x, int y) const { - const ESM::Pathgrid* pathgrid = search(x,y); - if (!pathgrid) - { - std::ostringstream msg; - msg << "Pathgrid in cell '" << x << " " << y << "' not found"; - throw std::runtime_error(msg.str()); - } - return pathgrid; - } - - const ESM::Pathgrid* find(const std::string& name) const { - const ESM::Pathgrid* pathgrid = search(name); - if (!pathgrid) - { - std::ostringstream msg; - msg << "Pathgrid in cell '" << name << "' not found"; - throw std::runtime_error(msg.str()); - } - return pathgrid; - } - - const ESM::Pathgrid *search(const ESM::Cell &cell) const { - if (!(cell.mData.mFlags & ESM::Cell::Interior)) - return search(cell.mData.mX, cell.mData.mY); - else - return search(cell.mName); - } - - const ESM::Pathgrid *find(const ESM::Cell &cell) const { - if (!(cell.mData.mFlags & ESM::Cell::Interior)) - return find(cell.mData.mX, cell.mData.mY); - else - return find(cell.mName); - } + const ESM::Pathgrid *search(int x, int y) const; + const ESM::Pathgrid *search(const std::string& name) const; + const ESM::Pathgrid *find(int x, int y) const; + const ESM::Pathgrid* find(const std::string& name) const; + const ESM::Pathgrid *search(const ESM::Cell &cell) const; + const ESM::Pathgrid *find(const ESM::Cell &cell) const; }; - template - class IndexedStore - { - protected: - typedef typename std::map Static; - Static mStatic; + template <> + class Store : public IndexedStore + { public: - typedef typename std::map::const_iterator iterator; - - IndexedStore() {} - - iterator begin() const { - return mStatic.begin(); - } - - iterator end() const { - return mStatic.end(); - } - - void load(ESM::ESMReader &esm) { - T record; - record.load(esm); - - // Try to overwrite existing record - std::pair ret = mStatic.insert(std::make_pair(record.mIndex, record)); - if (!ret.second) - ret.first->second = record; - } - - int getSize() const { - return mStatic.size(); - } - - void setUp() { - } - - const T *search(int index) const { - typename Static::const_iterator it = mStatic.find(index); - if (it != mStatic.end()) - return &(it->second); - return NULL; - } - - const T *find(int index) const { - const T *ptr = search(index); - if (ptr == 0) { - std::ostringstream msg; - msg << T::getRecordType() << " with index " << index << " not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } + Store(); }; template <> - struct Store : public IndexedStore + class Store : public IndexedStore { - Store() {} - }; - - template <> - struct Store : public IndexedStore - { - Store() {} + public: + Store(); }; template <> @@ -1016,70 +373,18 @@ namespace MWWorld public: typedef std::vector::const_iterator iterator; - Store() { - mStatic.reserve(ESM::Attribute::Length); - } + Store(); - const ESM::Attribute *search(size_t index) const { - if (index >= mStatic.size()) { - return 0; - } - return &mStatic.at(index); - } + const ESM::Attribute *search(size_t index) const; + const ESM::Attribute *find(size_t index) const; - const ESM::Attribute *find(size_t index) const { - const ESM::Attribute *ptr = search(index); - if (ptr == 0) { - std::ostringstream msg; - msg << "Attribute with index " << index << " not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } + void setUp(); - void setUp() { - for (int i = 0; i < ESM::Attribute::Length; ++i) { - mStatic.push_back( - ESM::Attribute( - ESM::Attribute::sAttributeIds[i], - ESM::Attribute::sGmstAttributeIds[i], - ESM::Attribute::sGmstAttributeDescIds[i] - ) - ); - } - } - - size_t getSize() const { - return mStatic.size(); - } - - iterator begin() const { - return mStatic.begin(); - } - - iterator end() const { - return mStatic.end(); - } + size_t getSize() const; + iterator begin() const; + iterator end() const; }; - template<> - inline void Store::setUp() - { - // DialInfos marked as deleted are kept during the loading phase, so that the linked list - // structure is kept intact for inserting further INFOs. Delete them now that loading is done. - for (Static::iterator it = mStatic.begin(); it != mStatic.end(); ++it) - { - ESM::Dialogue& dial = it->second; - dial.clearDeletedInfos(); - } - - mShared.clear(); - mShared.reserve(mStatic.size()); - std::map::iterator it = mStatic.begin(); - for (; it != mStatic.end(); ++it) { - mShared.push_back(&(it->second)); - } - } } //end namespace diff --git a/apps/openmw/mwworld/timestamp.cpp b/apps/openmw/mwworld/timestamp.cpp index bd07b91f37..ffb1cacb86 100644 --- a/apps/openmw/mwworld/timestamp.cpp +++ b/apps/openmw/mwworld/timestamp.cpp @@ -96,7 +96,7 @@ namespace MWWorld if (left.getHour() - #include "weather.hpp" -#include +#include +#include +#include +#include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwmechanics/actorutil.hpp" + #include "../mwsound/sound.hpp" #include "../mwrender/renderingmanager.hpp" +#include "../mwrender/sky.hpp" #include "player.hpp" #include "esmstore.hpp" #include "fallback.hpp" #include "cellstore.hpp" -using namespace Ogre; +#include + using namespace MWWorld; -using namespace MWSound; namespace { + static const int invalidWeatherID = -1; + + // linear interpolate between x and y based on factor. float lerp (float x, float y, float factor) { return x * (1-factor) + y * factor; } - - Ogre::ColourValue lerp (const Ogre::ColourValue& x, const Ogre::ColourValue& y, float factor) + // linear interpolate between x and y based on factor. + osg::Vec4f lerp (const osg::Vec4f& x, const osg::Vec4f& y, float factor) { return x * (1-factor) + y * factor; } } -void WeatherManager::setFallbackWeather(Weather& weather,const std::string& name) +template +T TimeOfDayInterpolator::getValue(const float gameHour, const TimeOfDaySettings& timeSettings) const { - std::string upper=name; - upper[0]=toupper(name[0]); - weather.mCloudsMaximumPercent = mFallback->getFallbackFloat("Weather_"+upper+"_Clouds_Maximum_Percent"); - weather.mTransitionDelta = mFallback->getFallbackFloat("Weather_"+upper+"_Transition_Delta"); - weather.mSkySunriseColor=mFallback->getFallbackColour("Weather_"+upper+"_Sky_Sunrise_Color"); - weather.mSkyDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Day_Color"); - weather.mSkySunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Sunset_Color"); - weather.mSkyNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Night_Color"); - weather.mFogSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Sunrise_Color"); - weather.mFogDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Day_Color"); - weather.mFogSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Sunset_Color"); - weather.mFogNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Night_Color"); - weather.mAmbientSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Sunrise_Color"); - weather.mAmbientDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Day_Color"); - weather.mAmbientSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Sunset_Color"); - weather.mAmbientNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Night_Color"); - weather.mSunSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Sunrise_Color"); - weather.mSunDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Day_Color"); - weather.mSunSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Sunset_Color"); - weather.mSunNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Night_Color"); - weather.mSunDiscSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Disc_Sunset_Color"); - weather.mLandFogDayDepth = mFallback->getFallbackFloat("Weather_"+upper+"_Land_Fog_Day_Depth"); - weather.mLandFogNightDepth = mFallback->getFallbackFloat("Weather_"+upper+"_Land_Fog_Night_Depth"); - weather.mWindSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Wind_Speed"); - weather.mCloudSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Cloud_Speed"); - weather.mGlareView = mFallback->getFallbackFloat("Weather_"+upper+"_Glare_View"); - weather.mCloudTexture = mFallback->getFallbackString("Weather_"+upper+"_Cloud_Texture"); + // TODO: use pre/post sunset/sunrise time values in [Weather] section + + // night + if (gameHour <= timeSettings.mNightEnd || gameHour >= timeSettings.mNightStart + 1) + return mNightValue; + // sunrise + else if (gameHour >= timeSettings.mNightEnd && gameHour <= timeSettings.mDayStart + 1) + { + if (gameHour <= timeSettings.mSunriseTime) + { + // fade in + float advance = timeSettings.mSunriseTime - gameHour; + float factor = advance / 0.5f; + return lerp(mSunriseValue, mNightValue, factor); + } + else + { + // fade out + float advance = gameHour - timeSettings.mSunriseTime; + float factor = advance / 3.f; + return lerp(mSunriseValue, mDayValue, factor); + } + } + // day + else if (gameHour >= timeSettings.mDayStart + 1 && gameHour <= timeSettings.mDayEnd - 1) + return mDayValue; + // sunset + else if (gameHour >= timeSettings.mDayEnd - 1 && gameHour <= timeSettings.mNightStart + 1) + { + if (gameHour <= timeSettings.mDayEnd + 1) + { + // fade in + float advance = (timeSettings.mDayEnd + 1) - gameHour; + float factor = (advance / 2); + return lerp(mSunsetValue, mDayValue, factor); + } + else + { + // fade out + float advance = gameHour - (timeSettings.mDayEnd + 1); + float factor = advance / 2.f; + return lerp(mSunsetValue, mNightValue, factor); + } + } + // shut up compiler + return T(); +} + + + +template class TimeOfDayInterpolator; +template class TimeOfDayInterpolator; + +Weather::Weather(const std::string& name, + const MWWorld::Fallback& fallback, + float stormWindSpeed, + float rainSpeed, + const std::string& particleEffect) + : mCloudTexture(fallback.getFallbackString("Weather_" + name + "_Cloud_Texture")) + , mSkyColor(fallback.getFallbackColour("Weather_" + name +"_Sky_Sunrise_Color"), + fallback.getFallbackColour("Weather_" + name + "_Sky_Day_Color"), + fallback.getFallbackColour("Weather_" + name + "_Sky_Sunset_Color"), + fallback.getFallbackColour("Weather_" + name + "_Sky_Night_Color")) + , mFogColor(fallback.getFallbackColour("Weather_" + name + "_Fog_Sunrise_Color"), + fallback.getFallbackColour("Weather_" + name + "_Fog_Day_Color"), + fallback.getFallbackColour("Weather_" + name + "_Fog_Sunset_Color"), + fallback.getFallbackColour("Weather_" + name + "_Fog_Night_Color")) + , mAmbientColor(fallback.getFallbackColour("Weather_" + name + "_Ambient_Sunrise_Color"), + fallback.getFallbackColour("Weather_" + name + "_Ambient_Day_Color"), + fallback.getFallbackColour("Weather_" + name + "_Ambient_Sunset_Color"), + fallback.getFallbackColour("Weather_" + name + "_Ambient_Night_Color")) + , mSunColor(fallback.getFallbackColour("Weather_" + name + "_Sun_Sunrise_Color"), + fallback.getFallbackColour("Weather_" + name + "_Sun_Day_Color"), + fallback.getFallbackColour("Weather_" + name + "_Sun_Sunset_Color"), + fallback.getFallbackColour("Weather_" + name + "_Sun_Night_Color")) + , mLandFogDepth(fallback.getFallbackFloat("Weather_" + name + "_Land_Fog_Day_Depth"), + fallback.getFallbackFloat("Weather_" + name + "_Land_Fog_Day_Depth"), + fallback.getFallbackFloat("Weather_" + name + "_Land_Fog_Day_Depth"), + fallback.getFallbackFloat("Weather_" + name + "_Land_Fog_Night_Depth")) + , mSunDiscSunsetColor(fallback.getFallbackColour("Weather_" + name + "_Sun_Disc_Sunset_Color")) + , mWindSpeed(fallback.getFallbackFloat("Weather_" + name + "_Wind_Speed")) + , mCloudSpeed(fallback.getFallbackFloat("Weather_" + name + "_Cloud_Speed")) + , mGlareView(fallback.getFallbackFloat("Weather_" + name + "_Glare_View")) + , mIsStorm(mWindSpeed > stormWindSpeed) + , mRainSpeed(rainSpeed) + , mRainFrequency(fallback.getFallbackFloat("Weather_" + name + "_Rain_Entrance_Speed")) + , mParticleEffect(particleEffect) + , mRainEffect(fallback.getFallbackBool("Weather_" + name + "_Using_Precip") ? "meshes\\raindrop.nif" : "") + , mTransitionDelta(fallback.getFallbackFloat("Weather_" + name + "_Transition_Delta")) + , mCloudsMaximumPercent(fallback.getFallbackFloat("Weather_" + name + "_Clouds_Maximum_Percent")) + , mThunderFrequency(fallback.getFallbackFloat("Weather_" + name + "_Thunder_Frequency")) + , mThunderThreshold(fallback.getFallbackFloat("Weather_" + name + "_Thunder_Threshold")) + , mThunderSoundID() + , mFlashDecrement(fallback.getFallbackFloat("Weather_" + name + "_Flash_Decrement")) + , mFlashBrightness(0.0f) +{ + mThunderSoundID[0] = fallback.getFallbackString("Weather_" + name + "_Thunder_Sound_ID_0"); + mThunderSoundID[1] = fallback.getFallbackString("Weather_" + name + "_Thunder_Sound_ID_1"); + mThunderSoundID[2] = fallback.getFallbackString("Weather_" + name + "_Thunder_Sound_ID_2"); + mThunderSoundID[3] = fallback.getFallbackString("Weather_" + name + "_Thunder_Sound_ID_3"); + + // TODO: support weathers that have both "Ambient Loop Sound ID" and "Rain Loop Sound ID", need to play both sounds at the same time. + + if (!mRainEffect.empty()) // NOTE: in vanilla, the weathers with rain seem to be hardcoded; changing Using_Precip has no effect + { + mAmbientLoopSoundID = fallback.getFallbackString("Weather_" + name + "_Rain_Loop_Sound_ID"); + if (mAmbientLoopSoundID.empty()) // default to "rain" if not set + mAmbientLoopSoundID = "rain"; + } + else + mAmbientLoopSoundID = fallback.getFallbackString("Weather_" + name + "_Ambient_Loop_Sound_ID"); + + if (Misc::StringUtils::ciEqual(mAmbientLoopSoundID, "None")) + mAmbientLoopSoundID.clear(); - bool usesPrecip = mFallback->getFallbackBool("Weather_"+upper+"_Using_Precip"); - if (usesPrecip) - weather.mRainEffect = "meshes\\raindrop.nif"; - weather.mRainSpeed = mRainSpeed; - weather.mRainFrequency = mFallback->getFallbackFloat("Weather_"+upper+"_Rain_Entrance_Speed"); /* -Unhandled: -Rain Diameter=600 ? -Rain Height Min=200 ? -Rain Height Max=700 ? -Rain Threshold=0.6 ? -Max Raindrops=650 ? -*/ - weather.mIsStorm = (name == "ashstorm" || name == "blight"); - - mWeatherSettings[name] = weather; + Unhandled: + Rain Diameter=600 ? + Rain Height Min=200 ? + Rain Height Max=700 ? + Rain Threshold=0.6 ? + Max Raindrops=650 ? + */ } - -float WeatherManager::calculateHourFade (const std::string& moonName) const +float Weather::transitionDelta() const { - float fadeOutStart=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_Out_Start"); - float fadeOutFinish=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_Out_Finish"); - float fadeInStart=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_In_Start"); - float fadeInFinish=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_In_Finish"); + // Transition Delta describes how quickly transitioning to the weather in question will take, in Hz. Note that the + // measurement is in real time, not in-game time. + return mTransitionDelta; +} - if (mHour >= fadeOutStart && mHour <= fadeOutFinish) - return (1 - ((mHour - fadeOutStart) / (fadeOutFinish - fadeOutStart))); - if (mHour >= fadeInStart && mHour <= fadeInFinish) - return (1 - ((mHour - fadeInStart) / (fadeInFinish - fadeInStart))); +float Weather::cloudBlendFactor(const float transitionRatio) const +{ + // Clouds Maximum Percent affects how quickly the sky transitions from one sky texture to the next. + return transitionRatio / mCloudsMaximumPercent; +} + +float Weather::calculateThunder(const float transitionRatio, const float elapsedSeconds, const bool isPaused) +{ + // When paused, the flash brightness remains the same and no new strikes can occur. + if(!isPaused) + { + // Morrowind doesn't appear to do any calculations unless the transition ratio is higher than the Thunder Threshold. + if(transitionRatio >= mThunderThreshold && mThunderFrequency > 0.0f) + { + flashDecrement(elapsedSeconds); + + if(Misc::Rng::rollProbability() <= thunderChance(transitionRatio, elapsedSeconds)) + { + lightningAndThunder(); + } + } + else + { + mFlashBrightness = 0.0f; + } + } + + return mFlashBrightness; +} + +inline void Weather::flashDecrement(const float elapsedSeconds) +{ + // The Flash Decrement is measured in whole units per second. This means that if the flash brightness was + // currently 1.0, then it should take approximately 0.25 seconds to decay to 0.0 (the minimum). + float decrement = mFlashDecrement * elapsedSeconds; + mFlashBrightness = decrement > mFlashBrightness ? 0.0f : mFlashBrightness - decrement; +} + +inline float Weather::thunderChance(const float transitionRatio, const float elapsedSeconds) const +{ + // This formula is reversed from the observation that with Thunder Frequency set to 1, there are roughly 10 strikes + // per minute. It doesn't appear to be tied to in game time as Timescale doesn't affect it. Various values of + // Thunder Frequency seem to change the average number of strikes in a linear fashion.. During a transition, it appears to + // scaled based on how far past it is past the Thunder Threshold. + float scaleFactor = (transitionRatio - mThunderThreshold) / (1.0f - mThunderThreshold); + return ((mThunderFrequency * 10.0f) / 60.0f) * elapsedSeconds * scaleFactor; +} + +inline void Weather::lightningAndThunder(void) +{ + // Morrowind seems to vary the intensity of the brightness based on which of the four sound IDs it selects. + // They appear to go from 0 (brightest, closest) to 3 (faintest, farthest). The value of 0.25 per distance + // was derived by setting the Flash Decrement to 0.1 and measuring how long each value took to decay to 0. + // TODO: Determine the distribution of each distance to see if it's evenly weighted. + unsigned int distance = Misc::Rng::rollDice(4); + // Flash brightness appears additive, since if multiple strikes occur, it takes longer for it to decay to 0. + mFlashBrightness += 1 - (distance * 0.25f); + MWBase::Environment::get().getSoundManager()->playSound(mThunderSoundID[distance], 1.0, 1.0); +} + +RegionWeather::RegionWeather(const ESM::Region& region) + : mWeather(invalidWeatherID) + , mChances() +{ + mChances.reserve(10); + mChances.push_back(region.mData.mClear); + mChances.push_back(region.mData.mCloudy); + mChances.push_back(region.mData.mFoggy); + mChances.push_back(region.mData.mOvercast); + mChances.push_back(region.mData.mRain); + mChances.push_back(region.mData.mThunder); + mChances.push_back(region.mData.mAsh); + mChances.push_back(region.mData.mBlight); + mChances.push_back(region.mData.mA); + mChances.push_back(region.mData.mB); +} + +RegionWeather::RegionWeather(const ESM::RegionWeatherState& state) + : mWeather(state.mWeather) + , mChances(state.mChances) +{ +} + +RegionWeather::operator ESM::RegionWeatherState() const +{ + ESM::RegionWeatherState state = + { + mWeather, + mChances + }; + + return state; +} + +void RegionWeather::setChances(const std::vector& chances) +{ + if(mChances.size() < chances.size()) + { + mChances.reserve(chances.size()); + } + + std::vector::const_iterator it = chances.begin(); + for(size_t i = 0; it != chances.end(); ++it, ++i) + { + mChances[i] = *it; + } + + // Regional weather no longer supports the current type, select a new weather pattern. + if((static_cast(mWeather) >= mChances.size()) || (mChances[mWeather] == 0)) + { + chooseNewWeather(); + } +} + +void RegionWeather::setWeather(int weatherID) +{ + mWeather = weatherID; +} + +int RegionWeather::getWeather() +{ + // If the region weather was already set (by ChangeWeather, or by a previous call) then just return that value. + // Note that the region weather will be expired periodically when the weather update timer expires. + if(mWeather == invalidWeatherID) + { + chooseNewWeather(); + } + + return mWeather; +} + +void RegionWeather::chooseNewWeather() +{ + // All probabilities must add to 100 (responsibility of the user). + // If chances A and B has values 30 and 70 then by generating 100 numbers 1..100, 30% will be lesser or equal 30 + // and 70% will be greater than 30 (in theory). + int chance = Misc::Rng::rollDice(100) + 1; // 1..100 + int sum = 0; + int i = 0; + for(; static_cast(i) < mChances.size(); ++i) + { + sum += mChances[i]; + if(chance <= sum) + break; + } + + mWeather = i; +} + +MoonModel::MoonModel(const std::string& name, const MWWorld::Fallback& fallback) + : mFadeInStart(fallback.getFallbackFloat("Moons_" + name + "_Fade_In_Start")) + , mFadeInFinish(fallback.getFallbackFloat("Moons_" + name + "_Fade_In_Finish")) + , mFadeOutStart(fallback.getFallbackFloat("Moons_" + name + "_Fade_Out_Start")) + , mFadeOutFinish(fallback.getFallbackFloat("Moons_" + name + "_Fade_Out_Finish")) + , mAxisOffset(fallback.getFallbackFloat("Moons_" + name + "_Axis_Offset")) + , mSpeed(fallback.getFallbackFloat("Moons_" + name + "_Speed")) + , mDailyIncrement(fallback.getFallbackFloat("Moons_" + name + "_Daily_Increment")) + , mFadeStartAngle(fallback.getFallbackFloat("Moons_" + name + "_Fade_Start_Angle")) + , mFadeEndAngle(fallback.getFallbackFloat("Moons_" + name + "_Fade_End_Angle")) + , mMoonShadowEarlyFadeAngle(fallback.getFallbackFloat("Moons_" + name + "_Moon_Shadow_Early_Fade_Angle")) +{ + // Morrowind appears to have a minimum speed in order to avoid situations where the moon couldn't conceivably + // complete a rotation in a single 24 hour period. The value of 180/23 was deduced from reverse engineering. + mSpeed = std::min(mSpeed, 180.0f / 23.0f); +} + +MWRender::MoonState MoonModel::calculateState(const TimeStamp& gameTime) const +{ + float rotationFromHorizon = angle(gameTime); + MWRender::MoonState state = + { + rotationFromHorizon, + mAxisOffset, // Reverse engineered from Morrowind's scene graph rotation matrices. + static_cast(phase(gameTime)), + shadowBlend(rotationFromHorizon), + earlyMoonShadowAlpha(rotationFromHorizon) * hourlyAlpha(gameTime.getHour()) + }; + + return state; +} + +inline float MoonModel::angle(const TimeStamp& gameTime) const +{ + // Morrowind's moons start travel on one side of the horizon (let's call it H-rise) and travel 180 degrees to the + // opposite horizon (let's call it H-set). Upon reaching H-set, they reset to H-rise until the next moon rise. + + // When calculating the angle of the moon, several cases have to be taken into account: + // 1. Moon rises and then sets in one day. + // 2. Moon sets and doesn't rise in one day (occurs when the moon rise hour is >= 24). + // 3. Moon sets and then rises in one day. + float moonRiseHourToday = moonRiseHour(gameTime.getDay()); + float moonRiseAngleToday = 0; + + if(gameTime.getHour() < moonRiseHourToday) + { + float moonRiseHourYesterday = moonRiseHour(gameTime.getDay() - 1); + if(moonRiseHourYesterday < 24) + { + float moonRiseAngleYesterday = rotation(24 - moonRiseHourYesterday); + if(moonRiseAngleYesterday < 180) + { + // The moon rose but did not set yesterday, so accumulate yesterday's angle with how much we've travelled today. + moonRiseAngleToday = rotation(gameTime.getHour()) + moonRiseAngleYesterday; + } + } + } else - return 1; + { + moonRiseAngleToday = rotation(gameTime.getHour() - moonRiseHourToday); + } + + if(moonRiseAngleToday >= 180) + { + // The moon set today, reset the angle to the horizon. + moonRiseAngleToday = 0; + } + + return moonRiseAngleToday; } -float WeatherManager::calculateAngleFade (const std::string& moonName, float angle) const +inline float MoonModel::moonRiseHour(unsigned int daysPassed) const { - float endAngle=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_End_Angle"); - float startAngle=mFallback->getFallbackFloat("Moons_"+moonName+"_Fade_Start_Angle"); - if (angle <= startAngle && angle >= endAngle) - return (1 - ((angle - endAngle)/(startAngle-endAngle))); - else if (angle > startAngle) - return 0.f; + // This arises from the start date of 16 Last Seed, 427 + // TODO: Find an alternate formula that doesn't rely on this day being fixed. + static const unsigned int startDay = 16; + + // This odd formula arises from the fact that on 16 Last Seed, 17 increments have occurred, meaning + // that upon starting a new game, it must only calculate the moon phase as far back as 1 Last Seed. + // Note that we don't modulo after adding the latest daily increment because other calculations need to + // know if doing so would cause the moon rise to be postponed until the next day (which happens when + // the moon rise hour is >= 24 in Morrowind). + return mDailyIncrement + std::fmod((daysPassed - 1 + startDay) * mDailyIncrement, 24.0f); +} + +inline float MoonModel::rotation(float hours) const +{ + // 15 degrees per hour was reverse engineered from the rotation matrices of the Morrowind scene graph. + // Note that this correlates to 360 / 24, which is a full rotation every 24 hours, so speed is a measure + // of whole rotations that could be completed in a day. + return 15.0f * mSpeed * hours; +} + +inline unsigned int MoonModel::phase(const TimeStamp& gameTime) const +{ + // Morrowind starts with a full moon on 16 Last Seed and then begins to wane 17 Last Seed, working on 3 day phase cycle. + // Note: this is an internal helper, and as such we don't want to return MWRender::MoonState::Phase since we can't + // forward declare it (C++11 strongly typed enums solve this). + + // If the moon didn't rise yet today, use yesterday's moon phase. + if(gameTime.getHour() < moonRiseHour(gameTime.getDay())) + return (gameTime.getDay() / 3) % 8; else - return 1.f; + return ((gameTime.getDay() + 1) / 3) % 8; } -WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fallback* fallback) : - mHour(14), mWindSpeed(0.f), mIsStorm(false), mStormDirection(0,1,0), mFallback(fallback), - mRendering(rendering), mCurrentWeather("clear"), mNextWeather(""), mFirstUpdate(true), - mRemainingTransitionTime(0), mThunderFlash(0), mThunderChance(0), mThunderChanceNeeded(50), - mTimePassed(0), mWeatherUpdateTime(0), mThunderSoundDelay(0) +inline float MoonModel::shadowBlend(float angle) const { - //Globals - mThunderSoundID0 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_0"); - mThunderSoundID1 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_1"); - mThunderSoundID2 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_2"); - mThunderSoundID3 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_3"); - mSunriseTime = mFallback->getFallbackFloat("Weather_Sunrise_Time"); - mSunsetTime = mFallback->getFallbackFloat("Weather_Sunset_Time"); - mSunriseDuration = mFallback->getFallbackFloat("Weather_Sunrise_Duration"); - mSunsetDuration = mFallback->getFallbackFloat("Weather_Sunset_Duration"); - mHoursBetweenWeatherChanges = mFallback->getFallbackFloat("Weather_Hours_Between_Weather_Changes"); - mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600; - mThunderFrequency = mFallback->getFallbackFloat("Weather_Thunderstorm_Thunder_Frequency"); - mThunderThreshold = mFallback->getFallbackFloat("Weather_Thunderstorm_Thunder_Threshold"); - mThunderSoundDelay = 0.25; + // The Fade End Angle and Fade Start Angle describe a region where the moon transitions from a solid disk + // that is roughly the color of the sky, to a textured surface. + // Depending on the current angle, the following values describe the ratio between the textured moon + // and the solid disk: + // 1. From Fade End Angle 1 to Fade Start Angle 1 (during moon rise): 0..1 + // 2. From Fade Start Angle 1 to Fade Start Angle 2 (between moon rise and moon set): 1 (textured) + // 3. From Fade Start Angle 2 to Fade End Angle 2 (during moon set): 1..0 + // 4. From Fade End Angle 2 to Fade End Angle 1 (between moon set and moon rise): 0 (solid disk) + float fadeAngle = mFadeStartAngle - mFadeEndAngle; + float fadeEndAngle2 = 180.0f - mFadeEndAngle; + float fadeStartAngle2 = 180.0f - mFadeStartAngle; + if((angle >= mFadeEndAngle) && (angle < mFadeStartAngle)) + return (angle - mFadeEndAngle) / fadeAngle; + else if((angle >= mFadeStartAngle) && (angle < fadeStartAngle2)) + return 1.0f; + else if((angle >= fadeStartAngle2) && (angle < fadeEndAngle2)) + return (fadeEndAngle2 - angle) / fadeAngle; + else + return 0.0f; +} - mRainSpeed = mFallback->getFallbackFloat("Weather_Precip_Gravity"); +inline float MoonModel::hourlyAlpha(float gameHour) const +{ + // The Fade Out Start / Finish and Fade In Start / Finish describe the hours at which the moon + // appears and disappears. + // Depending on the current hour, the following values describe how transparent the moon is. + // 1. From Fade Out Start to Fade Out Finish: 1..0 + // 2. From Fade Out Finish to Fade In Start: 0 (transparent) + // 3. From Fade In Start to Fade In Finish: 0..1 + // 4. From Fade In Finish to Fade Out Start: 1 (solid) + if((gameHour >= mFadeOutStart) && (gameHour < mFadeOutFinish)) + return (mFadeOutFinish - gameHour) / (mFadeOutFinish - mFadeOutStart); + else if((gameHour >= mFadeOutFinish) && (gameHour < mFadeInStart)) + return 0.0f; + else if((gameHour >= mFadeInStart) && (gameHour < mFadeInFinish)) + return (gameHour - mFadeInStart) / (mFadeInFinish - mFadeInStart); + else + return 1.0f; +} - //Some useful values - /* TODO: Use pre-sunrise_time, pre-sunset_time, - * post-sunrise_time, and post-sunset_time to better - * describe sunrise/sunset time. - * These values are fallbacks attached to weather. - */ - mNightStart = mSunsetTime + mSunsetDuration; - mNightEnd = mSunriseTime - 0.5f; - mDayStart = mSunriseTime + mSunriseDuration; - mDayEnd = mSunsetTime; +inline float MoonModel::earlyMoonShadowAlpha(float angle) const +{ + // The Moon Shadow Early Fade Angle describes an arc relative to Fade End Angle. + // Depending on the current angle, the following values describe how transparent the moon is. + // 1. From Moon Shadow Early Fade Angle 1 to Fade End Angle 1 (during moon rise): 0..1 + // 2. From Fade End Angle 1 to Fade End Angle 2 (between moon rise and moon set): 1 (solid) + // 3. From Fade End Angle 2 to Moon Shadow Early Fade Angle 2 (during moon set): 1..0 + // 4. From Moon Shadow Early Fade Angle 2 to Moon Shadow Early Fade Angle 1: 0 (transparent) + float moonShadowEarlyFadeAngle1 = mFadeEndAngle - mMoonShadowEarlyFadeAngle; + float fadeEndAngle2 = 180.0f - mFadeEndAngle; + float moonShadowEarlyFadeAngle2 = fadeEndAngle2 + mMoonShadowEarlyFadeAngle; + if((angle >= moonShadowEarlyFadeAngle1) && (angle < mFadeEndAngle)) + return (angle - moonShadowEarlyFadeAngle1) / mMoonShadowEarlyFadeAngle; + else if((angle >= mFadeEndAngle) && (angle < fadeEndAngle2)) + return 1.0f; + else if((angle >= fadeEndAngle2) && (angle < moonShadowEarlyFadeAngle2)) + return (moonShadowEarlyFadeAngle2 - angle) / mMoonShadowEarlyFadeAngle; + else + return 0.0f; +} - //Weather - Weather clear; - setFallbackWeather(clear,"clear"); +WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const MWWorld::Fallback& fallback, MWWorld::ESMStore& store) + : mStore(store) + , mRendering(rendering) + , mSunriseTime(fallback.getFallbackFloat("Weather_Sunrise_Time")) + , mSunsetTime(fallback.getFallbackFloat("Weather_Sunset_Time")) + , mSunriseDuration(fallback.getFallbackFloat("Weather_Sunrise_Duration")) + , mSunsetDuration(fallback.getFallbackFloat("Weather_Sunset_Duration")) + , mSunPreSunsetTime(fallback.getFallbackFloat("Weather_Sun_Pre-Sunset_Time")) + , mNightFade(0, 0, 0, 1) + , mHoursBetweenWeatherChanges(fallback.getFallbackFloat("Weather_Hours_Between_Weather_Changes")) + , mRainSpeed(fallback.getFallbackFloat("Weather_Precip_Gravity")) + , mUnderwaterFog(fallback.getFallbackFloat("Water_UnderwaterSunriseFog"), + fallback.getFallbackFloat("Water_UnderwaterDayFog"), + fallback.getFallbackFloat("Water_UnderwaterSunsetFog"), + fallback.getFallbackFloat("Water_UnderwaterNightFog")) + , mWeatherSettings() + , mMasser("Masser", fallback) + , mSecunda("Secunda", fallback) + , mWindSpeed(0.f) + , mIsStorm(false) + , mStormDirection(0,1,0) + , mCurrentRegion() + , mTimePassed(0) + , mFastForward(false) + , mWeatherUpdateTime(mHoursBetweenWeatherChanges) + , mTransitionFactor(0) + , mCurrentWeather(0) + , mNextWeather(0) + , mQueuedWeather(0) + , mRegions() + , mResult() + , mAmbientSound() + , mPlayingSoundID() +{ + mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration; + mTimeSettings.mNightEnd = mSunriseTime - 0.5f; + mTimeSettings.mDayStart = mSunriseTime + mSunriseDuration; + mTimeSettings.mDayEnd = mSunsetTime; + mTimeSettings.mSunriseTime = mSunriseTime; - Weather cloudy; - setFallbackWeather(cloudy,"cloudy"); + mWeatherSettings.reserve(10); + addWeather("Clear", fallback); // 0 + addWeather("Cloudy", fallback); // 1 + addWeather("Foggy", fallback); // 2 + addWeather("Overcast", fallback); // 3 + addWeather("Rain", fallback); // 4 + addWeather("Thunderstorm", fallback); // 5 + addWeather("Ashstorm", fallback, "meshes\\ashcloud.nif"); // 6 + addWeather("Blight", fallback, "meshes\\blightcloud.nif"); // 7 + addWeather("Snow", fallback, "meshes\\snow.nif"); // 8 + addWeather("Blizzard", fallback, "meshes\\blizzard.nif"); // 9 - Weather foggy; - setFallbackWeather(foggy,"foggy"); + Store::iterator it = store.get().begin(); + for(; it != store.get().end(); ++it) + { + std::string regionID = Misc::StringUtils::lowerCase(it->mId); + mRegions.insert(std::make_pair(regionID, RegionWeather(*it))); + } - Weather thunderstorm; - thunderstorm.mAmbientLoopSoundID = "rain heavy"; - thunderstorm.mRainEffect = "meshes\\raindrop.nif"; - setFallbackWeather(thunderstorm,"thunderstorm"); - - Weather rain; - rain.mAmbientLoopSoundID = "rain"; - rain.mRainEffect = "meshes\\raindrop.nif"; - setFallbackWeather(rain,"rain"); - - Weather overcast; - setFallbackWeather(overcast,"overcast"); - - Weather ashstorm; - ashstorm.mAmbientLoopSoundID = "ashstorm"; - ashstorm.mParticleEffect = "meshes\\ashcloud.nif"; - setFallbackWeather(ashstorm,"ashstorm"); - - Weather blight; - blight.mAmbientLoopSoundID = "blight"; - blight.mParticleEffect = "meshes\\blightcloud.nif"; - setFallbackWeather(blight,"blight"); - - Weather snow; - snow.mParticleEffect = "meshes\\snow.nif"; - setFallbackWeather(snow, "snow"); - - Weather blizzard; - blizzard.mAmbientLoopSoundID = "BM Blizzard"; - blizzard.mParticleEffect = "meshes\\blizzard.nif"; - setFallbackWeather(blizzard,"blizzard"); + forceWeather(0); } WeatherManager::~WeatherManager() @@ -195,48 +563,487 @@ WeatherManager::~WeatherManager() stopSounds(); } -void WeatherManager::setWeather(const String& weather, bool instant) +void WeatherManager::changeWeather(const std::string& regionID, const unsigned int weatherID) { - if (weather == mCurrentWeather && mNextWeather == "") + // In Morrowind, this seems to have the following behavior, when applied to the current region: + // - When there is no transition in progress, start transitioning to the new weather. + // - If there is a transition in progress, queue up the transition and process it when the current one completes. + // - If there is a transition in progress, and a queued transition, overwrite the queued transition. + // - If multiple calls to ChangeWeather are made while paused (console up), only the last call will be used, + // meaning that if there was no transition in progress, only the last ChangeWeather will be processed. + // If the region isn't current, Morrowind will store the new weather for the region in question. + + if(weatherID < mWeatherSettings.size()) { - mFirstUpdate = false; + std::string lowerCaseRegionID = Misc::StringUtils::lowerCase(regionID); + std::map::iterator it = mRegions.find(lowerCaseRegionID); + if(it != mRegions.end()) + { + it->second.setWeather(weatherID); + regionalWeatherChanged(it->first, it->second); + } + } +} + +void WeatherManager::modRegion(const std::string& regionID, const std::vector& chances) +{ + // Sets the region's probability for various weather patterns. Note that this appears to be saved permanently. + // In Morrowind, this seems to have the following behavior when applied to the current region: + // - If the region supports the current weather, no change in current weather occurs. + // - If the region no longer supports the current weather, and there is no transition in progress, begin to + // transition to a new supported weather type. + // - If the region no longer supports the current weather, and there is a transition in progress, queue a + // transition to a new supported weather type. + + std::string lowerCaseRegionID = Misc::StringUtils::lowerCase(regionID); + std::map::iterator it = mRegions.find(lowerCaseRegionID); + if(it != mRegions.end()) + { + it->second.setChances(chances); + regionalWeatherChanged(it->first, it->second); + } +} + +void WeatherManager::playerTeleported() +{ + // If the player teleports to an outdoors cell in a new region (for instance, by travelling), the weather needs to + // be changed immediately, and any transitions for the previous region discarded. + MWBase::World* world = MWBase::Environment::get().getWorld(); + if(world->isCellExterior() || world->isCellQuasiExterior()) + { + std::string playerRegion = Misc::StringUtils::lowerCase(world->getPlayerPtr().getCell()->getCell()->mRegion); + std::map::iterator it = mRegions.find(playerRegion); + if(it != mRegions.end() && playerRegion != mCurrentRegion) + { + mCurrentRegion = playerRegion; + forceWeather(it->second.getWeather()); + } + } +} + +void WeatherManager::update(float duration, bool paused) +{ + MWWorld::ConstPtr player = MWMechanics::getPlayer(); + MWBase::World& world = *MWBase::Environment::get().getWorld(); + TimeStamp time = world.getTimeStamp(); + + if(!paused || mFastForward) + { + // Add new transitions when either the player's current external region changes. + std::string playerRegion = Misc::StringUtils::lowerCase(player.getCell()->getCell()->mRegion); + if(updateWeatherTime() || updateWeatherRegion(playerRegion)) + { + std::map::iterator it = mRegions.find(mCurrentRegion); + if(it != mRegions.end()) + { + addWeatherTransition(it->second.getWeather()); + } + } + + updateWeatherTransitions(duration); + } + + const bool exterior = (world.isCellExterior() || world.isCellQuasiExterior()); + if(!exterior) + { + mRendering.setSkyEnabled(false); + stopSounds(); return; } - if (instant || mFirstUpdate) + calculateWeatherResult(time.getHour(), duration, paused); + + mWindSpeed = mResult.mWindSpeed; + mIsStorm = mResult.mIsStorm; + + if (mIsStorm) { - mNextWeather = ""; - mCurrentWeather = weather; + osg::Vec3f playerPos (player.getRefData().getPosition().asVec3()); + osg::Vec3f redMountainPos (19950, 72032, 27831); + + mStormDirection = (playerPos - redMountainPos); + mStormDirection.z() = 0; + mStormDirection.normalize(); + mRendering.getSkyManager()->setStormDirection(mStormDirection); + } + + // disable sun during night + if (time.getHour() >= mTimeSettings.mNightStart || time.getHour() <= mSunriseTime) + mRendering.getSkyManager()->sunDisable(); + else + mRendering.getSkyManager()->sunEnable(); + + // Update the sun direction. Run it east to west at a fixed angle from overhead. + // The sun's speed at day and night may differ, since mSunriseTime and mNightStart + // mark when the sun is level with the horizon. + { + // Shift times into a 24-hour window beginning at mSunriseTime... + float adjustedHour = time.getHour(); + float adjustedNightStart = mTimeSettings.mNightStart; + if ( time.getHour() < mSunriseTime ) + adjustedHour += 24.f; + if ( mTimeSettings.mNightStart < mSunriseTime ) + adjustedNightStart += 24.f; + + const bool is_night = adjustedHour >= adjustedNightStart; + const float dayDuration = adjustedNightStart - mSunriseTime; + const float nightDuration = 24.f - dayDuration; + + double theta; + if ( !is_night ) { + theta = M_PI * (adjustedHour - mSunriseTime) / dayDuration; + } else { + theta = M_PI * (1.f - (adjustedHour - adjustedNightStart) / nightDuration); + } + + osg::Vec3f final( + static_cast(cos(theta)), + -0.268f, // approx tan( -15 degrees ) + static_cast(sin(theta))); + mRendering.setSunDirection( final * -1 ); + } + + float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings); + + float peakHour = mSunriseTime + (mSunsetTime - mSunriseTime) / 2; + if (time.getHour() < mSunriseTime || time.getHour() > mSunsetTime) + mRendering.getSkyManager()->setGlareTimeOfDayFade(0); + else if (time.getHour() < peakHour) + mRendering.getSkyManager()->setGlareTimeOfDayFade(1 - (peakHour - time.getHour()) / (peakHour - mSunriseTime)); + else + mRendering.getSkyManager()->setGlareTimeOfDayFade(1 - (time.getHour() - peakHour) / (mSunsetTime - peakHour)); + + mRendering.getSkyManager()->setMasserState(mMasser.calculateState(time)); + mRendering.getSkyManager()->setSecundaState(mSecunda.calculateState(time)); + + mRendering.configureFog(mResult.mFogDepth, underwaterFog, mResult.mFogColor); + mRendering.setAmbientColour(mResult.mAmbientColor); + mRendering.setSunColour(mResult.mSunColor); + + mRendering.getSkyManager()->setWeather(mResult); + + // Play sounds + if (mPlayingSoundID != mResult.mAmbientLoopSoundID) + { + stopSounds(); + if (!mResult.mAmbientLoopSoundID.empty()) + mAmbientSound = MWBase::Environment::get().getSoundManager()->playSound(mResult.mAmbientLoopSoundID, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + + mPlayingSoundID = mResult.mAmbientLoopSoundID; + } + if (mAmbientSound.get()) + mAmbientSound->setVolume(mResult.mAmbientSoundVolume); +} + +void WeatherManager::stopSounds() +{ + if (mAmbientSound.get()) + MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); + mAmbientSound.reset(); + mPlayingSoundID.clear(); +} + +float WeatherManager::getWindSpeed() const +{ + return mWindSpeed; +} + +bool WeatherManager::isInStorm() const +{ + return mIsStorm; +} + +osg::Vec3f WeatherManager::getStormDirection() const +{ + return mStormDirection; +} + +void WeatherManager::advanceTime(double hours, bool incremental) +{ + // In Morrowind, when the player sleeps/waits, serves jail time, travels, or trains, all weather transitions are + // immediately applied, regardless of whatever transition time might have been remaining. + mTimePassed += hours; + mFastForward = !incremental ? true : mFastForward; +} + +unsigned int WeatherManager::getWeatherID() const +{ + return mCurrentWeather; +} + +bool WeatherManager::isDark() const +{ + TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp(); + bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior() + || MWBase::Environment::get().getWorld()->isCellQuasiExterior()); + return exterior && (time.getHour() < mSunriseTime || time.getHour() > mTimeSettings.mNightStart - 1); +} + +void WeatherManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) +{ + ESM::WeatherState state; + state.mCurrentRegion = mCurrentRegion; + state.mTimePassed = mTimePassed; + state.mFastForward = mFastForward; + state.mWeatherUpdateTime = mWeatherUpdateTime; + state.mTransitionFactor = mTransitionFactor; + state.mCurrentWeather = mCurrentWeather; + state.mNextWeather = mNextWeather; + state.mQueuedWeather = mQueuedWeather; + + std::map::iterator it = mRegions.begin(); + for(; it != mRegions.end(); ++it) + { + state.mRegions.insert(std::make_pair(it->first, it->second)); + } + + writer.startRecord(ESM::REC_WTHR); + state.save(writer); + writer.endRecord(ESM::REC_WTHR); +} + +bool WeatherManager::readRecord(ESM::ESMReader& reader, uint32_t type) +{ + if(ESM::REC_WTHR == type) + { + static const int oldestCompatibleSaveFormat = 2; + if(reader.getFormat() < oldestCompatibleSaveFormat) + { + // Weather state isn't really all that important, so to preserve older save games, we'll just discard the + // older weather records, rather than fail to handle the record. + reader.skipRecord(); + } + else + { + ESM::WeatherState state; + state.load(reader); + + mCurrentRegion.swap(state.mCurrentRegion); + mTimePassed = state.mTimePassed; + mFastForward = state.mFastForward; + mWeatherUpdateTime = state.mWeatherUpdateTime; + mTransitionFactor = state.mTransitionFactor; + mCurrentWeather = state.mCurrentWeather; + mNextWeather = state.mNextWeather; + mQueuedWeather = state.mQueuedWeather; + + mRegions.clear(); + std::map::iterator it = state.mRegions.begin(); + if(it == state.mRegions.end()) + { + // When loading an imported save, the region modifiers aren't currently being set, so just reset them. + importRegions(); + } + else + { + for(; it != state.mRegions.end(); ++it) + { + mRegions.insert(std::make_pair(it->first, RegionWeather(it->second))); + } + } + } + + return true; + } + + return false; +} + +void WeatherManager::clear() +{ + stopSounds(); + + mCurrentRegion = ""; + mTimePassed = 0.0f; + mWeatherUpdateTime = 0.0f; + forceWeather(0); + mRegions.clear(); + importRegions(); +} + +inline void WeatherManager::addWeather(const std::string& name, + const MWWorld::Fallback& fallback, + const std::string& particleEffect) +{ + static const float fStromWindSpeed = mStore.get().find("fStromWindSpeed")->getFloat(); + + Weather weather(name, fallback, fStromWindSpeed, mRainSpeed, particleEffect); + + mWeatherSettings.push_back(weather); +} + +inline void WeatherManager::importRegions() +{ + Store::iterator it = mStore.get().begin(); + for(; it != mStore.get().end(); ++it) + { + std::string regionID = Misc::StringUtils::lowerCase(it->mId); + mRegions.insert(std::make_pair(regionID, RegionWeather(*it))); + } +} + +inline void WeatherManager::regionalWeatherChanged(const std::string& regionID, RegionWeather& region) +{ + // If the region is current, then add a weather transition for it. + MWWorld::ConstPtr player = MWMechanics::getPlayer(); + if(player.isInCell()) + { + std::string playerRegion = Misc::StringUtils::lowerCase(player.getCell()->getCell()->mRegion); + if(!playerRegion.empty() && (playerRegion == regionID)) + { + addWeatherTransition(region.getWeather()); + } + } +} + +inline bool WeatherManager::updateWeatherTime() +{ + mWeatherUpdateTime -= mTimePassed; + mTimePassed = 0.0f; + if(mWeatherUpdateTime <= 0.0f) + { + // Expire all regional weather, so that any call to getWeather() will return a new weather ID. + std::map::iterator it = mRegions.begin(); + for(; it != mRegions.end(); ++it) + { + it->second.setWeather(invalidWeatherID); + } + + mWeatherUpdateTime += mHoursBetweenWeatherChanges; + + return true; + } + + return false; +} + +inline bool WeatherManager::updateWeatherRegion(const std::string& playerRegion) +{ + if(!playerRegion.empty() && playerRegion != mCurrentRegion) + { + mCurrentRegion = playerRegion; + + return true; + } + + return false; +} + +inline void WeatherManager::updateWeatherTransitions(const float elapsedRealSeconds) +{ + // When a player chooses to train, wait, or serves jail time, any transitions will be fast forwarded to the last + // weather type set, regardless of the remaining transition time. + if(!mFastForward && inTransition()) + { + const float delta = mWeatherSettings[mNextWeather].transitionDelta(); + mTransitionFactor -= elapsedRealSeconds * delta; + if(mTransitionFactor <= 0.0f) + { + mCurrentWeather = mNextWeather; + mNextWeather = mQueuedWeather; + mQueuedWeather = invalidWeatherID; + + // We may have begun processing the queued transition, so we need to apply the remaining time towards it. + if(inTransition()) + { + const float newDelta = mWeatherSettings[mNextWeather].transitionDelta(); + const float remainingSeconds = -(mTransitionFactor / delta); + mTransitionFactor = 1.0f - (remainingSeconds * newDelta); + } + else + { + mTransitionFactor = 0.0f; + } + } } else { - if (mNextWeather != "") + if(mQueuedWeather != invalidWeatherID) { - // transition more than 50% finished? - if (mRemainingTransitionTime/(mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600) <= 0.5) - mCurrentWeather = mNextWeather; + mCurrentWeather = mQueuedWeather; + } + else if(mNextWeather != invalidWeatherID) + { + mCurrentWeather = mNextWeather; } - mNextWeather = weather; - mRemainingTransitionTime = mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600.f; + mNextWeather = invalidWeatherID; + mQueuedWeather = invalidWeatherID; + mFastForward = false; } - mFirstUpdate = false; } -void WeatherManager::setResult(const String& weatherType) +inline void WeatherManager::forceWeather(const int weatherID) { - const Weather& current = mWeatherSettings[weatherType]; + mTransitionFactor = 0.0f; + mCurrentWeather = weatherID; + mNextWeather = invalidWeatherID; + mQueuedWeather = invalidWeatherID; +} + +inline bool WeatherManager::inTransition() +{ + return mNextWeather != invalidWeatherID; +} + +inline void WeatherManager::addWeatherTransition(const int weatherID) +{ + // In order to work like ChangeWeather expects, this method begins transitioning to the new weather immediately if + // no transition is in progress, otherwise it queues it to be transitioned. + + assert(weatherID >= 0 && static_cast(weatherID) < mWeatherSettings.size()); + + if(!inTransition() && (weatherID != mCurrentWeather)) + { + mNextWeather = weatherID; + mTransitionFactor = 1.0f; + } + else if(inTransition() && (weatherID != mNextWeather)) + { + mQueuedWeather = weatherID; + } +} + +inline void WeatherManager::calculateWeatherResult(const float gameHour, + const float elapsedSeconds, + const bool isPaused) +{ + float flash = 0.0f; + if(!inTransition()) + { + calculateResult(mCurrentWeather, gameHour); + flash = mWeatherSettings[mCurrentWeather].calculateThunder(1.0f, elapsedSeconds, isPaused); + } + else + { + calculateTransitionResult(1 - mTransitionFactor, gameHour); + float currentFlash = mWeatherSettings[mCurrentWeather].calculateThunder(mTransitionFactor, + elapsedSeconds, + isPaused); + float nextFlash = mWeatherSettings[mNextWeather].calculateThunder(1 - mTransitionFactor, + elapsedSeconds, + isPaused); + flash = currentFlash + nextFlash; + } + osg::Vec4f flashColor(flash, flash, flash, 0.0f); + + mResult.mFogColor += flashColor; + mResult.mAmbientColor += flashColor; + mResult.mSunColor += flashColor; +} + +inline void WeatherManager::calculateResult(const int weatherID, const float gameHour) +{ + const Weather& current = mWeatherSettings[weatherID]; mResult.mCloudTexture = current.mCloudTexture; mResult.mCloudBlendFactor = 0; - mResult.mCloudOpacity = current.mCloudsMaximumPercent; mResult.mWindSpeed = current.mWindSpeed; mResult.mCloudSpeed = current.mCloudSpeed; mResult.mGlareView = current.mGlareView; mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; mResult.mAmbientSoundVolume = 1.f; mResult.mEffectFade = 1.f; - mResult.mSunColor = current.mSunDiscSunsetColor; mResult.mIsStorm = current.mIsStorm; @@ -246,94 +1053,57 @@ void WeatherManager::setResult(const String& weatherType) mResult.mParticleEffect = current.mParticleEffect; mResult.mRainEffect = current.mRainEffect; - mResult.mNight = (mHour < mSunriseTime || mHour > mNightStart - 1); + mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart - 1); - mResult.mFogDepth = mResult.mNight ? current.mLandFogNightDepth : current.mLandFogDayDepth; + mResult.mFogDepth = current.mLandFogDepth.getValue(gameHour, mTimeSettings); + mResult.mFogColor = current.mFogColor.getValue(gameHour, mTimeSettings); + mResult.mAmbientColor = current.mAmbientColor.getValue(gameHour, mTimeSettings); + mResult.mSunColor = current.mSunColor.getValue(gameHour, mTimeSettings); + mResult.mSkyColor = current.mSkyColor.getValue(gameHour, mTimeSettings); + mResult.mNightFade = mNightFade.getValue(gameHour, mTimeSettings); - // night - if (mHour <= mNightEnd || mHour >= mNightStart + 1) + if (gameHour >= mSunsetTime - mSunPreSunsetTime) { - mResult.mFogColor = current.mFogNightColor; - mResult.mAmbientColor = current.mAmbientNightColor; - mResult.mSunColor = current.mSunNightColor; - mResult.mSkyColor = current.mSkyNightColor; - mResult.mNightFade = 1.f; + float factor = (gameHour - (mSunsetTime - mSunPreSunsetTime)) / mSunPreSunsetTime; + factor = std::min(1.f, factor); + mResult.mSunDiscColor = lerp(osg::Vec4f(1,1,1,1), current.mSunDiscSunsetColor, factor); + // The SunDiscSunsetColor in the INI isn't exactly the resulting color on screen, most likely because + // MW applied the color to the ambient term as well. After the ambient and emissive terms are added together, the fixed pipeline + // would then clamp the total lighting to (1,1,1). A noticable change in color tone can be observed when only one of the color components gets clamped. + // Unfortunately that means we can't use the INI color as is, have to replicate the above nonsense. + mResult.mSunDiscColor = mResult.mSunDiscColor + osg::componentMultiply(mResult.mSunDiscColor, mResult.mAmbientColor); + for (int i=0; i<3; ++i) + mResult.mSunDiscColor[i] = std::min(1.f, mResult.mSunDiscColor[i]); } + else + mResult.mSunDiscColor = osg::Vec4f(1,1,1,1); - // sunrise - else if (mHour >= mNightEnd && mHour <= mDayStart + 1) + if (gameHour >= mSunsetTime) { - if (mHour <= mSunriseTime) - { - // fade in - float advance = mSunriseTime - mHour; - float factor = advance / 0.5f; - mResult.mFogColor = lerp(current.mFogSunriseColor, current.mFogNightColor, factor); - mResult.mAmbientColor = lerp(current.mAmbientSunriseColor, current.mAmbientNightColor, factor); - mResult.mSunColor = lerp(current.mSunSunriseColor, current.mSunNightColor, factor); - mResult.mSkyColor = lerp(current.mSkySunriseColor, current.mSkyNightColor, factor); - mResult.mNightFade = factor; - } - else //if (mHour >= 6) - { - // fade out - float advance = mHour - mSunriseTime; - float factor = advance / 3.f; - mResult.mFogColor = lerp(current.mFogSunriseColor, current.mFogDayColor, factor); - mResult.mAmbientColor = lerp(current.mAmbientSunriseColor, current.mAmbientDayColor, factor); - mResult.mSunColor = lerp(current.mSunSunriseColor, current.mSunDayColor, factor); - mResult.mSkyColor = lerp(current.mSkySunriseColor, current.mSkyDayColor, factor); - } + float fade = std::min(1.f, (gameHour - mSunsetTime) / 2.f); + fade = fade*fade; + mResult.mSunDiscColor.a() = 1.f - fade; } + else if (gameHour >= mSunriseTime && gameHour <= mSunriseTime + 1) + { + mResult.mSunDiscColor.a() = gameHour - mSunriseTime; + } + else + mResult.mSunDiscColor.a() = 1; - // day - else if (mHour >= mDayStart + 1 && mHour <= mDayEnd - 1) - { - mResult.mFogColor = current.mFogDayColor; - mResult.mAmbientColor = current.mAmbientDayColor; - mResult.mSunColor = current.mSunDayColor; - mResult.mSkyColor = current.mSkyDayColor; - } - - // sunset - else if (mHour >= mDayEnd - 1 && mHour <= mNightStart + 1) - { - if (mHour <= mDayEnd + 1) - { - // fade in - float advance = (mDayEnd + 1) - mHour; - float factor = (advance / 2); - mResult.mFogColor = lerp(current.mFogSunsetColor, current.mFogDayColor, factor); - mResult.mAmbientColor = lerp(current.mAmbientSunsetColor, current.mAmbientDayColor, factor); - mResult.mSunColor = lerp(current.mSunSunsetColor, current.mSunDayColor, factor); - mResult.mSkyColor = lerp(current.mSkySunsetColor, current.mSkyDayColor, factor); - } - else //if (mHour >= 19) - { - // fade out - float advance = mHour - (mDayEnd + 1); - float factor = advance / 2.f; - mResult.mFogColor = lerp(current.mFogSunsetColor, current.mFogNightColor, factor); - mResult.mAmbientColor = lerp(current.mAmbientSunsetColor, current.mAmbientNightColor, factor); - mResult.mSunColor = lerp(current.mSunSunsetColor, current.mSunNightColor, factor); - mResult.mSkyColor = lerp(current.mSkySunsetColor, current.mSkyNightColor, factor); - mResult.mNightFade = factor; - } - } } -void WeatherManager::transition(float factor) +inline void WeatherManager::calculateTransitionResult(const float factor, const float gameHour) { - setResult(mCurrentWeather); - const WeatherResult current = mResult; - setResult(mNextWeather); - const WeatherResult other = mResult; + calculateResult(mCurrentWeather, gameHour); + const MWRender::WeatherResult current = mResult; + calculateResult(mNextWeather, gameHour); + const MWRender::WeatherResult other = mResult; mResult.mCloudTexture = current.mCloudTexture; mResult.mNextCloudTexture = other.mCloudTexture; - mResult.mCloudBlendFactor = factor; + mResult.mCloudBlendFactor = mWeatherSettings[mNextWeather].cloudBlendFactor(factor); - mResult.mCloudOpacity = lerp(current.mCloudOpacity, other.mCloudOpacity, factor); mResult.mFogColor = lerp(current.mFogColor, other.mFogColor, factor); mResult.mSunColor = lerp(current.mSunColor, other.mSunColor, factor); mResult.mSkyColor = lerp(current.mSkyColor, other.mSkyColor, factor); @@ -343,13 +1113,12 @@ void WeatherManager::transition(float factor) mResult.mFogDepth = lerp(current.mFogDepth, other.mFogDepth, factor); mResult.mWindSpeed = lerp(current.mWindSpeed, other.mWindSpeed, factor); mResult.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed, factor); - mResult.mCloudOpacity = lerp(current.mCloudOpacity, other.mCloudOpacity, factor); mResult.mGlareView = lerp(current.mGlareView, other.mGlareView, factor); mResult.mNightFade = lerp(current.mNightFade, other.mNightFade, factor); mResult.mNight = current.mNight; - if (factor < 0.5) + if(factor < 0.5) { mResult.mIsStorm = current.mIsStorm; mResult.mParticleEffect = current.mParticleEffect; @@ -375,477 +1144,3 @@ void WeatherManager::transition(float factor) } } -void WeatherManager::update(float duration, bool paused) -{ - float timePassed = static_cast(mTimePassed); - mTimePassed = 0; - - mWeatherUpdateTime -= timePassed; - - MWBase::World* world = MWBase::Environment::get().getWorld(); - const bool exterior = (world->isCellExterior() || world->isCellQuasiExterior()); - if (!exterior) - { - mRendering->skyDisable(); - mRendering->getSkyManager()->setLightningStrength(0.f); - stopSounds(); - return; - } - - switchToNextWeather(false); - - if (mNextWeather != "") - { - mRemainingTransitionTime -= timePassed; - if (mRemainingTransitionTime < 0) - { - mCurrentWeather = mNextWeather; - mNextWeather = ""; - } - } - - if (mNextWeather != "") - transition(1 - (mRemainingTransitionTime / (mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600))); - else - setResult(mCurrentWeather); - - mWindSpeed = mResult.mWindSpeed; - mIsStorm = mResult.mIsStorm; - - if (mIsStorm) - { - MWWorld::Ptr player = world->getPlayerPtr(); - Ogre::Vector3 playerPos (player.getRefData().getPosition().pos); - Ogre::Vector3 redMountainPos (19950, 72032, 27831); - - mStormDirection = (playerPos - redMountainPos); - mStormDirection.z = 0; - mRendering->getSkyManager()->setStormDirection(mStormDirection); - } - - mRendering->configureFog(mResult.mFogDepth, mResult.mFogColor); - - // disable sun during night - if (mHour >= mNightStart || mHour <= mSunriseTime) - mRendering->getSkyManager()->sunDisable(); - else - mRendering->getSkyManager()->sunEnable(); - - // Update the sun direction. Run it east to west at a fixed angle from overhead. - // The sun's speed at day and night may differ, since mSunriseTime and mNightStart - // mark when the sun is level with the horizon. - { - // Shift times into a 24-hour window beginning at mSunriseTime... - float adjustedHour = mHour; - float adjustedNightStart = mNightStart; - if ( mHour < mSunriseTime ) - adjustedHour += 24.f; - if ( mNightStart < mSunriseTime ) - adjustedNightStart += 24.f; - - const bool is_night = adjustedHour >= adjustedNightStart; - const float dayDuration = adjustedNightStart - mSunriseTime; - const float nightDuration = 24.f - dayDuration; - - double theta; - if ( !is_night ) { - theta = M_PI * (adjustedHour - mSunriseTime) / dayDuration; - } else { - theta = M_PI * (adjustedHour - adjustedNightStart) / nightDuration; - } - - Vector3 final( - static_cast(cos(theta)), - -0.268f, // approx tan( -15 degrees ) - static_cast(sin(theta))); - mRendering->setSunDirection( final, is_night ); - } - - /* - * TODO: import separated fadeInStart/Finish, fadeOutStart/Finish - * for masser and secunda - */ - - float fadeOutFinish=mFallback->getFallbackFloat("Moons_Masser_Fade_Out_Finish"); - float fadeInStart=mFallback->getFallbackFloat("Moons_Masser_Fade_In_Start"); - - //moon calculations - float moonHeight; - if (mHour >= fadeInStart) - moonHeight = mHour - fadeInStart; - else if (mHour <= fadeOutFinish) - moonHeight = mHour + fadeOutFinish; - else - moonHeight = 0; - - moonHeight /= (24.f - (fadeInStart - fadeOutFinish)); - - if (moonHeight != 0) - { - int facing = (moonHeight <= 1) ? 1 : -1; - Vector3 masser( - (moonHeight - 1) * facing, - (1 - moonHeight) * facing, - moonHeight); - - Vector3 secunda( - (moonHeight - 1) * facing * 1.25f, - (1 - moonHeight) * facing * 0.8f, - moonHeight); - - mRendering->getSkyManager()->setMasserDirection(masser); - mRendering->getSkyManager()->setSecundaDirection(secunda); - mRendering->getSkyManager()->masserEnable(); - mRendering->getSkyManager()->secundaEnable(); - - float angle = (1-moonHeight) * 90.f * facing; - float masserHourFade = calculateHourFade("Masser"); - float secundaHourFade = calculateHourFade("Secunda"); - float masserAngleFade = calculateAngleFade("Masser", angle); - float secundaAngleFade = calculateAngleFade("Secunda", angle); - - masserAngleFade *= masserHourFade; - secundaAngleFade *= secundaHourFade; - - mRendering->getSkyManager()->setMasserFade(masserAngleFade); - mRendering->getSkyManager()->setSecundaFade(secundaAngleFade); - } - else - { - mRendering->getSkyManager()->masserDisable(); - mRendering->getSkyManager()->secundaDisable(); - } - - if (!paused) - { - if (mCurrentWeather == "thunderstorm" && mNextWeather == "") - { - if (mThunderFlash > 0) - { - // play the sound after a delay - mThunderSoundDelay -= duration; - if (mThunderSoundDelay <= 0) - { - // pick a random sound - int sound = OEngine::Misc::Rng::rollDice(4); - std::string* soundName = NULL; - if (sound == 0) soundName = &mThunderSoundID0; - else if (sound == 1) soundName = &mThunderSoundID1; - else if (sound == 2) soundName = &mThunderSoundID2; - else if (sound == 3) soundName = &mThunderSoundID3; - if (soundName) - MWBase::Environment::get().getSoundManager()->playSound(*soundName, 1.0, 1.0); - mThunderSoundDelay = 1000; - } - - mThunderFlash -= duration; - if (mThunderFlash > 0) - mRendering->getSkyManager()->setLightningStrength( mThunderFlash / mThunderThreshold ); - else - { - mThunderChanceNeeded = static_cast(OEngine::Misc::Rng::rollDice(100)); - mThunderChance = 0; - mRendering->getSkyManager()->setLightningStrength( 0.f ); - } - } - else - { - // no thunder active - mThunderChance += duration*4; // chance increases by 4 percent every second - if (mThunderChance >= mThunderChanceNeeded) - { - mThunderFlash = mThunderThreshold; - - mRendering->getSkyManager()->setLightningStrength( mThunderFlash / mThunderThreshold ); - - mThunderSoundDelay = 0.25; - } - } - } - else - mRendering->getSkyManager()->setLightningStrength(0.f); - } - - - mRendering->setAmbientColour(mResult.mAmbientColor); - mRendering->sunEnable(false); - mRendering->setSunColour(mResult.mSunColor); - - mRendering->getSkyManager()->setWeather(mResult); - - // Play sounds - if (mPlayingSoundID != mResult.mAmbientLoopSoundID) - { - stopSounds(); - if (!mResult.mAmbientLoopSoundID.empty()) - mAmbientSound = MWBase::Environment::get().getSoundManager()->playSound(mResult.mAmbientLoopSoundID, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); - - mPlayingSoundID = mResult.mAmbientLoopSoundID; - } - if (mAmbientSound.get()) - mAmbientSound->setVolume(mResult.mAmbientSoundVolume); -} - -void WeatherManager::stopSounds() -{ - if (mAmbientSound.get()) - { - MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); - mAmbientSound.reset(); - mPlayingSoundID.clear(); - } -} - -std::string WeatherManager::nextWeather(const ESM::Region* region) const -{ - std::vector probability; - - RegionModMap::const_iterator iter = mRegionMods.find(Misc::StringUtils::lowerCase(region->mId)); - if(iter != mRegionMods.end()) - probability = iter->second; - else - { - probability.reserve(10); - probability.push_back(region->mData.mClear); - probability.push_back(region->mData.mCloudy); - probability.push_back(region->mData.mFoggy); - probability.push_back(region->mData.mOvercast); - probability.push_back(region->mData.mRain); - probability.push_back(region->mData.mThunder); - probability.push_back(region->mData.mAsh); - probability.push_back(region->mData.mBlight); - probability.push_back(region->mData.mA); - probability.push_back(region->mData.mB); - } - - /* - * All probabilities must add to 100 (responsibility of the user). - * If chances A and B has values 30 and 70 then by generating - * 100 numbers 1..100, 30% will be lesser or equal 30 and - * 70% will be greater than 30 (in theory). - */ - - int chance = OEngine::Misc::Rng::rollDice(100) + 1; // 1..100 - int sum = 0; - unsigned int i = 0; - for (; i < probability.size(); ++i) - { - sum += probability[i]; - if (chance < sum) - break; - } - - switch (i) - { - case 1: - return "cloudy"; - case 2: - return "foggy"; - case 3: - return "overcast"; - case 4: - return "rain"; - case 5: - return "thunderstorm"; - case 6: - return "ashstorm"; - case 7: - return "blight"; - case 8: - return "snow"; - case 9: - return "blizzard"; - default: // case 0 - return "clear"; - } -} - -void WeatherManager::setHour(const float hour) -{ - mHour = hour; -} - -unsigned int WeatherManager::getWeatherID() const -{ - // Source: http://www.uesp.net/wiki/Tes3Mod:GetCurrentWeather - - if (mCurrentWeather == "clear") - return 0; - else if (mCurrentWeather == "cloudy") - return 1; - else if (mCurrentWeather == "foggy") - return 2; - else if (mCurrentWeather == "overcast") - return 3; - else if (mCurrentWeather == "rain") - return 4; - else if (mCurrentWeather == "thunderstorm") - return 5; - else if (mCurrentWeather == "ashstorm") - return 6; - else if (mCurrentWeather == "blight") - return 7; - else if (mCurrentWeather == "snow") - return 8; - else if (mCurrentWeather == "blizzard") - return 9; - - else - return 0; -} - -void WeatherManager::changeWeather(const std::string& region, const unsigned int id) -{ - // make sure this region exists - MWBase::Environment::get().getWorld()->getStore().get().find(region); - - std::string weather; - if (id==0) - weather = "clear"; - else if (id==1) - weather = "cloudy"; - else if (id==2) - weather = "foggy"; - else if (id==3) - weather = "overcast"; - else if (id==4) - weather = "rain"; - else if (id==5) - weather = "thunderstorm"; - else if (id==6) - weather = "ashstorm"; - else if (id==7) - weather = "blight"; - else if (id==8) - weather = "snow"; - else if (id==9) - weather = "blizzard"; - else - weather = "clear"; - - mRegionOverrides[Misc::StringUtils::lowerCase(region)] = weather; - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (player.isInCell()) - { - std::string playerRegion = player.getCell()->getCell()->mRegion; - if (Misc::StringUtils::ciEqual(region, playerRegion)) - setWeather(weather); - } -} - -void WeatherManager::modRegion(const std::string ®ionid, const std::vector &chances) -{ - mRegionMods[Misc::StringUtils::lowerCase(regionid)] = chances; - // Start transitioning right away if the region no longer supports the current weather type - unsigned int current = getWeatherID(); - if(current >= chances.size() || chances[current] == 0) - mWeatherUpdateTime = 0.0f; -} - -float WeatherManager::getWindSpeed() const -{ - return mWindSpeed; -} - -bool WeatherManager::isDark() const -{ - bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior() - || MWBase::Environment::get().getWorld()->isCellQuasiExterior()); - return exterior && (mHour < mSunriseTime || mHour > mNightStart - 1); -} - -void WeatherManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) -{ - ESM::WeatherState state; - state.mHour = mHour; - state.mWindSpeed = mWindSpeed; - state.mCurrentWeather = mCurrentWeather; - state.mNextWeather = mNextWeather; - state.mCurrentRegion = mCurrentRegion; - state.mFirstUpdate = mFirstUpdate; - state.mRemainingTransitionTime = mRemainingTransitionTime; - state.mTimePassed = mTimePassed; - - writer.startRecord(ESM::REC_WTHR); - state.save(writer); - writer.endRecord(ESM::REC_WTHR); -} - -bool WeatherManager::readRecord(ESM::ESMReader& reader, uint32_t type) -{ - if(ESM::REC_WTHR == type) - { - // load first so that if it fails, we haven't accidentally reset the state below - ESM::WeatherState state; - state.load(reader); - - // swap in the loaded values now that we can't fail - mHour = state.mHour; - mWindSpeed = state.mWindSpeed; - mCurrentWeather.swap(state.mCurrentWeather); - mNextWeather.swap(state.mNextWeather); - mCurrentRegion.swap(state.mCurrentRegion); - mFirstUpdate = state.mFirstUpdate; - mRemainingTransitionTime = state.mRemainingTransitionTime; - mTimePassed = state.mTimePassed; - - return true; - } - - return false; -} - -void WeatherManager::clear() -{ - stopSounds(); - mRegionOverrides.clear(); - mRegionMods.clear(); - mThunderFlash = 0.0; - mThunderChance = 0.0; - mThunderChanceNeeded = 50.0; -} - -void WeatherManager::switchToNextWeather(bool instantly) -{ - MWBase::World* world = MWBase::Environment::get().getWorld(); - if (world->isCellExterior() || world->isCellQuasiExterior()) - { - std::string regionstr = Misc::StringUtils::lowerCase(world->getPlayerPtr().getCell()->getCell()->mRegion); - - if (mWeatherUpdateTime <= 0 || regionstr != mCurrentRegion) - { - mCurrentRegion = regionstr; - mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600; - - std::string weatherType = "clear"; - - if (mRegionOverrides.find(regionstr) != mRegionOverrides.end()) - { - weatherType = mRegionOverrides[regionstr]; - } - else - { - // get weather probabilities for the current region - const ESM::Region *region = world->getStore().get().search (regionstr); - - if (region != 0) - { - weatherType = nextWeather(region); - } - } - - setWeather(weatherType, instantly); - } - } -} - -bool WeatherManager::isInStorm() const -{ - return mIsStorm; -} - -Ogre::Vector3 WeatherManager::getStormDirection() const -{ - return mStormDirection; -} diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index a2e6681595..a5627a507c 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -3,15 +3,18 @@ #include #include +#include -#include -#include +#include #include "../mwbase/soundmanager.hpp" +#include "../mwrender/sky.hpp" + namespace ESM { struct Region; + struct RegionWeatherState; class ESMWriter; class ESMReader; } @@ -29,89 +32,61 @@ namespace Loading namespace MWWorld { class Fallback; + class TimeStamp; - /// Defines the actual weather that results from weather setting (see below), time of day and weather transition - struct WeatherResult + + struct TimeOfDaySettings { - std::string mCloudTexture; - std::string mNextCloudTexture; - float mCloudBlendFactor; - - Ogre::ColourValue mFogColor; - - Ogre::ColourValue mAmbientColor; - - Ogre::ColourValue mSkyColor; - - Ogre::ColourValue mSunColor; - - Ogre::ColourValue mSunDiscColor; - - float mFogDepth; - - float mWindSpeed; - - float mCloudSpeed; - - float mCloudOpacity; - - float mGlareView; - - bool mNight; // use night skybox - float mNightFade; // fading factor for night skybox - - bool mIsStorm; - - std::string mAmbientLoopSoundID; - float mAmbientSoundVolume; - - std::string mParticleEffect; - std::string mRainEffect; - float mEffectFade; - - float mRainSpeed; - float mRainFrequency; + float mNightStart; + float mNightEnd; + float mDayStart; + float mDayEnd; + float mSunriseTime; }; + /// Interpolates between 4 data points (sunrise, day, sunset, night) based on the time of day. + /// The template value could be a floating point number, or a color. + template + class TimeOfDayInterpolator + { + public: + TimeOfDayInterpolator(const T& sunrise, const T& day, const T& sunset, const T& night) + : mSunriseValue(sunrise), mDayValue(day), mSunsetValue(sunset), mNightValue(night) + { + } + + T getValue (const float gameHour, const TimeOfDaySettings& timeSettings) const; + + private: + T mSunriseValue, mDayValue, mSunsetValue, mNightValue; + }; /// Defines a single weather setting (according to INI) - struct Weather + class Weather { + public: + Weather(const std::string& name, + const MWWorld::Fallback& fallback, + float stormWindSpeed, + float rainSpeed, + const std::string& particleEffect); + std::string mCloudTexture; - // Sky (atmosphere) colors - Ogre::ColourValue mSkySunriseColor, - mSkyDayColor, - mSkySunsetColor, - mSkyNightColor; - - // Fog colors - Ogre::ColourValue mFogSunriseColor, - mFogDayColor, - mFogSunsetColor, - mFogNightColor; - - // Ambient lighting colors - Ogre::ColourValue mAmbientSunriseColor, - mAmbientDayColor, - mAmbientSunsetColor, - mAmbientNightColor; - - // Sun (directional) lighting colors - Ogre::ColourValue mSunSunriseColor, - mSunDayColor, - mSunSunsetColor, - mSunNightColor; + // Sky (atmosphere) color + TimeOfDayInterpolator mSkyColor; + // Fog color + TimeOfDayInterpolator mFogColor; + // Ambient lighting color + TimeOfDayInterpolator mAmbientColor; + // Sun (directional) lighting color + TimeOfDayInterpolator mSunColor; // Fog depth/density - float mLandFogDayDepth, - mLandFogNightDepth; + TimeOfDayInterpolator mLandFogDepth; - // Color modulation for the sun itself during sunset (not completely sure) - Ogre::ColourValue mSunDiscSunsetColor; - - // Duration of weather transition (in days) - float mTransitionDelta; + // Color modulation for the sun itself during sunset + osg::Vec4f mSunDiscSunsetColor; // Used by scripts to animate signs, etc based on the wind (GetWindSpeed) float mWindSpeed; @@ -119,10 +94,8 @@ namespace MWWorld // Cloud animation speed multiplier float mCloudSpeed; - // Multiplier for clouds transparency - float mCloudsMaximumPercent; - - // Value between 0 and 1, defines the strength of the sun glare effect + // Value between 0 and 1, defines the strength of the sun glare effect. + // Also appears to modify how visible the sun, moons, and stars are for various weather effects. float mGlareView; // Sound effect @@ -148,15 +121,90 @@ namespace MWWorld // Note: For Weather Blight, there is a "Disease Chance" (=0.1) setting. But according to MWSFD this feature // is broken in the vanilla game and was disabled. + + float transitionDelta() const; + float cloudBlendFactor(const float transitionRatio) const; + + float calculateThunder(const float transitionRatio, const float elapsedSeconds, const bool isPaused); + + private: + float mTransitionDelta; + float mCloudsMaximumPercent; + + // Note: In MW, only thunderstorms support these attributes, but in the interest of making weather more + // flexible, these settings are imported for all weather types. Only thunderstorms will normally have any + // non-zero values. + float mThunderFrequency; + float mThunderThreshold; + std::string mThunderSoundID[4]; + float mFlashDecrement; + + float mFlashBrightness; + + void flashDecrement(const float elapsedSeconds); + float thunderChance(const float transitionRatio, const float elapsedSeconds) const; + void lightningAndThunder(void); + }; + + /// A class for storing a region's weather. + class RegionWeather + { + public: + explicit RegionWeather(const ESM::Region& region); + explicit RegionWeather(const ESM::RegionWeatherState& state); + + operator ESM::RegionWeatherState() const; + + void setChances(const std::vector& chances); + + void setWeather(int weatherID); + + int getWeather(); + + private: + int mWeather; + std::vector mChances; + + void chooseNewWeather(); + }; + + /// A class that acts as a model for the moons. + class MoonModel + { + public: + MoonModel(const std::string& name, const MWWorld::Fallback& fallback); + + MWRender::MoonState calculateState(const TimeStamp& gameTime) const; + + private: + float mFadeInStart; + float mFadeInFinish; + float mFadeOutStart; + float mFadeOutFinish; + float mAxisOffset; + float mSpeed; + float mDailyIncrement; + float mFadeStartAngle; + float mFadeEndAngle; + float mMoonShadowEarlyFadeAngle; + + float angle(const TimeStamp& gameTime) const; + float moonRiseHour(unsigned int daysPassed) const; + float rotation(float hours) const; + unsigned int phase(const TimeStamp& gameTime) const; + float shadowBlend(float angle) const; + float hourlyAlpha(float gameHour) const; + float earlyMoonShadowAlpha(float angle) const; }; - /// /// Interface for weather settings - /// class WeatherManager { public: - WeatherManager(MWRender::RenderingManager*,MWWorld::Fallback* fallback); + // Have to pass fallback and Store, can't use singleton since World isn't fully constructed yet at the time + WeatherManager(MWRender::RenderingManager& rendering, + const MWWorld::Fallback& fallback, + MWWorld::ESMStore& store); ~WeatherManager(); /** @@ -164,8 +212,9 @@ namespace MWWorld * @param region that should be changed * @param ID of the weather setting to shift to */ - void changeWeather(const std::string& region, const unsigned int id); - void switchToNextWeather(bool instantly = true); + void changeWeather(const std::string& regionID, const unsigned int weatherID); + void modRegion(const std::string& regionID, const std::vector& chances); + void playerTeleported(); /** * Per-frame update @@ -176,24 +225,17 @@ namespace MWWorld void stopSounds(); - void setHour(const float hour); - float getWindSpeed() const; /// Are we in an ash or blight storm? bool isInStorm() const; - Ogre::Vector3 getStormDirection() const; + osg::Vec3f getStormDirection() const; - void advanceTime(double hours) - { - mTimePassed += hours*3600; - } + void advanceTime(double hours, bool incremental); unsigned int getWeatherID() const; - void modRegion(const std::string ®ionid, const std::vector &chances); - /// @see World::isDark bool isDark() const; @@ -204,68 +246,65 @@ namespace MWWorld void clear(); private: - float mHour; - float mWindSpeed; - bool mIsStorm; - Ogre::Vector3 mStormDirection; - - MWBase::SoundPtr mAmbientSound; - std::string mPlayingSoundID; - - MWWorld::Fallback* mFallback; - void setFallbackWeather(Weather& weather,const std::string& name); - MWRender::RenderingManager* mRendering; - - std::map mWeatherSettings; - - std::map mRegionOverrides; - - std::string mCurrentWeather; - std::string mNextWeather; - - std::string mCurrentRegion; - - bool mFirstUpdate; - - float mRemainingTransitionTime; - - float mThunderFlash; - float mThunderChance; - float mThunderChanceNeeded; - - double mTimePassed; // time passed since last update - - void transition(const float factor); - void setResult(const std::string& weatherType); - - float calculateHourFade (const std::string& moonName) const; - float calculateAngleFade (const std::string& moonName, float angle) const; - - void setWeather(const std::string& weatherType, bool instant=false); - std::string nextWeather(const ESM::Region* region) const; - WeatherResult mResult; - - typedef std::map > RegionModMap; - RegionModMap mRegionMods; - - float mRainSpeed; + MWWorld::ESMStore& mStore; + MWRender::RenderingManager& mRendering; float mSunriseTime; float mSunsetTime; float mSunriseDuration; float mSunsetDuration; - float mWeatherUpdateTime; + float mSunPreSunsetTime; + + TimeOfDaySettings mTimeSettings; + + // fading of night skydome + TimeOfDayInterpolator mNightFade; + float mHoursBetweenWeatherChanges; - float mThunderFrequency; - float mThunderThreshold; - float mThunderSoundDelay; - float mNightStart; - float mNightEnd; - float mDayStart; - float mDayEnd; - std::string mThunderSoundID0; - std::string mThunderSoundID1; - std::string mThunderSoundID2; - std::string mThunderSoundID3; + float mRainSpeed; + + // underwater fog not really related to weather, but we handle it here because it's convenient + TimeOfDayInterpolator mUnderwaterFog; + + std::vector mWeatherSettings; + MoonModel mMasser; + MoonModel mSecunda; + + float mWindSpeed; + bool mIsStorm; + osg::Vec3f mStormDirection; + + std::string mCurrentRegion; + float mTimePassed; + bool mFastForward; + float mWeatherUpdateTime; + float mTransitionFactor; + int mCurrentWeather; + int mNextWeather; + int mQueuedWeather; + std::map mRegions; + MWRender::WeatherResult mResult; + + MWBase::SoundPtr mAmbientSound; + std::string mPlayingSoundID; + + void addWeather(const std::string& name, + const MWWorld::Fallback& fallback, + const std::string& particleEffect = ""); + + void importRegions(); + + void regionalWeatherChanged(const std::string& regionID, RegionWeather& region); + bool updateWeatherTime(); + bool updateWeatherRegion(const std::string& playerRegion); + void updateWeatherTransitions(const float elapsedRealSeconds); + void forceWeather(const int weatherID); + + bool inTransition(); + void addWeatherTransition(const int weatherID); + + void calculateWeatherResult(const float gameHour, const float elapsedSeconds, const bool isPaused); + void calculateResult(const int weatherID, const float gameHour); + void calculateTransitionResult(const float factor, const float gameHour); }; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0f33f3d59b..15826009d6 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -7,27 +7,27 @@ #else #include #endif -#include "../mwbase/scriptmanager.hpp" -#include "../mwscript/globalscripts.hpp" -#include -#include -#include +#include +#include -#include - -#include -#include -#include +#include +#include #include -#include -#include +#include + +#include +#include +#include + +#include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/scriptmanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/movement.hpp" @@ -37,37 +37,43 @@ #include "../mwmechanics/combat.hpp" #include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors -#include "../mwrender/sky.hpp" #include "../mwrender/animation.hpp" +#include "../mwrender/renderingmanager.hpp" +#include "../mwrender/camera.hpp" +#include "../mwrender/vismask.hpp" #include "../mwscript/interpretercontext.hpp" +#include "../mwscript/globalscripts.hpp" #include "../mwclass/door.hpp" +#include "../mwphysics/physicssystem.hpp" +#include "../mwphysics/actor.hpp" +#include "../mwphysics/collisiontype.hpp" + #include "player.hpp" #include "manualref.hpp" #include "cellstore.hpp" -#include "cellfunctors.hpp" #include "containerstore.hpp" #include "inventorystore.hpp" #include "actionteleport.hpp" #include "projectilemanager.hpp" +#include "weather.hpp" #include "contentloader.hpp" #include "esmloader.hpp" -using namespace Ogre; - namespace { // Wraps a value to (-PI, PI] void wrap(float& rad) { + const float pi = static_cast(osg::PI); if (rad>0) - rad = std::fmod(rad+Ogre::Math::PI, 2.0f*Ogre::Math::PI)-Ogre::Math::PI; + rad = std::fmod(rad+pi, 2.0f*pi)-pi; else - rad = std::fmod(rad-Ogre::Math::PI, 2.0f*Ogre::Math::PI)+Ogre::Math::PI; + rad = std::fmod(rad-pi, 2.0f*pi)+pi; } } @@ -131,39 +137,33 @@ namespace MWWorld { if (mSky && (isCellExterior() || isCellQuasiExterior())) { - mRendering->skySetHour (mGlobalVariables["gamehour"].getFloat()); - mRendering->skySetDate (mGlobalVariables["day"].getInteger(), - mGlobalVariables["month"].getInteger()); + mRendering->skySetDate (mDay->getInteger(), mMonth->getInteger()); - mRendering->skyEnable(); + mRendering->setSkyEnabled(true); } else - mRendering->skyDisable(); + mRendering->setSkyEnabled(false); } - World::World (OEngine::Render::OgreRenderer& renderer, + World::World ( + osgViewer::Viewer* viewer, + osg::ref_ptr rootNode, + Resource::ResourceSystem* resourceSystem, const Files::Collections& fileCollections, const std::vector& contentFiles, - const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, - int activationDistanceOverride, const std::string& startCell, const std::string& startupScript) - : mFallback(fallbackMap), mPlayer (0), mLocalScripts (mStore), + int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, + const std::string& resourcePath) + : mResourceSystem(resourceSystem), mFallback(fallbackMap), mPlayer (0), mLocalScripts (mStore), mSky (true), mCells (mStore, mEsm), mGodMode(false), mScriptsEnabled(true), mContentFiles (contentFiles), mActivationDistanceOverride (activationDistanceOverride), mStartupScript(startupScript), mStartCell (startCell), mTeleportEnabled(true), mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0) { - mPhysics = new PhysicsSystem(renderer); - mPhysEngine = mPhysics->getEngine(); - - mProjectileManager.reset(new ProjectileManager(renderer.getScene(), *mPhysEngine)); - - mRendering = new MWRender::RenderingManager(renderer, resDir, cacheDir, mPhysEngine,&mFallback); - - mPhysEngine->setSceneManager(renderer.getScene()); - - mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback); + mPhysics = new MWPhysics::PhysicsSystem(resourceSystem, rootNode); + mRendering = new MWRender::RenderingManager(viewer, rootNode, resourceSystem, &mFallback, resourcePath); + mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering, mPhysics)); mEsm.resize(contentFiles.size()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); @@ -186,22 +186,40 @@ namespace MWWorld if (mEsm[0].getFormat() == 0) ensureNeededRecords(); + fillGlobalVariables(); + mStore.setUp(); mStore.movePlayerRecord(); mSwimHeightScale = mStore.get().find("fSwimHeightScale")->getFloat(); - mGlobalVariables.fill (mStore); + mWeatherManager = new MWWorld::WeatherManager(*mRendering, mFallback, mStore); mWorldScene = new Scene(*mRendering, mPhysics); } + void World::fillGlobalVariables() + { + mGlobalVariables.fill (mStore); + + mGameHour = &mGlobalVariables["gamehour"]; + mDaysPassed = &mGlobalVariables["dayspassed"]; + mDay = &mGlobalVariables["day"]; + mMonth = &mGlobalVariables["month"]; + mYear = &mGlobalVariables["year"]; + mTimeScale = &mGlobalVariables["timescale"]; + } + void World::startNewGame (bool bypass) { mGoToJail = false; mLevitationEnabled = true; mTeleportEnabled = true; + mGodMode = false; + mScriptsEnabled = true; + mSky = true; + // Rebuild player setupPlayer(); @@ -210,11 +228,15 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->updatePlayer(); + // we don't want old weather to persist on a new game + // Note that if reset later, the initial ChangeWeather that the chargen script calls will be lost. + delete mWeatherManager; + mWeatherManager = new MWWorld::WeatherManager(*mRendering, mFallback, mStore); + if (!bypass) { // set new game mark mGlobalVariables["chargenstate"].setInteger (1); - mGlobalVariables["pcrace"].setInteger (3); } else mGlobalVariables["chargenstate"].setInteger (-1); @@ -226,6 +248,7 @@ namespace MWWorld if (findExteriorPosition (mStartCell, pos)) { changeToExteriorCell (pos); + fixPosition(getPlayerPtr()); } else { @@ -252,17 +275,16 @@ namespace MWWorld } if (!bypass) - MWBase::Environment::get().getWindowManager()->playVideo(mFallback.getFallbackString("Movies_New_Game"), true); + { + std::string video = mFallback.getFallbackString("Movies_New_Game"); + if (!video.empty()) + MWBase::Environment::get().getWindowManager()->playVideo(video, true); + } // enable collision if (!mPhysics->toggleCollisionMode()) mPhysics->toggleCollisionMode(); - // we don't want old weather to persist on a new game - delete mWeatherManager; - mWeatherManager = 0; - mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback); - if (!mStartupScript.empty()) MWBase::Environment::get().getWindowManager()->executeInConsole(mStartupScript); } @@ -271,9 +293,7 @@ namespace MWWorld { mWeatherManager->clear(); mRendering->clear(); - mProjectileManager->clear(); - mLocalScripts.clear(); mWorldScene->changeToVoid(); @@ -283,7 +303,7 @@ namespace MWWorld if (mPlayer) { - mPlayer->clear(); + mPlayer->clear(); mPlayer->setCell(0); mPlayer->getPlayer().getRefData() = RefData(); mPlayer->set(mStore.get().find ("player")); @@ -293,13 +313,10 @@ namespace MWWorld mDoorStates.clear(); - mGodMode = false; - mScriptsEnabled = true; - mSky = true; mTeleportEnabled = true; mLevitationEnabled = true; - mGlobalVariables.fill (mStore); + fillGlobalVariables(); } int World::countSavedGameRecords() const @@ -328,7 +345,7 @@ namespace MWWorld iter!=mWorldScene->getActiveCells().end(); ++iter) { CellStore* cellstore = *iter; - mRendering->writeFog(cellstore); + MWBase::Environment::get().getWindowManager()->writeFog(cellstore); } MWMechanics::CreatureStats::writeActorIdCounter(writer); @@ -368,8 +385,9 @@ namespace MWWorld !mGlobalVariables.readRecord (reader, type) && !mPlayer->readRecord (reader, type) && !mWeatherManager->readRecord (reader, type) && - !mCells.readRecord (reader, type, contentFileMap) && - !mProjectileManager->readRecord (reader, type)) + !mCells.readRecord (reader, type, contentFileMap) + && !mProjectileManager->readRecord (reader, type) + ) { throw std::runtime_error ("unknown record in saved game"); } @@ -463,7 +481,6 @@ namespace MWWorld { // Must be cleared before mRendering is destroyed mProjectileManager->clear(); - delete mWeatherManager; delete mWorldScene; delete mRendering; @@ -663,22 +680,6 @@ namespace MWWorld throw std::runtime_error ("unknown ID: " + name); } - Ptr World::getPtrViaHandle (const std::string& handle) - { - Ptr res = searchPtrViaHandle (handle); - if (res.isEmpty ()) - throw std::runtime_error ("unknown Ogre handle: " + handle); - return res; - } - - Ptr World::searchPtrViaHandle (const std::string& handle) - { - if (mPlayer->getPlayer().getRefData().getHandle()==handle) - return mPlayer->getPlayer(); - - return mWorldScene->searchPtrViaHandle (handle); - } - Ptr World::searchPtrViaActorId (int actorId) { // The player is not registered in any CellStore so must be checked manually @@ -688,12 +689,12 @@ namespace MWWorld return mWorldScene->searchPtrViaActorId (actorId); } - struct FindContainerFunctor + struct FindContainerVisitor { - Ptr mContainedPtr; + ConstPtr mContainedPtr; Ptr mResult; - FindContainerFunctor(const Ptr& containedPtr) : mContainedPtr(containedPtr) {} + FindContainerVisitor(const ConstPtr& containedPtr) : mContainedPtr(containedPtr) {} bool operator() (Ptr ptr) { @@ -707,7 +708,7 @@ namespace MWWorld } }; - Ptr World::findContainer(const Ptr& ptr) + Ptr World::findContainer(const ConstPtr& ptr) { if (ptr.isInCell()) return Ptr(); @@ -719,11 +720,15 @@ namespace MWWorld const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt) { - FindContainerFunctor functor(ptr); - (*cellIt)->forEachContainer(functor); + FindContainerVisitor visitor(ptr); + (*cellIt)->forEachType(visitor); + if (visitor.mResult.isEmpty()) + (*cellIt)->forEachType(visitor); + if (visitor.mResult.isEmpty()) + (*cellIt)->forEachType(visitor); - if (!functor.mResult.isEmpty()) - return functor.mResult; + if (!visitor.mResult.isEmpty()) + return visitor.mResult; } return Ptr(); @@ -791,6 +796,9 @@ namespace MWWorld if (reference.getRefData().isEnabled()) { + if (reference == getPlayerPtr()) + throw std::runtime_error("can not disable player object"); + reference.getRefData().disable(); if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end() && reference.getRefData().getCount()) @@ -798,21 +806,21 @@ namespace MWWorld } } - void World::advanceTime (double hours) + void World::advanceTime (double hours, bool incremental) { MWBase::Environment::get().getMechanicsManager()->advanceTime(static_cast(hours * 3600)); - mWeatherManager->advanceTime (hours); + mWeatherManager->advanceTime (hours, incremental); - hours += mGlobalVariables["gamehour"].getFloat(); + hours += mGameHour->getFloat(); setHour (hours); int days = static_cast(hours / 24); if (days>0) - mGlobalVariables["dayspassed"].setInteger ( - days + mGlobalVariables["dayspassed"].getInteger()); + mDaysPassed->setInteger ( + days + mDaysPassed->getInteger()); } void World::setHour (double hour) @@ -824,14 +832,10 @@ namespace MWWorld hour = std::fmod (hour, 24); - mGlobalVariables["gamehour"].setFloat(static_cast(hour)); - - mRendering->skySetHour (hour); - - mWeatherManager->setHour(static_cast(hour)); + mGameHour->setFloat(static_cast(hour)); if (days>0) - setDay (days + mGlobalVariables["day"].getInteger()); + setDay (days + mDay->getInteger()); } void World::setDay (int day) @@ -839,7 +843,7 @@ namespace MWWorld if (day<1) day = 1; - int month = mGlobalVariables["month"].getInteger(); + int month = mMonth->getInteger(); while (true) { @@ -854,16 +858,16 @@ namespace MWWorld else { month = 0; - mGlobalVariables["year"].setInteger (mGlobalVariables["year"].getInteger()+1); + mYear->setInteger(mYear->getInteger()+1); } day -= days; } - mGlobalVariables["day"].setInteger (day); - mGlobalVariables["month"].setInteger (month); + mDay->setInteger(day); + mMonth->setInteger(month); - mRendering->skySetDate (day, month); + mRendering->skySetDate(day, month); } void World::setMonth (int month) @@ -876,30 +880,30 @@ namespace MWWorld int days = getDaysPerMonth (month); - if (mGlobalVariables["day"].getInteger()>days) - mGlobalVariables["day"].setInteger (days); + if (mDay->getInteger()>days) + mDay->setInteger (days); - mGlobalVariables["month"].setInteger (month); + mMonth->setInteger (month); if (years>0) - mGlobalVariables["year"].setInteger (years+mGlobalVariables["year"].getInteger()); + mYear->setInteger (years+mYear->getInteger()); - mRendering->skySetDate (mGlobalVariables["day"].getInteger(), month); + mRendering->skySetDate (mDay->getInteger(), month); } int World::getDay() const { - return mGlobalVariables["day"].getInteger(); + return mDay->getInteger(); } int World::getMonth() const { - return mGlobalVariables["month"].getInteger(); + return mMonth->getInteger(); } int World::getYear() const { - return mGlobalVariables["year"].getInteger(); + return mYear->getInteger(); } std::string World::getMonthName (int month) const @@ -924,24 +928,14 @@ namespace MWWorld TimeStamp World::getTimeStamp() const { - return TimeStamp (mGlobalVariables["gamehour"].getFloat(), - mGlobalVariables["dayspassed"].getInteger()); + return TimeStamp (mGameHour->getFloat(), mDaysPassed->getInteger()); } bool World::toggleSky() { - if (mSky) - { - mSky = false; - mRendering->skyDisable(); - return false; - } - else - { - mSky = true; - mRendering->skyEnable(); - return true; - } + mSky = !mSky; + mRendering->setSkyEnabled(mSky); + return mSky; } int World::getMasserPhase() const @@ -961,7 +955,7 @@ namespace MWWorld float World::getTimeScaleFactor() const { - return mGlobalVariables["timescale"].getFloat(); + return mTimeScale->getFloat(); } void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) @@ -973,6 +967,7 @@ namespace MWWorld // changed worldspace mProjectileManager->clear(); mRendering->notifyWorldSpaceChanged(); + mCurrentWorldSpace = cellName; } @@ -1017,16 +1012,17 @@ namespace MWWorld if (mActivationDistanceOverride >= 0) return static_cast(mActivationDistanceOverride); - return getStore().get().find("iMaxActivateDist")->getFloat() * 5 / 4; + static const int iMaxActivateDist = getStore().get().find("iMaxActivateDist")->getInt(); + return iMaxActivateDist * 5.f / 4.f; } MWWorld::Ptr World::getFacedObject() { - std::string facedHandle; + MWWorld::Ptr facedObject; if (MWBase::Environment::get().getWindowManager()->isGuiMode() && MWBase::Environment::get().getWindowManager()->isConsoleMode()) - getFacedHandle(facedHandle, getMaxActivationDistance() * 50, false); + facedObject = getFacedObject(getMaxActivationDistance() * 50, false); else { float telekinesisRangeBonus = @@ -1036,44 +1032,51 @@ namespace MWWorld float activationDistance = getMaxActivationDistance() + telekinesisRangeBonus; - getFacedHandle(facedHandle, activationDistance); + facedObject = getFacedObject(activationDistance); } - if (facedHandle.empty()) - return MWWorld::Ptr(); - - return getPtrViaHandle(facedHandle); + return facedObject; } - std::pair World::getHitContact(const MWWorld::Ptr &ptr, float distance) + osg::Matrixf World::getActorHeadTransform(const MWWorld::ConstPtr& actor) const + { + const MWRender::Animation *anim = mRendering->getAnimation(actor); + if(anim) + { + const osg::Node *node = anim->getNode("Head"); + if(!node) node = anim->getNode("Bip01 Head"); + if(node) + { + osg::MatrixList mats = node->getWorldMatrices(); + if(!mats.empty()) + return mats[0]; + } + } + return osg::Matrixf::translate(actor.getRefData().getPosition().asVec3()); + } + + + std::pair World::getHitContact(const MWWorld::ConstPtr &ptr, float distance) { const ESM::Position &posdata = ptr.getRefData().getPosition(); - Ogre::Vector3 pos(posdata.pos); - Ogre::Quaternion rot = Ogre::Quaternion(Ogre::Radian(posdata.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * - Ogre::Quaternion(Ogre::Radian(posdata.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); - MWRender::Animation *anim = mRendering->getAnimation(ptr); - if(anim != NULL) - { - Ogre::Node *node = anim->getNode("Head"); - if (node == NULL) - node = anim->getNode("Bip01 Head"); - if(node != NULL) - pos += node->_getDerivedPosition(); - } + osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1)); + osg::Vec3f pos = getActorHeadTransform(ptr).getTrans(); - std::pair result = mPhysics->getHitContact(ptr.getRefData().getHandle(), - pos, rot, distance); - if(result.first.empty()) - return std::make_pair(MWWorld::Ptr(), Ogre::Vector3(0.0f)); + std::pair result = mPhysics->getHitContact(ptr, pos, rot, distance); + if(result.first.isEmpty()) + return std::make_pair(MWWorld::Ptr(), osg::Vec3f()); - return std::make_pair(searchPtrViaHandle(result.first), result.second); + return std::make_pair(result.first, result.second); } void World::deleteObject (const Ptr& ptr) { - if (!ptr.getRefData().isDeleted()) + if (!ptr.getRefData().isDeleted() && ptr.getContainerStore() == NULL) { + if (ptr == getPlayerPtr()) + throw std::runtime_error("can not delete player object"); + ptr.getRefData().setCount(0); if (ptr.isInCell() @@ -1116,7 +1119,7 @@ namespace MWWorld ptr.getRefData().setPosition(pos); - Ogre::Vector3 vec(x, y, z); + osg::Vec3f vec(x, y, z); CellStore *currCell = ptr.isInCell() ? ptr.getCell() : NULL; // currCell == NULL should only happen for player, during initial startup bool isPlayer = ptr == mPlayer->getPlayer(); @@ -1147,7 +1150,7 @@ namespace MWWorld bool newCellActive = mWorldScene->isCellActive(*newCell); if (!currCellActive && newCellActive) { - newPtr = ptr.getClass().copyToCell(ptr, *newCell, pos); + newPtr = currCell->moveTo(ptr, newCell); mWorldScene->addObjectToScene(newPtr); std::string script = newPtr.getClass().getScript(newPtr); @@ -1163,18 +1166,18 @@ namespace MWWorld removeContainerScripts (ptr); haveToMove = false; - newPtr = ptr.getClass().copyToCell(ptr, *newCell); + newPtr = currCell->moveTo(ptr, newCell); newPtr.getRefData().setBaseNode(0); } else if (!currCellActive && !newCellActive) - newPtr = ptr.getClass().copyToCell(ptr, *newCell); + newPtr = currCell->moveTo(ptr, newCell); else // both cells active { - newPtr = ptr.getClass().copyToCell(ptr, *newCell, pos); + newPtr = currCell->moveTo(ptr, newCell); - mRendering->updateObjectCell(ptr, newPtr); - ptr.getRefData().setBaseNode(NULL); + mRendering->updatePtr(ptr, newPtr); MWBase::Environment::get().getSoundManager()->updatePtr (ptr, newPtr); + mPhysics->updatePtr(ptr, newPtr); MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager(); mechMgr->updateCell(ptr, newPtr); @@ -1189,17 +1192,16 @@ namespace MWWorld addContainerScripts (newPtr, newCell); } } - ptr.getRefData().setCount(0); } } if (haveToMove && newPtr.getRefData().getBaseNode()) { mRendering->moveObject(newPtr, vec); - mPhysics->moveObject (newPtr); + mPhysics->updatePosition(newPtr); } if (isPlayer) { - mWorldScene->playerMoved (vec); + mWorldScene->playerMoved(vec); } return newPtr; } @@ -1226,32 +1228,27 @@ namespace MWWorld void World::scaleObject (const Ptr& ptr, float scale) { ptr.getCellRef().setScale(scale); - ptr.getClass().adjustScale(ptr,scale); - if(ptr.getRefData().getBaseNode() == 0) - return; - mRendering->scaleObject(ptr, Vector3(scale,scale,scale)); - mPhysics->scaleObject(ptr); + mWorldScene->updateObjectScale(ptr); } - void World::rotateObjectImp (const Ptr& ptr, Ogre::Vector3 rot, bool adjust) + void World::rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, bool adjust) { - const float two_pi = Ogre::Math::TWO_PI; - const float pi = Ogre::Math::PI; + const float pi = static_cast(osg::PI); ESM::Position pos = ptr.getRefData().getPosition(); float *objRot = pos.rot; if(adjust) { - objRot[0] += rot.x; - objRot[1] += rot.y; - objRot[2] += rot.z; + objRot[0] += rot.x(); + objRot[1] += rot.y(); + objRot[2] += rot.z(); } else { - objRot[0] = rot.x; - objRot[1] = rot.y; - objRot[2] = rot.z; + objRot[0] = rot.x(); + objRot[1] = rot.y(); + objRot[2] = rot.z(); } if(ptr.getClass().isActor()) @@ -1260,51 +1257,19 @@ namespace MWWorld * currently it's done so for rotating the camera, which needs * clamping. */ - const float half_pi = Ogre::Math::HALF_PI; + const float half_pi = pi/2.f; if(objRot[0] < -half_pi) objRot[0] = -half_pi; else if(objRot[0] > half_pi) objRot[0] = half_pi; - } - else - { - while(objRot[0] < -pi) objRot[0] += two_pi; - while(objRot[0] > pi) objRot[0] -= two_pi; - } - while(objRot[1] < -pi) objRot[1] += two_pi; - while(objRot[1] > pi) objRot[1] -= two_pi; - - while(objRot[2] < -pi) objRot[2] += two_pi; - while(objRot[2] > pi) objRot[2] -= two_pi; + wrap(objRot[1]); + wrap(objRot[2]); + } ptr.getRefData().setPosition(pos); - if(ptr.getRefData().getBaseNode() == 0) - return; - - if (ptr.getClass().isActor()) - mWorldScene->updateObjectRotation(ptr); - else - mWorldScene->updateObjectLocalRotation(ptr); - } - - void World::localRotateObject (const Ptr& ptr, float x, float y, float z) - { - LocalRotation rot = ptr.getRefData().getLocalRotation(); - rot.rot[0]=Ogre::Degree(x).valueRadians(); - rot.rot[1]=Ogre::Degree(y).valueRadians(); - rot.rot[2]=Ogre::Degree(z).valueRadians(); - - wrap(rot.rot[0]); - wrap(rot.rot[1]); - wrap(rot.rot[2]); - - ptr.getRefData().setLocalRotation(rot); - - if (ptr.getRefData().getBaseNode() != 0) - { - mWorldScene->updateObjectLocalRotation(ptr); - } + if(ptr.getRefData().getBaseNode() != 0) + mWorldScene->updateObjectRotation(ptr, true); } void World::adjustPosition(const Ptr &ptr, bool force) @@ -1317,7 +1282,9 @@ namespace MWWorld return; } - float terrainHeight = mRendering->getTerrainHeightAt(Ogre::Vector3(pos.pos)); + float terrainHeight = -std::numeric_limits::max(); + if (ptr.getCell()->isExterior()) + terrainHeight = mRendering->getTerrainHeightAt(pos.asVec3()); if (pos.pos[2] < terrainHeight) pos.pos[2] = terrainHeight; @@ -1328,9 +1295,9 @@ namespace MWWorld if (force || !isFlying(ptr)) { - Ogre::Vector3 traced = mPhysics->traceDown(ptr, 500); - if (traced.z < pos.pos[2]) - pos.pos[2] = traced.z; + osg::Vec3f traced = mPhysics->traceDown(ptr, 500); + if (traced.z() < pos.pos[2]) + pos.pos[2] = traced.z(); } moveObject(ptr, ptr.getCell(), pos.pos[0], pos.pos[1], pos.pos[2]); @@ -1343,21 +1310,18 @@ namespace MWWorld pos.pos[2] += dist; actor.getRefData().setPosition(pos); - Ogre::Vector3 traced = mPhysics->traceDown(actor, dist*1.1f); - moveObject(actor, actor.getCell(), traced.x, traced.y, traced.z); + osg::Vec3f traced = mPhysics->traceDown(actor, dist*1.1f); + moveObject(actor, actor.getCell(), traced.x(), traced.y(), traced.z()); } void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust) { - rotateObjectImp(ptr, Ogre::Vector3(Ogre::Degree(x).valueRadians(), - Ogre::Degree(y).valueRadians(), - Ogre::Degree(z).valueRadians()), - adjust); + rotateObjectImp(ptr, osg::Vec3f(x, y, z), adjust); } - MWWorld::Ptr World::safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos) + MWWorld::Ptr World::safePlaceObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) { - return copyObjectToCell(ptr,cell,pos,false); + return copyObjectToCell(ptr,cell,pos,ptr.getRefData().getCount(),false); } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const @@ -1382,7 +1346,7 @@ namespace MWWorld cellY = static_cast(std::floor(y / cellSize)); } - void World::queueMovement(const Ptr &ptr, const Vector3 &velocity) + void World::queueMovement(const Ptr &ptr, const osg::Vec3f &velocity) { mPhysics->queueObjectMovement(ptr, velocity); } @@ -1390,32 +1354,34 @@ namespace MWWorld void World::doPhysics(float duration) { mPhysics->stepSimulation(duration); - processDoors(duration); mProjectileManager->update(duration); - const PtrVelocityList &results = mPhysics->applyQueuedMovement(duration); - PtrVelocityList::const_iterator player(results.end()); - for(PtrVelocityList::const_iterator iter(results.begin());iter != results.end();++iter) + const MWPhysics::PtrVelocityList &results = mPhysics->applyQueuedMovement(duration); + MWPhysics::PtrVelocityList::const_iterator player(results.end()); + for(MWPhysics::PtrVelocityList::const_iterator iter(results.begin());iter != results.end();++iter) { if(iter->first == getPlayerPtr()) { - /* Handle player last, in case a cell transition occurs */ + // Handle player last, in case a cell transition occurs player = iter; continue; } - moveObjectImp(iter->first, iter->second.x, iter->second.y, iter->second.z); + moveObjectImp(iter->first, iter->second.x(), iter->second.y(), iter->second.z()); } if(player != results.end()) - moveObjectImp(player->first, player->second.x, player->second.y, player->second.z); + moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z()); + + mPhysics->debugDraw(); } bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2) { - Ogre::Vector3 a(x1,y1,z1); - Ogre::Vector3 b(x2,y2,z2); - return mPhysics->castRay(a,b,false,true); + osg::Vec3f a(x1,y1,z1); + osg::Vec3f b(x2,y2,z2); + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(a, b, MWWorld::Ptr(), MWPhysics::CollisionType_World|MWPhysics::CollisionType_Door); + return result.mHit; } void World::processDoors(float duration) @@ -1432,30 +1398,36 @@ namespace MWWorld } else { - float oldRot = Ogre::Radian(it->first.getRefData().getLocalRotation().rot[2]).valueDegrees(); - float diff = duration * 90; - float targetRot = std::min(std::max(0.f, oldRot + diff * (it->second == 1 ? 1 : -1)), 90.f); - localRotateObject(it->first, 0, 0, targetRot); + const ESM::Position& objPos = it->first.getRefData().getPosition(); + float oldRot = objPos.rot[2]; - bool reached = (targetRot == 90.f && it->second) || targetRot == 0.f; + float minRot = it->first.getCellRef().getPosition().rot[2]; + float maxRot = minRot + osg::DegreesToRadians(90.f); + + float diff = duration * osg::DegreesToRadians(90.f); + float targetRot = std::min(std::max(minRot, oldRot + diff * (it->second == 1 ? 1 : -1)), maxRot); + rotateObject(it->first, objPos.rot[0], objPos.rot[1], targetRot); + // the rotation order we want to use + mWorldScene->updateObjectRotation(it->first, false); + + bool reached = (targetRot == maxRot && it->second) || targetRot == minRot; /// \todo should use convexSweepTest here - std::vector collisions = mPhysics->getCollisions(it->first, OEngine::Physic::CollisionType_Actor - , OEngine::Physic::CollisionType_Actor); - for (std::vector::iterator cit = collisions.begin(); cit != collisions.end(); ++cit) + std::vector collisions = mPhysics->getCollisions(it->first, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor); + for (std::vector::iterator cit = collisions.begin(); cit != collisions.end(); ++cit) { - MWWorld::Ptr ptr = getPtrViaHandle(*cit); + MWWorld::Ptr ptr = *cit; if (ptr.getClass().isActor()) { // Collided with actor, ask actor to try to avoid door - if(ptr != MWBase::Environment::get().getWorld()->getPlayerPtr() ) { + if(ptr != getPlayerPtr() ) { MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) //Only add it once seq.stack(MWMechanics::AiAvoidDoor(it->first),ptr); } // we need to undo the rotation - localRotateObject(it->first, 0, 0, oldRot); + rotateObject(it->first, objPos.rot[0], objPos.rot[1], oldRot); reached = false; } } @@ -1477,9 +1449,15 @@ namespace MWWorld return mPhysics->toggleCollisionMode(); } - bool World::toggleRenderMode (RenderMode mode) + bool World::toggleRenderMode (MWRender::RenderMode mode) { - return mRendering->toggleRenderMode (mode); + switch (mode) + { + case MWRender::Render_CollisionDebug: + return mPhysics->toggleDebugRendering(); + default: + return mRendering->toggleRenderMode(mode); + } } const ESM::Potion *World::createRecord (const ESM::Potion& record) @@ -1518,17 +1496,6 @@ namespace MWWorld if (Misc::StringUtils::ciEqual(record.mId, "player")) { - std::vector ids; - getStore().get().listIdentifier(ids); - - unsigned int i=0; - - for (; igetPlayer().get()->mBase; @@ -1539,7 +1506,7 @@ namespace MWWorld } const ESM::NPC *ret = mStore.insert(record); if (update) { - mRendering->renderPlayer(mPlayer->getPlayer()); + renderPlayer(); } return ret; } @@ -1581,29 +1548,90 @@ namespace MWWorld mWorldScene->update (duration, paused); - performUpdateSceneQueries (); - updateWindowManager (); updateSoundListener(); - if (!paused && mPlayer->getPlayer().getCell()->isExterior()) + updatePlayer(paused); + } + + void World::updatePlayer(bool paused) + { + MWWorld::Ptr player = getPlayerPtr(); + + // TODO: move to MWWorld::Player + + if (player.getCell()->isExterior()) { - ESM::Position pos = mPlayer->getPlayer().getRefData().getPosition(); - mPlayer->setLastKnownExteriorPosition(Ogre::Vector3(pos.pos)); + ESM::Position pos = player.getRefData().getPosition(); + mPlayer->setLastKnownExteriorPosition(pos.asVec3()); } + + bool isWerewolf = player.getClass().getNpcStats(player).isWerewolf(); + bool isFirstPerson = mRendering->getCamera()->isFirstPerson(); + if (isWerewolf && isFirstPerson) + { + float werewolfFov = mFallback.getFallbackFloat("General_Werewolf_FOV"); + if (werewolfFov != 0) + mRendering->overrideFieldOfView(werewolfFov); + MWBase::Environment::get().getWindowManager()->setWerewolfOverlay(true); + } + else + { + mRendering->resetFieldOfView(); + MWBase::Environment::get().getWindowManager()->setWerewolfOverlay(false); + } + + // Sink the camera while sneaking + bool sneaking = player.getClass().getCreatureStats(getPlayerPtr()).getStance(MWMechanics::CreatureStats::Stance_Sneak); + bool inair = !isOnGround(player); + bool swimming = isSwimming(player); + + static const float i1stPersonSneakDelta = getStore().get().find("i1stPersonSneakDelta")->getFloat(); + if(!paused && sneaking && !(swimming || inair)) + mRendering->getCamera()->setSneakOffset(i1stPersonSneakDelta); + else + mRendering->getCamera()->setSneakOffset(0.f); + + int blind = static_cast(player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Blind).getMagnitude()); + MWBase::Environment::get().getWindowManager()->setBlindness(std::max(0, std::min(100, blind))); + + int nightEye = static_cast(player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::NightEye).getMagnitude()); + mRendering->setNightEyeFactor(std::min(1.f, (nightEye/100.f))); + + mRendering->getCamera()->setCameraDistance(); + if(!mRendering->getCamera()->isFirstPerson()) + { + osg::Vec3f focal, camera; + mRendering->getCamera()->getPosition(focal, camera); + float radius = mRendering->getNearClipDistance()*2.5f; + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castSphere(focal, camera, radius); + if (result.mHit) + mRendering->getCamera()->setCameraDistance((result.mHitPos - focal).length() - radius, false, false); + } + } void World::updateSoundListener() { - Ogre::Vector3 playerPos = mPlayer->getPlayer().getRefData().getBaseNode()->getPosition(); - const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(getPlayerPtr().getRefData().getHandle()); - if(actor) playerPos.z += 1.85f * actor->getHalfExtents().z; - Ogre::Quaternion playerOrient = Ogre::Quaternion(Ogre::Radian(getPlayerPtr().getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * - Ogre::Quaternion(Ogre::Radian(getPlayerPtr().getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X) * - Ogre::Quaternion(Ogre::Radian(getPlayerPtr().getRefData().getPosition().rot[1]), Ogre::Vector3::NEGATIVE_UNIT_Y); - MWBase::Environment::get().getSoundManager()->setListenerPosDir(playerPos, playerOrient.yAxis(), - playerOrient.zAxis()); + const ESM::Position& refpos = getPlayerPtr().getRefData().getPosition(); + osg::Vec3f listenerPos; + + if (isFirstPerson()) + listenerPos = mRendering->getCameraPosition(); + else + listenerPos = refpos.asVec3() + osg::Vec3f(0, 0, 1.85f * mPhysics->getHalfExtents(getPlayerPtr()).z()); + + osg::Quat listenerOrient = osg::Quat(refpos.rot[1], osg::Vec3f(0,-1,0)) * + osg::Quat(refpos.rot[0], osg::Vec3f(-1,0,0)) * + osg::Quat(refpos.rot[2], osg::Vec3f(0,0,-1)); + + osg::Vec3f forward = listenerOrient * osg::Vec3f(0,1,0); + osg::Vec3f up = listenerOrient * osg::Vec3f(0,0,1); + + bool underwater = isUnderwater(getPlayerPtr().getCell(), listenerPos); + + MWBase::Environment::get().getSoundManager()->setListenerPosDir(listenerPos, forward, up, underwater); } void World::updateWindowManager () @@ -1616,55 +1644,27 @@ namespace MWWorld // retrieve object dimensions so we know where to place the floating label if (!object.isEmpty ()) { - Ogre::SceneNode* node = object.getRefData().getBaseNode(); - Ogre::AxisAlignedBox bounds = node->_getWorldAABB(); - if (bounds.isFinite()) - { - Vector4 screenCoords = mRendering->boundingBoxToScreen(bounds); - MWBase::Environment::get().getWindowManager()->setFocusObjectScreenCoords( - screenCoords[0], screenCoords[1], screenCoords[2], screenCoords[3]); - } + osg::Vec4f screenBounds = mRendering->getScreenBounds(object); + + MWBase::Environment::get().getWindowManager()->setFocusObjectScreenCoords( + screenBounds.x(), screenBounds.y(), screenBounds.z(), screenBounds.w()); } } - void World::performUpdateSceneQueries () - { - if (!mRendering->occlusionQuerySupported()) - { - // cast a ray from player to sun to detect if the sun is visible - // this is temporary until we find a better place to put this code - // currently its here because we need to access the physics system - const float* p = mPlayer->getPlayer().getRefData().getPosition().pos; - Vector3 sun = mRendering->getSkyManager()->getRealSunPos(); - mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun)); - } - } - - void World::getFacedHandle(std::string& facedHandle, float maxDistance, bool ignorePlayer) + MWWorld::Ptr World::getFacedObject(float maxDistance, bool ignorePlayer) { maxDistance += mRendering->getCameraDistance(); - std::vector < std::pair < float, std::string > > results; if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { float x, y; MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); - results = mPhysics->getFacedHandles(x, y, maxDistance); + return mRendering->castCameraToViewportRay(x, y, maxDistance, ignorePlayer).mHitObject; } else { - results = mPhysics->getFacedHandles(maxDistance); + return mRendering->castCameraToViewportRay(0.5f, 0.5f, maxDistance, ignorePlayer).mHitObject; } - - if (ignorePlayer && - !results.empty() && results.front().second == "player") - results.erase(results.begin()); - - if (results.empty() - || results.front().second.find("HeightField") != std::string::npos) // Blocked by terrain - facedHandle = ""; - else - facedHandle = results.front().second; } bool World::isCellExterior() const @@ -1705,29 +1705,34 @@ namespace MWWorld mWeatherManager->modRegion(regionid, chances); } - Ogre::Vector2 World::getNorthVector (CellStore* cell) + osg::Vec2f World::getNorthVector (CellStore* cell) { - MWWorld::CellRefList& statics = cell->get(); - MWWorld::LiveCellRef* ref = statics.find("northmarker"); - if (!ref) - return Vector2(0, 1); + MWWorld::Ptr northmarker = cell->search("northmarker"); - Ogre::Quaternion orient (Ogre::Radian(-ref->mData.getPosition().rot[2]), Ogre::Vector3::UNIT_Z); - Vector3 dir = orient * Ogre::Vector3(0,1,0); - Vector2 d = Vector2(dir.x, dir.y); + if (northmarker.isEmpty()) + return osg::Vec2f(0, 1); + + osg::Quat orient (-northmarker.getRefData().getPosition().rot[2], osg::Vec3f(0,0,1)); + osg::Vec3f dir = orient * osg::Vec3f(0,1,0); + osg::Vec2f d (dir.x(), dir.y()); return d; } - void World::getDoorMarkers (CellStore* cell, std::vector& out) + struct GetDoorMarkerVisitor { - MWWorld::CellRefList& doors = cell->get(); - CellRefList::List& refList = doors.mList; - for (CellRefList::List::iterator it = refList.begin(); it != refList.end(); ++it) + GetDoorMarkerVisitor(std::vector& out) + : mOut(out) { - MWWorld::LiveCellRef& ref = *it; + } - if (!ref.mData.isEnabled()) - continue; + std::vector& mOut; + + bool operator()(const MWWorld::Ptr& ptr) + { + MWWorld::LiveCellRef& ref = *static_cast* >(ptr.getBase()); + + if (!ref.mData.isEnabled() || ref.mData.isDeleted()) + return true; if (ref.mRef.getTeleport()) { @@ -1755,24 +1760,16 @@ namespace MWWorld newMarker.x = pos.pos[0]; newMarker.y = pos.pos[1]; - out.push_back(newMarker); + mOut.push_back(newMarker); } + return true; } - } + }; - void World::worldToInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y) + void World::getDoorMarkers (CellStore* cell, std::vector& out) { - mRendering->worldToInteriorMapPosition(position, nX, nY, x, y); - } - - Ogre::Vector2 World::interiorMapToWorldPosition(float nX, float nY, int x, int y) - { - return mRendering->interiorMapToWorldPosition(nX, nY, x, y); - } - - bool World::isPositionExplored (float nX, float nY, int x, int y, bool interior) - { - return mRendering->isPositionExplored(nX, nY, x, y, interior); + GetDoorMarkerVisitor visitor(out); + cell->forEachType(visitor); } void World::setWaterHeight(const float height) @@ -1783,12 +1780,12 @@ namespace MWWorld bool World::toggleWater() { - return mRendering->toggleWater(); + return mRendering->toggleRenderMode(MWRender::Render_Water); } bool World::toggleWorld() { - return mRendering->toggleWorld(); + return mRendering->toggleRenderMode(MWRender::Render_Scene); } void World::PCDropped (const Ptr& item) @@ -1800,28 +1797,27 @@ namespace MWWorld item.getRefData().getLocals().setVarByInt(script, "onpcdrop", 1); } - MWWorld::Ptr World::placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount) + MWWorld::Ptr World::placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) { - std::pair result = mPhysics->castRay(cursorX, cursorY); + const float maxDist = 200.f; - if (!result.first) - return MWWorld::Ptr(); + MWRender::RenderingManager::RayResult result = mRendering->castCameraToViewportRay(cursorX, cursorY, maxDist, true, true); CellStore* cell = getPlayerPtr().getCell(); ESM::Position pos = getPlayerPtr().getRefData().getPosition(); - pos.pos[0] = result.second[0]; - pos.pos[1] = result.second[1]; - pos.pos[2] = result.second[2]; + if (result.mHit) + { + pos.pos[0] = result.mHitPointWorld.x(); + pos.pos[1] = result.mHitPointWorld.y(); + pos.pos[2] = result.mHitPointWorld.z(); + } // We want only the Z part of the player's rotation pos.rot[0] = 0; pos.rot[1] = 0; // copy the object and set its count - int origCount = object.getRefData().getCount(); - object.getRefData().setCount(amount); - Ptr dropped = copyObjectToCell(object, cell, pos, true); - object.getRefData().setCount(origCount); + Ptr dropped = copyObjectToCell(object, cell, pos, amount, true); // only the player place items in the world, so no need to check actor PCDropped(dropped); @@ -1831,18 +1827,13 @@ namespace MWWorld bool World::canPlaceObject(float cursorX, float cursorY) { - Ogre::Vector3 normal(0,0,0); - std::string handle; - std::pair result = mPhysics->castRay(cursorX, cursorY, &normal, &handle); + const float maxDist = 200.f; + MWRender::RenderingManager::RayResult result = mRendering->castCameraToViewportRay(cursorX, cursorY, maxDist, true, true); - if (result.first) + if (result.mHit) { // check if the wanted position is on a flat surface, and not e.g. against a vertical wall - if (normal.angleBetween(Ogre::Vector3(0.f,0.f,1.f)).valueDegrees() >= 30) - return false; - - MWWorld::Ptr hitObject = searchPtrViaHandle(handle); - if (!hitObject.isEmpty() && hitObject.getClass().isActor()) + if (std::acos((result.mHitNormalWorld/result.mHitNormalWorld.length()) * osg::Vec3f(0,0,1)) >= osg::DegreesToRadians(30.f)) return false; return true; @@ -1852,29 +1843,8 @@ namespace MWWorld } - Ptr World::copyObjectToCell(const Ptr &object, CellStore* cell, ESM::Position pos, bool adjustPos) + Ptr World::copyObjectToCell(const ConstPtr &object, CellStore* cell, ESM::Position pos, int count, bool adjustPos) { - if (!object.getClass().isActor() && adjustPos) - { - // Adjust position so the location we wanted ends up in the middle of the object bounding box - Ogre::Vector3 min, max; - if (mPhysics->getObjectAABB(object, min, max)) { - Ogre::Quaternion xr(Ogre::Radian(-pos.rot[0]), Ogre::Vector3::UNIT_X); - Ogre::Quaternion yr(Ogre::Radian(-pos.rot[1]), Ogre::Vector3::UNIT_Y); - Ogre::Quaternion zr(Ogre::Radian(-pos.rot[2]), Ogre::Vector3::UNIT_Z); - - Ogre::Vector3 adjust ( - (min.x + max.x) / 2, - (min.y + max.y) / 2, - min.z - ); - adjust = (xr*yr*zr) * adjust; - pos.pos[0] -= adjust.x; - pos.pos[1] -= adjust.y; - pos.pos[2] -= adjust.z; - } - } - if (cell->isExterior()) { int cellX, cellY; @@ -1883,14 +1853,9 @@ namespace MWWorld } MWWorld::Ptr dropped = - object.getClass().copyToCell(object, *cell, pos); + object.getClass().copyToCell(object, *cell, pos, count); // Reset some position values that could be uninitialized if this item came from a container - LocalRotation localRotation; - localRotation.rot[0] = 0; - localRotation.rot[1] = 0; - localRotation.rot[2] = 0; - dropped.getRefData().setLocalRotation(localRotation); dropped.getCellRef().setPosition(pos); dropped.getCellRef().unsetRefNum(); @@ -1905,10 +1870,33 @@ namespace MWWorld addContainerScripts(dropped, cell); } + if (!object.getClass().isActor() && adjustPos && dropped.getRefData().getBaseNode()) + { + // Adjust position so the location we wanted ends up in the middle of the object bounding box + osg::ComputeBoundsVisitor computeBounds; + computeBounds.setTraversalMask(~MWRender::Mask_ParticleSystem); + dropped.getRefData().getBaseNode()->accept(computeBounds); + osg::BoundingBox bounds = computeBounds.getBoundingBox(); + if (bounds.valid()) + { + bounds.set(bounds._min - pos.asVec3(), bounds._max - pos.asVec3()); + + osg::Vec3f adjust ( + (bounds.xMin() + bounds.xMax()) / 2, + (bounds.yMin() + bounds.yMax()) / 2, + bounds.zMin() + ); + pos.pos[0] -= adjust.x(); + pos.pos[1] -= adjust.y(); + pos.pos[2] -= adjust.z(); + moveObject(dropped, pos.pos[0], pos.pos[1], pos.pos[2]); + } + } + return dropped; } - MWWorld::Ptr World::dropObjectOnGround (const Ptr& actor, const Ptr& object, int amount) + MWWorld::Ptr World::dropObjectOnGround (const Ptr& actor, const ConstPtr& object, int amount) { MWWorld::CellStore* cell = actor.getCell(); @@ -1918,23 +1906,18 @@ namespace MWWorld pos.rot[0] = 0; pos.rot[1] = 0; - Ogre::Vector3 orig = - Ogre::Vector3(pos.pos); - orig.z += 20; - Ogre::Vector3 dir = Ogre::Vector3(0, 0, -1); + osg::Vec3f orig = pos.asVec3(); + orig.z() += 20; + osg::Vec3f dir (0, 0, -1); - float len = 100.0; + float len = 1000000.0; - std::pair hit = - mPhysics->castRay(orig, dir, len); - if (hit.first) - pos.pos[2] = hit.second.z; + MWRender::RenderingManager::RayResult result = mRendering->castRay(orig, orig+dir*len, true, true); + if (result.mHit) + pos.pos[2] = result.mHitPointWorld.z(); // copy the object and set its count - int origCount = object.getRefData().getCount(); - object.getRefData().setCount(amount); - Ptr dropped = copyObjectToCell(object, cell, pos); - object.getRefData().setCount(origCount); + Ptr dropped = copyObjectToCell(object, cell, pos, amount, true); if(actor == mPlayer->getPlayer()) // Only call if dropped by player PCDropped(dropped); @@ -1946,30 +1929,24 @@ namespace MWWorld mRendering->processChangedSettings(settings); } - void World::getTriangleBatchCount(unsigned int &triangles, unsigned int &batches) - { - mRendering->getTriangleBatchCount(triangles, batches); - } - bool World::isFlying(const MWWorld::Ptr &ptr) const { const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); - bool isParalyzed = (stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0); if(!ptr.getClass().isActor()) return false; - if (ptr.getClass().getCreatureStats(ptr).isDead()) + if (stats.isDead()) return false; if (ptr.getClass().canFly(ptr)) - return !isParalyzed; + return !stats.isParalyzed(); if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0 && isLevitationEnabled()) return true; - const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(ptr.getRefData().getHandle()); + const MWPhysics::Actor* actor = mPhysics->getActor(ptr); if(!actor || !actor->getCollisionMode()) return true; @@ -1988,81 +1965,77 @@ namespace MWWorld return false; } - bool World::isSubmerged(const MWWorld::Ptr &object) const + bool World::isSubmerged(const MWWorld::ConstPtr &object) const { return isUnderwater(object, 1.0f/mSwimHeightScale); } - bool World::isSwimming(const MWWorld::Ptr &object) const + bool World::isSwimming(const MWWorld::ConstPtr &object) const { return isUnderwater(object, mSwimHeightScale); } - bool World::isWading(const MWWorld::Ptr &object) const + bool World::isWading(const MWWorld::ConstPtr &object) const { const float kneeDeep = 0.25f; return isUnderwater(object, kneeDeep); } - bool World::isUnderwater(const MWWorld::Ptr &object, const float heightRatio) const + bool World::isUnderwater(const MWWorld::ConstPtr &object, const float heightRatio) const { - const float *fpos = object.getRefData().getPosition().pos; - Ogre::Vector3 pos(fpos[0], fpos[1], fpos[2]); + osg::Vec3f pos (object.getRefData().getPosition().asVec3()); - const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(object.getRefData().getHandle()); - if (actor) - { - pos.z += heightRatio*2*actor->getHalfExtents().z; - } + pos.z() += heightRatio*2*mPhysics->getRenderingHalfExtents(object).z(); return isUnderwater(object.getCell(), pos); } - bool World::isUnderwater(const MWWorld::CellStore* cell, const Ogre::Vector3 &pos) const + bool World::isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const { if (!(cell->getCell()->mData.mFlags & ESM::Cell::HasWater)) { return false; } - return pos.z < cell->getWaterLevel(); + return pos.z() < cell->getWaterLevel(); } - // physactor->getOnGround() is not a reliable indicator of whether the actor - // is on the ground (defaults to false, which means code blocks such as - // CharacterController::update() may falsely detect "falling"). - // - // Also, collisions can move z position slightly off zero, giving a false - // indication. In order to reduce false detection of jumping, small distance - // below the actor is detected and ignored. A value of 1.5 is used here, but - // something larger may be more suitable. This change should resolve Bug#1271. - // - // TODO: There might be better places to update PhysicActor::mOnGround. bool World::isOnGround(const MWWorld::Ptr &ptr) const { - RefData &refdata = ptr.getRefData(); - OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle()); + return mPhysics->isOnGround(ptr); + } - if(!physactor) - return false; + void World::togglePOV() + { + mRendering->togglePOV(); + } - if(physactor->getOnGround()) - return true; - else - { - Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); - OEngine::Physic::ActorTracer tracer; - // a small distance above collision object is considered "on ground" - tracer.findGround(physactor, - pos, - pos - Ogre::Vector3(0, 0, 1.5f), // trace a small amount down - mPhysEngine); - if(tracer.mFraction < 1.0f) // collision, must be close to something below - { - physactor->setOnGround(true); - return true; - } - else - return false; - } + bool World::isFirstPerson() const + { + return mRendering->getCamera()->isFirstPerson(); + } + + void World::togglePreviewMode(bool enable) + { + mRendering->togglePreviewMode(enable); + } + + bool World::toggleVanityMode(bool enable) + { + return mRendering->toggleVanityMode(enable); + } + + void World::allowVanityMode(bool allow) + { + mRendering->allowVanityMode(allow); + } + + void World::togglePlayerLooking(bool enable) + { + mRendering->togglePlayerLooking(enable); + } + + void World::changeVanityModeScale(float factor) + { + mRendering->changeVanityModeScale(factor); } bool World::vanityRotateCamera(float * rot) @@ -2072,18 +2045,20 @@ namespace MWWorld void World::setCameraDistance(float dist, bool adjust, bool override_) { - return mRendering->setCameraDistance(dist, adjust, override_); + mRendering->setCameraDistance(dist, adjust, override_); } void World::setupPlayer() { const ESM::NPC *player = mStore.get().find("player"); if (!mPlayer) - mPlayer = new MWWorld::Player(player, *this); + mPlayer = new MWWorld::Player(player); else { // Remove the old CharacterController MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr()); + mPhysics->remove(getPlayerPtr()); + mRendering->removePlayer(getPlayerPtr()); mPlayer->set(player); } @@ -2094,16 +2069,19 @@ namespace MWWorld void World::renderPlayer() { - mRendering->renderPlayer(mPlayer->getPlayer()); + MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr()); - // At this point the Animation object is live, and the CharacterController associated with it must be created. - // It has to be done at this point: resetCamera below does animation->setViewMode -> CharacterController::forceStateUpdate - // so we should make sure not to use a "stale" controller for that. - MWBase::Environment::get().getMechanicsManager()->add(mPlayer->getPlayer()); + mRendering->renderPlayer(getPlayerPtr()); + + scaleObject(getPlayerPtr(), 1.f); // apply race height + + MWBase::Environment::get().getMechanicsManager()->add(getPlayerPtr()); + MWBase::Environment::get().getMechanicsManager()->watchActor(getPlayerPtr()); std::string model = getPlayerPtr().getClass().getModel(getPlayerPtr()); - model = Misc::ResourceHelpers::correctActorModelPath(model); - mPhysics->addActor(mPlayer->getPlayer(), model); + model = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS()); + mPhysics->remove(getPlayerPtr()); + mPhysics->addActor(getPlayerPtr(), model); } int World::canRest () @@ -2112,16 +2090,16 @@ namespace MWWorld Ptr player = mPlayer->getPlayer(); RefData &refdata = player.getRefData(); - Ogre::Vector3 playerPos(refdata.getPosition().pos); + osg::Vec3f playerPos(refdata.getPosition().asVec3()); - const OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle()); - if (!physactor) + const MWPhysics::Actor* actor = mPhysics->getActor(player); + if (!actor) throw std::runtime_error("can't find player"); - if((!physactor->getOnGround()&&physactor->getCollisionMode()) || isUnderwater(currentCell, playerPos) || isWalkingOnWater(player)) + if ((actor->getCollisionMode() && !mPhysics->isOnSolidGround(player)) || isUnderwater(currentCell, playerPos)) return 2; - if((currentCell->getCell()->mData.mFlags&ESM::Cell::NoSleep) || - player.getClass().getNpcStats(player).isWerewolf()) + + if((currentCell->getCell()->mData.mFlags&ESM::Cell::NoSleep) || player.getClass().getNpcStats(player).isWerewolf()) return 1; return 0; @@ -2132,12 +2110,12 @@ namespace MWWorld return mRendering->getAnimation(ptr); } - void World::frameStarted (float dt, bool paused) + const MWRender::Animation* World::getAnimation(const MWWorld::ConstPtr &ptr) const { - mRendering->frameStarted(dt, paused); + return mRendering->getAnimation(ptr); } - void World::screenshot(Ogre::Image &image, int w, int h) + void World::screenshot(osg::Image* image, int w, int h) { mRendering->screenshot(image, w, h); } @@ -2148,7 +2126,7 @@ namespace MWWorld switch (state) { case 0: - if (door.getRefData().getLocalRotation().rot[2] == 0) + if (door.getRefData().getPosition().rot[2] == door.getCellRef().getPosition().rot[2]) state = 1; // if closed, then open else state = 2; // if open, then close @@ -2173,45 +2151,42 @@ namespace MWWorld mDoorStates.erase(door); } - bool World::getPlayerStandingOn (const MWWorld::Ptr& object) + bool World::getPlayerStandingOn (const MWWorld::ConstPtr& object) { MWWorld::Ptr player = getPlayerPtr(); return mPhysics->isActorStandingOn(player, object); } - bool World::getActorStandingOn (const MWWorld::Ptr& object) + bool World::getActorStandingOn (const MWWorld::ConstPtr& object) { - std::vector actors; + std::vector actors; mPhysics->getActorsStandingOn(object, actors); return !actors.empty(); } - bool World::getPlayerCollidingWith (const MWWorld::Ptr& object) + bool World::getPlayerCollidingWith (const MWWorld::ConstPtr& object) { MWWorld::Ptr player = getPlayerPtr(); return mPhysics->isActorCollidingWith(player, object); } - bool World::getActorCollidingWith (const MWWorld::Ptr& object) + bool World::getActorCollidingWith (const MWWorld::ConstPtr& object) { - std::vector actors; + std::vector actors; mPhysics->getActorsCollidingWith(object, actors); return !actors.empty(); } - void World::hurtStandingActors(const Ptr &object, float healthPerSecond) + void World::hurtStandingActors(const ConstPtr &object, float healthPerSecond) { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; - std::vector actors; + std::vector actors; mPhysics->getActorsStandingOn(object, actors); - for (std::vector::iterator it = actors.begin(); it != actors.end(); ++it) + for (std::vector::iterator it = actors.begin(); it != actors.end(); ++it) { - MWWorld::Ptr actor = searchPtrViaHandle(*it); // Collision events are from the last frame, actor might no longer exist - if (actor.isEmpty()) - continue; - + MWWorld::Ptr actor = *it; MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); if (stats.isDead()) continue; @@ -2219,6 +2194,8 @@ namespace MWWorld health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); stats.setHealth(health); + mPhysics->markAsNonSolid (object); + if (healthPerSecond > 0.0f) { if (actor == getPlayerPtr()) @@ -2230,19 +2207,16 @@ namespace MWWorld } } - void World::hurtCollidingActors(const Ptr &object, float healthPerSecond) + void World::hurtCollidingActors(const ConstPtr &object, float healthPerSecond) { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; - std::vector actors; + std::vector actors; mPhysics->getActorsCollidingWith(object, actors); - for (std::vector::iterator it = actors.begin(); it != actors.end(); ++it) + for (std::vector::iterator it = actors.begin(); it != actors.end(); ++it) { - MWWorld::Ptr actor = searchPtrViaHandle(*it); // Collision events are from the last frame, actor might no longer exist - if (actor.isEmpty()) - continue; - + MWWorld::Ptr actor = *it; MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); if (stats.isDead()) continue; @@ -2250,6 +2224,8 @@ namespace MWWorld health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); stats.setHealth(health); + mPhysics->markAsNonSolid (object); + if (healthPerSecond > 0.0f) { if (actor == getPlayerPtr()) @@ -2277,102 +2253,101 @@ namespace MWWorld return false; } - Ogre::Vector3 World::getStormDirection() const + osg::Vec3f World::getStormDirection() const { if (isCellExterior() || isCellQuasiExterior()) return mWeatherManager->getStormDirection(); else - return Ogre::Vector3(0,1,0); + return osg::Vec3f(0,1,0); } - void World::getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out) + struct GetContainersOwnedByVisitor { - const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); - for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt) + GetContainersOwnedByVisitor(const MWWorld::ConstPtr& owner, std::vector& out) + : mOwner(owner) + , mOut(out) { - MWWorld::CellRefList& containers = (*cellIt)->get(); - CellRefList::List& refList = containers.mList; - for (CellRefList::List::iterator container = refList.begin(); container != refList.end(); ++container) - { - MWWorld::Ptr ptr (&*container, *cellIt); - if (ptr.getRefData().isDeleted()) - continue; - if (Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), npc.getCellRef().getRefId())) - out.push_back(ptr); - } } - } - struct ListHandlesFunctor - { - std::vector mHandles; + MWWorld::ConstPtr mOwner; + std::vector& mOut; - bool operator() (Ptr ptr) + bool operator()(const MWWorld::Ptr& ptr) { - Ogre::SceneNode* handle = ptr.getRefData().getBaseNode(); - if (handle) - mHandles.push_back(handle->getName()); + if (ptr.getRefData().isDeleted()) + return true; + + if (Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), mOwner.getCellRef().getRefId())) + mOut.push_back(ptr); + return true; } }; - void World::getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out) + void World::getContainersOwnedBy (const MWWorld::ConstPtr& owner, std::vector& out) { const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt) { - ListHandlesFunctor functor; - (*cellIt)->forEach(functor); - - for (std::vector::iterator it = functor.mHandles.begin(); it != functor.mHandles.end(); ++it) - if (Misc::StringUtils::ciEqual(searchPtrViaHandle(*it).getCellRef().getOwner(), npc.getCellRef().getRefId())) - out.push_back(searchPtrViaHandle(*it)); + GetContainersOwnedByVisitor visitor (owner, out); + (*cellIt)->forEachType(visitor); } } - bool World::getLOS(const MWWorld::Ptr& actor,const MWWorld::Ptr& targetActor) + struct ListObjectsVisitor + { + std::vector mObjects; + + bool operator() (Ptr ptr) + { + if (ptr.getRefData().getBaseNode()) + mObjects.push_back(ptr); + return true; + } + }; + + void World::getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out) + { + const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); + for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt) + { + ListObjectsVisitor visitor; + (*cellIt)->forEach(visitor); + + for (std::vector::iterator it = visitor.mObjects.begin(); it != visitor.mObjects.end(); ++it) + if (Misc::StringUtils::ciEqual(it->getCellRef().getOwner(), npc.getCellRef().getRefId())) + out.push_back(*it); + } + } + + bool World::getLOS(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& targetActor) { if (!targetActor.getRefData().isEnabled() || !actor.getRefData().isEnabled()) return false; // cannot get LOS unless both NPC's are enabled - if (!targetActor.getRefData().getBaseNode() || !targetActor.getRefData().getBaseNode()) + if (!targetActor.getRefData().getBaseNode() || !actor.getRefData().getBaseNode()) return false; // not in active cell - OEngine::Physic::PhysicActor* actor1 = mPhysEngine->getCharacter(actor.getRefData().getHandle()); - OEngine::Physic::PhysicActor* actor2 = mPhysEngine->getCharacter(targetActor.getRefData().getHandle()); - - if (!actor1 || !actor2) - return false; - - Ogre::Vector3 halfExt1 = actor1->getHalfExtents(); - const float* pos1 = actor.getRefData().getPosition().pos; - Ogre::Vector3 halfExt2 = actor2->getHalfExtents(); - const float* pos2 = targetActor.getRefData().getPosition().pos; - - btVector3 from(pos1[0],pos1[1],pos1[2]+halfExt1.z*2*0.9f); // eye level - btVector3 to(pos2[0],pos2[1],pos2[2]+halfExt2.z*2*0.9f); - - std::pair result = mPhysEngine->rayTest(from, to,false); - if(result.first == "") return true; - - return false; + return mPhysics->getLineOfSight(actor, targetActor); } - float World::getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist) + float World::getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist) { - btVector3 btFrom(from.x, from.y, from.z); - btVector3 btTo = btVector3(dir.x, dir.y, dir.z); - btTo.normalize(); - btTo = btFrom + btTo * maxDist; + osg::Vec3f to (dir); + to.normalize(); + to = from + (to * maxDist); - std::pair result = mPhysEngine->rayTest(btFrom, btTo, false); + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(from, to, MWWorld::Ptr(), + MWPhysics::CollisionType_World|MWPhysics::CollisionType_HeightMap|MWPhysics::CollisionType_Door); - if(result.second == -1) return maxDist; - else return result.second*(btTo-btFrom).length(); + if (!result.mHit) + return maxDist; + else + return (result.mHitPos - from).length(); } void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable) { - OEngine::Physic::PhysicActor *physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle()); + MWPhysics::Actor *physicActor = mPhysics->getActor(actor); if (physicActor) physicActor->enableCollisionBody(enable); } @@ -2390,7 +2365,8 @@ namespace MWWorld if (0 == cellStore) { return false; } - const DoorList &doors = cellStore->get().mList; + + const DoorList &doors = cellStore->getReadOnlyDoors().mList; for (DoorList::const_iterator it = doors.begin(); it != doors.end(); ++it) { if (!it->mRef.getTeleport()) { continue; @@ -2412,7 +2388,7 @@ namespace MWWorld if (0 != source) { // Find door leading to our current teleport door // and use it destination to position inside cell. - const DoorList &doors = source->get().mList; + const DoorList &doors = source->getReadOnlyDoors().mList; for (DoorList::const_iterator jt = doors.begin(); jt != doors.end(); ++jt) { if (it->mRef.getTeleport() && Misc::StringUtils::ciEqual(name, jt->mRef.getDestCell())) @@ -2426,7 +2402,7 @@ namespace MWWorld } } // Fall back to the first static location. - const StaticList &statics = cellStore->get().mList; + const StaticList &statics = cellStore->getReadOnlyStatics().mList; if ( statics.begin() != statics.end() ) { pos = statics.begin()->mRef.getPosition(); return true; @@ -2477,100 +2453,6 @@ namespace MWWorld mRendering->rebuildPtr(getPlayerPtr()); } - void World::setWerewolf(const MWWorld::Ptr& actor, bool werewolf) - { - MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats(actor); - - // The actor does not have to change state - if (npcStats.isWerewolf() == werewolf) - return; - - npcStats.setWerewolf(werewolf); - - // This is a bit dangerous. Equipped items other than WerewolfRobe may reference - // bones that do not even exist with the werewolf object root. - // Therefore, make sure to unequip everything at once, and only fire the change event - // (which will rebuild the animation parts) afterwards. unequipAll will do this for us. - MWWorld::InventoryStore& invStore = actor.getClass().getInventoryStore(actor); - invStore.unequipAll(actor); - - if(werewolf) - { - InventoryStore &inv = actor.getClass().getInventoryStore(actor); - - inv.equip(InventoryStore::Slot_Robe, inv.ContainerStore::add("werewolfrobe", 1, actor), actor); - } - else - { - actor.getClass().getContainerStore(actor).remove("werewolfrobe", 1, actor); - } - - // NpcAnimation::updateParts will already rebuild the animation when it detects change of Npc type. - // the following is just for reattaching the camera properly. - mRendering->rebuildPtr(actor); - - if(actor == getPlayerPtr()) - { - // Update the GUI only when called on the player - MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); - - if (werewolf) - { - windowManager->forceHide(MWGui::GW_Inventory); - windowManager->forceHide(MWGui::GW_Magic); - } - else - { - windowManager->unsetForceHide(MWGui::GW_Inventory); - windowManager->unsetForceHide(MWGui::GW_Magic); - } - - windowManager->setWerewolfOverlay(werewolf); - - // Witnesses of the player's transformation will make them a globally known werewolf - std::vector closeActors; - MWBase::Environment::get().getMechanicsManager()->getActorsInRange(Ogre::Vector3(actor.getRefData().getPosition().pos), - getStore().get().search("fAlarmRadius")->getFloat(), - closeActors); - - bool detected = false, reported = false; - for (std::vector::const_iterator it = closeActors.begin(); it != closeActors.end(); ++it) - { - if (*it == actor) - continue; - - if (!it->getClass().isNpc()) - continue; - - if (getLOS(*it, actor) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, *it)) - detected = true; - if (it->getClass().getCreatureStats(*it).getAiSetting(MWMechanics::CreatureStats::AI_Alarm).getModified() > 0) - reported = true; - } - - if (detected) - { - windowManager->messageBox("#{sWerewolfAlarmMessage}"); - setGlobalInt("pcknownwerewolf", 1); - - if (reported) - { - npcStats.setBounty(npcStats.getBounty()+ - MWBase::Environment::get().getWorld()->getStore().get().find("iWereWolfBounty")->getInt()); - windowManager->messageBox("#{sCrimeMessage}"); - } - } - } - } - - void World::applyWerewolfAcrobatics(const Ptr &actor) - { - const Store &gmst = getStore().get(); - MWMechanics::NpcStats &stats = actor.getClass().getNpcStats(actor); - - stats.getSkill(ESM::Skill::Acrobatics).setBase(gmst.find("fWerewolfAcrobatics")->getInt()); - } - bool World::getGodModeState() { return mGodMode; @@ -2639,7 +2521,7 @@ namespace MWWorld } // If this is a power, check if it was already used in the last 24h - if (!fail && spell->mData.mType == ESM::Spell::ST_Power && !stats.getSpells().canUsePower(spell->mId)) + if (!fail && spell->mData.mType == ESM::Spell::ST_Power && !stats.getSpells().canUsePower(spell)) { message = "#{sPowerAlreadyUsed}"; fail = true; @@ -2663,64 +2545,55 @@ namespace MWWorld { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - // Get the target to use for "on touch" effects + // Get the target to use for "on touch" effects, using the facing direction from Head node MWWorld::Ptr target; float distance = 192.f; // ?? + osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); + osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); - if (actor == getPlayerPtr()) + osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) + * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); + + osg::Vec3f direction = orient * osg::Vec3f(0,1,0); + osg::Vec3f dest = origin + direction * distance; + + // For actor targets, we want to use bounding boxes (physics raycast). + // This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise. + // For object targets, we want the detailed shapes (rendering raycast). + // If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf. + + MWPhysics::PhysicsSystem::RayResult result1 = mPhysics->castRay(origin, dest, actor, MWPhysics::CollisionType_Actor); + + MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); + + float dist1 = std::numeric_limits::max(); + float dist2 = std::numeric_limits::max(); + + if (result1.mHit) + dist1 = (origin - result1.mHitPos).length(); + if (result2.mHit) + dist2 = (origin - result2.mHitPointWorld).length(); + + if (dist1 <= dist2 && result1.mHit) { - // For the player, use camera to aim - std::string facedHandle; - getFacedHandle(facedHandle, distance); - if (!facedHandle.empty()) - target = getPtrViaHandle(facedHandle); + target = result1.mHitObject; + hitPosition = result1.mHitPos; } - else + else if (result2.mHit) { - // For NPCs use facing direction from Head node - Ogre::Vector3 origin(actor.getRefData().getPosition().pos); - MWRender::Animation *anim = mRendering->getAnimation(actor); - if(anim != NULL) - { - Ogre::Node *node = anim->getNode("Head"); - if (node == NULL) - node = anim->getNode("Bip01 Head"); - if(node != NULL) - origin += node->_getDerivedPosition(); - } - Ogre::Quaternion orient; - orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * - Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); - Ogre::Vector3 direction = orient.yAxis(); - Ogre::Vector3 dest = origin + direction * distance; - - - std::vector > collisions = mPhysEngine->rayTest2(btVector3(origin.x, origin.y, origin.z), btVector3(dest.x, dest.y, dest.z)); - for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end(); ++cIt) - { - MWWorld::Ptr collided = getPtrViaHandle(cIt->second); - if (collided != actor) - { - target = collided; - break; - } - } + target = result2.mHitObject; + hitPosition = result2.mHitPointWorld; } std::string selectedSpell = stats.getSpells().getSelectedSpell(); MWMechanics::CastSpell cast(actor, target); - if (!target.isEmpty()) - cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos); + cast.mHitPosition = hitPosition; if (!selectedSpell.empty()) { const ESM::Spell* spell = getStore().get().find(selectedSpell); - // A power can be used once per 24h - if (spell->mData.mType == ESM::Spell::ST_Power) - stats.getSpells().usePower(spell->mId); - cast.cast(spell); } else if (actor.getClass().hasInventoryStore(actor)) @@ -2731,15 +2604,15 @@ namespace MWWorld } } - void World::launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, - const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed) + void World::launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, + const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) { - mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed); + mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); } void World::launchMagicBolt (const std::string& model, const std::string &sound, const std::string &spellId, float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, const std::string& sourceName, const Ogre::Vector3& fallbackDirection) + const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) { mProjectileManager->launchMagicBolt(model, sound, spellId, speed, stack, effects, caster, sourceName, fallbackDirection); } @@ -2774,7 +2647,7 @@ namespace MWWorld } } - bool World::findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result) + bool World::findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, osg::Vec3f& result) { if (cell->isExterior()) return false; @@ -2793,7 +2666,7 @@ namespace MWWorld MWWorld::CellStore *next = getInterior( *i ); if ( !next ) continue; - const MWWorld::CellRefList& doors = next->getReadOnly(); + const MWWorld::CellRefList& doors = next->getReadOnlyDoors(); const CellRefList::List& refList = doors.mList; // Check if any door in the cell leads to an exterior directly @@ -2805,7 +2678,7 @@ namespace MWWorld if (ref.mRef.getDestCell().empty()) { ESM::Position pos = ref.mRef.getDoorDest(); - result = Ogre::Vector3(pos.pos); + result = pos.asVec3(); return true; } else @@ -2824,7 +2697,7 @@ namespace MWWorld return false; } - MWWorld::Ptr World::getClosestMarker( const MWWorld::Ptr &ptr, const std::string &id ) + MWWorld::ConstPtr World::getClosestMarker( const MWWorld::Ptr &ptr, const std::string &id ) { if ( ptr.getCell()->isExterior() ) { return getClosestMarkerFromExteriorPosition(mPlayer->getLastKnownExteriorPosition(), id); @@ -2836,7 +2709,7 @@ namespace MWWorld std::set< std::string >checkedCells; std::set< std::string >currentCells; std::set< std::string >nextCells; - MWWorld::Ptr closestMarker; + MWWorld::ConstPtr closestMarker; nextCells.insert( ptr.getCell()->getCell()->mName ); while ( !nextCells.empty() ) { @@ -2847,13 +2720,13 @@ namespace MWWorld checkedCells.insert( *i ); if ( !next ) continue; - closestMarker = next->search( id ); + closestMarker = next->searchConst( id ); if ( !closestMarker.isEmpty() ) { return closestMarker; } - const MWWorld::CellRefList& doors = next->getReadOnly(); + const MWWorld::CellRefList& doors = next->getReadOnlyDoors(); const CellRefList::List& doorList = doors.mList; // Check if any door in the cell leads to an exterior directly @@ -2865,7 +2738,7 @@ namespace MWWorld if (ref.mRef.getDestCell().empty()) { - Ogre::Vector3 worldPos = Ogre::Vector3(ref.mRef.getDoorDest().pos); + osg::Vec3f worldPos = ref.mRef.getDoorDest().asVec3(); return getClosestMarkerFromExteriorPosition(worldPos, id); } else @@ -2877,21 +2750,20 @@ namespace MWWorld } } } - return MWWorld::Ptr(); } - MWWorld::Ptr World::getClosestMarkerFromExteriorPosition( const Ogre::Vector3 worldPos, const std::string &id ) { - MWWorld::Ptr closestMarker; - float closestDistance = FLT_MAX; + MWWorld::ConstPtr World::getClosestMarkerFromExteriorPosition( const osg::Vec3f& worldPos, const std::string &id ) { + MWWorld::ConstPtr closestMarker; + float closestDistance = std::numeric_limits::max(); std::vector markers; mCells.getExteriorPtrs(id, markers); for (std::vector::iterator it2 = markers.begin(); it2 != markers.end(); ++it2) { ESM::Position pos = it2->getRefData().getPosition(); - Ogre::Vector3 markerPos = Ogre::Vector3(pos.pos); - float distance = worldPos.squaredDistance(markerPos); + osg::Vec3f markerPos = pos.asVec3(); + float distance = (worldPos - markerPos).length2(); if (distance < closestDistance) { closestDistance = distance; @@ -2907,7 +2779,7 @@ namespace MWWorld void World::teleportToClosestMarker (const MWWorld::Ptr& ptr, const std::string& id) { - MWWorld::Ptr closestMarker = getClosestMarker( ptr, id ); + MWWorld::ConstPtr closestMarker = getClosestMarker( ptr, id ); if ( closestMarker.isEmpty() ) { @@ -2922,21 +2794,21 @@ namespace MWWorld MWWorld::ActionTeleport action(cellName, closestMarker.getRefData().getPosition(), false); action.execute(ptr); } - + void World::updateWeather(float duration, bool paused) { if (mPlayer->wasTeleported()) { mPlayer->setTeleported(false); - mWeatherManager->switchToNextWeather(true); + mWeatherManager->playerTeleported(); } - + mWeatherManager->update(duration, paused); } - struct AddDetectedReference + struct AddDetectedReferenceVisitor { - AddDetectedReference(std::vector& out, Ptr detector, World::DetectionType type, float squaredDist) + AddDetectedReferenceVisitor(std::vector& out, Ptr detector, World::DetectionType type, float squaredDist) : mOut(out), mDetector(detector), mSquaredDist(squaredDist), mType(type) { } @@ -2945,10 +2817,9 @@ namespace MWWorld Ptr mDetector; float mSquaredDist; World::DetectionType mType; - bool operator() (MWWorld::Ptr ptr) + bool operator() (const MWWorld::Ptr& ptr) { - if (Ogre::Vector3(ptr.getRefData().getPosition().pos).squaredDistance( - Ogre::Vector3(mDetector.getRefData().getPosition().pos)) >= mSquaredDist) + if ((ptr.getRefData().getPosition().asVec3() - mDetector.getRefData().getPosition().asVec3()).length2() >= mSquaredDist) return true; if (!ptr.getRefData().isEnabled() || ptr.getRefData().isDeleted()) @@ -2977,7 +2848,7 @@ namespace MWWorld return true; } - bool needToAdd (MWWorld::Ptr ptr, MWWorld::Ptr detector) + bool needToAdd (const MWWorld::Ptr& ptr, const MWWorld::Ptr& detector) { if (mType == World::Detect_Creature) { @@ -3017,13 +2888,13 @@ namespace MWWorld dist = feetToGameUnits(dist); - AddDetectedReference functor (out, ptr, type, dist*dist); + AddDetectedReferenceVisitor visitor (out, ptr, type, dist*dist); const Scene::CellStoreCollection& active = mWorldScene->getActiveCells(); for (Scene::CellStoreCollection::const_iterator it = active.begin(); it != active.end(); ++it) { MWWorld::CellStore* cellStore = *it; - cellStore->forEach(functor); + cellStore->forEach(visitor); } } @@ -3068,13 +2939,13 @@ namespace MWWorld void World::confiscateStolenItems(const Ptr &ptr) { - MWWorld::Ptr prisonMarker = getClosestMarker( ptr, "prisonmarker" ); + MWWorld::ConstPtr prisonMarker = getClosestMarker( ptr, "prisonmarker" ); if ( prisonMarker.isEmpty() ) { std::cerr << "Failed to confiscate items: no closest prison marker found." << std::endl; return; } - std::string prisonName = prisonMarker.mRef->mRef.getDestCell(); + std::string prisonName = prisonMarker.getCellRef().getDestCell(); if ( prisonName.empty() ) { std::cerr << "Failed to confiscate items: prison marker not linked to prison interior" << std::endl; @@ -3130,7 +3001,7 @@ namespace MWWorld const ESM::CreatureLevList* list = getStore().get().find(creatureList); int iNumberCreatures = getStore().get().find("iNumberCreatures")->getInt(); - int numCreatures = 1 + OEngine::Misc::Rng::rollDice(iNumberCreatures); // [1, iNumberCreatures] + int numCreatures = 1 + Misc::Rng::rollDice(iNumberCreatures); // [1, iNumberCreatures] for (int i=0; igetPlayer().getRefData().getPosition(); - Ogre::Vector3 pos(ipos.pos); - Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z); + osg::Vec3f pos(ipos.asVec3()); + osg::Quat rot(-ipos.rot[2], osg::Vec3f(0,0,1)); const float distance = 50; - pos = pos + distance*rot.yAxis(); - ipos.pos[0] = pos.x; - ipos.pos[1] = pos.y; - ipos.pos[2] = pos.z; + pos = pos + (rot * osg::Vec3f(0,1,0)) * distance; + ipos.pos[0] = pos.x(); + ipos.pos[1] = pos.y(); + ipos.pos[2] = pos.z(); ipos.rot[0] = 0; ipos.rot[1] = 0; ipos.rot[2] = 0; @@ -3158,7 +3029,7 @@ namespace MWWorld } } - void World::spawnBloodEffect(const Ptr &ptr, const Vector3 &worldPosition) + void World::spawnBloodEffect(const Ptr &ptr, const osg::Vec3f &worldPosition) { if (ptr == getPlayerPtr() && Settings::Manager::getBool("hit fader", "GUI")) return; @@ -3181,19 +3052,19 @@ namespace MWWorld std::stringstream modelName; modelName << "Blood_Model_"; - int roll = OEngine::Misc::Rng::rollDice(3); // [0, 2] + int roll = Misc::Rng::rollDice(3); // [0, 2] modelName << roll; std::string model = "meshes\\" + getFallback()->getFallbackString(modelName.str()); mRendering->spawnEffect(model, texture, worldPosition); } - void World::spawnEffect(const std::string &model, const std::string &textureOverride, const Vector3 &worldPos) + void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos) { mRendering->spawnEffect(model, textureOverride, worldPos); } - void World::explodeSpell(const Vector3 &origin, const ESM::EffectList &effects, const Ptr &caster, ESM::RangeType rangeType, + void World::explodeSpell(const osg::Vec3f &origin, const ESM::EffectList &effects, const Ptr &caster, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName) { std::map > toApply; @@ -3207,7 +3078,7 @@ namespace MWWorld // Spawn the explosion orb effect const ESM::Static* areaStatic; - if (!effect->mCasting.empty()) + if (!effect->mArea.empty()) areaStatic = getStore().get().find (effect->mArea); else areaStatic = getStore().get().find ("VFX_DefaultArea"); @@ -3221,9 +3092,9 @@ namespace MWWorld { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(!effect->mAreaSound.empty()) - sndMgr->playManualSound3D(origin, effect->mAreaSound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); + sndMgr->playSound3D(origin, effect->mAreaSound, 1.0f, 1.0f); else - sndMgr->playManualSound3D(origin, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); + sndMgr->playSound3D(origin, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f); } // Get the actors in range of the effect std::vector objects; @@ -3279,7 +3150,7 @@ namespace MWWorld interpreterContext.executeActivation(object, actor); } - struct ResetActorsFunctor + struct ResetActorsVisitor { bool operator() (Ptr ptr) { @@ -3301,16 +3172,30 @@ namespace MWWorld iter!=mWorldScene->getActiveCells().end(); ++iter) { CellStore* cellstore = *iter; - ResetActorsFunctor functor; - cellstore->forEach(functor); + ResetActorsVisitor visitor; + cellstore->forEach(visitor); } } - bool World::isWalkingOnWater(const Ptr &actor) + bool World::isWalkingOnWater(const ConstPtr &actor) { - OEngine::Physic::PhysicActor* physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle()); + const MWPhysics::Actor* physicActor = mPhysics->getActor(actor); if (physicActor && physicActor->isWalkingOnWater()) return true; return false; } + + osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target) + { + osg::Vec3f weaponPos = getActorHeadTransform(actor).getTrans(); + osg::Vec3f targetPos = mPhysics->getPosition(target); + return (targetPos - weaponPos); + } + + float World::getHitDistance(const ConstPtr &actor, const ConstPtr &target) + { + osg::Vec3f weaponPos = getActorHeadTransform(actor).getTrans(); + return mPhysics->getHitDistance(weaponPos, target); + } + } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index bf25c20bbd..4ed97759ef 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -1,14 +1,13 @@ #ifndef GAME_MWWORLD_WORLDIMP_H #define GAME_MWWORLD_WORLDIMP_H -#include "../mwrender/debugging.hpp" - #include +#include + #include "ptr.hpp" #include "scene.hpp" #include "esmstore.hpp" -#include "physicssystem.hpp" #include "cells.hpp" #include "localscripts.hpp" #include "timestamp.hpp" @@ -19,9 +18,21 @@ #include "contentloader.hpp" -namespace Ogre +#include + +namespace osg { - class Vector3; + class Group; +} + +namespace osgViewer +{ + class Viewer; +} + +namespace Resource +{ + class ResourceSystem; } namespace ESM @@ -34,19 +45,18 @@ namespace Files class Collections; } -namespace Render -{ - class OgreRenderer; -} - namespace MWRender { class SkyManager; - class CellRender; class Animation; class Camera; } +namespace ToUTF8 +{ + class Utf8Encoder; +} + struct ContentLoader; namespace MWWorld @@ -59,6 +69,8 @@ namespace MWWorld class World : public MWBase::World { + Resource::ResourceSystem* mResourceSystem; + MWWorld::Fallback mFallback; MWRender::RenderingManager* mRendering; @@ -70,15 +82,20 @@ namespace MWWorld MWWorld::ESMStore mStore; LocalScripts mLocalScripts; MWWorld::Globals mGlobalVariables; - MWWorld::PhysicsSystem *mPhysics; + MWPhysics::PhysicsSystem *mPhysics; bool mSky; + ESM::Variant* mGameHour; + ESM::Variant* mDaysPassed; + ESM::Variant* mDay; + ESM::Variant* mMonth; + ESM::Variant* mYear; + ESM::Variant* mTimeScale; + Cells mCells; std::string mCurrentWorldSpace; - OEngine::Physic::PhysicEngine* mPhysEngine; - boost::shared_ptr mProjectileManager; bool mGodMode; @@ -89,8 +106,6 @@ namespace MWWorld World (const World&); World& operator= (const World&); - Ptr getPtrViaHandle (const std::string& handle, CellStore& cellStore); - int mActivationDistanceOverride; std::string mStartupScript; @@ -103,19 +118,20 @@ namespace MWWorld void updateWeather(float duration, bool paused = false); int getDaysPerMonth (int month) const; - void rotateObjectImp (const Ptr& ptr, Ogre::Vector3 rot, bool adjust); + void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, bool adjust); Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z); ///< @return an updated Ptr in case the Ptr's cell changes - Ptr copyObjectToCell(const Ptr &ptr, CellStore* cell, ESM::Position pos, bool adjustPos=true); + Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); void updateSoundListener(); void updateWindowManager (); - void performUpdateSceneQueries (); - void getFacedHandle(std::string& facedHandle, float maxDistance, bool ignorePlayer=true); - + void updatePlayer(bool paused); + MWWorld::Ptr getFacedObject(float maxDistance, bool ignorePlayer=true); + public: // FIXME void removeContainerScripts(const Ptr& reference); + private: void addContainerScripts(const Ptr& reference, CellStore* cell); void PCDropped (const Ptr& item); @@ -127,6 +143,8 @@ namespace MWWorld void ensureNeededRecords(); + void fillGlobalVariables(); + /** * @brief loadContentFiles - Loads content files (esm,esp,omwgame,omwaddon) * @param fileCollections- Container which holds content file names and their paths @@ -137,7 +155,7 @@ namespace MWWorld const std::vector& content, ContentLoader& contentLoader); float mSwimHeightScale; - bool isUnderwater(const MWWorld::Ptr &object, const float heightRatio) const; + bool isUnderwater(const MWWorld::ConstPtr &object, const float heightRatio) const; ///< helper function for implementing isSwimming(), isSubmerged(), isWading() bool mTeleportEnabled; @@ -147,17 +165,19 @@ namespace MWWorld float feetToGameUnits(float feet); - MWWorld::Ptr getClosestMarker( const MWWorld::Ptr &ptr, const std::string &id ); - MWWorld::Ptr getClosestMarkerFromExteriorPosition( const Ogre::Vector3 worldPos, const std::string &id ); + MWWorld::ConstPtr getClosestMarker( const MWWorld::Ptr &ptr, const std::string &id ); + MWWorld::ConstPtr getClosestMarkerFromExteriorPosition( const osg::Vec3f& worldPos, const std::string &id ); public: - World (OEngine::Render::OgreRenderer& renderer, + World ( + osgViewer::Viewer* viewer, + osg::ref_ptr rootNode, + Resource::ResourceSystem* resourceSystem, const Files::Collections& fileCollections, const std::vector& contentFiles, - const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, - int activationDistanceOverride, const std::string& startCell, const std::string& startupScript); + int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath); virtual ~World(); @@ -190,8 +210,6 @@ namespace MWWorld virtual void adjustSky(); - virtual void getTriangleBatchCount(unsigned int &triangles, unsigned int &batches); - virtual const Fallback *getFallback() const; virtual Player& getPlayer(); @@ -210,21 +228,12 @@ namespace MWWorld virtual bool isCellQuasiExterior() const; - virtual Ogre::Vector2 getNorthVector (CellStore* cell); + virtual osg::Vec2f getNorthVector (CellStore* cell); ///< get north vector for given interior cell virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out); ///< get a list of teleport door markers for a given cell, to be displayed on the local map - virtual void worldToInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y); - ///< see MWRender::LocalMap::worldToInteriorMapPosition - - virtual Ogre::Vector2 interiorMapToWorldPosition (float nX, float nY, int x, int y); - ///< see MWRender::LocalMap::interiorMapToWorldPosition - - virtual bool isPositionExplored (float nX, float nY, int x, int y, bool interior); - ///< see MWRender::LocalMap::isPositionExplored - virtual void setGlobalInt (const std::string& name, int value); ///< Set value independently from real type. @@ -257,16 +266,10 @@ namespace MWWorld ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. - virtual Ptr getPtrViaHandle (const std::string& handle); - ///< Return a pointer to a liveCellRef with the given Ogre handle. - - virtual Ptr searchPtrViaHandle (const std::string& handle); - ///< Return a pointer to a liveCellRef with the given Ogre handle or Ptr() if not found - virtual Ptr searchPtrViaActorId (int actorId); ///< Search is limited to the active cells. - virtual MWWorld::Ptr findContainer (const MWWorld::Ptr& ptr); + virtual MWWorld::Ptr findContainer (const MWWorld::ConstPtr& ptr); ///< Return a pointer to a liveCellRef which contains \a ptr. /// \note Search is limited to the active cells. @@ -281,7 +284,7 @@ namespace MWWorld virtual void disable (const Ptr& ptr); - virtual void advanceTime (double hours); + virtual void advanceTime (double hours, bool incremental = false); ///< Advance in-game time. virtual void setHour (double hour); @@ -341,9 +344,11 @@ namespace MWWorld /// Returns a pointer to the object the provided object would hit (if within the /// specified distance), and the point where the hit occurs. This will attempt to /// use the "Head" node as a basis. - virtual std::pair getHitContact(const MWWorld::Ptr &ptr, float distance); + virtual std::pair getHitContact(const MWWorld::ConstPtr &ptr, float distance); + /// @note No-op for items in containers. Use ContainerStore::removeItem instead. virtual void deleteObject (const Ptr& ptr); + virtual void undeleteObject (const Ptr& ptr); virtual MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z); @@ -354,14 +359,13 @@ namespace MWWorld virtual void scaleObject (const Ptr& ptr, float scale); - /// World rotates object, uses degrees + /// World rotates object, uses radians + /// @note Rotations via this method use a different rotation order than the initial rotations in the CS. This + /// could be considered a bug, but is needed for MW compatibility. /// \param adjust indicates rotation should be set or adjusted virtual void rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust = false); - /// Local rotates object, uses degrees - virtual void localRotateObject (const Ptr& ptr, float x, float y, float z); - - virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos); + virtual MWWorld::Ptr safePlaceObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos); ///< place an object in a "safe" location (ie not in the void, etc). Makes a copy of the Ptr. virtual float getMaxActivationDistance(); @@ -373,7 +377,7 @@ namespace MWWorld virtual void positionToIndex (float x, float y, int &cellX, int &cellY) const; ///< Convert position to cell numbers - virtual void queueMovement(const Ptr &ptr, const Ogre::Vector3 &velocity); + virtual void queueMovement(const Ptr &ptr, const osg::Vec3f &velocity); ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. @@ -385,7 +389,7 @@ namespace MWWorld /// collisions and gravity. ///< \return Resulting mode - virtual bool toggleRenderMode (RenderMode mode); + virtual bool toggleRenderMode (MWRender::RenderMode mode); ///< Toggle a render mode. ///< \return Resulting mode @@ -439,14 +443,14 @@ namespace MWWorld virtual void update (float duration, bool paused); - virtual MWWorld::Ptr placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount); + virtual MWWorld::Ptr placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount); ///< copy and place an object into the gameworld at the specified cursor position /// @param object /// @param cursor X (relative 0-1) /// @param cursor Y (relative 0-1) /// @param number of objects to place - virtual MWWorld::Ptr dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount); + virtual MWWorld::Ptr dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object, int amount); ///< copy and place an object into the gameworld at the given actor's position /// @param actor giving the dropped object position /// @param object @@ -460,39 +464,27 @@ namespace MWWorld virtual bool isFlying(const MWWorld::Ptr &ptr) const; virtual bool isSlowFalling(const MWWorld::Ptr &ptr) const; ///Is the head of the creature underwater? - virtual bool isSubmerged(const MWWorld::Ptr &object) const; - virtual bool isSwimming(const MWWorld::Ptr &object) const; - virtual bool isUnderwater(const MWWorld::CellStore* cell, const Ogre::Vector3 &pos) const; - virtual bool isWading(const MWWorld::Ptr &object) const; + virtual bool isSubmerged(const MWWorld::ConstPtr &object) const; + virtual bool isSwimming(const MWWorld::ConstPtr &object) const; + virtual bool isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const; + virtual bool isWading(const MWWorld::ConstPtr &object) const; virtual bool isOnGround(const MWWorld::Ptr &ptr) const; - virtual void togglePOV() { - mRendering->togglePOV(); - } + virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const; - virtual bool isFirstPerson() const { - return mRendering->getCamera()->isFirstPerson(); - } + virtual void togglePOV(); - virtual void togglePreviewMode(bool enable) { - mRendering->togglePreviewMode(enable); - } + virtual bool isFirstPerson() const; - virtual bool toggleVanityMode(bool enable) { - return mRendering->toggleVanityMode(enable); - } + virtual void togglePreviewMode(bool enable); - virtual void allowVanityMode(bool allow) { - mRendering->allowVanityMode(allow); - } + virtual bool toggleVanityMode(bool enable); - virtual void togglePlayerLooking(bool enable) { - mRendering->togglePlayerLooking(enable); - } + virtual void allowVanityMode(bool allow); - virtual void changeVanityModeScale(float factor) { - mRendering->changeVanityModeScale(factor); - } + virtual void togglePlayerLooking(bool enable); + + virtual void changeVanityModeScale(float factor); virtual bool vanityRotateCamera(float * rot); virtual void setCameraDistance(float dist, bool adjust = false, bool override = true); @@ -508,28 +500,28 @@ namespace MWWorld /// @note throws an exception when invoked on a teleport door virtual void activateDoor(const MWWorld::Ptr& door, int state); - virtual bool getPlayerStandingOn (const MWWorld::Ptr& object); ///< @return true if the player is standing on \a object - virtual bool getActorStandingOn (const MWWorld::Ptr& object); ///< @return true if any actor is standing on \a object - virtual bool getPlayerCollidingWith(const MWWorld::Ptr& object); ///< @return true if the player is colliding with \a object - virtual bool getActorCollidingWith (const MWWorld::Ptr& object); ///< @return true if any actor is colliding with \a object - virtual void hurtStandingActors (const MWWorld::Ptr& object, float dmgPerSecond); + virtual bool getPlayerStandingOn (const MWWorld::ConstPtr& object); ///< @return true if the player is standing on \a object + virtual bool getActorStandingOn (const MWWorld::ConstPtr& object); ///< @return true if any actor is standing on \a object + virtual bool getPlayerCollidingWith(const MWWorld::ConstPtr& object); ///< @return true if the player is colliding with \a object + virtual bool getActorCollidingWith (const MWWorld::ConstPtr& object); ///< @return true if any actor is colliding with \a object + virtual void hurtStandingActors (const MWWorld::ConstPtr& object, float dmgPerSecond); ///< Apply a health difference to any actors standing on \a object. /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. - virtual void hurtCollidingActors (const MWWorld::Ptr& object, float dmgPerSecond); + virtual void hurtCollidingActors (const MWWorld::ConstPtr& object, float dmgPerSecond); ///< Apply a health difference to any actors colliding with \a object. /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. virtual float getWindSpeed(); - virtual void getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out); + virtual void getContainersOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out); ///< get all containers in active cells owned by this Npc - virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out); + virtual void getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector& out); ///< get all items in active cells owned by this Npc - virtual bool getLOS(const MWWorld::Ptr& actor,const MWWorld::Ptr& targetActor); + virtual bool getLOS(const MWWorld::ConstPtr& actor,const MWWorld::ConstPtr& targetActor); ///< get Line of Sight (morrowind stupid implementation) - virtual float getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist); + virtual float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist); virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable); @@ -542,11 +534,11 @@ namespace MWWorld /// \todo Probably shouldn't be here virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr); + virtual const MWRender::Animation* getAnimation(const MWWorld::ConstPtr &ptr) const; virtual void reattachPlayerCamera(); /// \todo this does not belong here - virtual void frameStarted (float dt, bool paused); - virtual void screenshot (Ogre::Image& image, int w, int h); + virtual void screenshot (osg::Image* image, int w, int h); /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise @@ -568,10 +560,6 @@ namespace MWWorld /// Returns true if levitation spell effect is allowed. virtual bool isLevitationEnabled() const; - virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf); - - virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor); - virtual bool getGodModeState(); virtual bool toggleGodMode(); @@ -594,9 +582,9 @@ namespace MWWorld virtual void launchMagicBolt (const std::string& model, const std::string& sound, const std::string& spellId, float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, const std::string& sourceName, const Ogre::Vector3& fallbackDirection); - virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, - const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed); + const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection); + virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, + const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength); virtual const std::vector& getContentFiles() const; @@ -605,7 +593,7 @@ namespace MWWorld // Are we in an exterior or pseudo-exterior cell and it's night? virtual bool isDark() const; - virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result); + virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, osg::Vec3f& result); /// Teleports \a ptr to the closest reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) /// @note id must be lower case @@ -631,11 +619,11 @@ namespace MWWorld virtual void spawnRandomCreature(const std::string& creatureList); /// Spawn a blood effect for \a ptr at \a worldPosition - virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition); + virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition); - virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const Ogre::Vector3& worldPos); + virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos); - virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, + virtual void explodeSpell (const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName); virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor); @@ -644,12 +632,19 @@ namespace MWWorld virtual bool isInStorm() const; /// @see MWWorld::WeatherManager::getStormDirection - virtual Ogre::Vector3 getStormDirection() const; + virtual osg::Vec3f getStormDirection() const; /// Resets all actors in the current active cells to their original location within that cell. virtual void resetActors(); - virtual bool isWalkingOnWater (const MWWorld::Ptr& actor); + virtual bool isWalkingOnWater (const MWWorld::ConstPtr& actor); + + /// Return a vector aiming the actor's weapon towards a target. + /// @note The length of the vector is the distance between actor and target. + virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target); + + /// Return the distance between actor's weapon and target's collision box. + virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target); }; } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 2ffb7ffa0e..2300f97a36 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -4,8 +4,11 @@ if (GTEST_FOUND) include_directories(${GTEST_INCLUDE_DIRS}) file(GLOB UNITTEST_SRC_FILES - components/misc/test_*.cpp - mwdialogue/test_*.cpp + ../openmw/mwworld/store.cpp + ../openmw/mwworld/esmstore.cpp + mwworld/test_store.cpp + + mwdialogue/test_keywordsearch.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/components/misc/test_stringops.cpp b/apps/openmw_test_suite/components/misc/test_stringops.cpp deleted file mode 100644 index 55fe0e0c27..0000000000 --- a/apps/openmw_test_suite/components/misc/test_stringops.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include "components/misc/stringops.hpp" - -struct StringOpsTest : public ::testing::Test -{ - protected: - virtual void SetUp() - { - } - - virtual void TearDown() - { - } -}; diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp new file mode 100644 index 0000000000..ac21470ded --- /dev/null +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -0,0 +1,315 @@ +#include + +#include + +#include +#include +#include +#include + +#include "apps/openmw/mwworld/esmstore.hpp" + +static Loading::Listener dummyListener; + +/// Base class for tests of ESMStore that rely on external content files to produce the test results +struct ContentFileTest : public ::testing::Test +{ + protected: + + virtual void SetUp() + { + readContentFiles(); + + // load the content files + std::vector readerList; + readerList.resize(mContentFiles.size()); + + int index=0; + for (std::vector::const_iterator it = mContentFiles.begin(); it != mContentFiles.end(); ++it) + { + ESM::ESMReader lEsm; + lEsm.setEncoder(NULL); + lEsm.setIndex(index); + lEsm.setGlobalReaderList(&readerList); + lEsm.open(it->string()); + readerList[index] = lEsm; + mEsmStore.load(readerList[index], &dummyListener); + + ++index; + } + + mEsmStore.setUp(); + } + + virtual void TearDown() + { + } + + // read absolute path to content files from openmw.cfg + void readContentFiles() + { + boost::program_options::variables_map variables; + + boost::program_options::options_description desc("Allowed options"); + desc.add_options() + ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()->composing()) + ("content", boost::program_options::value >()->default_value(std::vector(), "") + ->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon") + ("data-local", boost::program_options::value()->default_value("")); + + boost::program_options::notify(variables); + + mConfigurationManager.readConfiguration(variables, desc, true); + + Files::PathContainer dataDirs, dataLocal; + if (!variables["data"].empty()) { + dataDirs = Files::PathContainer(variables["data"].as()); + } + + std::string local = variables["data-local"].as(); + if (!local.empty()) { + dataLocal.push_back(Files::PathContainer::value_type(local)); + } + + mConfigurationManager.processPaths (dataDirs); + mConfigurationManager.processPaths (dataLocal, true); + + if (!dataLocal.empty()) + dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); + + Files::Collections collections (dataDirs, true); + + std::vector contentFiles = variables["content"].as >(); + for (std::vector::iterator it = contentFiles.begin(); it != contentFiles.end(); ++it) + mContentFiles.push_back(collections.getPath(*it)); + } + +protected: + Files::ConfigurationManager mConfigurationManager; + MWWorld::ESMStore mEsmStore; + std::vector mContentFiles; +}; + +/// Print results of the dialogue merging process, i.e. the resulting linked list. +TEST_F(ContentFileTest, dialogue_merging_test) +{ + if (mContentFiles.empty()) + { + std::cout << "No content files found, skipping test" << std::endl; + return; + } + + const std::string file = "test_dialogue_merging.txt"; + + boost::filesystem::ofstream stream; + stream.open(file); + + const MWWorld::Store& dialStore = mEsmStore.get(); + for (MWWorld::Store::iterator it = dialStore.begin(); it != dialStore.end(); ++it) + { + const ESM::Dialogue& dial = *it; + stream << "Dialogue: " << dial.mId << std::endl; + + for (ESM::Dialogue::InfoContainer::const_iterator infoIt = dial.mInfo.begin(); infoIt != dial.mInfo.end(); ++infoIt) + { + const ESM::DialInfo& info = *infoIt; + stream << info.mId << std::endl; + } + stream << std::endl; + } + + std::cout << "dialogue_merging_test successful, results printed to " << file << std::endl; +} + +// Note: here we don't test records that don't use string names (e.g. Land, Pathgrid, Cell) +#define RUN_TEST_FOR_TYPES(func, arg1, arg2) \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); + +template +void printRecords(MWWorld::ESMStore& esmStore, std::ostream& outStream) +{ + const MWWorld::Store& store = esmStore.get(); + outStream << store.getSize() << " " << T::getRecordType() << " records" << std::endl; + + for (typename MWWorld::Store::iterator it = store.begin(); it != store.end(); ++it) + { + const T& record = *it; + outStream << record.mId << std::endl; + } + + outStream << std::endl; +} + +/// Print some basic diagnostics about the loaded content files, e.g. number of records and names of those records +/// Also used to test the iteration order of records +TEST_F(ContentFileTest, content_diagnostics_test) +{ + if (mContentFiles.empty()) + { + std::cout << "No content files found, skipping test" << std::endl; + return; + } + + const std::string file = "test_content_diagnostics.txt"; + + boost::filesystem::ofstream stream; + stream.open(file); + + RUN_TEST_FOR_TYPES(printRecords, mEsmStore, stream); + + std::cout << "diagnostics_test successful, results printed to " << file << std::endl; +} + +// TODO: +/// Print results of autocalculated NPC spell lists. Also serves as test for attribute/skill autocalculation which the spell autocalculation heavily relies on +/// - even incorrect rounding modes can completely change the resulting spell lists. +/* +TEST_F(ContentFileTest, autocalc_test) +{ + if (mContentFiles.empty()) + { + std::cout << "No content files found, skipping test" << std::endl; + return; + } + + +} +*/ + +/// Base class for tests of ESMStore that do not rely on external content files +struct StoreTest : public ::testing::Test +{ +protected: + MWWorld::ESMStore mEsmStore; +}; + + +/// Create an ESM file in-memory containing the specified record. +/// @param deleted Write record with deleted flag? +template +Files::IStreamPtr getEsmFile(T record, bool deleted) +{ + ESM::ESMWriter writer; + std::stringstream* stream = new std::stringstream; + writer.setFormat(0); + writer.save(*stream); + writer.startRecord(T::sRecordId); + record.save(writer, deleted); + writer.endRecord(T::sRecordId); + + return Files::IStreamPtr(stream); +} + +/// Tests deletion of records. +TEST_F(StoreTest, delete_test) +{ + const std::string recordId = "foobar"; + + typedef ESM::Apparatus RecordType; + + RecordType record; + record.blank(); + record.mId = recordId; + + ESM::ESMReader reader; + std::vector readerList; + readerList.push_back(reader); + reader.setGlobalReaderList(&readerList); + + // master file inserts a record + Files::IStreamPtr file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + ASSERT_TRUE (mEsmStore.get().getSize() == 1); + + // now a plugin deletes it + file = getEsmFile(record, true); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + ASSERT_TRUE (mEsmStore.get().getSize() == 0); + + // now another plugin inserts it again + // expected behaviour is the record to reappear rather than staying deleted + file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + ASSERT_TRUE (mEsmStore.get().getSize() == 1); +} + +/// Tests overwriting of records. +TEST_F(StoreTest, overwrite_test) +{ + const std::string recordId = "foobar"; + const std::string recordIdUpper = "Foobar"; + + typedef ESM::Apparatus RecordType; + + RecordType record; + record.blank(); + record.mId = recordId; + + ESM::ESMReader reader; + std::vector readerList; + readerList.push_back(reader); + reader.setGlobalReaderList(&readerList); + + // master file inserts a record + Files::IStreamPtr file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + // now a plugin overwrites it with changed data + record.mId = recordIdUpper; // change id to uppercase, to test case smashing while we're at it + record.mModel = "the_new_model"; + file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + // verify that changes were actually applied + const RecordType* overwrittenRec = mEsmStore.get().search(recordId); + + ASSERT_TRUE (overwrittenRec != NULL); + + ASSERT_TRUE (overwrittenRec && overwrittenRec->mModel == "the_new_model"); +} diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index dc2674680e..2f0af88c94 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -28,12 +28,6 @@ Wizard::InstallationPage::InstallationPage(QWidget *parent) : connect(mUnshield, SIGNAL(finished()), mThread, SLOT(quit())); - connect(mUnshield, SIGNAL(finished()), - mUnshield, SLOT(deleteLater())); - - connect(mUnshield, SIGNAL(finished()), - mThread, SLOT(deleteLater()));; - connect(mUnshield, SIGNAL(finished()), this, SLOT(installationFinished()), Qt::QueuedConnection); @@ -60,6 +54,7 @@ Wizard::InstallationPage::~InstallationPage() { if (mThread->isRunning()) { mUnshield->stopWorker(); + mThread->quit(); mThread->wait(); } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 7538511fe9..19cdf9535d 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -138,7 +138,7 @@ void Wizard::MainWizard::setupGameSettings() QString path(userPath + QLatin1String("openmw.cfg")); QFile file(path); - qDebug() << "Loading config file:" << qPrintable(path); + qDebug() << "Loading config file:" << path.toUtf8().constData(); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -163,7 +163,7 @@ void Wizard::MainWizard::setupGameSettings() paths.append(globalPath + QLatin1String("openmw.cfg")); foreach (const QString &path, paths) { - qDebug() << "Loading config file:" << qPrintable(path); + qDebug() << "Loading config file:" << path.toUtf8().constData(); QFile file(path); if (file.exists()) { @@ -197,7 +197,7 @@ void Wizard::MainWizard::setupLauncherSettings() QFile file(path); - qDebug() << "Loading config file:" << qPrintable(path); + qDebug() << "Loading config file:" << path.toUtf8().constData(); if (file.exists()) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 9daea2b71a..c8f7ca677c 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -45,9 +45,7 @@ Wizard::UnshieldWorker::~UnshieldWorker() void Wizard::UnshieldWorker::stopWorker() { - mMutex.lock(); mStopped = true; - mMutex.unlock(); } void Wizard::UnshieldWorker::setInstallComponent(Wizard::Component component, bool install) diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index c1d3cfff1f..3f922ad78a 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -104,7 +104,6 @@ namespace Wizard QTextCodec *mIniCodec; QWaitCondition mWait; - QMutex mMutex; QReadWriteLock mLock; diff --git a/cmake/COPYING-CMAKE-SCRIPTS b/cmake/COPYING-CMAKE-SCRIPTS index 8ee3ea36b2..d33c6f33bd 100644 --- a/cmake/COPYING-CMAKE-SCRIPTS +++ b/cmake/COPYING-CMAKE-SCRIPTS @@ -1,7 +1,7 @@ The following files are derived from the Thermite project (http://www.thermite3d.org) and are covered under the license below. -FindMYGUI.cmake, FindOGRE.cmake, FindOIS.cmake, FindBullet.cmake +FindMYGUI.cmake, FindBullet.cmake Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/cmake/FindBullet.cmake b/cmake/FindBullet.cmake index b75f3105a6..6d5c517afd 100644 --- a/cmake/FindBullet.cmake +++ b/cmake/FindBullet.cmake @@ -54,8 +54,8 @@ find_path(BULLET_INCLUDE_DIR NAMES btBulletCollisionCommon.h # Find the libraries -_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY BulletDynamics) -_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY_DEBUG BulletDynamics_Debug BulletDynamics_d) +#_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY BulletDynamics) +#_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY_DEBUG BulletDynamics_Debug BulletDynamics_d) _FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY BulletCollision) _FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY_DEBUG BulletCollision_Debug BulletCollision_d) _FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY BulletMath LinearMath) @@ -66,12 +66,13 @@ _FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY_DEBUG BulletMath_Debug BulletMath_ # all listed variables are TRUE include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Bullet DEFAULT_MSG - BULLET_DYNAMICS_LIBRARY BULLET_COLLISION_LIBRARY BULLET_MATH_LIBRARY + #BULLET_DYNAMICS_LIBRARY + BULLET_COLLISION_LIBRARY BULLET_MATH_LIBRARY BULLET_INCLUDE_DIR) set(BULLET_INCLUDE_DIRS ${BULLET_INCLUDE_DIR}) if(BULLET_FOUND) - _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_DYNAMICS_LIBRARY) + #_BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_DYNAMICS_LIBRARY) _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_COLLISION_LIBRARY) _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_MATH_LIBRARY) endif() diff --git a/cmake/FindCg.cmake b/cmake/FindCg.cmake deleted file mode 100644 index 4bd348c46f..0000000000 --- a/cmake/FindCg.cmake +++ /dev/null @@ -1,53 +0,0 @@ -#------------------------------------------------------------------- -# This file is part of the CMake build system for OGRE -# (Object-oriented Graphics Rendering Engine) -# For the latest info, see http://www.ogre3d.org/ -# -# The contents of this file are placed in the public domain. Feel -# free to make use of it in any way you like. -#------------------------------------------------------------------- - -# - Try to find Cg -# Once done, this will define -# -# Cg_FOUND - system has Cg -# Cg_INCLUDE_DIRS - the Cg include directories -# Cg_LIBRARIES - link these to use Cg - -include(FindPkgMacros) -findpkg_begin(Cg) - -# Get path, convert backslashes as ${ENV_${var}} -getenv_path(Cg_HOME) -getenv_path(OGRE_SOURCE) -getenv_path(OGRE_HOME) - -# construct search paths -set(Cg_PREFIX_PATH ${Cg_HOME} ${ENV_Cg_HOME} - ${OGRE_SOURCE}/Dependencies - ${ENV_OGRE_SOURCE}/Dependencies - ${OGRE_HOME} ${ENV_OGRE_HOME} - /opt/nvidia-cg-toolkit) -create_search_paths(Cg) -# redo search if prefix path changed -clear_if_changed(Cg_PREFIX_PATH - Cg_LIBRARY_FWK - Cg_LIBRARY_REL - Cg_LIBRARY_DBG - Cg_INCLUDE_DIR -) - -set(Cg_LIBRARY_NAMES Cg) -get_debug_names(Cg_LIBRARY_NAMES) - -use_pkgconfig(Cg_PKGC Cg) - -findpkg_framework(Cg) - -find_path(Cg_INCLUDE_DIR NAMES cg.h HINTS ${Cg_FRAMEWORK_INCLUDES} ${Cg_INC_SEARCH_PATH} ${Cg_PKGC_INCLUDE_DIRS} PATH_SUFFIXES Cg) -find_library(Cg_LIBRARY_REL NAMES ${Cg_LIBRARY_NAMES} HINTS ${Cg_LIB_SEARCH_PATH} ${Cg_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" release relwithdebinfo minsizerel) -find_library(Cg_LIBRARY_DBG NAMES ${Cg_LIBRARY_NAMES_DBG} HINTS ${Cg_LIB_SEARCH_PATH} ${Cg_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" debug) -make_library_set(Cg_LIBRARY) - -findpkg_finish(Cg) -add_parent_dir(Cg_INCLUDE_DIRS Cg_INCLUDE_DIR) diff --git a/cmake/FindDirectX.cmake b/cmake/FindDirectX.cmake deleted file mode 100644 index 4641b55a3d..0000000000 --- a/cmake/FindDirectX.cmake +++ /dev/null @@ -1,72 +0,0 @@ -#------------------------------------------------------------------- -# This file is part of the CMake build system for OGRE -# (Object-oriented Graphics Rendering Engine) -# For the latest info, see http://www.ogre3d.org/ -# -# The contents of this file are placed in the public domain. Feel -# free to make use of it in any way you like. -#------------------------------------------------------------------- - -# ----------------------------------------------------------------------------- -# Find DirectX9 SDK -# Define: -# DirectX9_FOUND -# DirectX9_INCLUDE_DIR -# DirectX9_LIBRARY -# DirectX9_ROOT_DIR - -if(WIN32) # The only platform it makes sense to check for DirectX9 SDK - include(FindPkgMacros) - findpkg_begin(DirectX9) - - # Get path, convert backslashes as ${ENV_DXSDK_DIR} - getenv_path(DXSDK_DIR) - getenv_path(DirectX_HOME) - getenv_path(DirectX_ROOT) - getenv_path(DirectX_BASE) - - # construct search paths - set(DirectX9_PREFIX_PATH - "${DXSDK_DIR}" "${ENV_DXSDK_DIR}" - "${DIRECTX_HOME}" "${ENV_DIRECTX_HOME}" - "${DIRECTX_ROOT}" "${ENV_DIRECTX_ROOT}" - "${DIRECTX_BASE}" "${ENV_DIRECTX_BASE}" - "C:/apps_x86/Microsoft DirectX SDK*" - "C:/Program Files (x86)/Microsoft DirectX SDK*" - "C:/apps/Microsoft DirectX SDK*" - "C:/Program Files/Microsoft DirectX SDK*" - "$ENV{ProgramFiles}/Microsoft DirectX SDK*" - ) - - create_search_paths(DirectX9) - # redo search if prefix path changed - clear_if_changed(DirectX9_PREFIX_PATH - DirectX9_LIBRARY - DirectX9_INCLUDE_DIR - ) - - find_path(DirectX9_INCLUDE_DIR NAMES d3d9.h D3DCommon.h HINTS ${DirectX9_INC_SEARCH_PATH}) - # dlls are in DirectX9_ROOT_DIR/Developer Runtime/x64|x86 - # lib files are in DirectX9_ROOT_DIR/Lib/x64|x86 - if(CMAKE_CL_64) - set(DirectX9_LIBPATH_SUFFIX "x64") - else(CMAKE_CL_64) - set(DirectX9_LIBPATH_SUFFIX "x86") - endif(CMAKE_CL_64) - find_library(DirectX9_LIBRARY NAMES d3d9 HINTS ${DirectX9_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX9_LIBPATH_SUFFIX}) - find_library(DirectX9_D3DX9_LIBRARY NAMES d3dx9 HINTS ${DirectX9_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX9_LIBPATH_SUFFIX}) - find_library(DirectX9_DXERR_LIBRARY NAMES DxErr HINTS ${DirectX9_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX9_LIBPATH_SUFFIX}) - find_library(DirectX9_DXGUID_LIBRARY NAMES dxguid HINTS ${DirectX9_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX9_LIBPATH_SUFFIX}) - - findpkg_finish(DirectX9) - set(DirectX9_LIBRARIES ${DirectX9_LIBRARIES} - ${DirectX9_D3DX9_LIBRARY} - ${DirectX9_DXERR_LIBRARY} - ${DirectX9_DXGUID_LIBRARY} - ) - - mark_as_advanced(DirectX9_D3DX9_LIBRARY DirectX9_DXERR_LIBRARY DirectX9_DXGUID_LIBRARY - DirectX9_DXGI_LIBRARY DirectX9_D3DCOMPILER_LIBRARY) - - -endif(WIN32) diff --git a/cmake/FindDirectX11.cmake b/cmake/FindDirectX11.cmake deleted file mode 100644 index 22d5b5441f..0000000000 --- a/cmake/FindDirectX11.cmake +++ /dev/null @@ -1,114 +0,0 @@ -#------------------------------------------------------------------- -# This file is part of the CMake build system for OGRE -# (Object-oriented Graphics Rendering Engine) -# For the latest info, see http://www.ogre3d.org/ -# -# The contents of this file are placed in the public domain. Feel -# free to make use of it in any way you like. -#------------------------------------------------------------------- - -# ----------------------------------------------------------------------------- -# Find DirectX11 SDK -# Define: -# DirectX11_FOUND -# DirectX11_INCLUDE_DIR -# DirectX11_LIBRARY -# DirectX11_ROOT_DIR - -if(WIN32) # The only platform it makes sense to check for DirectX11 SDK - include(FindPkgMacros) - findpkg_begin(DirectX11) - - # Get path, convert backslashes as ${ENV_DXSDK_DIR} - getenv_path(DXSDK_DIR) - getenv_path(DIRECTX_HOME) - getenv_path(DIRECTX_ROOT) - getenv_path(DIRECTX_BASE) - - # construct search paths - set(DirectX11_PREFIX_PATH - "${DXSDK_DIR}" "${ENV_DXSDK_DIR}" - "${DIRECTX_HOME}" "${ENV_DIRECTX_HOME}" - "${DIRECTX_ROOT}" "${ENV_DIRECTX_ROOT}" - "${DIRECTX_BASE}" "${ENV_DIRECTX_BASE}" - "C:/apps_x86/Microsoft DirectX SDK*" - "C:/Program Files (x86)/Microsoft DirectX SDK*" - "C:/apps/Microsoft DirectX SDK*" - "C:/Program Files/Microsoft DirectX SDK*" - "$ENV{ProgramFiles}/Microsoft DirectX SDK*" - ) - - if(OGRE_BUILD_PLATFORM_WINRT) - # Windows 8 SDK has custom layout - set(DirectX11_INC_SEARCH_PATH - "C:/Program Files (x86)/Windows Kits/8.0/Include/shared" - "C:/Program Files (x86)/Windows Kits/8.0/Include/um" - ) - set(DirectX11_LIB_SEARCH_PATH - "C:/Program Files (x86)/Windows Kits/8.0/Lib/win8/um" - ) - endif() - - create_search_paths(DirectX11) - # redo search if prefix path changed - clear_if_changed(DirectX11_PREFIX_PATH - DirectX11_LIBRARY - DirectX11_INCLUDE_DIR - ) - - # dlls are in DirectX11_ROOT_DIR/Developer Runtime/x64|x86 - # lib files are in DirectX11_ROOT_DIR/Lib/x64|x86 - if(CMAKE_CL_64) - set(DirectX11_LIBPATH_SUFFIX "x64") - else(CMAKE_CL_64) - set(DirectX11_LIBPATH_SUFFIX "x86") - endif(CMAKE_CL_64) - - # look for D3D11 components - find_path(DirectX11_INCLUDE_DIR NAMES D3D11Shader.h HINTS ${DirectX11_INC_SEARCH_PATH}) - find_library(DirectX11_DXERR_LIBRARY NAMES DxErr HINTS ${DirectX11_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX11_LIBPATH_SUFFIX}) - find_library(DirectX11_DXGUID_LIBRARY NAMES dxguid HINTS ${DirectX11_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX11_LIBPATH_SUFFIX}) - find_library(DirectX11_DXGI_LIBRARY NAMES dxgi HINTS ${DirectX11_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX11_LIBPATH_SUFFIX}) - find_library(DirectX11_D3DCOMPILER_LIBRARY NAMES d3dcompiler HINTS ${DirectX11_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX11_LIBPATH_SUFFIX}) - - find_library(DirectX11_LIBRARY NAMES d3d11 HINTS ${DirectX11_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX11_LIBPATH_SUFFIX}) - find_library(DirectX11_D3DX11_LIBRARY NAMES d3dx11 HINTS ${DirectX11_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX11_LIBPATH_SUFFIX}) - if (DirectX11_INCLUDE_DIR AND DirectX11_LIBRARY) - set(DirectX11_D3D11_FOUND TRUE) - set(DirectX11_INCLUDE_DIR ${DirectX11_INCLUDE_DIR}) - set(DirectX11_D3D11_LIBRARIES ${DirectX11_D3D11_LIBRARIES} - ${DirectX11_LIBRARY} - ${DirectX11_DXGI_LIBRARY} - ${DirectX11_DXGUID_LIBRARY} - ${DirectX11_D3DCOMPILER_LIBRARY} - ) - endif () - if (DirectX11_D3DX11_LIBRARY) - set(DirectX11_D3D11_LIBRARIES ${DirectX11_D3D11_LIBRARIES} ${DirectX11_D3DX11_LIBRARY}) - endif () - if (DirectX11_DXERR_LIBRARY) - set(DirectX11_D3D11_LIBRARIES ${DirectX11_D3D11_LIBRARIES} ${DirectX11_DXERR_LIBRARY}) - endif () - - findpkg_finish(DirectX11) - - set(DirectX11_LIBRARIES - ${DirectX11_D3D11_LIBRARIES} - ) - - if (OGRE_BUILD_PLATFORM_WINDOWS_PHONE) - set(DirectX11_FOUND TRUE) - set(DirectX11_INCLUDE_DIR "C:/Program Files (x86)/Microsoft Visual Studio 11.0/VC/WPSDK/WP80/include" CACHE STRING "" FORCE) - set(DirectX11_LIBRARY "C:/Program Files (x86)/Microsoft Visual Studio 11.0/VC/WPSDK/WP80/lib" CACHE STRING "" FORCE) - set(DirectX11_LIBRARIES ${DirectX11_LIBRARY}) - set(CMAKE_CXX_FLAGS "/EHsc" CACHE STRING "" FORCE) - endif () - - mark_as_advanced(DirectX11_INCLUDE_DIR - DirectX11_D3D11_LIBRARIES - DirectX11_D3DX11_LIBRARY - DirectX11_DXERR_LIBRARY - DirectX11_DXGUID_LIBRARY - DirectX11_DXGI_LIBRARY - DirectX11_D3DCOMPILER_LIBRARY) -endif(WIN32) \ No newline at end of file diff --git a/cmake/FindFreeImage.cmake b/cmake/FindFreeImage.cmake deleted file mode 100644 index 3b21a17d64..0000000000 --- a/cmake/FindFreeImage.cmake +++ /dev/null @@ -1,47 +0,0 @@ -#------------------------------------------------------------------- -# This file is part of the CMake build system for OGRE -# (Object-oriented Graphics Rendering Engine) -# For the latest info, see http://www.ogre3d.org/ -# -# The contents of this file are placed in the public domain. Feel -# free to make use of it in any way you like. -#------------------------------------------------------------------- - -# - Try to find FreeImage -# Once done, this will define -# -# FreeImage_FOUND - system has FreeImage -# FreeImage_INCLUDE_DIRS - the FreeImage include directories -# FreeImage_LIBRARIES - link these to use FreeImage - -include(FindPkgMacros) -findpkg_begin(FreeImage) - -# Get path, convert backslashes as ${ENV_${var}} -getenv_path(FREEIMAGE_HOME) - -# construct search paths -set(FreeImage_PREFIX_PATH ${FREEIMAGE_HOME} ${ENV_FREEIMAGE_HOME}) -create_search_paths(FreeImage) -# redo search if prefix path changed -clear_if_changed(FreeImage_PREFIX_PATH - FreeImage_LIBRARY_FWK - FreeImage_LIBRARY_REL - FreeImage_LIBRARY_DBG - FreeImage_INCLUDE_DIR -) - -set(FreeImage_LIBRARY_NAMES freeimage) -get_debug_names(FreeImage_LIBRARY_NAMES) - -use_pkgconfig(FreeImage_PKGC freeimage) - -findpkg_framework(FreeImage) - -find_path(FreeImage_INCLUDE_DIR NAMES FreeImage.h HINTS ${FreeImage_INC_SEARCH_PATH} ${FreeImage_PKGC_INCLUDE_DIRS}) -find_library(FreeImage_LIBRARY_REL NAMES ${FreeImage_LIBRARY_NAMES} HINTS ${FreeImage_LIB_SEARCH_PATH} ${FreeImage_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" release relwithdebinfo minsizerel) -find_library(FreeImage_LIBRARY_DBG NAMES ${FreeImage_LIBRARY_NAMES_DBG} HINTS ${FreeImage_LIB_SEARCH_PATH} ${FreeImage_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" debug) -make_library_set(FreeImage_LIBRARY) - -findpkg_finish(FreeImage) - diff --git a/cmake/FindMyGUI.cmake b/cmake/FindMyGUI.cmake index 2829a74d35..6e93a92ce6 100644 --- a/cmake/FindMyGUI.cmake +++ b/cmake/FindMyGUI.cmake @@ -25,38 +25,21 @@ IF (WIN32) #Windows IF(MINGW) FIND_PATH ( MYGUI_INCLUDE_DIRS MyGUI.h PATH_SUFFIXES MYGUI) - FIND_PATH ( MYGUI_PLATFORM_INCLUDE_DIRS MyGUI_OgrePlatform.h PATH_SUFFIXES MYGUI) FIND_LIBRARY ( MYGUI_LIBRARIES_REL NAMES libMyGUIEngine${CMAKE_SHARED_LIBRARY_SUFFIX} - libMyGUI.OgrePlatform${CMAKE_STATIC_LIBRARY_SUFFIX} HINTS ${MYGUI_LIB_DIR} PATH_SUFFIXES "" release relwithdebinfo minsizerel ) FIND_LIBRARY ( MYGUI_LIBRARIES_DBG NAMES libMyGUIEngine_d${CMAKE_SHARED_LIBRARY_SUFFIX} - libMyGUI.OgrePlatform_d${CMAKE_STATIC_LIBRARY_SUFFIX} - HINTS - ${MYGUI_LIB_DIR} - PATH_SUFFIXES "" debug ) - - FIND_LIBRARY ( MYGUI_PLATFORM_LIBRARIES_REL NAMES - libMyGUI.OgrePlatform${CMAKE_STATIC_LIBRARY_SUFFIX} - HINTS - ${MYGUI_LIB_DIR} - PATH_SUFFIXES "" release relwithdebinfo minsizerel ) - - FIND_LIBRARY ( MYGUI_PLATFORM_LIBRARIES_DBG NAMES - MyGUI.OgrePlatform_d${CMAKE_STATIC_LIBRARY_SUFFIX} HINTS ${MYGUI_LIB_DIR} PATH_SUFFIXES "" debug ) make_library_set ( MYGUI_LIBRARIES ) - make_library_set ( MYGUI_PLATFORM_LIBRARIES ) MESSAGE ("${MYGUI_LIBRARIES}") - MESSAGE ("${MYGUI_PLATFORM_LIBRARIES}") ENDIF(MINGW) SET(MYGUISDK $ENV{MYGUI_HOME}) @@ -66,35 +49,23 @@ IF (WIN32) #Windows STRING(REGEX REPLACE "[\\]" "/" MYGUISDK "${MYGUISDK}" ) find_path ( MYGUI_INCLUDE_DIRS MyGUI.h "${MYGUISDK}/MyGUIEngine/include" NO_DEFAULT_PATH ) - find_path ( MYGUI_PLATFORM_INCLUDE_DIRS MyGUI_OgrePlatform.h "${MYGUISDK}/Platforms/Ogre/OgrePlatform/include" NO_DEFAULT_PATH ) SET ( MYGUI_LIB_DIR ${MYGUISDK}/lib ${MYGUISDK}/*/lib ) if ( MYGUI_STATIC ) set(LIB_SUFFIX "Static") + find_package(freetype) endif ( MYGUI_STATIC ) - find_library ( MYGUI_LIBRARIES_REL NAMES MyGUIEngine${LIB_SUFFIX}.lib MyGUI.OgrePlatform.lib HINTS ${MYGUI_LIB_DIR} PATH_SUFFIXES "" release relwithdebinfo minsizerel ) - find_library ( MYGUI_LIBRARIES_DBG NAMES MyGUIEngine${LIB_SUFFIX}_d.lib MyGUI.OgrePlatform_d.lib HINTS ${MYGUI_LIB_DIR} PATH_SUFFIXES "" debug ) - - find_library ( MYGUI_PLATFORM_LIBRARIES_REL NAMES MyGUI.OgrePlatform.lib HINTS ${MYGUI_LIB_DIR} PATH_SUFFIXES "" release relwithdebinfo minsizerel ) - find_library ( MYGUI_PLATFORM_LIBRARIES_DBG NAMES MyGUI.OgrePlatform_d.lib HINTS ${MYGUI_LIB_DIR} PATH_SUFFIXES "" debug ) + find_library ( MYGUI_LIBRARIES_REL NAMES MyGUIEngine${LIB_SUFFIX}.lib HINTS ${MYGUI_LIB_DIR} PATH_SUFFIXES "" release relwithdebinfo minsizerel ) + find_library ( MYGUI_LIBRARIES_DBG NAMES MyGUIEngine${LIB_SUFFIX}_d.lib HINTS ${MYGUI_LIB_DIR} PATH_SUFFIXES "" debug ) make_library_set ( MYGUI_LIBRARIES ) - make_library_set ( MYGUI_PLATFORM_LIBRARIES ) MESSAGE ("${MYGUI_LIBRARIES}") - MESSAGE ("${MYGUI_PLATFORM_LIBRARIES}") #findpkg_finish ( "MYGUI" ) ENDIF (MYGUISDK) - IF (OGRESOURCE) - MESSAGE(STATUS "Using MyGUI in OGRE dependencies") - STRING(REGEX REPLACE "[\\]" "/" OGRESDK "${OGRESOURCE}" ) - SET(MYGUI_INCLUDE_DIRS ${OGRESOURCE}/OgreMain/include/MYGUI) - SET(MYGUI_LIB_DIR ${OGRESOURCE}/lib) - SET(MYGUI_LIBRARIES debug Debug/MyGUIEngine_d optimized Release/MyGUIEngine) - ENDIF (OGRESOURCE) ELSE (WIN32) #Unix CMAKE_MINIMUM_REQUIRED(VERSION 2.4.7 FATAL_ERROR) FIND_PACKAGE(PkgConfig) @@ -106,21 +77,18 @@ ELSE (WIN32) #Unix SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS}) SET(MYGUI_LIB_DIR ${MYGUI_LIBDIR}) SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "") - SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform") ELSE (MYGUI_INCLUDE_DIRS) FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI) FIND_LIBRARY(MYGUI_LIBRARIES myguistatic PATHS /usr/lib /usr/local/lib) - SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform") SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES}) STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") ENDIF (MYGUI_INCLUDE_DIRS) ELSE (NOT APPLE) - SET(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${MYGUI_DEPENDENCIES_DIR} ${OGRE_DEPENDENCIES_DIR}) + SET(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${MYGUI_DEPENDENCIES_DIR}) FIND_PACKAGE(freetype) FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI) FIND_LIBRARY(MYGUI_LIBRARIES MyGUIEngineStatic PATHS /usr/lib /usr/local/lib) - SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform") SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES}) STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") @@ -131,11 +99,9 @@ ELSE (WIN32) #Unix SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS}) SET(MYGUI_LIB_DIR ${MYGUI_LIBDIR}) SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "") - SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform") ELSE (MYGUI_INCLUDE_DIRS) FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI) FIND_LIBRARY(MYGUI_LIBRARIES mygui PATHS /usr/lib /usr/local/lib) - SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform") SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES}) STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") @@ -147,14 +113,12 @@ ENDIF (WIN32) IF (NOT WIN32) # This does not work on Windows for paths with spaces in them SEPARATE_ARGUMENTS(MYGUI_INCLUDE_DIRS) SEPARATE_ARGUMENTS(MYGUI_LIBRARIES) - SEPARATE_ARGUMENTS(MYGUI_PLATFORM_LIBRARIES) ENDIF (NOT WIN32) SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} ${FREETYPE_LIBRARIES}) SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS} CACHE PATH "") SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "") -SET(MYGUI_PLATFORM_LIBRARIES ${MYGUI_PLATFORM_LIBRARIES} CACHE STRING "") SET(MYGUI_LIB_DIR ${MYGUI_LIB_DIR} CACHE PATH "") IF (NOT APPLE OR NOT MYGUI_STATIC) # we need explicit freetype libs only on OS X for static build, for other cases just make it TRUE @@ -182,11 +146,12 @@ IF (MYGUI_FOUND) IF (NOT MYGUI_FIND_QUIETLY) MESSAGE(STATUS "MyGUI version: ${MYGUI_VERSION}") ENDIF (NOT MYGUI_FIND_QUIETLY) - -ELSE (MYGUI_FOUND) - IF (MYGUI_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find MYGUI") - ENDIF (MYGUI_FIND_REQUIRED) ENDIF (MYGUI_FOUND) +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(MyGUI DEFAULT_MSG + MYGUI_INCLUDE_DIRS + FREETYPE_LIBRARIES + MYGUI_LIBRARIES) + CMAKE_POLICY(POP) diff --git a/cmake/FindOGRE.cmake b/cmake/FindOGRE.cmake deleted file mode 100644 index f2acf9d337..0000000000 --- a/cmake/FindOGRE.cmake +++ /dev/null @@ -1,568 +0,0 @@ -#------------------------------------------------------------------- -# This file is part of the CMake build system for OGRE -# (Object-oriented Graphics Rendering Engine) -# For the latest info, see http://www.ogre3d.org/ -# -# The contents of this file are placed in the public domain. Feel -# free to make use of it in any way you like. -#------------------------------------------------------------------- - -# - Try to find OGRE -# If you have multiple versions of Ogre installed, use the CMake or -# the environment variable OGRE_HOME to point to the path where the -# desired Ogre version can be found. -# By default this script will look for a dynamic Ogre build. If you -# need to link against static Ogre libraries, set the CMake variable -# OGRE_STATIC to TRUE. -# -# Once done, this will define -# -# OGRE_FOUND - system has OGRE -# 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) -# -# Additionally this script searches for the following optional -# parts of the Ogre package: -# Plugin_BSPSceneManager, Plugin_CgProgramManager, -# Plugin_OctreeSceneManager, Plugin_OctreeZone, -# Plugin_ParticleFX, Plugin_PCZSceneManager, -# RenderSystem_GL, RenderSystem_Direct3D9, RenderSystem_Direct3D10, -# Paging, Terrain -# -# 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}_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) -# -# Finally, the following variables are defined: -# -# OGRE_PLUGIN_DIR_REL - The directory where the release versions of -# the OGRE plugins are located -# OGRE_PLUGIN_DIR_DBG - The directory where the debug versions of -# the OGRE plugins are located -# OGRE_MEDIA_DIR - The directory where the OGRE sample media is -# located, if available - -include(FindPkgMacros) -include(PreprocessorUtils) -findpkg_begin(OGRE) - - -# Get path, convert backslashes as ${ENV_${var}} -getenv_path(OGRE_HOME) -getenv_path(OGRE_SDK) -getenv_path(OGRE_SOURCE) -getenv_path(OGRE_BUILD) -getenv_path(OGRE_DEPENDENCIES_DIR) -getenv_path(PROGRAMFILES) - -# Determine whether to search for a dynamic or static build -if (OGRE_STATIC) - set(OGRE_LIB_SUFFIX "Static") -else () - set(OGRE_LIB_SUFFIX "") -endif () - - -set(OGRE_LIBRARY_NAMES "OgreMain${OGRE_LIB_SUFFIX}") -get_debug_names(OGRE_LIBRARY_NAMES) - -# construct search paths from environmental hints and -# OS specific guesses -if (WIN32) - set(OGRE_PREFIX_GUESSES - ${ENV_PROGRAMFILES}/OGRE - C:/OgreSDK - ) -elseif (UNIX) - set(OGRE_PREFIX_GUESSES - /opt/ogre - /opt/OGRE - /usr/lib${LIB_SUFFIX}/ogre - /usr/lib${LIB_SUFFIX}/OGRE - /usr/local/lib${LIB_SUFFIX}/ogre - /usr/local/lib${LIB_SUFFIX}/OGRE - $ENV{HOME}/ogre - $ENV{HOME}/OGRE - ) -endif () -set(OGRE_PREFIX_PATH - ${OGRE_HOME} ${OGRE_SDK} ${ENV_OGRE_HOME} ${ENV_OGRE_SDK} - ${OGRE_PREFIX_GUESSES} -) -create_search_paths(OGRE) -# If both OGRE_BUILD and OGRE_SOURCE are set, prepare to find Ogre in a build dir -set(OGRE_PREFIX_SOURCE ${OGRE_SOURCE} ${ENV_OGRE_SOURCE}) -set(OGRE_PREFIX_BUILD ${OGRE_BUILD} ${ENV_OGRE_BUILD}) -set(OGRE_PREFIX_DEPENDENCIES_DIR ${OGRE_DEPENDENCIES_DIR} ${ENV_OGRE_DEPENDENCIES_DIR}) -if (OGRE_PREFIX_SOURCE AND OGRE_PREFIX_BUILD) - foreach(dir ${OGRE_PREFIX_SOURCE}) - set(OGRE_INC_SEARCH_PATH ${dir}/OgreMain/include ${dir}/Dependencies/include ${dir}/iPhoneDependencies/include ${OGRE_INC_SEARCH_PATH}) - set(OGRE_LIB_SEARCH_PATH ${dir}/lib ${dir}/Dependencies/lib ${dir}/iPhoneDependencies/lib ${OGRE_LIB_SEARCH_PATH}) - set(OGRE_BIN_SEARCH_PATH ${dir}/Samples/Common/bin ${OGRE_BIN_SEARCH_PATH}) - endforeach(dir) - foreach(dir ${OGRE_PREFIX_BUILD}) - set(OGRE_INC_SEARCH_PATH ${dir}/include ${OGRE_INC_SEARCH_PATH}) - set(OGRE_LIB_SEARCH_PATH ${dir}/lib ${OGRE_LIB_SEARCH_PATH}) - 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}) - set(OGRE_BIN_SEARCH_PATH ${OGRE_PREFIX_DEPENDENCIES_DIR}/bin ${OGRE_BIN_SEARCH_PATH}) - endif() -else() - set(OGRE_PREFIX_SOURCE "NOTFOUND") - set(OGRE_PREFIX_BUILD "NOTFOUND") -endif () - -# redo search if any of the environmental hints changed -set(OGRE_COMPONENTS Paging Terrain Overlay - Plugin_BSPSceneManager Plugin_CgProgramManager Plugin_OctreeSceneManager - Plugin_OctreeZone Plugin_PCZSceneManager Plugin_ParticleFX - RenderSystem_Direct3D10 RenderSystem_Direct3D9 RenderSystem_GL RenderSystem_GLES2) -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}) - set(OGRE_RESET_VARS ${OGRE_RESET_VARS} - OGRE_${comp}_INCLUDE_DIR OGRE_${comp}_LIBRARY_FWK - OGRE_${comp}_LIBRARY_DBG OGRE_${comp}_LIBRARY_REL - ) -endforeach (comp) -set(OGRE_PREFIX_WATCH ${OGRE_PREFIX_PATH} ${OGRE_PREFIX_SOURCE} ${OGRE_PREFIX_BUILD}) -clear_if_changed(OGRE_PREFIX_WATCH ${OGRE_RESET_VARS}) - -# try to locate Ogre via pkg-config -use_pkgconfig(OGRE_PKGC "OGRE${OGRE_LIB_SUFFIX}") - -if(NOT OGRE_BUILD_PLATFORM_IPHONE AND APPLE) - # try to find framework on OSX - findpkg_framework(OGRE) -else() - set(OGRE_LIBRARY_FWK "") -endif() - -# locate Ogre include files -find_path(OGRE_CONFIG_INCLUDE_DIR NAMES OgreBuildSettings.h HINTS ${OGRE_INC_SEARCH_PATH} ${OGRE_FRAMEWORK_INCLUDES} ${OGRE_PKGC_INCLUDE_DIRS} PATH_SUFFIXES "OGRE") -find_path(OGRE_INCLUDE_DIR NAMES OgreRoot.h HINTS ${OGRE_CONFIG_INCLUDE_DIR} ${OGRE_INC_SEARCH_PATH} ${OGRE_FRAMEWORK_INCLUDES} ${OGRE_PKGC_INCLUDE_DIRS} PATH_SUFFIXES "OGRE") -set(OGRE_INCOMPATIBLE FALSE) - -if (OGRE_INCLUDE_DIR) - if (NOT OGRE_CONFIG_INCLUDE_DIR) - set(OGRE_CONFIG_INCLUDE_DIR ${OGRE_INCLUDE_DIR}) - endif () - # determine Ogre version - file(READ ${OGRE_INCLUDE_DIR}/OgrePrerequisites.h OGRE_TEMP_VERSION_CONTENT) - get_preprocessor_entry(OGRE_TEMP_VERSION_CONTENT OGRE_VERSION_MAJOR OGRE_VERSION_MAJOR) - get_preprocessor_entry(OGRE_TEMP_VERSION_CONTENT OGRE_VERSION_MINOR OGRE_VERSION_MINOR) - get_preprocessor_entry(OGRE_TEMP_VERSION_CONTENT OGRE_VERSION_PATCH OGRE_VERSION_PATCH) - get_preprocessor_entry(OGRE_TEMP_VERSION_CONTENT OGRE_VERSION_NAME OGRE_VERSION_NAME) - set(OGRE_VERSION "${OGRE_VERSION_MAJOR}.${OGRE_VERSION_MINOR}.${OGRE_VERSION_PATCH}") - pkg_message(OGRE "Found Ogre ${OGRE_VERSION_NAME} (${OGRE_VERSION})") - - # determine configuration settings - set(OGRE_CONFIG_HEADERS - ${OGRE_CONFIG_INCLUDE_DIR}/OgreBuildSettings.h - ${OGRE_CONFIG_INCLUDE_DIR}/OgreConfig.h - ) - foreach(CFG_FILE ${OGRE_CONFIG_HEADERS}) - if (EXISTS ${CFG_FILE}) - set(OGRE_CONFIG_HEADER ${CFG_FILE}) - break() - endif() - endforeach() - if (OGRE_CONFIG_HEADER) - file(READ ${OGRE_CONFIG_HEADER} OGRE_TEMP_CONFIG_CONTENT) - has_preprocessor_entry(OGRE_TEMP_CONFIG_CONTENT OGRE_STATIC_LIB OGRE_CONFIG_STATIC) - get_preprocessor_entry(OGRE_TEMP_CONFIG_CONTENT OGRE_THREAD_SUPPORT OGRE_CONFIG_THREADS) - get_preprocessor_entry(OGRE_TEMP_CONFIG_CONTENT OGRE_THREAD_PROVIDER OGRE_CONFIG_THREAD_PROVIDER) - get_preprocessor_entry(OGRE_TEMP_CONFIG_CONTENT OGRE_NO_FREEIMAGE OGRE_CONFIG_FREEIMAGE) - if (OGRE_CONFIG_STATIC AND OGRE_STATIC) - elseif (OGRE_CONFIG_STATIC OR OGRE_STATIC) - pkg_message(OGRE "Build type (static, dynamic) does not match the requested one.") - set(OGRE_INCOMPATIBLE TRUE) - endif () - else () - pkg_message(OGRE "Could not determine Ogre build configuration.") - set(OGRE_INCOMPATIBLE TRUE) - endif () -else () - set(OGRE_INCOMPATIBLE FALSE) -endif () - -find_library(OGRE_LIBRARY_REL NAMES ${OGRE_LIBRARY_NAMES} HINTS ${OGRE_LIB_SEARCH_PATH} ${OGRE_PKGC_LIBRARY_DIRS} ${OGRE_FRAMEWORK_SEARCH_PATH} PATH_SUFFIXES "" "release" "relwithdebinfo" "minsizerel") -find_library(OGRE_LIBRARY_DBG NAMES ${OGRE_LIBRARY_NAMES_DBG} HINTS ${OGRE_LIB_SEARCH_PATH} ${OGRE_PKGC_LIBRARY_DIRS} ${OGRE_FRAMEWORK_SEARCH_PATH} PATH_SUFFIXES "" "debug") -make_library_set(OGRE_LIBRARY) - -if(APPLE) - set(OGRE_LIBRARY_DBG ${OGRE_LIB_SEARCH_PATH}) -endif() -if (OGRE_INCOMPATIBLE) - set(OGRE_LIBRARY "NOTFOUND") -endif () - -set(OGRE_INCLUDE_DIR ${OGRE_CONFIG_INCLUDE_DIR} ${OGRE_INCLUDE_DIR}) -list(REMOVE_DUPLICATES OGRE_INCLUDE_DIR) -findpkg_finish(OGRE) -add_parent_dir(OGRE_INCLUDE_DIRS OGRE_INCLUDE_DIR) -if (OGRE_SOURCE) - # If working from source rather than SDK, add samples include - set(OGRE_INCLUDE_DIRS ${OGRE_INCLUDE_DIRS} "${OGRE_SOURCE}/Samples/Common/include") -endif() - -mark_as_advanced(OGRE_CONFIG_INCLUDE_DIR OGRE_MEDIA_DIR OGRE_PLUGIN_DIR_REL OGRE_PLUGIN_DIR_DBG) - -if (NOT OGRE_FOUND) - return() -endif () - - -# look for required Ogre dependencies in case of static build and/or threading -if (OGRE_STATIC) - set(OGRE_DEPS_FOUND TRUE) - find_package(Cg QUIET) - find_package(DirectX QUIET) - find_package(FreeImage QUIET) - find_package(Freetype QUIET) - find_package(OpenGL QUIET) - find_package(OpenGLES2 QUIET) - find_package(ZLIB QUIET) - find_package(ZZip QUIET) - if (UNIX AND (NOT APPLE AND NOT ANDROID)) - find_package(X11 QUIET) - find_library(XAW_LIBRARY NAMES Xaw Xaw7 PATHS ${DEP_LIB_SEARCH_DIR} ${X11_LIB_SEARCH_PATH}) - if (NOT XAW_LIBRARY OR NOT X11_Xt_FOUND) - set(X11_FOUND FALSE) - endif () - endif () - if (APPLE AND NOT OGRE_BUILD_PLATFORM_IPHONE) - find_package(Cocoa QUIET) - find_package(Carbon QUIET) - if (NOT Cocoa_FOUND OR NOT Carbon_FOUND) - set(OGRE_DEPS_FOUND FALSE) - endif () - endif () - if (APPLE AND OGRE_BUILD_PLATFORM_IPHONE) - find_package(iPhoneSDK QUIET) - if (NOT iPhoneSDK_FOUND) - set(OGRE_DEPS_FOUND FALSE) - endif () - endif () - -if (ANDROID) - set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${OGRE_LIBRARY_FWK} ${ZZip_LIBRARIES} ${ZLIB_LIBRARIES} - ${FreeImage_LIBRARIES} ${FREETYPE_LIBRARIES} - ${Cocoa_LIBRARIES} ${Carbon_LIBRARIES}) -else () - set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${OGRE_LIBRARY_FWK} ${ZZip_LIBRARIES} ${ZLIB_LIBRARIES} - ${FreeImage_LIBRARIES} ${FREETYPE_LIBRARIES} - ${X11_LIBRARIES} ${X11_Xt_LIBRARIES} ${XAW_LIBRARY} ${X11_Xrandr_LIB} - ${Cocoa_LIBRARIES} ${Carbon_LIBRARIES}) -endif() - - if (NOT ZLIB_FOUND OR NOT ZZip_FOUND) - set(OGRE_DEPS_FOUND FALSE) - endif () - if (NOT FreeImage_FOUND AND NOT OGRE_CONFIG_FREEIMAGE) - set(OGRE_DEPS_FOUND FALSE) - endif () - if (NOT FREETYPE_FOUND) - set(OGRE_DEPS_FOUND FALSE) - endif () - if (UNIX AND NOT APPLE AND NOT ANDROID) - if (NOT X11_FOUND) - set(OGRE_DEPS_FOUND FALSE) - endif () - endif () - - if (OGRE_CONFIG_THREADS) - if (OGRE_CONFIG_THREAD_PROVIDER EQUAL 1) - find_package(Boost COMPONENTS thread QUIET) - if (NOT Boost_THREAD_FOUND) - set(OGRE_DEPS_FOUND FALSE) - else () - set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${Boost_LIBRARIES}) - set(OGRE_INCLUDE_DIRS ${OGRE_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) - endif () - elseif (OGRE_CONFIG_THREAD_PROVIDER EQUAL 2) - find_package(POCO QUIET) - if (NOT POCO_FOUND) - set(OGRE_DEPS_FOUND FALSE) - else () - set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${POCO_LIBRARIES}) - set(OGRE_INCLUDE_DIRS ${OGRE_INCLUDE_DIRS} ${POCO_INCLUDE_DIRS}) - endif () - elseif (OGRE_CONFIG_THREAD_PROVIDER EQUAL 3) - find_package(TBB QUIET) - if (NOT TBB_FOUND) - set(OGRE_DEPS_FOUND FALSE) - else () - set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${TBB_LIBRARIES}) - set(OGRE_INCLUDE_DIRS ${OGRE_INCLUDE_DIRS} ${TBB_INCLUDE_DIRS}) - 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) - endif () -endif () - -if (NOT OGRE_FOUND) - return() -endif () - - -get_filename_component(OGRE_LIBRARY_DIR_REL "${OGRE_LIBRARY_REL}" PATH) -get_filename_component(OGRE_LIBRARY_DIR_DBG "${OGRE_LIBRARY_DBG}" PATH) -set(OGRE_LIBRARY_DIRS ${OGRE_LIBRARY_DIR_REL} ${OGRE_LIBRARY_DIR_DBG}) - -# find binaries -if (NOT OGRE_STATIC) - if (WIN32) - find_file(OGRE_BINARY_REL NAMES "OgreMain.dll" HINTS ${OGRE_BIN_SEARCH_PATH} - PATH_SUFFIXES "" release relwithdebinfo minsizerel) - find_file(OGRE_BINARY_DBG NAMES "OgreMain_d.dll" HINTS ${OGRE_BIN_SEARCH_PATH} - PATH_SUFFIXES "" debug ) - endif() - mark_as_advanced(OGRE_BINARY_REL OGRE_BINARY_DBG) -endif() - - -######################################################### -# Find Ogre components -######################################################### - -set(OGRE_COMPONENT_SEARCH_PATH_REL - ${OGRE_LIBRARY_DIR_REL}/.. - ${OGRE_LIBRARY_DIR_REL}/../.. - ${OGRE_BIN_SEARCH_PATH} -) -set(OGRE_COMPONENT_SEARCH_PATH_DBG - ${OGRE_LIBRARY_DIR_DBG}/.. - ${OGRE_LIBRARY_DIR_DBG}/../.. - ${OGRE_BIN_SEARCH_PATH} -) - -macro(ogre_find_component COMPONENT HEADER) - findpkg_begin(OGRE_${COMPONENT}) - find_path(OGRE_${COMPONENT}_INCLUDE_DIR NAMES ${HEADER} HINTS ${OGRE_INCLUDE_DIRS} ${OGRE_INCLUDE_DIR}/OGRE/${COMPONENT} ${OGRE_PREFIX_SOURCE} PATH_SUFFIXES ${COMPONENT} OGRE/${COMPONENT} Components/${COMPONENT}/include) - set(OGRE_${COMPONENT}_LIBRARY_NAMES "Ogre${COMPONENT}${OGRE_LIB_SUFFIX}") - get_debug_names(OGRE_${COMPONENT}_LIBRARY_NAMES) - find_library(OGRE_${COMPONENT}_LIBRARY_REL NAMES ${OGRE_${COMPONENT}_LIBRARY_NAMES} HINTS ${OGRE_LIBRARY_DIR_REL} PATH_SUFFIXES "" "release" "relwithdebinfo" "minsizerel") - find_library(OGRE_${COMPONENT}_LIBRARY_DBG NAMES ${OGRE_${COMPONENT}_LIBRARY_NAMES_DBG} HINTS ${OGRE_LIBRARY_DIR_DBG} PATH_SUFFIXES "" "debug") - make_library_set(OGRE_${COMPONENT}_LIBRARY) - findpkg_finish(OGRE_${COMPONENT}) - if (OGRE_${COMPONENT}_FOUND) - if (APPLE) - include_directories("${OGRE_INCLUDE_DIR}/OGRE/${COMPONENT}") - endif() - # find binaries - if (NOT OGRE_STATIC) - if (WIN32) - find_file(OGRE_${COMPONENT}_BINARY_REL NAMES "Ogre${COMPONENT}.dll" HINTS ${OGRE_COMPONENT_SEARCH_PATH_REL} PATH_SUFFIXES "" bin bin/release bin/relwithdebinfo bin/minsizerel release) - find_file(OGRE_${COMPONENT}_BINARY_DBG NAMES "Ogre${COMPONENT}_d.dll" HINTS ${OGRE_COMPONENT_SEARCH_PATH_DBG} PATH_SUFFIXES "" bin bin/debug debug) - endif() - mark_as_advanced(OGRE_${COMPONENT}_BINARY_REL OGRE_${COMPONENT}_BINARY_DBG) - endif() - endif() -endmacro() - -# look for Paging component -ogre_find_component(Paging OgrePaging.h) -# look for Overlay component -ogre_find_component(Overlay OgreOverlaySystem.h) -# look for Terrain component -ogre_find_component(Terrain OgreTerrain.h) -# look for Property component -ogre_find_component(Property OgreProperty.h) -# look for RTShaderSystem component -ogre_find_component(RTShaderSystem OgreRTShaderSystem.h) - - -######################################################### -# Find Ogre plugins -######################################################### - -macro(ogre_find_plugin PLUGIN HEADER) - # On Unix, the plugins might have no prefix - if (CMAKE_FIND_LIBRARY_PREFIXES) - 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} - RenderSystems RenderSystems/${PLUGIN_NAME} ${ARGN}) - 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}") - get_debug_names(OGRE_${PLUGIN}_LIBRARY_NAMES) - set(OGRE_${PLUGIN}_LIBRARY_FWK ${OGRE_LIBRARY_FWK}) - # Search for release plugins in OGRE dir with version suffix - find_library(OGRE_${PLUGIN}_LIBRARY_REL NAMES ${OGRE_${PLUGIN}_LIBRARY_NAMES} - HINTS ${OGRE_LIBRARY_DIRS} PATH_SUFFIXES "" OGRE-${OGRE_VERSION} opt release release/opt relwithdebinfo relwithdebinfo/opt minsizerel minsizerel/opt) - if(NOT EXISTS "${OGRE_${PLUGIN}_LIBRARY_REL}") - # Search for release plugins in OGRE dir without version suffix - find_library(OGRE_${PLUGIN}_LIBRARY_REL NAMES ${OGRE_${PLUGIN}_LIBRARY_NAMES} - HINTS ${OGRE_LIBRARY_DIRS} PATH_SUFFIXES "" OGRE opt release release/opt relwithdebinfo relwithdebinfo/opt minsizerel minsizerel/opt) - endif() - # Search for debug plugins in OGRE dir with version suffix - find_library(OGRE_${PLUGIN}_LIBRARY_DBG NAMES ${OGRE_${PLUGIN}_LIBRARY_NAMES_DBG} - HINTS ${OGRE_LIBRARY_DIRS} PATH_SUFFIXES "" OGRE-${OGRE_VERSION} opt debug debug/opt) - if(NOT EXISTS "${OGRE_${PLUGIN}_LIBRARY_DBG}") - # Search for debug plugins in OGRE dir without version suffix - find_library(OGRE_${PLUGIN}_LIBRARY_DBG NAMES ${OGRE_${PLUGIN}_LIBRARY_NAMES_DBG} - HINTS ${OGRE_LIBRARY_DIRS} PATH_SUFFIXES "" OGRE opt debug debug/opt) - endif() - make_library_set(OGRE_${PLUGIN}_LIBRARY) - - if (OGRE_${PLUGIN}_LIBRARY OR OGRE_${PLUGIN}_INCLUDE_DIR) - set(OGRE_${PLUGIN}_FOUND TRUE) - if (OGRE_${PLUGIN}_INCLUDE_DIR) - set(OGRE_${PLUGIN}_INCLUDE_DIRS ${OGRE_${PLUGIN}_INCLUDE_DIR}) - endif() - set(OGRE_${PLUGIN}_LIBRARIES ${OGRE_${PLUGIN}_LIBRARY}) - endif () - - mark_as_advanced(OGRE_${PLUGIN}_INCLUDE_DIR OGRE_${PLUGIN}_LIBRARY_REL OGRE_${PLUGIN}_LIBRARY_DBG OGRE_${PLUGIN}_LIBRARY_FWK) - - # look for plugin dirs - if (OGRE_${PLUGIN}_FOUND) - if (NOT OGRE_PLUGIN_DIR_REL OR NOT OGRE_PLUGIN_DIR_DBG) - if (WIN32) - set(OGRE_PLUGIN_SEARCH_PATH_REL - ${OGRE_LIBRARY_DIR_REL}/.. - ${OGRE_LIBRARY_DIR_REL}/../.. - ${OGRE_BIN_SEARCH_PATH} - ) - set(OGRE_PLUGIN_SEARCH_PATH_DBG - ${OGRE_LIBRARY_DIR_DBG}/.. - ${OGRE_LIBRARY_DIR_DBG}/../.. - ${OGRE_BIN_SEARCH_PATH} - ) - find_path(OGRE_PLUGIN_DIR_REL NAMES "${PLUGIN}.dll" HINTS ${OGRE_PLUGIN_SEARCH_PATH_REL} - PATH_SUFFIXES "" bin bin/release bin/relwithdebinfo bin/minsizerel release) - find_path(OGRE_PLUGIN_DIR_DBG NAMES "${PLUGIN}_d.dll" HINTS ${OGRE_PLUGIN_SEARCH_PATH_DBG} - PATH_SUFFIXES "" bin bin/debug debug) - elseif (UNIX) - get_filename_component(OGRE_PLUGIN_DIR_TMP ${OGRE_${PLUGIN}_LIBRARY_REL} PATH) - # For some reason this fails - #set(OGRE_PLUGIN_DIR_REL ${OGRE_PLUGIN_DIR_TMP} CACHE STRING "Ogre plugin dir (release)") - set(OGRE_PLUGIN_DIR_REL ${OGRE_PLUGIN_DIR_TMP}) - get_filename_component(OGRE_PLUGIN_DIR_TMP ${OGRE_${PLUGIN}_LIBRARY_DBG} PATH) - # Same here - #set(OGRE_PLUGIN_DIR_DBG ${OGRE_PLUGIN_DIR_TMP} CACHE STRING "Ogre plugin dir (debug)") - set(OGRE_PLUGIN_DIR_DBG ${OGRE_PLUGIN_DIR_TMP}) - endif () - endif () - - # find binaries - if (NOT OGRE_STATIC) - if (WIN32) - find_file(OGRE_${PLUGIN}_REL NAMES "${PLUGIN}.dll" HINTS ${OGRE_PLUGIN_DIR_REL}) - find_file(OGRE_${PLUGIN}_DBG NAMES "${PLUGIN}_d.dll" HINTS ${OGRE_PLUGIN_DIR_DBG}) - endif() - mark_as_advanced(OGRE_${PLUGIN}_REL OGRE_${PLUGIN}_DBG) - endif() - - endif () - - if (TMP_CMAKE_LIB_PREFIX) - set(CMAKE_FIND_LIBRARY_PREFIXES ${TMP_CMAKE_LIB_PREFIX}) - endif () -endmacro(ogre_find_plugin) - -ogre_find_plugin(Plugin_PCZSceneManager OgrePCZSceneManager.h PCZ PlugIns/PCZSceneManager/include) -ogre_find_plugin(Plugin_OctreeZone OgreOctreeZone.h PCZ PlugIns/OctreeZone/include) -ogre_find_plugin(Plugin_BSPSceneManager OgreBspSceneManager.h PlugIns/BSPSceneManager/include) -ogre_find_plugin(Plugin_CgProgramManager OgreCgProgram.h PlugIns/CgProgramManager/include) -ogre_find_plugin(Plugin_OctreeSceneManager OgreOctreeSceneManager.h PlugIns/OctreeSceneManager/include) -ogre_find_plugin(Plugin_ParticleFX OgreParticleFXPrerequisites.h PlugIns/ParticleFX/include) -ogre_find_plugin(RenderSystem_GL OgreGLRenderSystem.h RenderSystems/GL/include) -ogre_find_plugin(RenderSystem_GLES2 OgreGLES2RenderSystem.h RenderSystems/GLES2/include) -ogre_find_plugin(RenderSystem_Direct3D9 OgreD3D9RenderSystem.h RenderSystems/Direct3D9/include) -ogre_find_plugin(RenderSystem_Direct3D10 OgreD3D10RenderSystem.h RenderSystems/Direct3D10/include) -ogre_find_plugin(RenderSystem_Direct3D11 OgreD3D11RenderSystem.h RenderSystems/Direct3D11/include) - -if (OGRE_STATIC) - # check if dependencies for plugins are met - if (NOT DirectX9_FOUND) - set(OGRE_RenderSystem_Direct3D9_FOUND FALSE) - else () - set(OGRE_INCLUDE_DIRS ${OGRE_INCLUDE_DIRS} ${DirectX9_INCLUDE_DIR}) - endif () - if (NOT DirectX_D3D10_FOUND) - set(OGRE_RenderSystem_Direct3D10_FOUND FALSE) - endif () - if (NOT DirectX_D3D11_FOUND) - set(OGRE_RenderSystem_Direct3D11_FOUND FALSE) - else () - set(OGRE_INCLUDE_DIRS ${OGRE_INCLUDE_DIRS} ${DirectX_D3D11_INCLUDE_DIR}) - endif () - if (NOT OPENGL_FOUND) - set(OGRE_RenderSystem_GL_FOUND FALSE) - endif () - if (NOT OPENGLES_FOUND AND NOT OPENGLES2_FOUND) - set(OGRE_RenderSystem_GLES_FOUND FALSE) - endif () - if (NOT Cg_FOUND) - set(OGRE_Plugin_CgProgramManager_FOUND FALSE) - endif () - - set(OGRE_RenderSystem_Direct3D9_LIBRARIES ${OGRE_RenderSystem_Direct3D9_LIBRARIES} - ${DirectX9_LIBRARIES} - ) - set(OGRE_RenderSystem_Direct3D10_LIBRARIES ${OGRE_RenderSystem_Direct3D10_LIBRARIES} - ${DirectX_D3D10_LIBRARIES} - ) - set(OGRE_RenderSystem_Direct3D11_LIBRARIES ${OGRE_RenderSystem_Direct3D11_LIBRARIES} - ${DirectX_D3D11_LIBRARIES} - ) - set(OGRE_RenderSystem_GL_LIBRARIES ${OGRE_RenderSystem_GL_LIBRARIES} - ${OPENGL_LIBRARIES} - ) - set(OGRE_RenderSystem_GLES2_LIBRARIES ${OGRE_RenderSystem_GLES2_LIBRARIES} - ${OPENGLES2_LIBRARIES} - ) - set(OGRE_Plugin_CgProgramManager_LIBRARIES ${OGRE_Plugin_CgProgramManager_LIBRARIES} - ${Cg_LIBRARIES} - ) -endif () - -# look for the media directory -set(OGRE_MEDIA_SEARCH_PATH - ${OGRE_SOURCE} - ${OGRE_LIBRARY_DIR_REL}/.. - ${OGRE_LIBRARY_DIR_DBG}/.. - ${OGRE_LIBRARY_DIR_REL}/../.. - ${OGRE_LIBRARY_DIR_DBG}/../.. - ${OGRE_PREFIX_SOURCE} -) -set(OGRE_MEDIA_SEARCH_SUFFIX - Samples/Media - Media - media - share/OGRE/media -) - -clear_if_changed(OGRE_PREFIX_WATCH OGRE_MEDIA_DIR) -find_path(OGRE_MEDIA_DIR NAMES packs/cubemapsJS.zip HINTS ${OGRE_MEDIA_SEARCH_PATH} - PATHS ${OGRE_PREFIX_PATH} PATH_SUFFIXES ${OGRE_MEDIA_SEARCH_SUFFIX}) diff --git a/cmake/FindOpenGLES.cmake b/cmake/FindOpenGLES.cmake new file mode 100644 index 0000000000..7ee2c07f18 --- /dev/null +++ b/cmake/FindOpenGLES.cmake @@ -0,0 +1,94 @@ +#------------------------------------------------------------------- +# This file is part of the CMake build system for OGRE +# (Object-oriented Graphics Rendering Engine) +# For the latest info, see http://www.ogre3d.org/ +# +# The contents of this file are placed in the public domain. Feel +# free to make use of it in any way you like. +#------------------------------------------------------------------- + +# - Try to find OpenGLES +# Once done this will define +# +# OPENGLES_FOUND - system has OpenGLES +# OPENGLES_INCLUDE_DIR - the GL include directory +# OPENGLES_LIBRARIES - Link these to use OpenGLES + +IF (WIN32) + IF (CYGWIN) + + FIND_PATH(OPENGLES_INCLUDE_DIR GLES/gl.h ) + + FIND_LIBRARY(OPENGLES_gl_LIBRARY libgles_cm ) + + ELSE (CYGWIN) + + IF(BORLAND) + SET (OPENGLES_gl_LIBRARY import32 CACHE STRING "OpenGL ES 1.x library for win32") + ELSE(BORLAND) + #MS compiler - todo - fix the following line: + SET (OPENGLES_gl_LIBRARY ${OGRE_SOURCE_DIR}/Dependencies/lib/release/libgles_cm.lib CACHE STRING "OpenGL ES 1.x library for win32") + ENDIF(BORLAND) + + ENDIF (CYGWIN) + +ELSE (WIN32) + + IF (APPLE) + + #create_search_paths(/Developer/Platforms) + #findpkg_framework(OpenGLES) + #set(OPENGLES_gl_LIBRARY "-framework OpenGLES") + + ELSE(APPLE) + + FIND_PATH(OPENGLES_INCLUDE_DIR GLES/gl.h + /opt/vc/include + /opt/graphics/OpenGL/include + /usr/openwin/share/include + /usr/X11R6/include + /usr/include + ) + + FIND_LIBRARY(OPENGLES_gl_LIBRARY + NAMES GLES_CM GLESv1_CM + PATHS /opt/vc/lib + /opt/graphics/OpenGL/lib + /usr/openwin/lib + /usr/shlib /usr/X11R6/lib + /usr/lib + ) + + # On Unix OpenGL most certainly always requires X11. + # Feel free to tighten up these conditions if you don't + # think this is always true. + + #IF (OPENGLES_gl_LIBRARY) + # IF(NOT X11_FOUND) + # INCLUDE(FindX11) + # ENDIF(NOT X11_FOUND) + # IF (X11_FOUND) + # SET (OPENGLES_LIBRARIES ${X11_LIBRARIES}) + # ENDIF (X11_FOUND) + #ENDIF (OPENGLES_gl_LIBRARY) + + ENDIF(APPLE) +ENDIF (WIN32) + +SET( OPENGLES_FOUND "NO" ) +IF(OPENGLES_gl_LIBRARY) + + SET( OPENGLES_LIBRARIES ${OPENGLES_gl_LIBRARY} ${OPENGLES_LIBRARIES}) + + SET( OPENGLES_FOUND "YES" ) + +ENDIF(OPENGLES_gl_LIBRARY) + +MARK_AS_ADVANCED( + OPENGLES_INCLUDE_DIR + OPENGLES_gl_LIBRARY +) + +INCLUDE(FindPackageHandleStandardArgs) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(OPENGLES REQUIRED_VARS OPENGLES_LIBRARIES OPENGLES_INCLUDE_DIR) diff --git a/cmake/FindZZip.cmake b/cmake/FindZZip.cmake deleted file mode 100644 index 68fe043f36..0000000000 --- a/cmake/FindZZip.cmake +++ /dev/null @@ -1,48 +0,0 @@ -#------------------------------------------------------------------- -# This file is part of the CMake build system for OGRE -# (Object-oriented Graphics Rendering Engine) -# For the latest info, see http://www.ogre3d.org/ -# -# The contents of this file are placed in the public domain. Feel -# free to make use of it in any way you like. -#------------------------------------------------------------------- - -# - Try to find zziplib -# Once done, this will define -# -# ZZip_FOUND - system has ZZip -# ZZip_INCLUDE_DIRS - the ZZip include directories -# ZZip_LIBRARIES - link these to use ZZip - -include(FindPkgMacros) -findpkg_begin(ZZip) - -# Get path, convert backslashes as ${ENV_${var}} -getenv_path(ZZIP_HOME) - - -# construct search paths -set(ZZip_PREFIX_PATH ${ZZIP_HOME} ${ENV_ZZIP_HOME}) -create_search_paths(ZZip) -# redo search if prefix path changed -clear_if_changed(ZZip_PREFIX_PATH - ZZip_LIBRARY_FWK - ZZip_LIBRARY_REL - ZZip_LIBRARY_DBG - ZZip_INCLUDE_DIR -) - -set(ZZip_LIBRARY_NAMES zzip zziplib) -get_debug_names(ZZip_LIBRARY_NAMES) - -use_pkgconfig(ZZip_PKGC zziplib) - -findpkg_framework(ZZip) - -find_path(ZZip_INCLUDE_DIR NAMES zzip/zzip.h HINTS ${ZZip_INC_SEARCH_PATH} ${ZZip_PKGC_INCLUDE_DIRS}) -find_library(ZZip_LIBRARY_REL NAMES ${ZZip_LIBRARY_NAMES} HINTS ${ZZip_LIB_SEARCH_PATH} ${ZZip_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" release relwithdebinfo minsizerel) -find_library(ZZip_LIBRARY_DBG NAMES ${ZZip_LIBRARY_NAMES_DBG} HINTS ${ZZip_LIB_SEARCH_PATH} ${ZZip_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" debug) -make_library_set(ZZip_LIBRARY) - -findpkg_finish(ZZip) - diff --git a/cmake/GitVersion.cmake b/cmake/GitVersion.cmake index 0087461a12..0679e406c2 100644 --- a/cmake/GitVersion.cmake +++ b/cmake/GitVersion.cmake @@ -21,4 +21,4 @@ else (SUCCESS) message(WARNING "Failed to get valid version information from Git") endif (SUCCESS) -configure_file(${VERSION_HPP_IN} ${VERSION_HPP}) +configure_file(${VERSION_IN_FILE} ${VERSION_FILE}) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 1b33b10f6d..bbbff234c4 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -1,49 +1,61 @@ project (Components) # Version file -set (VERSION_HPP_IN ${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp.cmake) -set (VERSION_HPP ${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp) +set (VERSION_IN_FILE "${OpenMW_SOURCE_DIR}/files/version.in") +set (VERSION_FILE "${OpenMW_BINARY_DIR}/resources/version") if (GIT_CHECKOUT) - add_custom_target (git-version - COMMAND ${CMAKE_COMMAND} - -DGIT_EXECUTABLE=${GIT_EXECUTABLE} + add_custom_target (git-version + COMMAND ${CMAKE_COMMAND} + -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} - -DVERSION_HPP_IN=${VERSION_HPP_IN} - -DVERSION_HPP=${VERSION_HPP} - -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} - -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} - -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} - -DOPENMW_VERSION=${OPENMW_VERSION} + -DVERSION_IN_FILE=${VERSION_IN_FILE} + -DVERSION_FILE=${VERSION_FILE} + -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} + -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} + -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} + -DOPENMW_VERSION=${OPENMW_VERSION} -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake - VERBATIM) + VERBATIM) else (GIT_CHECKOUT) - configure_file(${VERSION_HPP_IN} ${VERSION_HPP}) + configure_file(${VERSION_IN_FILE} ${VERSION_FILE}) endif (GIT_CHECKOUT) +if (OPENGL_ES) + find_package(OpenGLES REQUIRED) +else() + find_package(OpenGL REQUIRED) +endif() + # source files add_component_dir (settings settings ) -add_component_dir (nifoverrides - nifoverrides +add_component_dir (bsa + bsa_file ) -add_component_dir (bsa - bsa_archive bsa_file resources +add_component_dir (vfs + manager archive bsaarchive filesystemarchive registerarchives + ) + +add_component_dir (resource + scenemanager keyframemanager texturemanager resourcesystem bulletshapemanager bulletshape niffilemanager objectcache + ) + +add_component_dir (sceneutil + clone attach lightmanager visitor util statesetupdater controller skeleton riggeometry lightcontroller positionattitudetransform + # not used yet + #workqueue ) add_component_dir (nif - controlled effect niftypes record controller extra node record_ptr data niffile property nifkey data node base nifstream + controlled effect niftypes record controller extra node record_ptr data niffile property nifkey base nifstream ) -add_component_dir (nifcache - nifcache - ) - -add_component_dir (nifogre - ogrenifloader skeleton material mesh particles controller +add_component_dir (nifosg + nifloader controller particle userdata ) add_component_dir (nifbullet @@ -70,7 +82,7 @@ add_component_dir (esmterrain ) add_component_dir (misc - utf8stream stringops resourcehelpers + utf8stream stringops resourcehelpers rng ) IF(NOT WIN32 AND NOT APPLE) @@ -79,7 +91,7 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF() add_component_dir (files linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager - constrainedfiledatastream lowlevelfile + lowlevelfile constrainedfilestream memorystream ) add_component_dir (compiler @@ -98,17 +110,16 @@ add_component_dir (translation translation ) -add_definitions(-DTERRAIN_USE_SHADER=1) add_component_dir (terrain - quadtreenode chunk world defaultworld terraingrid storage material buffercache defs + storage world buffercache defs terraingrid material ) add_component_dir (loadinglistener loadinglistener ) -add_component_dir (ogreinit - ogreinit ogreplugin +add_component_dir (myguiplatform + myguirendermanager myguidatamanager myguiplatform myguitexture myguiloglistener additivelayer scalinglayer ) add_component_dir (widgets @@ -119,6 +130,10 @@ add_component_dir (fontloader fontloader ) +add_component_dir (sdlutil + sdlgraphicswindow imagetosurface sdlinputwrapper sdlvideowrapper OISCompat events sdlcursormanager + ) + add_component_dir (version version ) @@ -126,29 +141,31 @@ add_component_dir (version set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) -add_component_qt_dir (contentselector - model/modelitem model/esmfile - model/naturalsort model/contentmodel - model/loadordererror - view/combobox view/contentselector - ) -add_component_qt_dir (config - gamesettings - launchersettings - settingsbase - ) +if (USE_QT) + add_component_qt_dir (contentselector + model/modelitem model/esmfile + model/naturalsort model/contentmodel + model/loadordererror + view/combobox view/contentselector + ) + add_component_qt_dir (config + gamesettings + launchersettings + settingsbase + ) -add_component_qt_dir (process - processinvoker -) + add_component_qt_dir (process + processinvoker + ) -if (DESIRED_QT_VERSION MATCHES 4) - include(${QT_USE_FILE}) - QT4_WRAP_UI(ESM_UI_HDR ${ESM_UI}) - QT4_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) -else() - QT5_WRAP_UI(ESM_UI_HDR ${ESM_UI}) - QT5_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) + if (DESIRED_QT_VERSION MATCHES 4) + include(${QT_USE_FILE}) + QT4_WRAP_UI(ESM_UI_HDR ${ESM_UI}) + QT4_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) + else() + QT5_WRAP_UI(ESM_UI_HDR ${ESM_UI}) + QT5_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) + endif() endif() if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") @@ -161,28 +178,46 @@ include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) +if (OPENGL_ES) + set(GL_LIB ${OPENGLES_gl_LIBRARY}) +else() + set(GL_LIB ${OPENGL_gl_LIBRARY}) +endif() + target_link_libraries(components ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${Boost_WAVE_LIBRARY} - ${OGRE_LIBRARIES} - ${OENGINE_LIBRARY} + ${OSG_LIBRARIES} + ${OPENTHREADS_LIBRARIES} + ${OSGPARTICLE_LIBRARIES} + ${OSGUTIL_LIBRARIES} + ${OSGDB_LIBRARIES} + ${OSGVIEWER_LIBRARIES} + ${OSGGA_LIBRARIES} + ${OSGFX_LIBRARIES} + ${OSGANIMATION_LIBRARIES} ${BULLET_LIBRARIES} -) + ${SDL2_LIBRARY} + # For MyGUI platform + ${GL_LIB} + ${MYGUI_LIBRARIES} + ) if (WIN32) target_link_libraries(components ${Boost_LOCALE_LIBRARY}) endif() -if (DESIRED_QT_VERSION MATCHES 4) - target_link_libraries(components - ${QT_QTCORE_LIBRARY} - ${QT_QTGUI_LIBRARY}) -else() - qt5_use_modules(components Widgets Core) +if (USE_QT) + if (DESIRED_QT_VERSION MATCHES 4) + target_link_libraries(components + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY}) + else() + qt5_use_modules(components Widgets Core) + endif() endif() if (GIT_CHECKOUT) @@ -190,7 +225,10 @@ if (GIT_CHECKOUT) endif (GIT_CHECKOUT) if (WIN32) -target_link_libraries(components shlwapi) + target_link_libraries(components shlwapi) + if(MINGW) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNOGDI") + endif(MINGW) endif() # Fix for not visible pthreads functions for linker with glibc 2.15 diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp deleted file mode 100644 index 4f656f9c49..0000000000 --- a/components/bsa/bsa_archive.cpp +++ /dev/null @@ -1,385 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (cpp_bsaarchive.cpp) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - 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 - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -#include "bsa_archive.hpp" - -#include - -#include -#include -#include -#include -#include -#include "bsa_file.hpp" - -#include "../files/constrainedfiledatastream.hpp" - -using namespace Ogre; - -static bool fsstrict = false; - -static char strict_normalize_char(char ch) -{ - return ch == '\\' ? '/' : ch; -} - -static char nonstrict_normalize_char(char ch) -{ - return ch == '\\' ? '/' : std::tolower(ch,std::locale::classic()); -} - -template -static std::string normalize_path(T1 begin, T2 end) -{ - std::string normalized; - normalized.reserve(std::distance(begin, end)); - char (*normalize_char)(char) = fsstrict ? &strict_normalize_char : &nonstrict_normalize_char; - std::transform(begin, end, std::back_inserter(normalized), normalize_char); - return normalized; -} - -/// An OGRE Archive wrapping a BSAFile archive -class DirArchive: public Ogre::Archive -{ - typedef std::map index; - - index mIndex; - - index::const_iterator lookup_filename (std::string const & filename) const - { - std::string normalized = normalize_path (filename.begin (), filename.end ()); - return mIndex.find (normalized); - } - -public: - - DirArchive(const String& name) - : Archive(name, "Dir") - { - typedef boost::filesystem::recursive_directory_iterator directory_iterator; - - directory_iterator end; - - size_t prefix = name.size (); - - if (name.size () > 0 && name [prefix - 1] != '\\' && name [prefix - 1] != '/') - ++prefix; - - for (directory_iterator i (name); i != end; ++i) - { - if(boost::filesystem::is_directory (*i)) - continue; - - std::string proper = i->path ().string (); - - std::string searchable = normalize_path (proper.begin () + prefix, proper.end ()); - - mIndex.insert (std::make_pair (searchable, proper)); - } - } - - bool isCaseSensitive() const { return fsstrict; } - - // The archive is loaded in the constructor, and never unloaded. - void load() {} - void unload() {} - - DataStreamPtr open(const String& filename, bool readonly = true) const - { - index::const_iterator i = lookup_filename (filename); - - if (i == mIndex.end ()) - { - std::ostringstream os; - os << "The file '" << filename << "' could not be found."; - throw std::runtime_error (os.str ()); - } - - return openConstrainedFileDataStream (i->second.c_str ()); - } - - StringVectorPtr list(bool recursive = true, bool dirs = false) - { - return find ("*", recursive, dirs); - } - - FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false) - { - return findFileInfo ("*", recursive, dirs); - } - - StringVectorPtr find(const String& pattern, bool recursive = true, - bool dirs = false) - { - std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); - StringVectorPtr ptr = StringVectorPtr(new StringVector()); - for(index::const_iterator iter = mIndex.begin();iter != mIndex.end();++iter) - { - if(Ogre::StringUtil::match(iter->first, normalizedPattern) || - (recursive && Ogre::StringUtil::match(iter->first, "*/"+normalizedPattern))) - ptr->push_back(iter->first); - } - return ptr; - } - - bool exists(const String& filename) - { - return lookup_filename(filename) != mIndex.end (); - } - - time_t getModifiedTime(const String&) { return 0; } - - FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, - bool dirs = false) const - { - std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); - FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); - - index::const_iterator i = mIndex.find(normalizedPattern); - if(i != mIndex.end()) - { - std::string::size_type pt = i->first.rfind('/'); - if(pt == std::string::npos) - pt = 0; - - FileInfo fi; - fi.archive = const_cast(this); - fi.path = i->first.substr(0, pt); - fi.filename = i->first.substr((i->first[pt]=='/') ? pt+1 : pt); - fi.compressedSize = fi.uncompressedSize = 0; - - ptr->push_back(fi); - } - else - { - for(index::const_iterator iter = mIndex.begin();iter != mIndex.end();++iter) - { - if(Ogre::StringUtil::match(iter->first, normalizedPattern) || - (recursive && Ogre::StringUtil::match(iter->first, "*/"+normalizedPattern))) - { - std::string::size_type pt = iter->first.rfind('/'); - if(pt == std::string::npos) - pt = 0; - - FileInfo fi; - fi.archive = const_cast(this); - fi.path = iter->first.substr(0, pt); - fi.filename = iter->first.substr((iter->first[pt]=='/') ? pt+1 : pt); - fi.compressedSize = fi.uncompressedSize = 0; - - ptr->push_back(fi); - } - } - } - - return ptr; - } -}; - -class BSAArchive : public Archive -{ - Bsa::BSAFile arc; - - static const char *extractFilename(const Bsa::BSAFile::FileStruct &entry) - { - return entry.name; - } - -public: - BSAArchive(const String& name) - : Archive(name, "BSA") - { arc.open(name); } - - bool isCaseSensitive() const { return false; } - - // The archive is loaded in the constructor, and never unloaded. - void load() {} - void unload() {} - - DataStreamPtr open(const String& filename, bool readonly = true) const - { - // Get a non-const reference to arc. This is a hack and it's all - // OGRE's fault. You should NOT expect an open() command not to - // have any side effects on the archive, and hence this function - // should not have been declared const in the first place. - Bsa::BSAFile *narc = const_cast(&arc); - - // Open the file - return narc->getFile(filename.c_str()); - } - - bool exists(const String& filename) { - return arc.exists(filename.c_str()); - } - - time_t getModifiedTime(const String&) { return 0; } - - // This is never called as far as I can see. - StringVectorPtr list(bool recursive = true, bool dirs = false) - { - return find ("*", recursive, dirs); - } - - // Also never called. - FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false) - { - return findFileInfo ("*", recursive, dirs); - } - - StringVectorPtr find(const String& pattern, bool recursive = true, - bool dirs = false) - { - std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); - const Bsa::BSAFile::FileList &filelist = arc.getList(); - StringVectorPtr ptr = StringVectorPtr(new StringVector()); - for(Bsa::BSAFile::FileList::const_iterator iter = filelist.begin();iter != filelist.end();++iter) - { - std::string ent = normalize_path(iter->name, iter->name+std::strlen(iter->name)); - if(Ogre::StringUtil::match(ent, normalizedPattern) || - (recursive && Ogre::StringUtil::match(ent, "*/"+normalizedPattern))) - ptr->push_back(iter->name); - } - return ptr; - } - - FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, - bool dirs = false) const - { - std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); - FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); - const Bsa::BSAFile::FileList &filelist = arc.getList(); - - for(Bsa::BSAFile::FileList::const_iterator iter = filelist.begin();iter != filelist.end();++iter) - { - std::string ent = normalize_path(iter->name, iter->name+std::strlen(iter->name)); - if(Ogre::StringUtil::match(ent, normalizedPattern) || - (recursive && Ogre::StringUtil::match(ent, "*/"+normalizedPattern))) - { - std::string::size_type pt = ent.rfind('/'); - if(pt == std::string::npos) - pt = 0; - - FileInfo fi; - fi.archive = const_cast(this); - fi.path = std::string(iter->name, pt); - fi.filename = std::string(iter->name + ((ent[pt]=='/') ? pt+1 : pt)); - fi.compressedSize = fi.uncompressedSize = iter->fileSize; - - ptr->push_back(fi); - } - } - - return ptr; - } -}; - -// An archive factory for BSA archives -class BSAArchiveFactory : public ArchiveFactory -{ -public: - const String& getType() const - { - static String name = "BSA"; - return name; - } - - Archive *createInstance( const String& name ) - { - return new BSAArchive(name); - } - - virtual Archive* createInstance(const String& name, bool readOnly) - { - return new BSAArchive(name); - } - - void destroyInstance( Archive* arch) { delete arch; } -}; - -class DirArchiveFactory : public ArchiveFactory -{ -public: - const String& getType() const - { - static String name = "Dir"; - return name; - } - - Archive *createInstance( const String& name ) - { - return new DirArchive(name); - } - - virtual Archive* createInstance(const String& name, bool readOnly) - { - return new DirArchive(name); - } - - void destroyInstance( Archive* arch) { delete arch; } -}; - - -static bool init = false; -static bool init2 = false; - -static void insertBSAFactory() -{ - if(!init) - { - ArchiveManager::getSingleton().addArchiveFactory( new BSAArchiveFactory ); - init = true; - } -} - -static void insertDirFactory() -{ - if(!init2) - { - ArchiveManager::getSingleton().addArchiveFactory( new DirArchiveFactory ); - init2 = true; - } -} - - -namespace Bsa -{ - -// The function below is the only publicly exposed part of this file - -void addBSA(const std::string& name, const std::string& group) -{ - insertBSAFactory(); - ResourceGroupManager::getSingleton(). - addResourceLocation(name, "BSA", group, true); -} - -void addDir(const std::string& name, const bool& fs, const std::string& group) -{ - fsstrict = fs; - insertDirFactory(); - - ResourceGroupManager::getSingleton(). - addResourceLocation(name, "Dir", group, true); -} - -} diff --git a/components/bsa/bsa_archive.hpp b/components/bsa/bsa_archive.hpp deleted file mode 100644 index 7f9ebaae10..0000000000 --- a/components/bsa/bsa_archive.hpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (cpp_bsaarchive.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - 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 - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -#include -#include - -#ifndef BSA_BSA_ARCHIVE_H -#define BSA_BSA_ARCHIVE_H - -namespace Bsa -{ - -/// Add the given BSA file as an input archive in the Ogre resource -/// system. -void addBSA(const std::string& file, const std::string& group="General"); -void addDir(const std::string& file, const bool& fs, const std::string& group="General"); - -} - -#endif diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 3bf73ede27..2b9a3f6325 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -28,8 +28,6 @@ #include #include -#include "../files/constrainedfiledatastream.hpp" - using namespace std; using namespace Bsa; @@ -168,7 +166,7 @@ void BSAFile::open(const string &file) readHeader(); } -Ogre::DataStreamPtr BSAFile::getFile(const char *file) +Files::IStreamPtr BSAFile::getFile(const char *file) { assert(file); int i = getIndex(file); @@ -176,5 +174,11 @@ Ogre::DataStreamPtr BSAFile::getFile(const char *file) fail("File not found: " + string(file)); const FileStruct &fs = files[i]; - return openConstrainedFileDataStream (filename.c_str (), fs.offset, fs.fileSize); + + return Files::openConstrainedFileStream (filename.c_str (), fs.offset, fs.fileSize); +} + +Files::IStreamPtr BSAFile::getFile(const FileStruct *file) +{ + return Files::openConstrainedFileStream (filename.c_str (), file->offset, file->fileSize); } diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 017adf1e31..8ed63f35da 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -25,12 +25,13 @@ #define BSA_BSA_FILE_H #include -#include #include #include #include -#include +#include + +#include namespace Bsa @@ -72,7 +73,7 @@ private: struct iltstr { bool operator()(const char *s1, const char *s2) const - { return strcasecmp(s1,s2) < 0; } + { return Misc::StringUtils::ciLess(s1, s2); } }; /** A map used for fast file name lookup. The value is the index into @@ -116,7 +117,9 @@ public: /** Open a file contained in the archive. Throws an exception if the file doesn't exist. */ - Ogre::DataStreamPtr getFile(const char *file); + Files::IStreamPtr getFile(const char *file); + + Files::IStreamPtr getFile(const FileStruct* file); /// Get a list of all files const FileList &getList() const diff --git a/components/bsa/resources.cpp b/components/bsa/resources.cpp deleted file mode 100644 index b66da1a766..0000000000 --- a/components/bsa/resources.cpp +++ /dev/null @@ -1,53 +0,0 @@ - -#include "resources.hpp" - -#include - -#include -#include - -#include "bsa_archive.hpp" - -void Bsa::registerResources (const Files::Collections& collections, - const std::vector& archives, bool useLooseFiles, bool fsStrict) -{ - const Files::PathContainer& dataDirs = collections.getPaths(); - - int i=0; - - if (useLooseFiles) - for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) - { - // Last data dir has the highest priority - std::string groupName = "Data" + Ogre::StringConverter::toString(dataDirs.size()-i, 8, '0'); - Ogre::ResourceGroupManager::getSingleton ().createResourceGroup (groupName); - - std::string dataDirectory = iter->string(); - std::cout << "Data dir " << dataDirectory << std::endl; - Bsa::addDir(dataDirectory, fsStrict, groupName); - ++i; - } - - i=0; - for (std::vector::const_iterator archive = archives.begin(); archive != archives.end(); ++archive) - { - if (collections.doesExist(*archive)) - { - // Last BSA has the highest priority - std::string groupName = "DataBSA" + Ogre::StringConverter::toString(archives.size()-i, 8, '0'); - - Ogre::ResourceGroupManager::getSingleton ().createResourceGroup (groupName); - - const std::string archivePath = collections.getPath(*archive).string(); - std::cout << "Adding BSA archive " << archivePath << std::endl; - Bsa::addBSA(archivePath, groupName); - ++i; - } - else - { - std::stringstream message; - message << "Archive '" << *archive << "' not found"; - throw std::runtime_error(message.str()); - } - } -} diff --git a/components/bsa/resources.hpp b/components/bsa/resources.hpp deleted file mode 100644 index 8c3fb7bef8..0000000000 --- a/components/bsa/resources.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef BSA_BSA_RESOURCES_H -#define BSA_BSA_RESOURCES_H - -#include -#include - -#include "../files/collections.hpp" - -namespace Bsa -{ - void registerResources (const Files::Collections& collections, - const std::vector& archives, bool useLooseFiles, bool fsStrict); - ///< Register resources directories and archives as OGRE resources groups -} - -#endif diff --git a/components/compiler/controlparser.cpp b/components/compiler/controlparser.cpp index aefe6d16da..b202467db8 100644 --- a/components/compiler/controlparser.cpp +++ b/components/compiler/controlparser.cpp @@ -1,4 +1,3 @@ - #include "controlparser.hpp" #include diff --git a/components/compiler/declarationparser.cpp b/components/compiler/declarationparser.cpp index 7961b8f411..ffac252d54 100644 --- a/components/compiler/declarationparser.cpp +++ b/components/compiler/declarationparser.cpp @@ -1,4 +1,3 @@ - #include "declarationparser.hpp" #include diff --git a/components/compiler/discardparser.cpp b/components/compiler/discardparser.cpp index 6028968bb2..da114fb3dd 100644 --- a/components/compiler/discardparser.cpp +++ b/components/compiler/discardparser.cpp @@ -1,4 +1,3 @@ - #include "discardparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/errorhandler.cpp b/components/compiler/errorhandler.cpp index bcd30ef2d5..7f02255db2 100644 --- a/components/compiler/errorhandler.cpp +++ b/components/compiler/errorhandler.cpp @@ -1,4 +1,3 @@ - #include "errorhandler.hpp" namespace Compiler @@ -33,7 +32,10 @@ namespace Compiler void ErrorHandler::warning (const std::string& message, const TokenLoc& loc) { - if (mWarningsMode==1) + if (mWarningsMode==1 || + // temporarily change from mode 2 to mode 1 if error downgrading is enabled to + // avoid infinite recursion + (mWarningsMode==2 && mDowngradeErrors)) { ++mWarnings; report (message, loc, WarningMessage); diff --git a/components/compiler/errorhandler.hpp b/components/compiler/errorhandler.hpp index c92e7bb8d9..ea904e3851 100644 --- a/components/compiler/errorhandler.hpp +++ b/components/compiler/errorhandler.hpp @@ -1,4 +1,3 @@ - #ifndef COMPILER_ERRORHANDLER_H_INCLUDED #define COMPILER_ERRORHANDLER_H_INCLUDED diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index dc36b58d82..c0375b4366 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -1,4 +1,3 @@ - #include "exprparser.hpp" #include @@ -353,7 +352,10 @@ namespace Compiler if (extensions->isInstruction (keyword, argumentType, hasExplicit)) { // pretend this is not a keyword - return parseName (loc.mLiteral, loc, scanner); + std::string name = loc.mLiteral; + if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') + name = name.substr (1, name.size()-2); + return parseName (name, loc, scanner); } } @@ -650,6 +652,13 @@ namespace Compiler return true; } + if (code ==Scanner::S_plus && mNextOperand) + { + // Also unary, but +, just ignore it + mTokenLoc = loc; + return true; + } + if (code==Scanner::S_open) { if (mNextOperand) diff --git a/components/compiler/extensions.cpp b/components/compiler/extensions.cpp index c2b11c6156..dbb953e201 100644 --- a/components/compiler/extensions.cpp +++ b/components/compiler/extensions.cpp @@ -1,4 +1,3 @@ - #include "extensions.hpp" #include diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index a16e653c3a..8f7650191a 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -179,7 +179,7 @@ namespace Compiler extensions.registerInstruction ("setjournalindex", "cl", opcodeSetJournalIndex); extensions.registerFunction ("getjournalindex", 'l', "c", opcodeGetJournalIndex); extensions.registerInstruction ("addtopic", "S" , opcodeAddTopic); - extensions.registerInstruction ("choice", "j/SlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSl", opcodeChoice); + extensions.registerInstruction ("choice", "j/SlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSl", opcodeChoice); extensions.registerInstruction("forcegreeting","",opcodeForceGreeting, opcodeForceGreetingExplicit); extensions.registerInstruction("goodbye", "", opcodeGoodbye); @@ -308,8 +308,9 @@ namespace Compiler extensions.registerInstruction ("enablelevitation", "", opcodeEnableLevitation); extensions.registerFunction ("getpcinjail", 'l', "", opcodeGetPcInJail); extensions.registerFunction ("getpctraveling", 'l', "", opcodeGetPcTraveling); - extensions.registerInstruction ("betacomment", "S", opcodeBetaComment, opcodeBetaCommentExplicit); - extensions.registerInstruction ("bc", "S", opcodeBetaComment, opcodeBetaCommentExplicit); + extensions.registerInstruction ("betacomment", "/S", opcodeBetaComment, opcodeBetaCommentExplicit); + extensions.registerInstruction ("bc", "/S", opcodeBetaComment, opcodeBetaCommentExplicit); + extensions.registerInstruction ("ori", "/S", opcodeBetaComment, opcodeBetaCommentExplicit); // 'ori' stands for 'ObjectReferenceInfo' extensions.registerInstruction ("addtolevcreature", "ccl", opcodeAddToLevCreature); extensions.registerInstruction ("removefromlevcreature", "ccl", opcodeRemoveFromLevCreature); extensions.registerInstruction ("addtolevitem", "ccl", opcodeAddToLevItem); diff --git a/components/compiler/extensions0.hpp b/components/compiler/extensions0.hpp index 83f3a44fa6..e9ce6a6d6a 100644 --- a/components/compiler/extensions0.hpp +++ b/components/compiler/extensions0.hpp @@ -44,7 +44,7 @@ namespace Compiler namespace Gui { - void registerExtensions (Extensions& extensions); + void registerExtensions (Extensions& extensions); } namespace Misc diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index 423841ac3e..8711d92fec 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -1,7 +1,5 @@ #include "fileparser.hpp" -#include - #include "tokenloc.hpp" #include "scanner.hpp" @@ -65,7 +63,6 @@ namespace Compiler if (mState==BeginState && keyword==Scanner::K_begin) { mState = NameState; - scanner.allowNameStartingwithDigit(); return true; } @@ -112,7 +109,6 @@ namespace Compiler scanner.scan (mScriptParser); mState = EndNameState; - scanner.allowNameStartingwithDigit(); return true; } diff --git a/components/compiler/generator.cpp b/components/compiler/generator.cpp index ead0c72901..7e6437e20a 100644 --- a/components/compiler/generator.cpp +++ b/components/compiler/generator.cpp @@ -1,4 +1,3 @@ - #include "generator.hpp" #include diff --git a/components/compiler/junkparser.cpp b/components/compiler/junkparser.cpp index cfa94044e7..7608e9bab4 100644 --- a/components/compiler/junkparser.cpp +++ b/components/compiler/junkparser.cpp @@ -1,4 +1,3 @@ - #include "junkparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index a71672916b..ce1e1e463c 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -1,4 +1,3 @@ - #include "lineparser.hpp" #include @@ -222,6 +221,23 @@ namespace Compiler bool LineParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { + if (mState==MessageState || mState==MessageCommaState) + { + if (const Extensions *extensions = getContext().getExtensions()) + { + std::string argumentType; // ignored + bool hasExplicit = false; // ignored + if (extensions->isInstruction (keyword, argumentType, hasExplicit)) + { + // pretend this is not a keyword + std::string name = loc.mLiteral; + if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') + name = name.substr (1, name.size()-2); + return parseName (name, loc, scanner); + } + } + } + if (mState==SetMemberVarState) { mMemberName = loc.mLiteral; @@ -395,7 +411,12 @@ namespace Compiler } case Scanner::K_set: mState = SetState; return true; - case Scanner::K_messagebox: mState = MessageState; return true; + + case Scanner::K_messagebox: + + mState = MessageState; + scanner.enableStrictKeywords(); + return true; case Scanner::K_return: @@ -539,7 +560,7 @@ namespace Compiler } if (mAllowExpression && mState==BeginState && - (code==Scanner::S_open || code==Scanner::S_minus)) + (code==Scanner::S_open || code==Scanner::S_minus || code==Scanner::S_plus)) { scanner.putbackSpecial (code, loc); parseExpression (scanner, loc); diff --git a/components/compiler/literals.cpp b/components/compiler/literals.cpp index 626b03afbe..ee2c4d3450 100644 --- a/components/compiler/literals.cpp +++ b/components/compiler/literals.cpp @@ -1,4 +1,3 @@ - #include "literals.hpp" #include diff --git a/components/compiler/locals.cpp b/components/compiler/locals.cpp index 60a5704bf3..768fc077ca 100644 --- a/components/compiler/locals.cpp +++ b/components/compiler/locals.cpp @@ -1,4 +1,3 @@ - #include "locals.hpp" #include diff --git a/components/compiler/nullerrorhandler.cpp b/components/compiler/nullerrorhandler.cpp index ee28847059..a0db53a002 100644 --- a/components/compiler/nullerrorhandler.cpp +++ b/components/compiler/nullerrorhandler.cpp @@ -1,4 +1,3 @@ - #include "nullerrorhandler.hpp" void Compiler::NullErrorHandler::report (const std::string& message, const TokenLoc& loc, Type type) {} diff --git a/components/compiler/nullerrorhandler.hpp b/components/compiler/nullerrorhandler.hpp index bb4db99a28..3dcff92508 100644 --- a/components/compiler/nullerrorhandler.hpp +++ b/components/compiler/nullerrorhandler.hpp @@ -1,4 +1,3 @@ - #ifndef COMPILER_NULLERRORHANDLER_H_INCLUDED #define COMPILER_NULLERRORHANDLER_H_INCLUDED diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index a4aab8aa1b..e7d51d934d 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -228,8 +228,8 @@ namespace Compiler const int opcodeGetLockedExplicit = 0x20001c8; const int opcodeGetEffect = 0x20001cf; const int opcodeGetEffectExplicit = 0x20001d0; - const int opcodeBetaComment = 0x2000247; - const int opcodeBetaCommentExplicit = 0x2000248; + const int opcodeBetaComment = 0x2002d; + const int opcodeBetaCommentExplicit = 0x2002e; const int opcodeAddSoulGem = 0x20001f3; const int opcodeAddSoulGemExplicit = 0x20001f4; const int opcodeRemoveSoulGem = 0x20027; diff --git a/components/compiler/output.cpp b/components/compiler/output.cpp index 46e04b8dc5..785b2ce843 100644 --- a/components/compiler/output.cpp +++ b/components/compiler/output.cpp @@ -1,4 +1,3 @@ - #include "output.hpp" #include diff --git a/components/compiler/parser.cpp b/components/compiler/parser.cpp index 0f442c3504..fe019718a6 100644 --- a/components/compiler/parser.cpp +++ b/components/compiler/parser.cpp @@ -1,4 +1,3 @@ - #include "parser.hpp" #include diff --git a/components/compiler/quickfileparser.cpp b/components/compiler/quickfileparser.cpp index 4e9f76e13c..53aaf96e5d 100644 --- a/components/compiler/quickfileparser.cpp +++ b/components/compiler/quickfileparser.cpp @@ -1,4 +1,3 @@ - #include "quickfileparser.hpp" #include "skipparser.hpp" diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 83d4359621..b370f74a17 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -1,4 +1,3 @@ - #include "scanner.hpp" #include @@ -27,6 +26,7 @@ namespace Compiler if (c=='\n') { + mStrictKeywords = false; mLoc.mColumn = 0; ++mLoc.mLine; mLoc.mLiteral.clear(); @@ -48,9 +48,6 @@ namespace Compiler bool Scanner::scanToken (Parser& parser) { - bool allowDigit = mNameStartingWithDigit; - mNameStartingWithDigit = false; - switch (mPutback) { case Putback_Special: @@ -115,7 +112,6 @@ namespace Compiler else if (isWhitespace (c)) { mLoc.mLiteral.clear(); - mNameStartingWithDigit = allowDigit; return true; } else if (c==':') @@ -124,7 +120,7 @@ namespace Compiler mLoc.mLiteral.clear(); return true; } - else if (std::isalpha (c) || c=='_' || c=='"' || (allowDigit && std::isdigit (c))) + else if (std::isalpha (c) || c=='_' || c=='"') { bool cont = false; @@ -180,10 +176,18 @@ namespace Compiler { value += c; } - else if (std::isalpha (c) || c=='_') - error = true; - else if (c=='.' && !error) + else if (c!='-' && isStringCharacter (c)) { + error = true; + value += c; + } + else if (c=='.') + { + if (error) + { + putback (c); + break; + } return scanFloat (value, parser, cont); } else @@ -194,7 +198,15 @@ namespace Compiler } if (error) - return false; + { + /// workaround that allows names to begin with digits + /// \todo disable + TokenLoc loc (mLoc); + mLoc.mLiteral.clear(); + cont = parser.parseName (value, loc, *this); + return true; +// return false; + } TokenLoc loc (mLoc); mLoc.mLiteral.clear(); @@ -281,8 +293,13 @@ namespace Compiler if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') { name = name.substr (1, name.size()-2); - cont = parser.parseName (name, loc, *this); - return true; +// allow keywords enclosed in "" +/// \todo optionally disable + if (mStrictKeywords) + { + cont = parser.parseName (name, loc, *this); + return true; + } } int i = 0; @@ -555,7 +572,7 @@ namespace Compiler const Extensions *extensions) : mErrorHandler (errorHandler), mStream (inputStream), mExtensions (extensions), mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0), - mNameStartingWithDigit (false) + mStrictKeywords (false) { } @@ -608,8 +625,8 @@ namespace Compiler mExtensions->listKeywords (keywords); } - void Scanner::allowNameStartingwithDigit() + void Scanner::enableStrictKeywords() { - mNameStartingWithDigit = true; + mStrictKeywords = true; } } diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index ed01bbe44e..270782c744 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -37,7 +37,7 @@ namespace Compiler float mPutbackFloat; std::string mPutbackName; TokenLoc mPutbackLoc; - bool mNameStartingWithDigit; + bool mStrictKeywords; public: @@ -117,16 +117,18 @@ namespace Compiler ///< put back a float token void putbackName (const std::string& name, const TokenLoc& loc); - ///< put back a name toekn + ///< put back a name token void putbackKeyword (int keyword, const TokenLoc& loc); ///< put back a keyword token void listKeywords (std::vector& keywords); - ///< Append all known keywords to \a kaywords. + ///< Append all known keywords to \a keywords. - /// For the next token allow names to start with a digit. - void allowNameStartingwithDigit(); + /// Do not accept keywords in quotation marks anymore. + /// + /// \attention This mode lasts only until the next newline is reached. + void enableStrictKeywords(); }; } diff --git a/components/compiler/scriptparser.cpp b/components/compiler/scriptparser.cpp index ea11be5f03..a3bf232888 100644 --- a/components/compiler/scriptparser.cpp +++ b/components/compiler/scriptparser.cpp @@ -1,4 +1,3 @@ - #include "scriptparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/skipparser.cpp b/components/compiler/skipparser.cpp index c7cb31f58e..3e704253dd 100644 --- a/components/compiler/skipparser.cpp +++ b/components/compiler/skipparser.cpp @@ -1,4 +1,3 @@ - #include "skipparser.hpp" #include "scanner.hpp" diff --git a/components/compiler/streamerrorhandler.cpp b/components/compiler/streamerrorhandler.cpp index fc1a059432..9ca8aa74bf 100644 --- a/components/compiler/streamerrorhandler.cpp +++ b/components/compiler/streamerrorhandler.cpp @@ -1,4 +1,3 @@ - #include "streamerrorhandler.hpp" #include "tokenloc.hpp" diff --git a/components/compiler/streamerrorhandler.hpp b/components/compiler/streamerrorhandler.hpp index 96e02b5882..85de1833ab 100644 --- a/components/compiler/streamerrorhandler.hpp +++ b/components/compiler/streamerrorhandler.hpp @@ -1,4 +1,3 @@ - #ifndef COMPILER_STREAMERRORHANDLER_H_INCLUDED #define COMPILER_STREAMERRORHANDLER_H_INCLUDED diff --git a/components/compiler/stringparser.cpp b/components/compiler/stringparser.cpp index a86c15794f..f8798eccd4 100644 --- a/components/compiler/stringparser.cpp +++ b/components/compiler/stringparser.cpp @@ -1,12 +1,14 @@ - #include "stringparser.hpp" #include #include +#include + #include "scanner.hpp" #include "generator.hpp" -#include +#include "context.hpp" +#include "extensions.hpp" namespace Compiler { @@ -33,6 +35,25 @@ namespace Compiler return Parser::parseName (name, loc, scanner); } + bool StringParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) + { + if (const Extensions *extensions = getContext().getExtensions()) + { + std::string argumentType; // ignored + bool hasExplicit = false; // ignored + if (extensions->isInstruction (keyword, argumentType, hasExplicit)) + { + // pretend this is not a keyword + std::string name = loc.mLiteral; + if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') + name = name.substr (1, name.size()-2); + return parseName (name, loc, scanner); + } + } + + return Parser::parseKeyword (keyword, loc, scanner); + } + bool StringParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (code==Scanner::S_comma && mState==StartState) diff --git a/components/compiler/stringparser.hpp b/components/compiler/stringparser.hpp index 3859a24965..52469128fd 100644 --- a/components/compiler/stringparser.hpp +++ b/components/compiler/stringparser.hpp @@ -32,6 +32,10 @@ namespace Compiler ///< Handle a name token. /// \return fetch another token? + virtual bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner); + ///< Handle a keyword token. + /// \return fetch another token? + virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner); ///< Handle a special character token. /// \return fetch another token? diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index ca6bfd80da..a897806c2a 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -454,3 +454,11 @@ QStringList Config::GameSettings::getContentList() const return Config::LauncherSettings::reverse(values(sContentKey)); } +void Config::GameSettings::clear() +{ + mSettings.clear(); + mUserSettings.clear(); + mDataDirs.clear(); + mDataLocal.clear(); +} + diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index 992a3e5655..eeac893c23 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -72,6 +72,8 @@ namespace Config void setContentList(const QStringList& fileNames); QStringList getContentList() const; + void clear(); + private: Files::ConfigurationManager &mCfgMgr; diff --git a/components/config/launchersettings.cpp b/components/config/launchersettings.cpp index 1d4b428c91..8f34988262 100644 --- a/components/config/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -25,8 +25,6 @@ QStringList Config::LauncherSettings::subKeys(const QString &key) QMap settings = SettingsBase::getSettings(); QStringList keys = settings.uniqueKeys(); - qDebug() << keys; - QRegExp keyRe("(.+)/"); QStringList result; diff --git a/components/config/settingsbase.hpp b/components/config/settingsbase.hpp index c798d2893b..08cd0bfc6f 100644 --- a/components/config/settingsbase.hpp +++ b/components/config/settingsbase.hpp @@ -101,6 +101,11 @@ namespace Config return true; } + void clear() + { + mSettings.clear(); + } + private: Map mSettings; diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 769afee37b..8dc4351f6d 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -7,7 +7,7 @@ #include #include -#include "components/esm/esmreader.hpp" +#include ContentSelectorModel::ContentModel::ContentModel(QObject *parent, QIcon warningIcon) : QAbstractTableModel(parent), diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 78aa20cd26..87c9cea082 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -1,6 +1,6 @@ #include "contentselector.hpp" -#include "../model/esmfile.hpp" +#include #include diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 4e9fcfb3c8..9f775d5978 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -4,7 +4,7 @@ #include #include "ui_contentselector.h" -#include "../model/contentmodel.hpp" +#include class QSortFilterProxyModel; diff --git a/components/esm/cellid.cpp b/components/esm/cellid.cpp index 3c6e23ffd9..f77e2eb551 100644 --- a/components/esm/cellid.cpp +++ b/components/esm/cellid.cpp @@ -1,4 +1,3 @@ - #include "cellid.hpp" #include "esmreader.hpp" @@ -35,3 +34,26 @@ bool ESM::operator!= (const CellId& left, const CellId& right) { return !(left==right); } + +bool ESM::operator < (const CellId& left, const CellId& right) +{ + if (left.mPaged < right.mPaged) + return true; + if (left.mPaged > right.mPaged) + return false; + + if (left.mPaged) + { + if (left.mIndex.mX < right.mIndex.mX) + return true; + if (left.mIndex.mX > right.mIndex.mX) + return false; + + if (left.mIndex.mY < right.mIndex.mY) + return true; + if (left.mIndex.mY > right.mIndex.mY) + return false; + } + + return left.mWorldspace < right.mWorldspace; +} diff --git a/components/esm/cellid.hpp b/components/esm/cellid.hpp index 44a1b387a2..56d5a674ea 100644 --- a/components/esm/cellid.hpp +++ b/components/esm/cellid.hpp @@ -26,6 +26,7 @@ namespace ESM bool operator== (const CellId& left, const CellId& right); bool operator!= (const CellId& left, const CellId& right); + bool operator< (const CellId& left, const CellId& right); } #endif diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index c3b889df59..7acaed86d5 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -1,15 +1,14 @@ - #include "cellref.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" -void ESM::RefNum::load (ESMReader& esm, bool wide) +void ESM::RefNum::load (ESMReader& esm, bool wide, const std::string& tag) { if (wide) - esm.getHNT (*this, "FRMR", 8); + esm.getHNT (*this, tag.c_str(), 8); else - esm.getHNT (mIndex, "FRMR"); + esm.getHNT (mIndex, tag.c_str()); } void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const @@ -25,10 +24,10 @@ void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const } -void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) +void ESM::CellRef::load (ESMReader& esm, bool &isDeleted, bool wideRefNum) { loadId(esm, wideRefNum); - loadData(esm); + loadData(esm, isDeleted); } void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) @@ -40,71 +39,98 @@ void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) if (esm.isNextSub ("NAM0")) esm.skipHSub(); + blank(); + mRefNum.load (esm, wideRefNum); mRefID = esm.getHNString ("NAME"); } -void ESM::CellRef::loadData(ESMReader &esm) +void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted) { - // Again, UNAM sometimes appears after NAME and sometimes later. - // Or perhaps this UNAM means something different? - mReferenceBlocked = -1; - esm.getHNOT (mReferenceBlocked, "UNAM"); + isDeleted = false; - mScale = 1.0; - esm.getHNOT (mScale, "XSCL"); - - mOwner = esm.getHNOString ("ANAM"); - mGlobalVariable = esm.getHNOString ("BNAM"); - mSoul = esm.getHNOString ("XSOL"); - - mFaction = esm.getHNOString ("CNAM"); - mFactionRank = -2; - esm.getHNOT (mFactionRank, "INDX"); - - mGoldValue = 1; - mChargeInt = -1; - mEnchantmentCharge = -1; - - esm.getHNOT (mEnchantmentCharge, "XCHG"); - - esm.getHNOT (mChargeInt, "INTV"); - - esm.getHNOT (mGoldValue, "NAM9"); - - // Present for doors that teleport you to another cell. - if (esm.isNextSub ("DODT")) + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) { - mTeleport = true; - esm.getHT (mDoorDest); - mDestCell = esm.getHNOString ("DNAM"); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'U','N','A','M'>::value: + esm.getHT(mReferenceBlocked); + break; + case ESM::FourCC<'X','S','C','L'>::value: + esm.getHT(mScale); + break; + case ESM::FourCC<'A','N','A','M'>::value: + mOwner = esm.getHString(); + break; + case ESM::FourCC<'B','N','A','M'>::value: + mGlobalVariable = esm.getHString(); + break; + case ESM::FourCC<'X','S','O','L'>::value: + mSoul = esm.getHString(); + break; + case ESM::FourCC<'C','N','A','M'>::value: + mFaction = esm.getHString(); + break; + case ESM::FourCC<'I','N','D','X'>::value: + esm.getHT(mFactionRank); + break; + case ESM::FourCC<'X','C','H','G'>::value: + esm.getHT(mEnchantmentCharge); + break; + case ESM::FourCC<'I','N','T','V'>::value: + esm.getHT(mChargeInt); + break; + case ESM::FourCC<'N','A','M','9'>::value: + esm.getHT(mGoldValue); + break; + case ESM::FourCC<'D','O','D','T'>::value: + esm.getHT(mDoorDest); + mTeleport = true; + break; + case ESM::FourCC<'D','N','A','M'>::value: + mDestCell = esm.getHString(); + break; + case ESM::FourCC<'F','L','T','V'>::value: + esm.getHT(mLockLevel); + break; + case ESM::FourCC<'K','N','A','M'>::value: + mKey = esm.getHString(); + break; + case ESM::FourCC<'T','N','A','M'>::value: + mTrap = esm.getHString(); + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mPos, 24); + break; + case ESM::FourCC<'N','A','M','0'>::value: + esm.skipHSub(); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } } - else - mTeleport = false; - - mLockLevel = 0; //Set to 0 to indicate no lock - esm.getHNOT (mLockLevel, "FLTV"); - - mKey = esm.getHNOString ("KNAM"); - mTrap = esm.getHNOString ("TNAM"); - - esm.getHNOT (mReferenceBlocked, "UNAM"); - if (esm.isNextSub("FLTV")) // no longer used - esm.skipHSub(); - - esm.getHNOT(mPos, "DATA", 24); - - if (esm.isNextSub("NAM0")) - esm.skipHSub(); } -void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) const +void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool isDeleted) const { mRefNum.save (esm, wideRefNum); esm.writeHNCString("NAME", mRefID); + if (isDeleted) { + esm.writeHNCString("DELE", ""); + return; + } + if (mScale != 1.0) { esm.writeHNT("XSCL", mScale); } @@ -135,7 +161,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons } if (!inInventory && mLockLevel != 0) { - esm.writeHNT("FLTV", mLockLevel); + esm.writeHNT("FLTV", mLockLevel); } if (!inInventory) @@ -154,7 +180,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons void ESM::CellRef::blank() { mRefNum.unset(); - mRefID.clear(); + mRefID.clear(); mScale = 1; mOwner.clear(); mGlobalVariable.clear(); @@ -170,7 +196,7 @@ void ESM::CellRef::blank() mTrap.clear(); mReferenceBlocked = -1; mTeleport = false; - + for (int i=0; i<3; ++i) { mDoorDest.pos[i] = 0; diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index e9959611b3..3c646cc616 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -16,7 +16,7 @@ namespace ESM unsigned int mIndex; int mContentFile; - void load (ESMReader& esm, bool wide = false); + void load (ESMReader& esm, bool wide = false, const std::string& tag = "FRMR"); void save (ESMWriter &esm, bool wide = false, const std::string& tag = "FRMR") const; @@ -34,7 +34,6 @@ namespace ESM class CellRef { public: - // Reference number // Note: Currently unused for items in containers RefNum mRefNum; @@ -100,14 +99,14 @@ namespace ESM Position mPos; /// Calls loadId and loadData - void load (ESMReader& esm, bool wideRefNum = false); + void load (ESMReader& esm, bool &isDeleted, bool wideRefNum = false); void loadId (ESMReader& esm, bool wideRefNum = false); /// Implicitly called by load - void loadData (ESMReader& esm); + void loadData (ESMReader& esm, bool &isDeleted); - void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false) const; + void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/cellstate.cpp b/components/esm/cellstate.cpp index 4df04d0e52..83b130dcd9 100644 --- a/components/esm/cellstate.cpp +++ b/components/esm/cellstate.cpp @@ -1,4 +1,3 @@ - #include "cellstate.hpp" #include "esmreader.hpp" diff --git a/components/esm/containerstate.cpp b/components/esm/containerstate.cpp index 80ad5cbdc8..301549d597 100644 --- a/components/esm/containerstate.cpp +++ b/components/esm/containerstate.cpp @@ -1,4 +1,3 @@ - #include "containerstate.hpp" void ESM::ContainerState::load (ESMReader &esm) diff --git a/components/esm/creaturestate.cpp b/components/esm/creaturestate.cpp index c15becd981..bffa4e5e45 100644 --- a/components/esm/creaturestate.cpp +++ b/components/esm/creaturestate.cpp @@ -1,4 +1,3 @@ - #include "creaturestate.hpp" void ESM::CreatureState::load (ESMReader &esm) diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 75c1c28bc2..9bdbf96685 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -1,4 +1,6 @@ #include "creaturestats.hpp" +#include "esmreader.hpp" +#include "esmwriter.hpp" void ESM::CreatureStats::load (ESMReader &esm) { @@ -39,8 +41,8 @@ void ESM::CreatureStats::load (ESMReader &esm) if (esm.isNextSub("HOST")) esm.skipHSub(); // Hostile, no longer used - mAttackingOrSpell = false; - esm.getHNOT (mAttackingOrSpell, "ATCK"); + if (esm.isNextSub("ATCK")) + esm.skipHSub(); // attackingOrSpell, no longer used mKnockdown = false; esm.getHNOT (mKnockdown, "KNCK"); @@ -60,8 +62,8 @@ void ESM::CreatureStats::load (ESMReader &esm) mMovementFlags = 0; esm.getHNOT (mMovementFlags, "MOVE"); - mAttackStrength = 0; - esm.getHNOT (mAttackStrength, "ASTR"); + if (esm.isNextSub("ASTR")) + esm.skipHSub(); // attackStrength, no longer used mFallHeight = 0; esm.getHNOT (mFallHeight, "FALL"); @@ -149,9 +151,6 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mAttacked) esm.writeHNT ("ATKD", mAttacked); - if (mAttackingOrSpell) - esm.writeHNT ("ATCK", mAttackingOrSpell); - if (mKnockdown) esm.writeHNT ("KNCK", mKnockdown); @@ -170,9 +169,6 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mMovementFlags) esm.writeHNT ("MOVE", mMovementFlags); - if (mAttackStrength) - esm.writeHNT ("ASTR", mAttackStrength); - if (mFallHeight) esm.writeHNT ("FALL", mFallHeight); @@ -235,14 +231,12 @@ void ESM::CreatureStats::blank() mTalkedTo = false; mAlarmed = false; mAttacked = false; - mAttackingOrSpell = false; mKnockdown = false; mKnockdownOneFrame = false; mKnockdownOverOneFrame = false; mHitRecovery = false; mBlock = false; mMovementFlags = 0; - mAttackStrength = 0.f; mFallHeight = 0.f; mRecalcDynamicStats = false; mDrawState = 0; diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 2a03136d08..426e890559 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -45,14 +45,12 @@ namespace ESM bool mTalkedTo; bool mAlarmed; bool mAttacked; - bool mAttackingOrSpell; bool mKnockdown; bool mKnockdownOneFrame; bool mKnockdownOverOneFrame; bool mHitRecovery; bool mBlock; unsigned int mMovementFlags; - float mAttackStrength; float mFallHeight; std::string mLastHitObject; std::string mLastHitAttemptObject; diff --git a/components/esm/debugprofile.cpp b/components/esm/debugprofile.cpp index 6c05fac2a2..66e338d0c4 100644 --- a/components/esm/debugprofile.cpp +++ b/components/esm/debugprofile.cpp @@ -1,4 +1,3 @@ - #include "debugprofile.hpp" #include "esmreader.hpp" @@ -7,15 +6,48 @@ unsigned int ESM::DebugProfile::sRecordId = REC_DBGP; -void ESM::DebugProfile::load (ESMReader& esm) +void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted) { - mDescription = esm.getHNString ("DESC"); - mScriptText = esm.getHNString ("SCRP"); - esm.getHNT (mFlags, "FLAG"); + isDeleted = false; + + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::FourCC<'S','C','R','P'>::value: + mScriptText = esm.getHString(); + break; + case ESM::FourCC<'F','L','A','G'>::value: + esm.getHT(mFlags); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } } -void ESM::DebugProfile::save (ESMWriter& esm) const +void ESM::DebugProfile::save (ESMWriter& esm, bool isDeleted) const { + esm.writeHNCString ("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString ("DESC", mDescription); esm.writeHNCString ("SCRP", mScriptText); esm.writeHNT ("FLAG", mFlags); diff --git a/components/esm/debugprofile.hpp b/components/esm/debugprofile.hpp index b54e8ff5fc..c056750a88 100644 --- a/components/esm/debugprofile.hpp +++ b/components/esm/debugprofile.hpp @@ -27,8 +27,8 @@ namespace ESM unsigned int mFlags; - void load (ESMReader& esm); - void save (ESMWriter& esm) const; + void load (ESMReader& esm, bool &isDeleted); + void save (ESMWriter& esm, bool isDeleted = false) const; /// Set record to default state (does not touch the ID). void blank(); diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 7ef8102c29..8066b622ae 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -3,6 +3,8 @@ #include +#include + namespace ESM { @@ -13,7 +15,7 @@ struct TimeStamp }; // Pixel color value. Standard four-byte rr,gg,bb,aa format. -typedef int32_t Color; +typedef uint32_t Color; enum Specialization { @@ -36,7 +38,14 @@ enum RangeType struct Position { float pos[3]; + + // In radians float rot[3]; + + osg::Vec3f asVec3() const + { + return osg::Vec3f(pos[0], pos[1], pos[2]); + } }; #pragma pack(pop) @@ -124,5 +133,12 @@ enum RecNameInts REC_DBGP = FourCC<'D','B','G','P'>::value ///< only used in project files }; +/// Common subrecords +enum SubRecNameInts +{ + SREC_DELE = ESM::FourCC<'D','E','L','E'>::value, + SREC_NAME = ESM::FourCC<'N','A','M','E'>::value +}; + } #endif diff --git a/components/esm/dialoguestate.cpp b/components/esm/dialoguestate.cpp index f302e36dc8..2b1887e4eb 100644 --- a/components/esm/dialoguestate.cpp +++ b/components/esm/dialoguestate.cpp @@ -1,4 +1,3 @@ - #include "dialoguestate.hpp" #include "esmreader.hpp" diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 2804f89d41..6ef14a70e0 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -1,7 +1,6 @@ #include "esmreader.hpp" -#include -#include "../files/constrainedfiledatastream.hpp" +#include namespace ESM { @@ -16,7 +15,7 @@ using namespace Misc; ESM_Context ESMReader::getContext() { // Update the file position before returning - mCtx.filePos = mEsm->tell(); + mCtx.filePos = mEsm->tellg(); return mCtx; } @@ -26,6 +25,7 @@ ESMReader::ESMReader() , mBuffer(50*1024) , mGlobalReaderList(NULL) , mEncoder(NULL) + , mFileSize(0) { } @@ -44,12 +44,12 @@ void ESMReader::restoreContext(const ESM_Context &rc) mCtx = rc; // Make sure we seek to the right place - mEsm->seek(mCtx.filePos); + mEsm->seekg(mCtx.filePos); } void ESMReader::close() { - mEsm.setNull(); + mEsm.reset(); mCtx.filename.clear(); mCtx.leftFile = 0; mCtx.leftRec = 0; @@ -59,15 +59,22 @@ void ESMReader::close() mCtx.subName.val = 0; } -void ESMReader::openRaw(Ogre::DataStreamPtr _esm, const std::string &name) +void ESMReader::openRaw(Files::IStreamPtr _esm, const std::string& name) { close(); mEsm = _esm; mCtx.filename = name; - mCtx.leftFile = mEsm->size(); + mEsm->seekg(0, mEsm->end); + mCtx.leftFile = mFileSize = mEsm->tellg(); + mEsm->seekg(0, mEsm->beg); } -void ESMReader::open(Ogre::DataStreamPtr _esm, const std::string &name) +void ESMReader::openRaw(const std::string& filename) +{ + openRaw(Files::openConstrainedFileStream(filename.c_str()), filename); +} + +void ESMReader::open(Files::IStreamPtr _esm, const std::string &name) { openRaw(_esm, name); @@ -81,12 +88,7 @@ void ESMReader::open(Ogre::DataStreamPtr _esm, const std::string &name) void ESMReader::open(const std::string &file) { - open (openConstrainedFileDataStream (file.c_str ()), file); -} - -void ESMReader::openRaw(const std::string &file) -{ - openRaw (openConstrainedFileDataStream (file.c_str ()), file); + open (Files::openConstrainedFileStream (file.c_str ()), file); } int64_t ESMReader::getHNLong(const char *name) @@ -174,6 +176,22 @@ bool ESMReader::isNextSub(const char* name) return !mCtx.subCached; } +bool ESMReader::peekNextSub(const char *name) +{ + if (!mCtx.leftRec) + return false; + + getSubName(); + + mCtx.subCached = true; + return mCtx.subName == name; +} + +void ESMReader::cacheSubName() +{ + mCtx.subCached = true; +} + // Read subrecord name. This gets called a LOT, so I've optimized it // slightly. void ESMReader::getSubName() @@ -263,6 +281,7 @@ void ESMReader::skipRecord() { skip(mCtx.leftRec); mCtx.leftRec = 0; + mCtx.subCached = false; } void ESMReader::getRecHeader(uint32_t &flags) @@ -296,9 +315,7 @@ void ESMReader::getExact(void*x, int size) { try { - int t = mEsm->read(x, size); - if (t != size) - fail("Read error"); + mEsm->read((char*)x, size); } catch (std::exception& e) { @@ -340,8 +357,8 @@ void ESMReader::fail(const std::string &msg) ss << "\n File: " << mCtx.filename; ss << "\n Record: " << mCtx.recName.toString(); ss << "\n Subrecord: " << mCtx.subName.toString(); - if (!mEsm.isNull()) - ss << "\n Offset: 0x" << hex << mEsm->tell(); + if (mEsm.get()) + ss << "\n Offset: 0x" << hex << mEsm->tellg(); throw std::runtime_error(ss.str()); } @@ -350,4 +367,14 @@ void ESMReader::setEncoder(ToUTF8::Utf8Encoder* encoder) mEncoder = encoder; } +size_t ESMReader::getFileOffset() +{ + return mEsm->tellg(); +} + +void ESMReader::skip(int bytes) +{ + mEsm->seekg(getFileOffset()+bytes); +} + } diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 2df2a86b31..4772aeb6fc 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include @@ -63,20 +63,18 @@ public: /// Raw opening. Opens the file and sets everything up but doesn't /// parse the header. - void openRaw(Ogre::DataStreamPtr _esm, const std::string &name); + void openRaw(Files::IStreamPtr _esm, const std::string &name); /// Load ES file from a new stream, parses the header. Closes the /// currently open file first, if any. - void open(Ogre::DataStreamPtr _esm, const std::string &name); + void open(Files::IStreamPtr _esm, const std::string &name); void open(const std::string &file); - void openRaw(const std::string &file); + void openRaw(const std::string &filename); - /// Get the file size. Make sure that the file has been opened! - size_t getFileSize() { return mEsm->size(); } /// Get the current position in the file. Make sure that the file has been opened! - size_t getFileOffset() { return mEsm->tell(); } + size_t getFileOffset(); // This is a quick hack for multiple esm/esp files. Each plugin introduces its own // terrain palette, but ESMReader does not pass a reference to the correct plugin @@ -185,6 +183,11 @@ public: */ bool isNextSub(const char* name); + bool peekNextSub(const char* name); + + // Store the current subrecord name for the next call of getSubName() + void cacheSubName(); + // Read subrecord name. This gets called a LOT, so I've optimized it // slightly. void getSubName(); @@ -252,8 +255,7 @@ public: // them from native encoding to UTF8 in the process. std::string getString(int size); - void skip(int bytes) { mEsm->seek(mEsm->tell()+bytes); } - uint64_t getOffset() { return mEsm->tell(); } + void skip(int bytes); /// Used for error handling void fail(const std::string &msg); @@ -264,8 +266,10 @@ public: /// Get record flags of last record unsigned int getRecordFlags() { return mRecordFlags; } + size_t getFileSize() const { return mFileSize; } + private: - Ogre::DataStreamPtr mEsm; + Files::IStreamPtr mEsm; ESM_Context mCtx; @@ -280,6 +284,9 @@ private: std::vector *mGlobalReaderList; ToUTF8::Utf8Encoder* mEncoder; + + size_t mFileSize; + }; } #endif diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index c64678e702..4d99991434 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -174,6 +174,15 @@ namespace ESM endRecord(name); } + void ESMWriter::writeFixedSizeString(const std::string &data, int size) + { + std::string string; + if (!data.empty()) + string = mEncoder ? mEncoder->getLegacyEnc(data) : data; + string.resize(size); + write(string.c_str(), string.size()); + } + void ESMWriter::writeHString(const std::string& data) { if (data.size() == 0) diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index 30cec58b46..d11b3c940b 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -120,6 +120,7 @@ public: void startSubRecord(const std::string& name); void endRecord(const std::string& name); void endRecord(uint32_t name); + void writeFixedSizeString(const std::string& data, int size); void writeHString(const std::string& data); void writeHCString(const std::string& data); void writeName(const std::string& data); diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index a80427bbed..c3658f1520 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -1,4 +1,3 @@ - #include "filter.hpp" #include "esmreader.hpp" @@ -7,14 +6,46 @@ unsigned int ESM::Filter::sRecordId = REC_FILT; -void ESM::Filter::load (ESMReader& esm) +void ESM::Filter::load (ESMReader& esm, bool &isDeleted) { - mFilter = esm.getHNString ("FILT"); - mDescription = esm.getHNString ("DESC"); + isDeleted = false; + + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + break; + case ESM::FourCC<'F','I','L','T'>::value: + mFilter = esm.getHString(); + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } } -void ESM::Filter::save (ESMWriter& esm) const +void ESM::Filter::save (ESMWriter& esm, bool isDeleted) const { + esm.writeHNCString ("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString ("FILT", mFilter); esm.writeHNCString ("DESC", mDescription); } diff --git a/components/esm/filter.hpp b/components/esm/filter.hpp index bc3dd7bdcb..b1c511ebba 100644 --- a/components/esm/filter.hpp +++ b/components/esm/filter.hpp @@ -18,8 +18,8 @@ namespace ESM std::string mFilter; - void load (ESMReader& esm); - void save (ESMWriter& esm) const; + void load (ESMReader& esm, bool &isDeleted); + void save (ESMWriter& esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/globalscript.cpp b/components/esm/globalscript.cpp index 0129f8eb7d..a42cdc2309 100644 --- a/components/esm/globalscript.cpp +++ b/components/esm/globalscript.cpp @@ -1,4 +1,3 @@ - #include "globalscript.hpp" #include "esmreader.hpp" diff --git a/components/esm/inventorystate.cpp b/components/esm/inventorystate.cpp index 4eaaa9f9f1..b24128ec35 100644 --- a/components/esm/inventorystate.cpp +++ b/components/esm/inventorystate.cpp @@ -1,4 +1,3 @@ - #include "inventorystate.hpp" #include "esmreader.hpp" @@ -32,13 +31,20 @@ void ESM::InventoryState::load (ESMReader &esm) ++index; } - + //Next item is Levelled item while (esm.isNextSub("LEVM")) { + //Get its name std::string id = esm.getHString(); int count; + std::string parentGroup = ""; + //Then get its count esm.getHNT (count, "COUN"); - mLevelledItemMap[id] = count; + //Old save formats don't have information about parent group; check for that + if(esm.isNextSub("LGRP")) + //Newest saves contain parent group + parentGroup = esm.getHString(); + mLevelledItemMap[std::make_pair(id, parentGroup)] = count; } while (esm.isNextSub("MAGI")) @@ -80,10 +86,11 @@ void ESM::InventoryState::save (ESMWriter &esm) const iter->save (esm, true); } - for (std::map::const_iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end(); ++it) + for (std::map, int>::const_iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end(); ++it) { - esm.writeHNString ("LEVM", it->first); + esm.writeHNString ("LEVM", it->first.first); esm.writeHNT ("COUN", it->second); + esm.writeHNString("LGRP", it->first.second); } for (TEffectMagnitudes::const_iterator it = mPermanentMagicEffectMagnitudes.begin(); it != mPermanentMagicEffectMagnitudes.end(); ++it) diff --git a/components/esm/inventorystate.hpp b/components/esm/inventorystate.hpp index d5c317beb9..f4bb0ab487 100644 --- a/components/esm/inventorystate.hpp +++ b/components/esm/inventorystate.hpp @@ -20,7 +20,7 @@ namespace ESM // std::map mEquipmentSlots; - std::map mLevelledItemMap; + std::map, int> mLevelledItemMap; typedef std::map > > TEffectMagnitudes; TEffectMagnitudes mPermanentMagicEffectMagnitudes; diff --git a/components/esm/journalentry.cpp b/components/esm/journalentry.cpp index 445213de40..93011e581b 100644 --- a/components/esm/journalentry.cpp +++ b/components/esm/journalentry.cpp @@ -1,4 +1,3 @@ - #include "journalentry.hpp" #include "esmreader.hpp" diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index b5adce5509..b9934d3d39 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -8,14 +8,20 @@ namespace ESM { unsigned int Activator::sRecordId = REC_ACTI; - void Activator::load(ESMReader &esm) + void Activator::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -25,13 +31,29 @@ namespace ESM case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } - void Activator::save(ESMWriter &esm) const + void Activator::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); diff --git a/components/esm/loadacti.hpp b/components/esm/loadacti.hpp index d9a55023bc..4cc72d5283 100644 --- a/components/esm/loadacti.hpp +++ b/components/esm/loadacti.hpp @@ -17,8 +17,8 @@ struct Activator std::string mId, mName, mScript, mModel; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index 18db512c0c..2049045024 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -8,16 +8,23 @@ namespace ESM { unsigned int Potion::sRecordId = REC_ALCH; - void Potion::load(ESMReader &esm) + void Potion::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mEffects.mList.clear(); + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -37,15 +44,31 @@ namespace ESM case ESM::FourCC<'E','N','A','M'>::value: mEffects.add(esm); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) - esm.fail("Missing ALDT"); + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing ALDT subrecord"); } - void Potion::save(ESMWriter &esm) const + void Potion::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("TEXT", mIcon); esm.writeHNOCString("SCRI", mScript); diff --git a/components/esm/loadalch.hpp b/components/esm/loadalch.hpp index b90a7c448d..9ef390ebd9 100644 --- a/components/esm/loadalch.hpp +++ b/components/esm/loadalch.hpp @@ -33,8 +33,8 @@ struct Potion std::string mId, mName, mModel, mIcon, mScript; EffectList mEffects; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp index f2c82aacfb..490881fae7 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm/loadappa.cpp @@ -8,47 +8,69 @@ namespace ESM { unsigned int Apparatus::sRecordId = REC_APPA; -void Apparatus::load(ESMReader &esm) -{ - bool hasData = false; - while (esm.hasMoreSubs()) + void Apparatus::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) - { - case ESM::FourCC<'M','O','D','L'>::value: - mModel = esm.getHString(); - break; - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'A','A','D','T'>::value: - esm.getHT(mData); - hasData = true; - break; - case ESM::FourCC<'S','C','R','I'>::value: - mScript = esm.getHString(); - break; - case ESM::FourCC<'I','T','E','X'>::value: - mIcon = esm.getHString(); - break; - default: - esm.fail("Unknown subrecord"); - } - } - if (!hasData) - esm.fail("Missing AADT"); -} + isDeleted = false; -void Apparatus::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); - esm.writeHNT("AADT", mData, 16); - esm.writeHNOCString("SCRI", mScript); - esm.writeHNCString("ITEX", mIcon); -} + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'A','A','D','T'>::value: + esm.getHT(mData); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing AADT subrecord"); + } + + void Apparatus::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNCString("MODL", mModel); + esm.writeHNCString("FNAM", mName); + esm.writeHNT("AADT", mData, 16); + esm.writeHNOCString("SCRI", mScript); + esm.writeHNCString("ITEX", mIcon); + } void Apparatus::blank() { diff --git a/components/esm/loadappa.hpp b/components/esm/loadappa.hpp index f18b046486..0590d33edf 100644 --- a/components/esm/loadappa.hpp +++ b/components/esm/loadappa.hpp @@ -38,8 +38,8 @@ struct Apparatus AADTstruct mData; std::string mId, mModel, mIcon, mScript, mName; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index 066551d6fe..f2526554af 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -38,16 +38,23 @@ namespace ESM unsigned int Armor::sRecordId = REC_ARMO; - void Armor::load(ESMReader &esm) + void Armor::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mParts.mParts.clear(); + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -70,16 +77,32 @@ namespace ESM case ESM::FourCC<'I','N','D','X'>::value: mParts.add(esm); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing CTDT subrecord"); } - void Armor::save(ESMWriter &esm) const + void Armor::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); diff --git a/components/esm/loadarmo.hpp b/components/esm/loadarmo.hpp index 54416fd318..ef3bb734c0 100644 --- a/components/esm/loadarmo.hpp +++ b/components/esm/loadarmo.hpp @@ -96,8 +96,8 @@ struct Armor std::string mId, mName, mModel, mIcon, mScript, mEnchant; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index ed24ded57b..09113e8d1b 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -8,40 +8,61 @@ namespace ESM { unsigned int BodyPart::sRecordId = REC_BODY; - -void BodyPart::load(ESMReader &esm) -{ - bool hasData = false; - while (esm.hasMoreSubs()) + void BodyPart::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'M','O','D','L'>::value: - mModel = esm.getHString(); - break; - case ESM::FourCC<'F','N','A','M'>::value: - mRace = esm.getHString(); - break; - case ESM::FourCC<'B','Y','D','T'>::value: - esm.getHT(mData, 4); - hasData = true; - break; - default: - esm.fail("Unknown subrecord"); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mRace = esm.getHString(); + break; + case ESM::FourCC<'B','Y','D','T'>::value: + esm.getHT(mData, 4); + hasData = true; + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing BYDT subrecord"); } - if (!hasData) - esm.fail("Missing BYDT subrecord"); -} -void BodyPart::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mRace); - esm.writeHNT("BYDT", mData, 4); -} + void BodyPart::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mRace); + esm.writeHNT("BYDT", mData, 4); + } void BodyPart::blank() { diff --git a/components/esm/loadbody.hpp b/components/esm/loadbody.hpp index c48c313056..bf320330ff 100644 --- a/components/esm/loadbody.hpp +++ b/components/esm/loadbody.hpp @@ -60,8 +60,8 @@ struct BodyPart BYDTstruct mData; std::string mId, mModel, mRace; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbook.cpp b/components/esm/loadbook.cpp index 47f52fc31a..617040be42 100644 --- a/components/esm/loadbook.cpp +++ b/components/esm/loadbook.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Book::sRecordId = REC_BOOK; - void Book::load(ESMReader &esm) + void Book::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -39,15 +45,31 @@ namespace ESM case ESM::FourCC<'T','E','X','T'>::value: mText = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing BKDT subrecord"); } - void Book::save(ESMWriter &esm) const + void Book::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("BKDT", mData, 20); diff --git a/components/esm/loadbook.hpp b/components/esm/loadbook.hpp index 6211b3e457..3d50356aea 100644 --- a/components/esm/loadbook.hpp +++ b/components/esm/loadbook.hpp @@ -28,8 +28,8 @@ struct Book std::string mName, mModel, mIcon, mScript, mEnchant, mText; std::string mId; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index e0cd83ea07..9a4d98bb7b 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -8,41 +8,63 @@ namespace ESM { unsigned int BirthSign::sRecordId = REC_BSGN; -void BirthSign::load(ESMReader &esm) -{ - mPowers.mList.clear(); - while (esm.hasMoreSubs()) + void BirthSign::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + mPowers.mList.clear(); + + bool hasName = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'T','N','A','M'>::value: - mTexture = esm.getHString(); - break; - case ESM::FourCC<'D','E','S','C'>::value: - mDescription = esm.getHString(); - break; - case ESM::FourCC<'N','P','C','S'>::value: - mPowers.add(esm); - break; - default: - esm.fail("Unknown subrecord"); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'T','N','A','M'>::value: + mTexture = esm.getHString(); + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::FourCC<'N','P','C','S'>::value: + mPowers.add(esm); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } -} -void BirthSign::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mName); - esm.writeHNOCString("TNAM", mTexture); - esm.writeHNOCString("DESC", mDescription); + void BirthSign::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); - mPowers.save(esm); -} + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNOCString("FNAM", mName); + esm.writeHNOCString("TNAM", mTexture); + esm.writeHNOCString("DESC", mDescription); + + mPowers.save(esm); + } void BirthSign::blank() { diff --git a/components/esm/loadbsgn.hpp b/components/esm/loadbsgn.hpp index f91f91c974..24d27a7f85 100644 --- a/components/esm/loadbsgn.hpp +++ b/components/esm/loadbsgn.hpp @@ -22,8 +22,8 @@ struct BirthSign // List of powers and abilities that come with this birth sign. SpellList mPowers; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 94f4b0b6e7..eb9d6b8d8e 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -52,173 +52,215 @@ namespace ESM return ref.mRefNum == refNum; } -void Cell::load(ESMReader &esm, bool saveContext) -{ - loadData(esm); - loadCell(esm, saveContext); -} - -void Cell::loadCell(ESMReader &esm, bool saveContext) -{ - mRefNumCounter = 0; - - if (mData.mFlags & Interior) + void Cell::load(ESMReader &esm, bool &isDeleted, bool saveContext) { - // Interior cells - if (esm.isNextSub("INTV")) + loadNameAndData(esm, isDeleted); + loadCell(esm, saveContext); + } + + void Cell::loadNameAndData(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; + + blank(); + + bool hasData = false; + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) { - int waterl; - esm.getHT(waterl); - mWater = (float) waterl; - mWaterInt = true; - } - else if (esm.isNextSub("WHGT")) - { - esm.getHT(mWater); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mName = esm.getHString(); + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mData, 12); + hasData = true; + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } } - // Quasi-exterior cells have a region (which determines the - // weather), pure interior cells have ambient lighting - // instead. - if (mData.mFlags & QuasiEx) - mRegion = esm.getHNOString("RGNN"); - else if (esm.isNextSub("AMBI")) - esm.getHT(mAmbi); + if (!hasData) + esm.fail("Missing DATA subrecord"); + + mCellId.mPaged = !(mData.mFlags & Interior); + + if (mCellId.mPaged) + { + mCellId.mWorldspace = "sys::default"; + mCellId.mIndex.mX = mData.mX; + mCellId.mIndex.mY = mData.mY; + } + else + { + mCellId.mWorldspace = Misc::StringUtils::lowerCase (mName); + mCellId.mIndex.mX = 0; + mCellId.mIndex.mY = 0; + } } - else + + void Cell::loadCell(ESMReader &esm, bool saveContext) { - // Exterior cells - mRegion = esm.getHNOString("RGNN"); + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'I','N','T','V'>::value: + int waterl; + esm.getHT(waterl); + mWater = static_cast(waterl); + mWaterInt = true; + break; + case ESM::FourCC<'W','H','G','T'>::value: + esm.getHT(mWater); + mWaterInt = false; + break; + case ESM::FourCC<'A','M','B','I'>::value: + esm.getHT(mAmbi); + break; + case ESM::FourCC<'R','G','N','N'>::value: + mRegion = esm.getHString(); + break; + case ESM::FourCC<'N','A','M','5'>::value: + esm.getHT(mMapColor); + break; + case ESM::FourCC<'N','A','M','0'>::value: + esm.getHT(mRefNumCounter); + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } + } - mMapColor = 0; - esm.getHNOT(mMapColor, "NAM5"); - } - if (esm.isNextSub("NAM0")) { - esm.getHT(mRefNumCounter); + if (saveContext) + { + mContextList.push_back(esm.getContext()); + esm.skipRecord(); + } } - if (saveContext) { + void Cell::postLoad(ESMReader &esm) + { + // Save position of the cell references and move on mContextList.push_back(esm.getContext()); esm.skipRecord(); } -} -void Cell::loadData(ESMReader &esm) -{ - // Ignore this for now, it might mean we should delete the entire - // cell? - // TODO: treat the special case "another plugin moved this ref, but we want to delete it"! - if (esm.isNextSub("DELE")) { - esm.skipHSub(); - } - - esm.getHNT(mData, "DATA", 12); -} - -void Cell::postLoad(ESMReader &esm) -{ - // Save position of the cell references and move on - mContextList.push_back(esm.getContext()); - esm.skipRecord(); -} - -void Cell::save(ESMWriter &esm) const -{ - esm.writeHNT("DATA", mData, 12); - if (mData.mFlags & Interior) + void Cell::save(ESMWriter &esm, bool isDeleted) const { - if (mWaterInt) { - int water = - (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5); - esm.writeHNT("INTV", water); - } else { - esm.writeHNT("WHGT", mWater); + esm.writeHNOCString("NAME", mName); + esm.writeHNT("DATA", mData, 12); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; } - if (mData.mFlags & QuasiEx) + if (mData.mFlags & Interior) + { + if (mWaterInt) { + int water = + (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5); + esm.writeHNT("INTV", water); + } else { + esm.writeHNT("WHGT", mWater); + } + + if (mData.mFlags & QuasiEx) + esm.writeHNOCString("RGNN", mRegion); + else + esm.writeHNT("AMBI", mAmbi, 16); + } + else + { esm.writeHNOCString("RGNN", mRegion); - else - esm.writeHNT("AMBI", mAmbi, 16); - } - else - { - esm.writeHNOCString("RGNN", mRegion); - if (mMapColor != 0) - esm.writeHNT("NAM5", mMapColor); + if (mMapColor != 0) + esm.writeHNT("NAM5", mMapColor); + } + + if (mRefNumCounter != 0) + esm.writeHNT("NAM0", mRefNumCounter); } - if (mRefNumCounter != 0) - esm.writeHNT("NAM0", mRefNumCounter); -} - -void Cell::restore(ESMReader &esm, int iCtx) const -{ - esm.restoreContext(mContextList.at (iCtx)); -} - -std::string Cell::getDescription() const -{ - if (mData.mFlags & Interior) + void Cell::restore(ESMReader &esm, int iCtx) const { - return mName; + esm.restoreContext(mContextList.at (iCtx)); } - else - { - std::ostringstream stream; - stream << mData.mX << ", " << mData.mY; - return stream.str(); - } -} -bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool& deleted, bool ignoreMoves, MovedCellRef *mref) -{ - // TODO: Try and document reference numbering, I don't think this has been done anywhere else. - if (!esm.hasMoreSubs()) - return false; - - // NOTE: We should not need this check. It is a safety check until we have checked - // more plugins, and how they treat these moved references. - if (esm.isNextSub("MVRF")) + std::string Cell::getDescription() const { - if (ignoreMoves) + if (mData.mFlags & Interior) { - esm.getHT (mref->mRefNum.mIndex); - esm.getHNOT (mref->mTarget, "CNDT"); - adjustRefNum (mref->mRefNum, esm); + return mName; } else { - // skip rest of cell record (moved references), they are handled elsewhere - esm.skipRecord(); // skip MVRF, CNDT + std::ostringstream stream; + stream << mData.mX << ", " << mData.mY; + return stream.str(); + } + } + + bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool &isDeleted, bool ignoreMoves, MovedCellRef *mref) + { + isDeleted = false; + + // TODO: Try and document reference numbering, I don't think this has been done anywhere else. + if (!esm.hasMoreSubs()) return false; + + // NOTE: We should not need this check. It is a safety check until we have checked + // more plugins, and how they treat these moved references. + if (esm.isNextSub("MVRF")) + { + if (ignoreMoves) + { + esm.getHT (mref->mRefNum.mIndex); + esm.getHNOT (mref->mTarget, "CNDT"); + adjustRefNum (mref->mRefNum, esm); + } + else + { + // skip rest of cell record (moved references), they are handled elsewhere + esm.skipRecord(); // skip MVRF, CNDT + return false; + } } + + if (esm.peekNextSub("FRMR")) + { + ref.load (esm, isDeleted); + + // Identify references belonging to a parent file and adapt the ID accordingly. + adjustRefNum (ref.mRefNum, esm); + return true; + } + return false; } - ref.load (esm); - - // Identify references belonging to a parent file and adapt the ID accordingly. - adjustRefNum (ref.mRefNum, esm); - - if (esm.isNextSub("DELE")) + bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) { - esm.skipHSub(); - deleted = true; + esm.getHT(mref.mRefNum.mIndex); + esm.getHNOT(mref.mTarget, "CNDT"); + + adjustRefNum (mref.mRefNum, esm); + + return true; } - else - deleted = false; - - return true; -} - -bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) -{ - esm.getHT(mref.mRefNum.mIndex); - esm.getHNOT(mref.mTarget, "CNDT"); - - adjustRefNum (mref.mRefNum, esm); - - return true; -} void Cell::blank() { @@ -239,25 +281,8 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) mAmbi.mFogDensity = 0; } - CellId Cell::getCellId() const + const CellId& Cell::getCellId() const { - CellId id; - - id.mPaged = !(mData.mFlags & Interior); - - if (id.mPaged) - { - id.mWorldspace = "sys::default"; - id.mIndex.mX = mData.mX; - id.mIndex.mY = mData.mY; - } - else - { - id.mWorldspace = Misc::StringUtils::lowerCase (mName); - id.mIndex.mX = 0; - id.mIndex.mY = 0; - } - - return id; + return mCellId; } } diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 12fb8c0684..f92e0b5b70 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -8,6 +8,7 @@ #include "esmcommon.hpp" #include "defs.hpp" #include "cellref.hpp" +#include "cellid.hpp" namespace MWWorld { @@ -18,7 +19,6 @@ namespace ESM { class ESMReader; class ESMWriter; -struct CellId; /* Moved cell reference tracking object. This mainly stores the target cell of the reference, so we can easily know where it has been moved when another @@ -97,6 +97,8 @@ struct Cell std::vector mContextList; // File position; multiple positions for multiple plugin support DATAstruct mData; + CellId mCellId; + AMBIstruct mAmbi; float mWater; // Water level @@ -116,11 +118,11 @@ struct Cell // This method is left in for compatibility with esmtool. Parsing moved references currently requires // passing ESMStore, bit it does not know about this parameter, so we do it this way. - void load(ESMReader &esm, bool saveContext = true); // Load everything (except references) - void loadData(ESMReader &esm); // Load DATAstruct only - void loadCell(ESMReader &esm, bool saveContext = true); // Load everything, except DATAstruct and references + void load(ESMReader &esm, bool &isDeleted, bool saveContext = true); // Load everything (except references) + void loadNameAndData(ESMReader &esm, bool &isDeleted); // Load NAME and DATAstruct + void loadCell(ESMReader &esm, bool saveContext = true); // Load everything, except NAME, DATAstruct and references - void save(ESMWriter &esm) const; + void save(ESMWriter &esm, bool isDeleted = false) const; bool isExterior() const { @@ -159,8 +161,11 @@ struct Cell reuse one memory location without blanking it between calls. */ /// \param ignoreMoves ignore MVRF record and read reference like a regular CellRef. - static bool getNextRef(ESMReader &esm, - CellRef &ref, bool& deleted, bool ignoreMoves = false, MovedCellRef *mref = 0); + static bool getNextRef(ESMReader &esm, + CellRef &ref, + bool &isDeleted, + bool ignoreMoves = false, + MovedCellRef *mref = 0); /* This fetches an MVRF record, which is used to track moved references. * Since they are comparably rare, we use a separate method for this. @@ -170,7 +175,7 @@ struct Cell void blank(); ///< Set record to default state (does not touch the ID/index). - CellId getCellId() const; + const CellId& getCellId() const; }; } #endif diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index 66acaea721..61960b87db 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -22,7 +22,6 @@ namespace ESM "sSpecializationStealth" }; - int& Class::CLDTstruct::getSkill (int index, bool major) { if (index<0 || index>=5) @@ -39,15 +38,21 @@ namespace ESM return mSkills[index][major ? 1 : 0]; } - void Class::load(ESMReader &esm) + void Class::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -60,15 +65,31 @@ namespace ESM case ESM::FourCC<'D','E','S','C'>::value: mDescription = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing CLDT subrecord"); } - void Class::save(ESMWriter &esm) const + void Class::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNOCString("FNAM", mName); esm.writeHNT("CLDT", mData, 60); esm.writeHNOString("DESC", mDescription); diff --git a/components/esm/loadclas.hpp b/components/esm/loadclas.hpp index 972b48e889..833dd6757d 100644 --- a/components/esm/loadclas.hpp +++ b/components/esm/loadclas.hpp @@ -73,8 +73,8 @@ struct Class std::string mId, mName, mDescription; CLDTstruct mData; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp index 5f49b5e709..2ef69e5e91 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm/loadclot.cpp @@ -8,16 +8,23 @@ namespace ESM { unsigned int Clothing::sRecordId = REC_CLOT; - void Clothing::load(ESMReader &esm) + void Clothing::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mParts.mParts.clear(); + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -40,16 +47,32 @@ namespace ESM case ESM::FourCC<'I','N','D','X'>::value: mParts.add(esm); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing CTDT subrecord"); } - void Clothing::save(ESMWriter &esm) const + void Clothing::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("CTDT", mData, 12); diff --git a/components/esm/loadclot.hpp b/components/esm/loadclot.hpp index 6945f224a4..39e5ea6729 100644 --- a/components/esm/loadclot.hpp +++ b/components/esm/loadclot.hpp @@ -48,8 +48,8 @@ struct Clothing std::string mId, mName, mModel, mIcon, mEnchant, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index 3481189c37..739b0d7db6 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -24,17 +24,24 @@ namespace ESM unsigned int Container::sRecordId = REC_CONT; - void Container::load(ESMReader &esm) + void Container::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mInventory.mList.clear(); + + bool hasName = false; bool hasWeight = false; bool hasFlags = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -59,18 +66,34 @@ namespace ESM case ESM::FourCC<'N','P','C','O'>::value: mInventory.add(esm); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasWeight) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasWeight && !isDeleted) esm.fail("Missing CNDT subrecord"); - if (!hasFlags) + if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } - void Container::save(ESMWriter &esm) const + void Container::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("CNDT", mWeight, 4); diff --git a/components/esm/loadcont.hpp b/components/esm/loadcont.hpp index ab587f9353..4c847f4e2e 100644 --- a/components/esm/loadcont.hpp +++ b/components/esm/loadcont.hpp @@ -52,8 +52,8 @@ struct Container int mFlags; InventoryList mInventory; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index 50c47349ca..b517820896 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -1,5 +1,7 @@ #include "loadcrea.hpp" +#include + #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" @@ -8,8 +10,10 @@ namespace ESM { unsigned int Creature::sRecordId = REC_CREA; - void Creature::load(ESMReader &esm) + void Creature::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mPersistent = (esm.getRecordFlags() & 0x0400) != 0; mAiPackage.mList.clear(); @@ -19,14 +23,19 @@ namespace ESM { mScale = 1.f; mHasAI = false; + + bool hasName = false; bool hasNpdt = false; bool hasFlags = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -72,18 +81,40 @@ namespace ESM { case AI_CNDT: mAiPackage.add(esm); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + case ESM::FourCC<'I','N','D','X'>::value: + // seems to occur only in .ESS files, unsure of purpose + int index; + esm.getHT(index); + std::cerr << "Creature::load: Unhandled INDX " << index << std::endl; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasNpdt) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasNpdt && !isDeleted) esm.fail("Missing NPDT subrecord"); - if (!hasFlags) + if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } - void Creature::save(ESMWriter &esm) const + void Creature::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("CNAM", mOriginal); esm.writeHNOCString("FNAM", mName); @@ -96,7 +127,12 @@ namespace ESM { mInventory.save(esm); mSpells.save(esm); - if (mHasAI) { + if (mAiData.mHello != 0 + || mAiData.mFight != 0 + || mAiData.mFlee != 0 + || mAiData.mAlarm != 0 + || mAiData.mServices != 0) + { esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); } mTransport.save(esm); @@ -115,7 +151,7 @@ namespace ESM { for (int i=0; i<6; ++i) mData.mAttack[i] = 0; mData.mGold = 0; mFlags = 0; - mScale = 0; + mScale = 1.f; mModel.clear(); mName.clear(); mScript.clear(); diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index 47e5954a5f..a5147619cf 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -91,7 +91,6 @@ struct Creature InventoryList mInventory; SpellList mSpells; - bool mHasAI; AIData mAiData; AIPackageList mAiPackage; @@ -99,8 +98,8 @@ struct Creature const std::vector& getTransport() const; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index f2da8f3775..bc87c4f57d 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -10,121 +10,150 @@ namespace ESM { unsigned int Dialogue::sRecordId = REC_DIAL; -void Dialogue::load(ESMReader &esm) -{ - esm.getSubNameIs("DATA"); - esm.getSubHeader(); - int si = esm.getSubSize(); - if (si == 1) - esm.getT(mType); - else if (si == 4) + void Dialogue::load(ESMReader &esm, bool &isDeleted) { - // These are just markers, their values are not used. - int i; - esm.getT(i); - esm.getHNT(i, "DELE"); - mType = Deleted; + loadId(esm); + loadData(esm, isDeleted); } - else - esm.fail("Unknown sub record size"); -} -void Dialogue::save(ESMWriter &esm) const -{ - if (mType != Deleted) - esm.writeHNT("DATA", mType); - else + void Dialogue::loadId(ESMReader &esm) { - esm.writeHNT("DATA", (int)1); - esm.writeHNT("DELE", (int)1); + mId = esm.getHNString("NAME"); } -} -void Dialogue::blank() -{ - mInfo.clear(); -} + void Dialogue::loadData(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; -void Dialogue::readInfo(ESMReader &esm, bool merge) -{ - const std::string& id = esm.getHNOString("INAM"); + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'D','A','T','A'>::value: + { + esm.getSubHeader(); + int size = esm.getSubSize(); + if (size == 1) + { + esm.getT(mType); + } + else + { + esm.skip(size); + } + break; + } + case ESM::SREC_DELE: + esm.skipHSub(); + mType = Unknown; + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + } - if (!merge || mInfo.empty()) + void Dialogue::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + } + else + { + esm.writeHNT("DATA", mType); + } + } + + void Dialogue::blank() + { + mInfo.clear(); + } + + void Dialogue::readInfo(ESMReader &esm, bool merge) { ESM::DialInfo info; - info.mId = id; - info.load(esm); - mLookup[id] = mInfo.insert(mInfo.end(), info); - return; - } + info.loadId(esm); - ESM::Dialogue::InfoContainer::iterator it = mInfo.end(); + bool isDeleted = false; + if (!merge || mInfo.empty()) + { + info.loadData(esm, isDeleted); + mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted); - std::map::iterator lookup; + return; + } - lookup = mLookup.find(id); + InfoContainer::iterator it = mInfo.end(); - ESM::DialInfo info; - if (lookup != mLookup.end()) - { - it = lookup->second; + LookupMap::iterator lookup; + lookup = mLookup.find(info.mId); - // Merge with existing record. Only the subrecords that are present in - // the new record will be overwritten. - it->load(esm); - info = *it; + if (lookup != mLookup.end()) + { + it = lookup->second.first; - // Since the record merging may have changed the next/prev linked list connection, we need to re-insert the record - mInfo.erase(it); - mLookup.erase(lookup); - } - else - { - info.mId = id; - info.load(esm); - } + // Merge with existing record. Only the subrecords that are present in + // the new record will be overwritten. + it->loadData(esm, isDeleted); + info = *it; - if (info.mNext.empty()) - { - mLookup[id] = mInfo.insert(mInfo.end(), info); - return; - } - if (info.mPrev.empty()) - { - mLookup[id] = mInfo.insert(mInfo.begin(), info); - return; - } - - lookup = mLookup.find(info.mPrev); - if (lookup != mLookup.end()) - { - it = lookup->second; - - mLookup[id] = mInfo.insert(++it, info); - return; - } - - lookup = mLookup.find(info.mNext); - if (lookup != mLookup.end()) - { - it = lookup->second; - - mLookup[id] = mInfo.insert(it, info); - return; - } - - std::cerr << "Failed to insert info " << id << std::endl; -} - -void Dialogue::clearDeletedInfos() -{ - for (InfoContainer::iterator it = mInfo.begin(); it != mInfo.end(); ) - { - if (it->mQuestStatus == DialInfo::QS_Deleted) - it = mInfo.erase(it); + // Since the record merging may have changed the next/prev linked list connection, we need to re-insert the record + mInfo.erase(it); + mLookup.erase(lookup); + } else - ++it; + { + info.loadData(esm, isDeleted); + } + + if (info.mNext.empty()) + { + mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted); + return; + } + if (info.mPrev.empty()) + { + mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.begin(), info), isDeleted); + return; + } + + lookup = mLookup.find(info.mPrev); + if (lookup != mLookup.end()) + { + it = lookup->second.first; + + mLookup[info.mId] = std::make_pair(mInfo.insert(++it, info), isDeleted); + return; + } + + lookup = mLookup.find(info.mNext); + if (lookup != mLookup.end()) + { + it = lookup->second.first; + + mLookup[info.mId] = std::make_pair(mInfo.insert(it, info), isDeleted); + return; + } + + std::cerr << "Failed to insert info " << info.mId << std::endl; + } + + void Dialogue::clearDeletedInfos() + { + LookupMap::const_iterator current = mLookup.begin(); + LookupMap::const_iterator end = mLookup.end(); + for (; current != end; ++current) + { + if (current->second.second) + { + mInfo.erase(current->second.first); + } + } + mLookup.clear(); } } - -} diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index 58598d3536..b80cbd74c3 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -31,7 +31,7 @@ struct Dialogue Greeting = 2, Persuasion = 3, Journal = 4, - Deleted = -1 + Unknown = -1 // Used for deleted dialogues }; std::string mId; @@ -39,17 +39,24 @@ struct Dialogue typedef std::list InfoContainer; - typedef std::map LookupMap; + // Parameters: Info ID, (Info iterator, Deleted flag) + typedef std::map > LookupMap; InfoContainer mInfo; // This is only used during the loading phase to speed up DialInfo merging. LookupMap mLookup; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + ///< Loads all sub-records of Dialogue record + void loadId(ESMReader &esm); + ///< Loads NAME sub-record of Dialogue record + void loadData(ESMReader &esm, bool &isDeleted); + ///< Loads all sub-records of Dialogue record, except NAME sub-record - /// Remove all INFOs marked as QS_Deleted from mInfos. + void save(ESMWriter &esm, bool isDeleted = false) const; + + /// Remove all INFOs that are deleted void clearDeletedInfos(); /// Read the next info record diff --git a/components/esm/loaddoor.cpp b/components/esm/loaddoor.cpp index f446eed611..6d8c0978c8 100644 --- a/components/esm/loaddoor.cpp +++ b/components/esm/loaddoor.cpp @@ -8,14 +8,20 @@ namespace ESM { unsigned int Door::sRecordId = REC_DOOR; - void Door::load(ESMReader &esm) + void Door::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -31,14 +37,30 @@ namespace ESM case ESM::FourCC<'A','N','A','M'>::value: mCloseSound = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } - void Door::save(ESMWriter &esm) const + void Door::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); diff --git a/components/esm/loaddoor.hpp b/components/esm/loaddoor.hpp index 3073f4e9de..3afe5d5e4b 100644 --- a/components/esm/loaddoor.hpp +++ b/components/esm/loaddoor.hpp @@ -17,8 +17,8 @@ struct Door std::string mId, mName, mModel, mScript, mOpenSound, mCloseSound; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index 54690d9a0b..ae83c63f70 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -8,37 +8,58 @@ namespace ESM { unsigned int Enchantment::sRecordId = REC_ENCH; -void Enchantment::load(ESMReader &esm) -{ - mEffects.mList.clear(); - bool hasData = false; - while (esm.hasMoreSubs()) + void Enchantment::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) - { - case ESM::FourCC<'E','N','D','T'>::value: - esm.getHT(mData, 16); - hasData = true; - break; - case ESM::FourCC<'E','N','A','M'>::value: - mEffects.add(esm); - break; - default: - esm.fail("Unknown subrecord"); - break; - } - } - if (!hasData) - esm.fail("Missing ENDT subrecord"); -} + isDeleted = false; + mEffects.mList.clear(); -void Enchantment::save(ESMWriter &esm) const -{ - esm.writeHNT("ENDT", mData, 16); - mEffects.save(esm); -} + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'E','N','D','T'>::value: + esm.getHT(mData, 16); + hasData = true; + break; + case ESM::FourCC<'E','N','A','M'>::value: + mEffects.add(esm); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing ENDT subrecord"); + } + + void Enchantment::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNT("ENDT", mData, 16); + mEffects.save(esm); + } void Enchantment::blank() { diff --git a/components/esm/loadench.hpp b/components/esm/loadench.hpp index cfcdd4edce..7b93b519c3 100644 --- a/components/esm/loadench.hpp +++ b/components/esm/loadench.hpp @@ -42,8 +42,8 @@ struct Enchantment ENDTstruct mData; EffectList mEffects; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index 006ca0ce00..75c482d201 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -26,69 +26,92 @@ namespace ESM return mSkills[index]; } -void Faction::load(ESMReader &esm) -{ - mReactions.clear(); - for (int i=0;i<10;++i) - mRanks[i].clear(); - - int rankCounter=0; - bool hasData = false; - while (esm.hasMoreSubs()) + void Faction::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + mReactions.clear(); + for (int i=0;i<10;++i) + mRanks[i].clear(); + + int rankCounter = 0; + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'R','N','A','M'>::value: - if (rankCounter >= 10) - esm.fail("Rank out of range"); - mRanks[rankCounter++] = esm.getHString(); - break; - case ESM::FourCC<'F','A','D','T'>::value: - esm.getHT(mData, 240); - if (mData.mIsHidden > 1) - esm.fail("Unknown flag!"); - hasData = true; - break; - case ESM::FourCC<'A','N','A','M'>::value: + esm.getSubName(); + switch (esm.retSubName().val) { - std::string faction = esm.getHString(); - int reaction; - esm.getHNT(reaction, "INTV"); - mReactions[faction] = reaction; - break; + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','N','A','M'>::value: + if (rankCounter >= 10) + esm.fail("Rank out of range"); + mRanks[rankCounter++] = esm.getHString(); + break; + case ESM::FourCC<'F','A','D','T'>::value: + esm.getHT(mData, 240); + if (mData.mIsHidden > 1) + esm.fail("Unknown flag!"); + hasData = true; + break; + case ESM::FourCC<'A','N','A','M'>::value: + { + std::string faction = esm.getHString(); + int reaction; + esm.getHNT(reaction, "INTV"); + mReactions[faction] = reaction; + break; + } + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; } - default: - esm.fail("Unknown subrecord"); + } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing FADT subrecord"); + } + + void Faction::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNOCString("FNAM", mName); + + for (int i = 0; i < 10; i++) + { + if (mRanks[i].empty()) + break; + + esm.writeHNString("RNAM", mRanks[i], 32); + } + + esm.writeHNT("FADT", mData, 240); + + for (std::map::const_iterator it = mReactions.begin(); it != mReactions.end(); ++it) + { + esm.writeHNString("ANAM", it->first); + esm.writeHNT("INTV", it->second); } } - if (!hasData) - esm.fail("Missing FADT subrecord"); -} -void Faction::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mName); - - for (int i = 0; i < 10; i++) - { - if (mRanks[i].empty()) - break; - - esm.writeHNString("RNAM", mRanks[i], 32); - } - - esm.writeHNT("FADT", mData, 240); - - for (std::map::const_iterator it = mReactions.begin(); it != mReactions.end(); ++it) - { - esm.writeHNString("ANAM", it->first); - esm.writeHNT("INTV", it->second); - } -} void Faction::blank() { diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index 8645e23fd8..cc715d2660 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -62,8 +62,8 @@ struct Faction // Name of faction ranks (may be empty for NPC factions) std::string mRanks[10]; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadglob.cpp b/components/esm/loadglob.cpp index a78ed1a1be..72ecce503c 100644 --- a/components/esm/loadglob.cpp +++ b/components/esm/loadglob.cpp @@ -1,19 +1,42 @@ #include "loadglob.hpp" +#include "esmreader.hpp" +#include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Global::sRecordId = REC_GLOB; - void Global::load (ESMReader &esm) + void Global::load (ESMReader &esm, bool &isDeleted) { - mValue.read (esm, ESM::Variant::Format_Global); + isDeleted = false; + + mId = esm.getHNString ("NAME"); + + if (esm.isNextSub ("DELE")) + { + esm.skipHSub(); + isDeleted = true; + } + else + { + mValue.read (esm, ESM::Variant::Format_Global); + } } - void Global::save (ESMWriter &esm) const + void Global::save (ESMWriter &esm, bool isDeleted) const { - mValue.write (esm, ESM::Variant::Format_Global); + esm.writeHNCString ("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString ("DELE", ""); + } + else + { + mValue.write (esm, ESM::Variant::Format_Global); + } } void Global::blank() diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp index cc5dbbdcfc..0533cc95ea 100644 --- a/components/esm/loadglob.hpp +++ b/components/esm/loadglob.hpp @@ -24,8 +24,8 @@ struct Global std::string mId; Variant mValue; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadgmst.cpp b/components/esm/loadgmst.cpp index 21d66339a9..1ebb002e68 100644 --- a/components/esm/loadgmst.cpp +++ b/components/esm/loadgmst.cpp @@ -1,18 +1,24 @@ #include "loadgmst.hpp" +#include "esmreader.hpp" +#include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int GameSetting::sRecordId = REC_GMST; - void GameSetting::load (ESMReader &esm) + void GameSetting::load (ESMReader &esm, bool &isDeleted) { + isDeleted = false; // GameSetting record can't be deleted now (may be changed in the future) + + mId = esm.getHNString("NAME"); mValue.read (esm, ESM::Variant::Format_Gmst); } - void GameSetting::save (ESMWriter &esm) const + void GameSetting::save (ESMWriter &esm, bool /*isDeleted*/) const { + esm.writeHNCString("NAME", mId); mValue.write (esm, ESM::Variant::Format_Gmst); } diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp index d9d9048b61..73a723e810 100644 --- a/components/esm/loadgmst.hpp +++ b/components/esm/loadgmst.hpp @@ -26,7 +26,7 @@ struct GameSetting Variant mValue; - void load(ESMReader &esm); + void load(ESMReader &esm, bool &isDeleted); /// \todo remove the get* functions (redundant, since mValue has equivalent functions now). @@ -39,7 +39,7 @@ struct GameSetting std::string getString() const; ///< Throwns an exception if GMST is not of type string. - void save(ESMWriter &esm) const; + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index a2bade1c5e..246baf0265 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -8,156 +8,140 @@ namespace ESM { unsigned int DialInfo::sRecordId = REC_INFO; -void DialInfo::load(ESMReader &esm) -{ - mQuestStatus = QS_None; - mFactionLess = false; - - mPrev = esm.getHNString("PNAM"); - mNext = esm.getHNString("NNAM"); - - // Since there's no way to mark selects as "deleted", we have to clear the SelectStructs from all previous loadings - mSelects.clear(); - - // Not present if deleted - if (esm.isNextSub("DATA")) { - esm.getHT(mData, 12); + void DialInfo::load(ESMReader &esm, bool &isDeleted) + { + loadId(esm); + loadData(esm, isDeleted); } - if (!esm.hasMoreSubs()) - return; - - // What follows is somewhat spaghetti-ish, but it's worth if for - // an extra speedup. INFO is by far the most common record type. - - // subName is a reference to the original, so it changes whenever - // a new sub name is read. esm.isEmptyOrGetName() will get the - // next name for us, or return true if there are no more records. - esm.getSubName(); - const NAME &subName = esm.retSubName(); - - if (subName.val == REC_ONAM) + void DialInfo::loadId(ESMReader &esm) { - mActor = esm.getHString(); - if (esm.isEmptyOrGetName()) + mId = esm.getHNString("INAM"); + } + + void DialInfo::loadData(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; + + mQuestStatus = QS_None; + mFactionLess = false; + + mPrev = esm.getHNString("PNAM"); + mNext = esm.getHNString("NNAM"); + + // Since there's no way to mark selects as "deleted", we have to clear the SelectStructs from all previous loadings + mSelects.clear(); + + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mData, 12); + break; + case ESM::FourCC<'O','N','A','M'>::value: + mActor = esm.getHString(); + break; + case ESM::FourCC<'R','N','A','M'>::value: + mRace = esm.getHString(); + break; + case ESM::FourCC<'C','N','A','M'>::value: + mClass = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + { + mFaction = esm.getHString(); + if (mFaction == "FFFF") + { + mFactionLess = true; + } + break; + } + case ESM::FourCC<'A','N','A','M'>::value: + mCell = esm.getHString(); + break; + case ESM::FourCC<'D','N','A','M'>::value: + mPcFaction = esm.getHString(); + break; + case ESM::FourCC<'S','N','A','M'>::value: + mSound = esm.getHString(); + break; + case ESM::SREC_NAME: + mResponse = esm.getHString(); + break; + case ESM::FourCC<'S','C','V','R'>::value: + { + SelectStruct ss; + ss.mSelectRule = esm.getHString(); + ss.mValue.read(esm, Variant::Format_Info); + mSelects.push_back(ss); + break; + } + case ESM::FourCC<'B','N','A','M'>::value: + mResultScript = esm.getHString(); + break; + case ESM::FourCC<'Q','S','T','N'>::value: + mQuestStatus = QS_Name; + esm.skipRecord(); + break; + case ESM::FourCC<'Q','S','T','F'>::value: + mQuestStatus = QS_Finished; + esm.skipRecord(); + break; + case ESM::FourCC<'Q','S','T','R'>::value: + mQuestStatus = QS_Restart; + esm.skipRecord(); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + } + + void DialInfo::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("INAM", mId); + esm.writeHNCString("PNAM", mPrev); + esm.writeHNCString("NNAM", mNext); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); return; + } + + esm.writeHNT("DATA", mData, 12); + esm.writeHNOCString("ONAM", mActor); + esm.writeHNOCString("RNAM", mRace); + esm.writeHNOCString("CNAM", mClass); + esm.writeHNOCString("FNAM", mFaction); + esm.writeHNOCString("ANAM", mCell); + esm.writeHNOCString("DNAM", mPcFaction); + esm.writeHNOCString("SNAM", mSound); + esm.writeHNOString("NAME", mResponse); + + for (std::vector::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it) + { + esm.writeHNString("SCVR", it->mSelectRule); + it->mValue.write (esm, Variant::Format_Info); + } + + esm.writeHNOString("BNAM", mResultScript); + + switch(mQuestStatus) + { + case QS_Name: esm.writeHNT("QSTN",'\1'); break; + case QS_Finished: esm.writeHNT("QSTF", '\1'); break; + case QS_Restart: esm.writeHNT("QSTR", '\1'); break; + default: break; + } } - if (subName.val == REC_RNAM) - { - mRace = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_CNAM) - { - mClass = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - - if (subName.val == REC_FNAM) - { - mFaction = esm.getHString(); - if (mFaction == "FFFF") - mFactionLess = true; - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_ANAM) - { - mCell = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_DNAM) - { - mPcFaction = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_SNAM) - { - mSound = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_NAME) - { - mResponse = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - - while (subName.val == REC_SCVR) - { - SelectStruct ss; - - ss.mSelectRule = esm.getHString(); - - ss.mValue.read (esm, Variant::Format_Info); - - mSelects.push_back(ss); - - if (esm.isEmptyOrGetName()) - return; - } - - if (subName.val == REC_BNAM) - { - mResultScript = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - - if (subName.val == REC_QSTN) - mQuestStatus = QS_Name; - else if (subName.val == REC_QSTF) - mQuestStatus = QS_Finished; - else if (subName.val == REC_QSTR) - mQuestStatus = QS_Restart; - else if (subName.val == REC_DELE) - mQuestStatus = QS_Deleted; - else - esm.fail( - "Don't know what to do with " + subName.toString() - + " in INFO " + mId); - - if (mQuestStatus != QS_None) - // Skip rest of record - esm.skipRecord(); -} - -void DialInfo::save(ESMWriter &esm) const -{ - esm.writeHNCString("PNAM", mPrev); - esm.writeHNCString("NNAM", mNext); - esm.writeHNT("DATA", mData, 12); - esm.writeHNOCString("ONAM", mActor); - esm.writeHNOCString("RNAM", mRace); - esm.writeHNOCString("CNAM", mClass); - esm.writeHNOCString("FNAM", mFaction); - esm.writeHNOCString("ANAM", mCell); - esm.writeHNOCString("DNAM", mPcFaction); - esm.writeHNOCString("SNAM", mSound); - esm.writeHNOString("NAME", mResponse); - - for (std::vector::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it) - { - esm.writeHNString("SCVR", it->mSelectRule); - it->mValue.write (esm, Variant::Format_Info); - } - - esm.writeHNOString("BNAM", mResultScript); - - switch(mQuestStatus) - { - case QS_Name: esm.writeHNT("QSTN",'\1'); break; - case QS_Finished: esm.writeHNT("QSTF", '\1'); break; - case QS_Restart: esm.writeHNT("QSTR", '\1'); break; - case QS_Deleted: esm.writeHNT("DELE", '\1'); break; - default: break; - } -} void DialInfo::blank() { diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 54003b0d96..8123a9ace8 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -59,8 +59,7 @@ struct DialInfo QS_None = 0, QS_Name = 1, QS_Finished = 2, - QS_Restart = 3, - QS_Deleted + QS_Restart = 3 }; // Rules for when to include this item in the final list of options @@ -106,8 +105,14 @@ struct DialInfo REC_DELE = 0x454c4544 }; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + ///< Loads all sub-records of Info record + void loadId(ESMReader &esm); + ///< Loads only Id of Info record (INAM sub-record) + void loadData(ESMReader &esm, bool &isDeleted); + ///< Loads all sub-records of Info record, except INAM sub-record + + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index 7e0cc3168d..e00e73ab0b 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Ingredient::sRecordId = REC_INGR; - void Ingredient::load(ESMReader &esm) + void Ingredient::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -33,12 +39,19 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing IRDT subrecord"); // horrible hack to fix broken data in records @@ -65,8 +78,16 @@ namespace ESM } } - void Ingredient::save(ESMWriter &esm) const + void Ingredient::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("IRDT", mData, 56); diff --git a/components/esm/loadingr.hpp b/components/esm/loadingr.hpp index 5846a97809..c0f4450238 100644 --- a/components/esm/loadingr.hpp +++ b/components/esm/loadingr.hpp @@ -31,8 +31,8 @@ struct Ingredient IRDTstruct mData; std::string mId, mName, mModel, mIcon, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index b0897ec67d..e7be033219 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -1,5 +1,7 @@ #include "loadland.hpp" +#include + #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" @@ -8,211 +10,315 @@ namespace ESM { unsigned int Land::sRecordId = REC_LAND; -void Land::LandData::save(ESMWriter &esm) -{ - if (mDataTypes & Land::DATA_VNML) { - esm.writeHNT("VNML", mNormals, sizeof(mNormals)); - } - if (mDataTypes & Land::DATA_VHGT) { - VHGT offsets; - offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE; - offsets.mUnk1 = mUnk1; - offsets.mUnk2 = mUnk2; + void Land::LandData::save(ESMWriter &esm) const + { + if (mDataTypes & Land::DATA_VNML) { + esm.writeHNT("VNML", mNormals, sizeof(mNormals)); + } + if (mDataTypes & Land::DATA_VHGT) { + VHGT offsets; + offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE; + offsets.mUnk1 = mUnk1; + offsets.mUnk2 = mUnk2; - float prevY = mHeights[0]; - int number = 0; // avoid multiplication - for (int i = 0; i < LAND_SIZE; ++i) { - float diff = (mHeights[number] - prevY) / HEIGHT_SCALE; - offsets.mHeightData[number] = - (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); - - float prevX = prevY = mHeights[number]; - ++number; - - for (int j = 1; j < LAND_SIZE; ++j) { - diff = (mHeights[number] - prevX) / HEIGHT_SCALE; + float prevY = mHeights[0]; + int number = 0; // avoid multiplication + for (int i = 0; i < LAND_SIZE; ++i) { + float diff = (mHeights[number] - prevY) / HEIGHT_SCALE; offsets.mHeightData[number] = (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); - prevX = mHeights[number]; + float prevX = prevY = mHeights[number]; ++number; - } - } - esm.writeHNT("VHGT", offsets, sizeof(VHGT)); - } - if (mDataTypes & Land::DATA_WNAM) { - esm.writeHNT("WNAM", mWnam, 81); - } - if (mDataTypes & Land::DATA_VCLR) { - esm.writeHNT("VCLR", mColours, 3*LAND_NUM_VERTS); - } - if (mDataTypes & Land::DATA_VTEX) { - static uint16_t vtex[LAND_NUM_TEXTURES]; - transposeTextureData(mTextures, vtex); - esm.writeHNT("VTEX", vtex, sizeof(vtex)); - } -} -void Land::LandData::transposeTextureData(uint16_t *in, uint16_t *out) -{ - int readPos = 0; //bit ugly, but it works - for ( int y1 = 0; y1 < 4; y1++ ) - for ( int x1 = 0; x1 < 4; x1++ ) - for ( int y2 = 0; y2 < 4; y2++) - for ( int x2 = 0; x2 < 4; x2++ ) - out[(y1*4+y2)*16+(x1*4+x2)] = in[readPos++]; -} + for (int j = 1; j < LAND_SIZE; ++j) { + diff = (mHeights[number] - prevX) / HEIGHT_SCALE; + offsets.mHeightData[number] = + (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); -Land::Land() - : mFlags(0) - , mX(0) - , mY(0) - , mPlugin(0) - , mEsm(NULL) - , mDataTypes(0) - , mDataLoaded(false) - , mLandData(NULL) -{ -} - -Land::~Land() -{ - delete mLandData; -} - -void Land::load(ESMReader &esm) -{ - mEsm = &esm; - mPlugin = mEsm->getIndex(); - - // Get the grid location - esm.getSubNameIs("INTV"); - esm.getSubHeaderIs(8); - esm.getT(mX); - esm.getT(mY); - - esm.getHNT(mFlags, "DATA"); - - // Store the file position - mContext = esm.getContext(); - - // Skip these here. Load the actual data when the cell is loaded. - if (esm.isNextSub("VNML")) - { - esm.skipHSubSize(12675); - mDataTypes |= DATA_VNML; - } - if (esm.isNextSub("VHGT")) - { - esm.skipHSubSize(4232); - mDataTypes |= DATA_VHGT; - } - if (esm.isNextSub("WNAM")) - { - esm.skipHSubSize(81); - mDataTypes |= DATA_WNAM; - } - if (esm.isNextSub("VCLR")) - { - esm.skipHSubSize(12675); - mDataTypes |= DATA_VCLR; - } - if (esm.isNextSub("VTEX")) - { - esm.skipHSubSize(512); - mDataTypes |= DATA_VTEX; - } - - mDataLoaded = 0; - mLandData = NULL; -} - -void Land::save(ESMWriter &esm) const -{ - esm.startSubRecord("INTV"); - esm.writeT(mX); - esm.writeT(mY); - esm.endRecord("INTV"); - - esm.writeHNT("DATA", mFlags); -} - -void Land::loadData(int flags) -{ - // Try to load only available data - flags = flags & mDataTypes; - // Return if all required data is loaded - if ((mDataLoaded & flags) == flags) { - return; - } - // Create storage if nothing is loaded - if (mLandData == NULL) { - mLandData = new LandData; - mLandData->mDataTypes = mDataTypes; - } - mEsm->restoreContext(mContext); - - if (mEsm->isNextSub("VNML")) { - condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); - } - - if (mEsm->isNextSub("VHGT")) { - static VHGT vhgt; - if (condLoad(flags, DATA_VHGT, &vhgt, sizeof(vhgt))) { - float rowOffset = vhgt.mHeightOffset; - for (int y = 0; y < LAND_SIZE; y++) { - rowOffset += vhgt.mHeightData[y * LAND_SIZE]; - - mLandData->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE; - - float colOffset = rowOffset; - for (int x = 1; x < LAND_SIZE; x++) { - colOffset += vhgt.mHeightData[y * LAND_SIZE + x]; - mLandData->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE; + prevX = mHeights[number]; + ++number; } } - mLandData->mUnk1 = vhgt.mUnk1; - mLandData->mUnk2 = vhgt.mUnk2; + esm.writeHNT("VHGT", offsets, sizeof(VHGT)); + } + if (mDataTypes & Land::DATA_WNAM) { + esm.writeHNT("WNAM", mWnam, 81); + } + if (mDataTypes & Land::DATA_VCLR) { + esm.writeHNT("VCLR", mColours, 3*LAND_NUM_VERTS); + } + if (mDataTypes & Land::DATA_VTEX) { + static uint16_t vtex[LAND_NUM_TEXTURES]; + transposeTextureData(mTextures, vtex); + esm.writeHNT("VTEX", vtex, sizeof(vtex)); } } - if (mEsm->isNextSub("WNAM")) { - condLoad(flags, DATA_WNAM, mLandData->mWnam, 81); + Land::Land() + : mFlags(0) + , mX(0) + , mY(0) + , mPlugin(0) + , mEsm(NULL) + , mDataTypes(0) + , mDataLoaded(false) + , mLandData(NULL) + { } - if (mEsm->isNextSub("VCLR")) - condLoad(flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); - if (mEsm->isNextSub("VTEX")) { - static uint16_t vtex[LAND_NUM_TEXTURES]; - if (condLoad(flags, DATA_VTEX, vtex, sizeof(vtex))) { - LandData::transposeTextureData(vtex, mLandData->mTextures); - } - } -} -void Land::unloadData() -{ - if (mDataLoaded) + void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out) + { + int readPos = 0; //bit ugly, but it works + for ( int y1 = 0; y1 < 4; y1++ ) + for ( int x1 = 0; x1 < 4; x1++ ) + for ( int y2 = 0; y2 < 4; y2++) + for ( int x2 = 0; x2 < 4; x2++ ) + out[(y1*4+y2)*16+(x1*4+x2)] = in[readPos++]; + } + + Land::~Land() { delete mLandData; - mLandData = NULL; + } + + void Land::load(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; + + mEsm = &esm; + mPlugin = mEsm->getIndex(); + + bool hasLocation = false; + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'I','N','T','V'>::value: + esm.getSubHeaderIs(8); + esm.getT(mX); + esm.getT(mY); + hasLocation = true; + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mFlags); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } + } + + if (!hasLocation) + esm.fail("Missing INTV subrecord"); + + mContext = esm.getContext(); + + // Skip the land data here. Load it when the cell is loaded. + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'V','N','M','L'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VNML; + break; + case ESM::FourCC<'V','H','G','T'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VHGT; + break; + case ESM::FourCC<'W','N','A','M'>::value: + esm.skipHSub(); + mDataTypes |= DATA_WNAM; + break; + case ESM::FourCC<'V','C','L','R'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VCLR; + break; + case ESM::FourCC<'V','T','E','X'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VTEX; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + mDataLoaded = 0; + mLandData = NULL; + } + + void Land::save(ESMWriter &esm, bool isDeleted) const + { + esm.startSubRecord("INTV"); + esm.writeT(mX); + esm.writeT(mY); + esm.endRecord("INTV"); + + esm.writeHNT("DATA", mFlags); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + if (mLandData) + { + mLandData->save(esm); + } + } + + void Land::loadData(int flags) const + { + // Try to load only available data + flags = flags & mDataTypes; + // Return if all required data is loaded + if ((mDataLoaded & flags) == flags) { + return; + } + // Create storage if nothing is loaded + if (mLandData == NULL) { + mLandData = new LandData; + mLandData->mDataTypes = mDataTypes; + } + mEsm->restoreContext(mContext); + + if (mEsm->isNextSub("VNML")) { + condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); + } + + if (mEsm->isNextSub("VHGT")) { + static VHGT vhgt; + if (condLoad(flags, DATA_VHGT, &vhgt, sizeof(vhgt))) { + float rowOffset = vhgt.mHeightOffset; + for (int y = 0; y < LAND_SIZE; y++) { + rowOffset += vhgt.mHeightData[y * LAND_SIZE]; + + mLandData->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE; + + float colOffset = rowOffset; + for (int x = 1; x < LAND_SIZE; x++) { + colOffset += vhgt.mHeightData[y * LAND_SIZE + x]; + mLandData->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE; + } + } + mLandData->mUnk1 = vhgt.mUnk1; + mLandData->mUnk2 = vhgt.mUnk2; + } + } + + if (mEsm->isNextSub("WNAM")) { + condLoad(flags, DATA_WNAM, mLandData->mWnam, 81); + } + if (mEsm->isNextSub("VCLR")) + condLoad(flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); + if (mEsm->isNextSub("VTEX")) { + static uint16_t vtex[LAND_NUM_TEXTURES]; + if (condLoad(flags, DATA_VTEX, vtex, sizeof(vtex))) { + LandData::transposeTextureData(vtex, mLandData->mTextures); + } + } + } + + void Land::unloadData() + { + if (mDataLoaded) + { + delete mLandData; + mLandData = NULL; + mDataLoaded = 0; + } + } + + bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const + { + if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) { + mEsm->getHExact(ptr, size); + mDataLoaded |= dataFlag; + return true; + } + mEsm->skipHSubSize(size); + return false; + } + + bool Land::isDataLoaded(int flags) const + { + return (mDataLoaded & flags) == (flags & mDataTypes); + } + + Land::Land (const Land& land) + : mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin), + mEsm (land.mEsm), mContext (land.mContext), mDataTypes (land.mDataTypes), + mDataLoaded (land.mDataLoaded), + mLandData (land.mLandData ? new LandData (*land.mLandData) : 0) + {} + + Land& Land::operator= (Land land) + { + swap (land); + return *this; + } + + void Land::swap (Land& land) + { + std::swap (mFlags, land.mFlags); + std::swap (mX, land.mX); + std::swap (mY, land.mY); + std::swap (mPlugin, land.mPlugin); + std::swap (mEsm, land.mEsm); + std::swap (mContext, land.mContext); + std::swap (mDataTypes, land.mDataTypes); + std::swap (mDataLoaded, land.mDataLoaded); + std::swap (mLandData, land.mLandData); + } + + const Land::LandData *Land::getLandData (int flags) const + { + if (!(flags & mDataTypes)) + return 0; + + loadData (flags); + return mLandData; + } + + const Land::LandData *Land::getLandData() const + { + return mLandData; + } + + Land::LandData *Land::getLandData() + { + return mLandData; + } + + void Land::add (int flags) + { + if (!mLandData) + mLandData = new LandData; + + mDataTypes |= flags; + mDataLoaded |= flags; + } + + void Land::remove (int flags) + { + mDataTypes &= ~flags; + mDataLoaded &= ~flags; + + if (!mDataLoaded) + { + delete mLandData; + mLandData = 0; + } } } - -bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) -{ - if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) { - mEsm->getHExact(ptr, size); - mDataLoaded |= dataFlag; - return true; - } - mEsm->skipHSubSize(size); - return false; -} - -bool Land::isDataLoaded(int flags) const -{ - return (mDataLoaded & flags) == (flags & mDataTypes); -} - -} diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 7dc5e8adb3..65ac88cda3 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -35,7 +35,6 @@ struct Land ESM_Context mContext; int mDataTypes; - int mDataLoaded; enum { @@ -77,35 +76,45 @@ struct Land struct LandData { + // Initial reference height for the first vertex, only needed for filling mHeights float mHeightOffset; + // Height in world space for each vertex float mHeights[LAND_NUM_VERTS]; + + // 24-bit normals, these aren't always correct though. Edge and corner normals may be garbage. VNML mNormals[LAND_NUM_VERTS * 3]; + + // 2D array of texture indices. An index can be used to look up an ESM::LandTexture, + // but to do so you must subtract 1 from the index first! + // An index of 0 indicates the default texture. uint16_t mTextures[LAND_NUM_TEXTURES]; - char mColours[3 * LAND_NUM_VERTS]; + // 24-bit RGB color for each vertex + unsigned char mColours[3 * LAND_NUM_VERTS]; + + // DataTypes available in this LandData, accessing data that is not available is an undefined operation int mDataTypes; // low-LOD heightmap (used for rendering the global map) signed char mWnam[81]; + // ??? short mUnk1; uint8_t mUnk2; - void save(ESMWriter &esm); - static void transposeTextureData(uint16_t *in, uint16_t *out); + void save(ESMWriter &esm) const; + static void transposeTextureData(const uint16_t *in, uint16_t *out); }; - LandData *mLandData; - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank() {} /** * Actually loads data */ - void loadData(int flags); + void loadData(int flags) const; /** * Frees memory allocated for land data @@ -116,14 +125,41 @@ struct Land /// @note We only check data types that *can* be loaded (present in mDataTypes) bool isDataLoaded(int flags) const; + Land (const Land& land); + + Land& operator= (Land land); + + void swap (Land& land); + + /// Return land data with at least the data types specified in \a flags loaded (if they + /// are available). Will return a 0-pointer if there is no data for any of the + /// specified types. + const LandData *getLandData (int flags) const; + + /// Return land data without loading first anything. Can return a 0-pointer. + const LandData *getLandData() const; + + /// Return land data without loading first anything. Can return a 0-pointer. + LandData *getLandData(); + + /// \attention Must not be called on objects that aren't fully loaded. + /// + /// \note Added data fields will be uninitialised + void add (int flags); + + /// \attention Must not be called on objects that aren't fully loaded. + void remove (int flags); + private: - Land(const Land& land); - Land& operator=(const Land& land); /// Loads data and marks it as loaded /// \return true if data is actually loaded from file, false otherwise /// including the case when data is already loaded - bool condLoad(int flags, int dataFlag, void *ptr, unsigned int size); + bool condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const; + + mutable int mDataLoaded; + + mutable LandData *mLandData; }; } diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp index ca5c5d74d8..8c0d503244 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm/loadlevlist.cpp @@ -6,42 +6,84 @@ namespace ESM { - - void LevelledListBase::load(ESMReader &esm) + void LevelledListBase::load(ESMReader &esm, bool &isDeleted) { - esm.getHNT(mFlags, "DATA"); - esm.getHNT(mChanceNone, "NNAM"); + isDeleted = false; - if (esm.isNextSub("INDX")) + bool hasName = false; + bool hasList = false; + while (esm.hasMoreSubs()) { - int len; - esm.getHT(len); - mList.resize(len); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mFlags); + break; + case ESM::FourCC<'N','N','A','M'>::value: + esm.getHT(mChanceNone); + break; + case ESM::FourCC<'I','N','D','X'>::value: + { + int length = 0; + esm.getHT(length); + mList.resize(length); + + // If this levelled list was already loaded by a previous content file, + // we overwrite the list. Merging lists should probably be left to external tools, + // with the limited amount of information there is in the records, all merging methods + // will be flawed in some way. For a proper fix the ESM format would have to be changed + // to actually track list changes instead of including the whole list for every file + // that does something with that list. + for (size_t i = 0; i < mList.size(); i++) + { + LevelItem &li = mList[i]; + li.mId = esm.getHNString(mRecName); + esm.getHNT(li.mLevel, "INTV"); + } + + hasList = true; + break; + } + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + { + if (!hasList) + { + // Original engine ignores rest of the record, even if there are items following + mList.clear(); + esm.skipRecord(); + } + else + { + esm.fail("Unknown subrecord"); + } + break; + } + } } - else + + if (!hasName) + esm.fail("Missing NAME subrecord"); + } + + void LevelledListBase::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) { - // Original engine ignores rest of the record, even if there are items following - mList.clear(); - esm.skipRecord(); + esm.writeHNCString("DELE", ""); return; } - // If this levelled list was already loaded by a previous content file, - // we overwrite the list. Merging lists should probably be left to external tools, - // with the limited amount of information there is in the records, all merging methods - // will be flawed in some way. For a proper fix the ESM format would have to be changed - // to actually track list changes instead of including the whole list for every file - // that does something with that list. - - for (size_t i = 0; i < mList.size(); i++) - { - LevelItem &li = mList[i]; - li.mId = esm.getHNString(mRecName); - esm.getHNT(li.mLevel, "INTV"); - } - } - void LevelledListBase::save(ESMWriter &esm) const - { esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); esm.writeHNT("INDX", mList.size()); diff --git a/components/esm/loadlevlist.hpp b/components/esm/loadlevlist.hpp index dc6fcda5ec..ed4131c165 100644 --- a/components/esm/loadlevlist.hpp +++ b/components/esm/loadlevlist.hpp @@ -36,8 +36,8 @@ struct LevelledListBase std::vector mList; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadligh.cpp b/components/esm/loadligh.cpp index 26d70d964a..20c700b238 100644 --- a/components/esm/loadligh.cpp +++ b/components/esm/loadligh.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Light::sRecordId = REC_LIGH; - void Light::load(ESMReader &esm) + void Light::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -36,15 +42,31 @@ namespace ESM case ESM::FourCC<'S','N','A','M'>::value: mSound = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing LHDT subrecord"); } - void Light::save(ESMWriter &esm) const + void Light::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("ITEX", mIcon); diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp index ed8c36665c..8509c64b6d 100644 --- a/components/esm/loadligh.hpp +++ b/components/esm/loadligh.hpp @@ -47,8 +47,8 @@ struct Light std::string mSound, mScript, mModel, mIcon, mName, mId; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index 2747a6f787..fc6af86990 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Lockpick::sRecordId = REC_LOCK; - void Lockpick::load(ESMReader &esm) + void Lockpick::load(ESMReader &esm, bool &isDeleted) { - bool hasData = true; + isDeleted = false; + + bool hasName = false; + bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -33,16 +39,32 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing LKDT subrecord"); } - void Lockpick::save(ESMWriter &esm) const + void Lockpick::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadlock.hpp b/components/esm/loadlock.hpp index 0d678cd64c..9db41af978 100644 --- a/components/esm/loadlock.hpp +++ b/components/esm/loadlock.hpp @@ -27,8 +27,8 @@ struct Lockpick Data mData; std::string mId, mName, mModel, mIcon, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index c3e2d50ff5..a42ae7c5bf 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -8,21 +8,58 @@ namespace ESM { unsigned int LandTexture::sRecordId = REC_LTEX; -void LandTexture::load(ESMReader &esm) -{ - esm.getHNT(mIndex, "INTV"); - mTexture = esm.getHNString("DATA"); -} -void LandTexture::save(ESMWriter &esm) const -{ - esm.writeHNT("INTV", mIndex); - esm.writeHNCString("DATA", mTexture); -} + void LandTexture::load(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; -void LandTexture::blank() -{ - mTexture.clear(); - mIndex = -1; -} + bool hasName = false; + bool hasIndex = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'I','N','T','V'>::value: + esm.getHT(mIndex); + hasIndex = true; + break; + case ESM::FourCC<'D','A','T','A'>::value: + mTexture = esm.getHString(); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasIndex) + esm.fail("Missing INTV subrecord"); + } + void LandTexture::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + esm.writeHNT("INTV", mIndex); + esm.writeHNCString("DATA", mTexture); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + } + } + + void LandTexture::blank() + { + mTexture.clear(); + mIndex = -1; + } } diff --git a/components/esm/loadltex.hpp b/components/esm/loadltex.hpp index 50a7881054..2cb5abf0c8 100644 --- a/components/esm/loadltex.hpp +++ b/components/esm/loadltex.hpp @@ -34,11 +34,11 @@ struct LandTexture std::string mId, mTexture; int mIndex; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; + void blank(); ///< Set record to default state (does not touch the ID). - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; }; } #endif diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 6f859ab3cd..eef58aa2f1 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -189,8 +189,10 @@ namespace ESM { unsigned int MagicEffect::sRecordId = REC_MGEF; -void MagicEffect::load(ESMReader &esm) +void MagicEffect::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; // MagicEffect record can't be deleted now (may be changed in the future) + esm.getHNT(mIndex, "INDX"); mId = indexToId (mIndex); @@ -209,8 +211,7 @@ void MagicEffect::load(ESMReader &esm) while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); @@ -250,7 +251,7 @@ void MagicEffect::load(ESMReader &esm) } } } -void MagicEffect::save(ESMWriter &esm) const +void MagicEffect::save(ESMWriter &esm, bool /*isDeleted*/) const { esm.writeHNT("INDX", mIndex); diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index 32b8a85a6d..a52ed797f9 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -31,9 +31,9 @@ struct MagicEffect CastTouch = 0x80, // Allows range - cast on touch. CastTarget = 0x100, // Allows range - cast on target. UncappedDamage = 0x1000, // Negates multiple cap behaviours. Allows an effect to reduce an attribute below zero; removes the normal minimum effect duration of 1 second. - NonRecastable = 0x4000, // Does not land if parent spell is already affecting target. Shows "you cannot re-cast" message for self target. + NonRecastable = 0x4000, // Does not land if parent spell is already affecting target. Shows "you cannot re-cast" message for self target. Unreflectable = 0x10000, // Cannot be reflected, the effect always lands normally. - CasterLinked = 0x20000, // Must quench if caster is dead, or not an NPC/creature. Not allowed in containter/door trap spells. + CasterLinked = 0x20000, // Must quench if caster is dead, or not an NPC/creature. Not allowed in containter/door trap spells. // Originally modifiable flags AllowSpellmaking = 0x200, // Can be used for spellmaking @@ -96,8 +96,8 @@ struct MagicEffect // sMagicCreature04ID/05ID. int mIndex; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; /// Set record to default state (does not touch the ID/index). void blank(); diff --git a/components/esm/loadmisc.cpp b/components/esm/loadmisc.cpp index 81c094f2bc..199b4e3b2c 100644 --- a/components/esm/loadmisc.cpp +++ b/components/esm/loadmisc.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Miscellaneous::sRecordId = REC_MISC; - void Miscellaneous::load(ESMReader &esm) + void Miscellaneous::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -33,14 +39,32 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing MCDT subrecord"); } - void Miscellaneous::save(ESMWriter &esm) const + void Miscellaneous::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("MCDT", mData, 12); diff --git a/components/esm/loadmisc.hpp b/components/esm/loadmisc.hpp index 6e0b4e01b8..e7a3239042 100644 --- a/components/esm/loadmisc.hpp +++ b/components/esm/loadmisc.hpp @@ -32,8 +32,8 @@ struct Miscellaneous std::string mId, mName, mModel, mIcon, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 44d2987853..e524e6a7cc 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -8,24 +8,30 @@ namespace ESM { unsigned int NPC::sRecordId = REC_NPC_; - void NPC::load(ESMReader &esm) + void NPC::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mPersistent = (esm.getRecordFlags() & 0x0400) != 0; mSpells.mList.clear(); mInventory.mList.clear(); mTransport.mList.clear(); mAiPackage.mList.clear(); + mHasAI = false; + bool hasName = false; bool hasNpdt = false; bool hasFlags = false; - mHasAI = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -92,17 +98,33 @@ namespace ESM case AI_CNDT: mAiPackage.add(esm); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasNpdt) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasNpdt && !isDeleted) esm.fail("Missing NPDT subrecord"); - if (!hasFlags) + if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } - void NPC::save(ESMWriter &esm) const + void NPC::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNOCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNCString("RNAM", mRace); @@ -121,7 +143,12 @@ namespace ESM mInventory.save(esm); mSpells.save(esm); - if (mHasAI) { + if (mAiData.mHello != 0 + || mAiData.mFight != 0 + || mAiData.mFlee != 0 + || mAiData.mAlarm != 0 + || mAiData.mServices != 0) + { esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); } diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index 9bda9560e1..5b89f40554 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -130,8 +130,8 @@ struct NPC // body parts std::string mHair, mHead; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; bool isMale() const; diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index fc0974c9d8..69ee60eeb8 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -32,98 +32,130 @@ namespace ESM { } -void Pathgrid::load(ESMReader &esm) -{ - esm.getHNT(mData, "DATA", 12); - mCell = esm.getHNString("NAME"); - - mPoints.clear(); - mEdges.clear(); - - // keep track of total connections so we can reserve edge vector size - int edgeCount = 0; - - if (esm.isNextSub("PGRP")) + void Pathgrid::load(ESMReader &esm, bool &isDeleted) { - esm.getSubHeader(); - int size = esm.getSubSize(); - // Check that the sizes match up. Size = 16 * s2 (path points) - if (size != static_cast (sizeof(Point) * mData.mS2)) - esm.fail("Path point subrecord size mismatch"); - else - { - int pointCount = mData.mS2; - mPoints.reserve(pointCount); - for (int i = 0; i < pointCount; ++i) - { - Point p; - esm.getExact(&p, sizeof(Point)); - mPoints.push_back(p); - edgeCount += p.mConnectionNum; - } - } - } + isDeleted = false; - if (esm.isNextSub("PGRC")) - { - esm.getSubHeader(); - int size = esm.getSubSize(); - if (size % sizeof(int) != 0) - esm.fail("PGRC size not a multiple of 4"); - else - { - int rawConnNum = size / sizeof(int); - std::vector rawConnections; - rawConnections.reserve(rawConnNum); - for (int i = 0; i < rawConnNum; ++i) - { - int currentValue; - esm.getT(currentValue); - rawConnections.push_back(currentValue); - } + mPoints.clear(); + mEdges.clear(); - std::vector::const_iterator rawIt = rawConnections.begin(); - int pointIndex = 0; - mEdges.reserve(edgeCount); - for(PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it, ++pointIndex) + // keep track of total connections so we can reserve edge vector size + int edgeCount = 0; + + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) { - unsigned char connectionNum = (*it).mConnectionNum; - for (int i = 0; i < connectionNum; ++i) { - Edge edge; - edge.mV0 = pointIndex; - edge.mV1 = *rawIt; - ++rawIt; - mEdges.push_back(edge); + case ESM::SREC_NAME: + mCell = esm.getHString(); + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mData, 12); + hasData = true; + break; + case ESM::FourCC<'P','G','R','P'>::value: + { + esm.getSubHeader(); + int size = esm.getSubSize(); + // Check that the sizes match up. Size = 16 * s2 (path points) + if (size != static_cast (sizeof(Point) * mData.mS2)) + esm.fail("Path point subrecord size mismatch"); + else + { + int pointCount = mData.mS2; + mPoints.reserve(pointCount); + for (int i = 0; i < pointCount; ++i) + { + Point p; + esm.getExact(&p, sizeof(Point)); + mPoints.push_back(p); + edgeCount += p.mConnectionNum; + } + } + break; } + case ESM::FourCC<'P','G','R','C'>::value: + { + esm.getSubHeader(); + int size = esm.getSubSize(); + if (size % sizeof(int) != 0) + esm.fail("PGRC size not a multiple of 4"); + else + { + int rawConnNum = size / sizeof(int); + std::vector rawConnections; + rawConnections.reserve(rawConnNum); + for (int i = 0; i < rawConnNum; ++i) + { + int currentValue; + esm.getT(currentValue); + rawConnections.push_back(currentValue); + } + + std::vector::const_iterator rawIt = rawConnections.begin(); + int pointIndex = 0; + mEdges.reserve(edgeCount); + for(PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it, ++pointIndex) + { + unsigned char connectionNum = (*it).mConnectionNum; + for (int i = 0; i < connectionNum; ++i) { + Edge edge; + edge.mV0 = pointIndex; + edge.mV1 = *rawIt; + ++rawIt; + mEdges.push_back(edge); + } + } + } + break; + } + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; } } - } -} -void Pathgrid::save(ESMWriter &esm) const -{ - esm.writeHNT("DATA", mData, 12); - esm.writeHNCString("NAME", mCell); - if (!mPoints.empty()) - { - esm.startSubRecord("PGRP"); - for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it) - { - esm.writeT(*it); - } - esm.endRecord("PGRP"); + if (!hasData) + esm.fail("Missing DATA subrecord"); } - if (!mEdges.empty()) + void Pathgrid::save(ESMWriter &esm, bool isDeleted) const { - esm.startSubRecord("PGRC"); - for (std::vector::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) + esm.writeHNCString("NAME", mCell); + esm.writeHNT("DATA", mData, 12); + + if (isDeleted) { - esm.writeT(it->mV1); + esm.writeHNCString("DELE", ""); + return; + } + + if (!mPoints.empty()) + { + esm.startSubRecord("PGRP"); + for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it) + { + esm.writeT(*it); + } + esm.endRecord("PGRP"); + } + + if (!mEdges.empty()) + { + esm.startSubRecord("PGRC"); + for (std::vector::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) + { + esm.writeT(it->mV1); + } + esm.endRecord("PGRC"); } - esm.endRecord("PGRC"); } -} void Pathgrid::blank() { diff --git a/components/esm/loadpgrd.hpp b/components/esm/loadpgrd.hpp index f33ccbedf9..d1003eb865 100644 --- a/components/esm/loadpgrd.hpp +++ b/components/esm/loadpgrd.hpp @@ -53,8 +53,8 @@ struct Pathgrid typedef std::vector EdgeList; EdgeList mEdges; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index c5f80c5844..baf466490b 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Probe::sRecordId = REC_PROB; - void Probe::load(ESMReader &esm) + void Probe::load(ESMReader &esm, bool &isDeleted) { - bool hasData = true; + isDeleted = false; + + bool hasName = false; + bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -33,16 +39,32 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing PBDT subrecord"); } - void Probe::save(ESMWriter &esm) const + void Probe::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadprob.hpp b/components/esm/loadprob.hpp index c737757aa3..da203b456b 100644 --- a/components/esm/loadprob.hpp +++ b/components/esm/loadprob.hpp @@ -27,8 +27,8 @@ struct Probe Data mData; std::string mId, mName, mModel, mIcon, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index e88454d4c1..a371751446 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -18,44 +18,65 @@ namespace ESM return static_cast(male ? mMale : mFemale); } -void Race::load(ESMReader &esm) -{ - mPowers.mList.clear(); - - bool hasData = false; - while (esm.hasMoreSubs()) + void Race::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + mPowers.mList.clear(); + + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'R','A','D','T'>::value: - esm.getHT(mData, 140); - hasData = true; - break; - case ESM::FourCC<'D','E','S','C'>::value: - mDescription = esm.getHString(); - break; - case ESM::FourCC<'N','P','C','S'>::value: - mPowers.add(esm); - break; - default: - esm.fail("Unknown subrecord"); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','A','D','T'>::value: + esm.getHT(mData, 140); + hasData = true; + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::FourCC<'N','P','C','S'>::value: + mPowers.add(esm); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing RADT subrecord"); + } + void Race::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNOCString("FNAM", mName); + esm.writeHNT("RADT", mData, 140); + mPowers.save(esm); + esm.writeHNOString("DESC", mDescription); } - if (!hasData) - esm.fail("Missing RADT subrecord"); -} -void Race::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mName); - esm.writeHNT("RADT", mData, 140); - mPowers.save(esm); - esm.writeHNOString("DESC", mDescription); -} void Race::blank() { diff --git a/components/esm/loadrace.hpp b/components/esm/loadrace.hpp index 553d2e68b1..bf0573075c 100644 --- a/components/esm/loadrace.hpp +++ b/components/esm/loadrace.hpp @@ -68,8 +68,8 @@ struct Race std::string mId, mName, mDescription; SpellList mPowers; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index 1b08b72172..e5382f50b6 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -8,62 +8,102 @@ namespace ESM { unsigned int Region::sRecordId = REC_REGN; -void Region::load(ESMReader &esm) -{ - mName = esm.getHNOString("FNAM"); + void Region::load(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; - esm.getSubNameIs("WEAT"); - esm.getSubHeader(); - if (esm.getVer() == VER_12) - { - mData.mA = 0; - mData.mB = 0; - esm.getExact(&mData, sizeof(mData) - 2); - } - else if (esm.getVer() == VER_13) - { - // May include the additional two bytes (but not necessarily) - if (esm.getSubSize() == sizeof(mData)) - esm.getExact(&mData, sizeof(mData)); - else + bool hasName = false; + while (esm.hasMoreSubs()) { - mData.mA = 0; - mData.mB = 0; - esm.getExact(&mData, sizeof(mData)-2); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'W','E','A','T'>::value: + { + esm.getSubHeader(); + if (esm.getVer() == VER_12) + { + mData.mA = 0; + mData.mB = 0; + esm.getExact(&mData, sizeof(mData) - 2); + } + else if (esm.getVer() == VER_13) + { + // May include the additional two bytes (but not necessarily) + if (esm.getSubSize() == sizeof(mData)) + { + esm.getExact(&mData, sizeof(mData)); + } + else + { + mData.mA = 0; + mData.mB = 0; + esm.getExact(&mData, sizeof(mData)-2); + } + } + else + { + esm.fail("Don't know what to do in this version"); + } + break; + } + case ESM::FourCC<'B','N','A','M'>::value: + mSleepList = esm.getHString(); + break; + case ESM::FourCC<'C','N','A','M'>::value: + esm.getHT(mMapColor); + break; + case ESM::FourCC<'S','N','A','M'>::value: + SoundRef sr; + esm.getHT(sr, 33); + mSoundList.push_back(sr); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + } + + void Region::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNOCString("FNAM", mName); + + if (esm.getVersion() == VER_12) + esm.writeHNT("WEAT", mData, sizeof(mData) - 2); + else + esm.writeHNT("WEAT", mData); + + esm.writeHNOCString("BNAM", mSleepList); + + esm.writeHNT("CNAM", mMapColor); + for (std::vector::const_iterator it = mSoundList.begin(); it != mSoundList.end(); ++it) + { + esm.writeHNT("SNAM", *it); } } - else - esm.fail("Don't know what to do in this version"); - - mSleepList = esm.getHNOString("BNAM"); - - esm.getHNT(mMapColor, "CNAM"); - - mSoundList.clear(); - while (esm.hasMoreSubs()) - { - SoundRef sr; - esm.getHNT(sr, "SNAM", 33); - mSoundList.push_back(sr); - } -} -void Region::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mName); - - if (esm.getVersion() == VER_12) - esm.writeHNT("WEAT", mData, sizeof(mData) - 2); - else - esm.writeHNT("WEAT", mData); - - esm.writeHNOCString("BNAM", mSleepList); - - esm.writeHNT("CNAM", mMapColor); - for (std::vector::const_iterator it = mSoundList.begin(); it != mSoundList.end(); ++it) - { - esm.writeHNT("SNAM", *it); - } -} void Region::blank() { diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index 1e241fffbe..a946e488e2 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -45,14 +45,14 @@ struct Region WEATstruct mData; int mMapColor; // RGBA - // sleepList refers to a eveled list of creatures you can meet if + // sleepList refers to a leveled list of creatures you can meet if // you sleep outside in this region. std::string mId, mName, mSleepList; std::vector mSoundList; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index f90f9e39dc..e4af3d9378 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -8,48 +8,70 @@ namespace ESM { unsigned int Repair::sRecordId = REC_REPA; -void Repair::load(ESMReader &esm) -{ - bool hasData = true; - while (esm.hasMoreSubs()) + void Repair::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'M','O','D','L'>::value: - mModel = esm.getHString(); - break; - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'R','I','D','T'>::value: - esm.getHT(mData, 16); - hasData = true; - break; - case ESM::FourCC<'S','C','R','I'>::value: - mScript = esm.getHString(); - break; - case ESM::FourCC<'I','T','E','X'>::value: - mIcon = esm.getHString(); - break; - default: - esm.fail("Unknown subrecord"); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','I','D','T'>::value: + esm.getHT(mData, 16); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing RIDT subrecord"); } - if (!hasData) - esm.fail("Missing RIDT subrecord"); -} -void Repair::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); + void Repair::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); - esm.writeHNT("RIDT", mData, 16); - esm.writeHNOString("SCRI", mScript); - esm.writeHNOCString("ITEX", mIcon); -} + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); + + esm.writeHNT("RIDT", mData, 16); + esm.writeHNOString("SCRI", mScript); + esm.writeHNOCString("ITEX", mIcon); + } void Repair::blank() { diff --git a/components/esm/loadrepa.hpp b/components/esm/loadrepa.hpp index e765bc93a5..2537c53cb6 100644 --- a/components/esm/loadrepa.hpp +++ b/components/esm/loadrepa.hpp @@ -27,8 +27,8 @@ struct Repair Data mData; std::string mId, mName, mModel, mIcon, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 0c2bdd42ff..b46d5b658a 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -4,9 +4,10 @@ #include "esmwriter.hpp" #include "defs.hpp" +#include + namespace ESM { - unsigned int Script::sRecordId = REC_SCPT; void Script::loadSCVR(ESMReader &esm) @@ -56,21 +57,25 @@ namespace ESM } } - void Script::load(ESMReader &esm) + void Script::load(ESMReader &esm, bool &isDeleted) { - SCHD data; - esm.getHNT(data, "SCHD", 52); - mData = data.mData; - mId = data.mName.toString(); + isDeleted = false; mVarNames.clear(); + bool hasHeader = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'S','C','H','D'>::value: + SCHD data; + esm.getHT(data, 52); + mData = data.mData; + mId = data.mName.toString(); + hasHeader = true; + break; case ESM::FourCC<'S','C','V','R'>::value: // list of local variables loadSCVR(esm); @@ -83,13 +88,21 @@ namespace ESM case ESM::FourCC<'S','C','T','X'>::value: mScriptText = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } + + if (!hasHeader) + esm.fail("Missing SCHD subrecord"); } - void Script::save(ESMWriter &esm) const + void Script::save(ESMWriter &esm, bool isDeleted) const { std::string varNameString; if (!mVarNames.empty()) @@ -104,6 +117,12 @@ namespace ESM esm.writeHNT("SCHD", data, 52); + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + if (!mVarNames.empty()) { esm.startSubRecord("SCVR"); diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index 56390f3841..b8a06406dd 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -50,8 +50,8 @@ public: /// Script source code std::string mScriptText; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadskil.cpp b/components/esm/loadskil.cpp index 7883b8a1a9..c520897910 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm/loadskil.cpp @@ -129,15 +129,16 @@ namespace ESM unsigned int Skill::sRecordId = REC_SKIL; - void Skill::load(ESMReader &esm) + void Skill::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; // Skill record can't be deleted now (may be changed in the future) + bool hasIndex = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'I','N','D','X'>::value: esm.getHT(mIndex); @@ -164,7 +165,7 @@ namespace ESM mId = indexToId (mIndex); } - void Skill::save(ESMWriter &esm) const + void Skill::save(ESMWriter &esm, bool /*isDeleted*/) const { esm.writeHNT("INDX", mIndex); esm.writeHNT("SKDT", mData, 24); diff --git a/components/esm/loadskil.hpp b/components/esm/loadskil.hpp index e001842970..5430b422d1 100644 --- a/components/esm/loadskil.hpp +++ b/components/esm/loadskil.hpp @@ -78,8 +78,8 @@ struct Skill static const std::string sIconNames[Length]; static const boost::array sSkillIds; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index 5ee6f5245c..d84fe624d0 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int SoundGenerator::sRecordId = REC_SNDG; - void SoundGenerator::load(ESMReader &esm) + void SoundGenerator::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mType, 4); hasData = true; @@ -27,18 +33,35 @@ namespace ESM case ESM::FourCC<'S','N','A','M'>::value: mSound = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) - esm.fail("Missing DATA"); + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing DATA subrecord"); } - void SoundGenerator::save(ESMWriter &esm) const + void SoundGenerator::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNT("DATA", mType, 4); esm.writeHNOCString("CNAM", mCreature); esm.writeHNOCString("SNAM", mSound); + } void SoundGenerator::blank() diff --git a/components/esm/loadsndg.hpp b/components/esm/loadsndg.hpp index 056958f0a8..70b221e98c 100644 --- a/components/esm/loadsndg.hpp +++ b/components/esm/loadsndg.hpp @@ -36,8 +36,8 @@ struct SoundGenerator std::string mId, mCreature, mSound; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index 690c1b4484..82f169e54f 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Sound::sRecordId = REC_SOUN; - void Sound::load(ESMReader &esm) + void Sound::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'F','N','A','M'>::value: mSound = esm.getHString(); break; @@ -24,16 +30,32 @@ namespace ESM esm.getHT(mData, 3); hasData = true; break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) - esm.fail("Missing DATA"); + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing DATA subrecord"); } - void Sound::save(ESMWriter &esm) const + void Sound::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNOCString("FNAM", mSound); esm.writeHNT("DATA", mData, 3); } diff --git a/components/esm/loadsoun.hpp b/components/esm/loadsoun.hpp index ff2202ca7d..937e22be88 100644 --- a/components/esm/loadsoun.hpp +++ b/components/esm/loadsoun.hpp @@ -23,8 +23,8 @@ struct Sound SOUNstruct mData; std::string mId, mSound; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index 96c048e0a9..728b7bc2a7 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -8,17 +8,23 @@ namespace ESM { unsigned int Spell::sRecordId = REC_SPEL; - void Spell::load(ESMReader &esm) + void Spell::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mEffects.mList.clear(); + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t val = esm.retSubName().val; - - switch (val) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -31,14 +37,32 @@ namespace ESM esm.getHT(s, 24); mEffects.mList.push_back(s); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing SPDT subrecord"); } - void Spell::save(ESMWriter &esm) const + void Spell::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNOCString("FNAM", mName); esm.writeHNT("SPDT", mData, 12); mEffects.save(esm); @@ -51,7 +75,6 @@ namespace ESM mData.mFlags = 0; mName.clear(); - mEffects.mList.clear(); } } diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp index 491da1d179..1763d0991c 100644 --- a/components/esm/loadspel.hpp +++ b/components/esm/loadspel.hpp @@ -45,8 +45,8 @@ struct Spell std::string mId, mName; EffectList mEffects; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp index 7380dd0a7f..ab4c09750d 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm/loadsscr.cpp @@ -8,37 +8,51 @@ namespace ESM { unsigned int StartScript::sRecordId = REC_SSCR; - void StartScript::load(ESMReader &esm) + void StartScript::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + bool hasData = false; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'D','A','T','A'>::value: mData = esm.getHString(); hasData = true; break; - case ESM::FourCC<'N','A','M','E'>::value: - mId = esm.getHString(); - hasName = true; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) - esm.fail("Missing DATA"); + if (!hasName) esm.fail("Missing NAME"); + if (!hasData && !isDeleted) + esm.fail("Missing DATA"); } - void StartScript::save(ESMWriter &esm) const + void StartScript::save(ESMWriter &esm, bool isDeleted) const { - esm.writeHNString("DATA", mData); - esm.writeHNString("NAME", mId); + esm.writeHNCString("NAME", mId); + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + } + else + { + esm.writeHNString("DATA", mData); + } } void StartScript::blank() diff --git a/components/esm/loadsscr.hpp b/components/esm/loadsscr.hpp index dc7ad6a42a..ce2ff49e77 100644 --- a/components/esm/loadsscr.hpp +++ b/components/esm/loadsscr.hpp @@ -27,8 +27,8 @@ struct StartScript std::string mId; // Load a record and add it to the list - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index ed90b04751..62d495ee34 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -8,15 +8,47 @@ namespace ESM { unsigned int Static::sRecordId = REC_STAT; - void Static::load(ESMReader &esm) + void Static::load(ESMReader &esm, bool &isDeleted) { - mPersistent = (esm.getRecordFlags() & 0x0400) != 0; + isDeleted = false; - mModel = esm.getHNString("MODL"); + bool hasName = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } - void Static::save(ESMWriter &esm) const + void Static::save(ESMWriter &esm, bool isDeleted) const { - esm.writeHNCString("MODL", mModel); + esm.writeHNCString("NAME", mId); + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + } + else + { + esm.writeHNCString("MODL", mModel); + } } void Static::blank() diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index 21a9e66e88..930cdb8491 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -28,10 +28,8 @@ struct Static std::string mId, mModel; - bool mPersistent; - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index 9c0c55b8f6..df35a2579d 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -1,4 +1,3 @@ - #include "loadtes3.hpp" #include "esmcommon.hpp" @@ -71,7 +70,13 @@ void ESM::Header::save (ESMWriter &esm) if (mFormat>0) esm.writeHNT ("FORM", mFormat); - esm.writeHNT ("HEDR", mData, 300); + esm.startSubRecord("HEDR"); + esm.writeT(mData.version); + esm.writeT(mData.type); + esm.writeFixedSizeString(mData.author.toString(), 32); + esm.writeFixedSizeString(mData.desc.toString(), 256); + esm.writeT(mData.records); + esm.endRecord("HEDR"); for (std::vector::iterator iter = mMaster.begin(); iter != mMaster.end(); ++iter) diff --git a/components/esm/loadweap.cpp b/components/esm/loadweap.cpp index 981a5815a2..880a26bcb3 100644 --- a/components/esm/loadweap.cpp +++ b/components/esm/loadweap.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Weapon::sRecordId = REC_WEAP; - void Weapon::load(ESMReader &esm) + void Weapon::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -36,15 +42,30 @@ namespace ESM case ESM::FourCC<'E','N','A','M'>::value: mEnchant = esm.getHString(); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing WPDT subrecord"); } - void Weapon::save(ESMWriter &esm) const + void Weapon::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("WPDT", mData, 32); diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp index f66e9f3a68..eddcaee4f1 100644 --- a/components/esm/loadweap.hpp +++ b/components/esm/loadweap.hpp @@ -69,8 +69,8 @@ struct Weapon std::string mId, mName, mModel, mIcon, mEnchant, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/locals.cpp b/components/esm/locals.cpp index f0cfd49f03..bd51be08f6 100644 --- a/components/esm/locals.cpp +++ b/components/esm/locals.cpp @@ -1,4 +1,3 @@ - #include "locals.hpp" #include "esmreader.hpp" diff --git a/components/esm/npcstate.cpp b/components/esm/npcstate.cpp index 724d673265..6c9988d50d 100644 --- a/components/esm/npcstate.cpp +++ b/components/esm/npcstate.cpp @@ -1,4 +1,3 @@ - #include "npcstate.hpp" void ESM::NpcState::load (ESMReader &esm) diff --git a/components/esm/npcstats.cpp b/components/esm/npcstats.cpp index cc1d6b3ddc..10c0b6f169 100644 --- a/components/esm/npcstats.cpp +++ b/components/esm/npcstats.cpp @@ -1,4 +1,3 @@ - #include "npcstats.hpp" #include "esmreader.hpp" @@ -31,18 +30,44 @@ void ESM::NpcStats::load (ESMReader &esm) esm.getHNOT (mDisposition, "DISP"); for (int i=0; i<27; ++i) + mSkills[i].load (esm); + + mWerewolfDeprecatedData = false; + if (esm.peekNextSub("STBA")) { - mSkills[i].mRegular.load (esm); - mSkills[i].mWerewolf.load (esm); + // we have deprecated werewolf skills, stored interleaved + // Load into one big vector, then remove every 2nd value + mWerewolfDeprecatedData = true; + std::vector > skills(mSkills, mSkills + sizeof(mSkills)/sizeof(mSkills[0])); + + for (int i=0; i<27; ++i) + { + ESM::StatState skill; + skill.load(esm); + skills.push_back(skill); + } + + int i=0; + for (std::vector >::iterator it = skills.begin(); it != skills.end(); ++i) + { + if (i%2 == 1) + it = skills.erase(it); + else + ++it; + } + assert(skills.size() == 27); + std::copy(skills.begin(), skills.end(), mSkills); } + // No longer used bool hasWerewolfAttributes = false; esm.getHNOT (hasWerewolfAttributes, "HWAT"); - if (hasWerewolfAttributes) { + ESM::StatState dummy; for (int i=0; i<8; ++i) - mWerewolfAttributes[i].load (esm); + dummy.load(esm); + mWerewolfDeprecatedData = true; } mIsWerewolf = false; @@ -61,7 +86,7 @@ void ESM::NpcStats::load (ESMReader &esm) if (esm.isNextSub("PROF")) esm.skipHSub(); // int profit - // No longer used. Now part of CreatureStats. + // No longer used if (esm.isNextSub("ASTR")) esm.skipHSub(); // attackStrength @@ -112,14 +137,7 @@ void ESM::NpcStats::save (ESMWriter &esm) const esm.writeHNT ("DISP", mDisposition); for (int i=0; i<27; ++i) - { - mSkills[i].mRegular.save (esm); - mSkills[i].mWerewolf.save (esm); - } - - esm.writeHNT ("HWAT", true); - for (int i=0; i<8; ++i) - mWerewolfAttributes[i].save (esm); + mSkills[i].save (esm); if (mIsWerewolf) esm.writeHNT ("WOLF", mIsWerewolf); @@ -151,6 +169,7 @@ void ESM::NpcStats::save (ESMWriter &esm) const void ESM::NpcStats::blank() { + mWerewolfDeprecatedData = false; mIsWerewolf = false; mDisposition = 0; mBounty = 0; diff --git a/components/esm/npcstats.hpp b/components/esm/npcstats.hpp index 0138ab2098..9b27f587c7 100644 --- a/components/esm/npcstats.hpp +++ b/components/esm/npcstats.hpp @@ -16,12 +16,6 @@ namespace ESM struct NpcStats { - struct Skill - { - StatState mRegular; - StatState mWerewolf; - }; - struct Faction { bool mExpelled; @@ -31,12 +25,13 @@ namespace ESM Faction(); }; - StatState mWerewolfAttributes[8]; bool mIsWerewolf; + bool mWerewolfDeprecatedData; + std::map mFactions; // lower case IDs int mDisposition; - Skill mSkills[27]; + StatState mSkills[27]; int mBounty; int mReputation; int mWerewolfKills; diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index 9ef1ccf80d..d736bab66d 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -1,4 +1,3 @@ - #include "objectstate.hpp" #include "esmreader.hpp" @@ -6,7 +5,10 @@ void ESM::ObjectState::load (ESMReader &esm) { - mRef.loadData(esm); + mVersion = esm.getFormat(); + + bool isDeleted; + mRef.loadData(esm, isDeleted); mHasLocals = 0; esm.getHNOT (mHasLocals, "HLOC"); @@ -22,7 +24,8 @@ void ESM::ObjectState::load (ESMReader &esm) esm.getHNOT (mPosition, "POS_", 24); - esm.getHNOT (mLocalRotation, "LROT", 12); + if (esm.isNextSub("LROT")) + esm.skipHSub(); // local rotation, no longer used // obsolete int unused; @@ -50,10 +53,7 @@ void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const esm.writeHNT ("COUN", mCount); if (!inInventory) - { esm.writeHNT ("POS_", mPosition, 24); - esm.writeHNT ("LROT", mLocalRotation, 12); - } if (!mHasCustomState) esm.writeHNT ("HCUS", false); @@ -69,7 +69,6 @@ void ESM::ObjectState::blank() { mPosition.pos[i] = 0; mPosition.rot[i] = 0; - mLocalRotation[i] = 0; } mHasCustomState = true; } diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp index d1077733a5..215cb74baa 100644 --- a/components/esm/objectstate.hpp +++ b/components/esm/objectstate.hpp @@ -24,12 +24,13 @@ namespace ESM unsigned char mEnabled; int mCount; ESM::Position mPosition; - float mLocalRotation[3]; // Is there any class-specific state following the ObjectState bool mHasCustomState; - ObjectState() : mHasCustomState(true) + unsigned int mVersion; + + ObjectState() : mHasCustomState(true), mVersion(0) {} /// @note Does not load the CellRef ID, it should already be loaded before calling this method diff --git a/components/esm/player.cpp b/components/esm/player.cpp index 70c4b79e25..9ec53240a2 100644 --- a/components/esm/player.cpp +++ b/components/esm/player.cpp @@ -1,4 +1,3 @@ - #include "player.hpp" #include "esmreader.hpp" @@ -31,6 +30,14 @@ void ESM::Player::load (ESMReader &esm) esm.getHNOT (mCurrentCrimeId, "CURD"); mPaidCrimeId = -1; esm.getHNOT (mPaidCrimeId, "PAYD"); + + if (esm.hasMoreSubs()) + { + for (int i=0; i mSaveAttributes[ESM::Attribute::Length]; + StatState mSaveSkills[ESM::Skill::Length]; + void load (ESMReader &esm); void save (ESMWriter &esm) const; }; diff --git a/components/esm/projectilestate.cpp b/components/esm/projectilestate.cpp index 85c00025b6..70ae4c5ee6 100644 --- a/components/esm/projectilestate.cpp +++ b/components/esm/projectilestate.cpp @@ -52,6 +52,7 @@ namespace ESM esm.writeHNString ("BOW_", mBowId); esm.writeHNT ("VEL_", mVelocity); + esm.writeHNT ("STR_", mAttackStrength); } void ProjectileState::load(ESMReader &esm) @@ -60,6 +61,9 @@ namespace ESM mBowId = esm.getHNString ("BOW_"); esm.getHNT (mVelocity, "VEL_"); + + mAttackStrength = 1.f; + esm.getHNOT(mAttackStrength, "STR_"); } } diff --git a/components/esm/projectilestate.hpp b/components/esm/projectilestate.hpp index 51cd5d8c40..3471fbfc79 100644 --- a/components/esm/projectilestate.hpp +++ b/components/esm/projectilestate.hpp @@ -3,8 +3,8 @@ #include -#include -#include +#include +#include #include "effectlist.hpp" @@ -45,6 +45,7 @@ namespace ESM { std::string mBowId; Vector3 mVelocity; + float mAttackStrength; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm/queststate.cpp b/components/esm/queststate.cpp index c8cff7adc9..5408cd2ffd 100644 --- a/components/esm/queststate.cpp +++ b/components/esm/queststate.cpp @@ -1,4 +1,3 @@ - #include "queststate.hpp" #include "esmreader.hpp" diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index b5e0810dbc..cf9f68c9a8 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -1,4 +1,3 @@ - #include "savedgame.hpp" #include "esmreader.hpp" @@ -6,6 +5,7 @@ #include "defs.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; +int ESM::SavedGame::sCurrentFormat = 2; void ESM::SavedGame::load (ESMReader &esm) { diff --git a/components/esm/savedgame.hpp b/components/esm/savedgame.hpp index 3e7cae775a..aa0429657b 100644 --- a/components/esm/savedgame.hpp +++ b/components/esm/savedgame.hpp @@ -15,6 +15,8 @@ namespace ESM { static unsigned int sRecordId; + static int sCurrentFormat; + struct TimeStamp { float mGameHour; diff --git a/components/esm/statstate.cpp b/components/esm/statstate.cpp new file mode 100644 index 0000000000..c17bedd817 --- /dev/null +++ b/components/esm/statstate.cpp @@ -0,0 +1,52 @@ +#include "statstate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +namespace ESM +{ + template + StatState::StatState() : mBase(0), mMod(0), mCurrent(0), mDamage(0), mProgress(0) {} + + template + void StatState::load(ESMReader &esm) + { + esm.getHNT(mBase, "STBA"); + + mMod = 0; + esm.getHNOT(mMod, "STMO"); + mCurrent = 0; + esm.getHNOT(mCurrent, "STCU"); + + // mDamage was changed to a float; ensure backwards compatibility + T oldDamage = 0; + esm.getHNOT(oldDamage, "STDA"); + mDamage = static_cast(oldDamage); + + esm.getHNOT(mDamage, "STDF"); + + mProgress = 0; + esm.getHNOT(mProgress, "STPR"); + } + + template + void StatState::save(ESMWriter &esm) const + { + esm.writeHNT("STBA", mBase); + + if (mMod != 0) + esm.writeHNT("STMO", mMod); + + if (mCurrent) + esm.writeHNT("STCU", mCurrent); + + if (mDamage) + esm.writeHNT("STDF", mDamage); + + if (mProgress) + esm.writeHNT("STPR", mProgress); + } +} + +template struct ESM::StatState; +template struct ESM::StatState; diff --git a/components/esm/statstate.hpp b/components/esm/statstate.hpp index f57ba9f30e..47aeb0331d 100644 --- a/components/esm/statstate.hpp +++ b/components/esm/statstate.hpp @@ -1,11 +1,11 @@ #ifndef OPENMW_ESM_STATSTATE_H #define OPENMW_ESM_STATSTATE_H -#include "esmreader.hpp" -#include "esmwriter.hpp" - namespace ESM { + class ESMReader; + class ESMWriter; + // format 0, saved games only template @@ -23,48 +23,6 @@ namespace ESM void load (ESMReader &esm); void save (ESMWriter &esm) const; }; - - template - StatState::StatState() : mBase (0), mMod (0), mCurrent (0), mDamage (0), mProgress (0) {} - - template - void StatState::load (ESMReader &esm) - { - esm.getHNT (mBase, "STBA"); - - mMod = 0; - esm.getHNOT (mMod, "STMO"); - mCurrent = 0; - esm.getHNOT (mCurrent, "STCU"); - - // mDamage was changed to a float; ensure backwards compatibility - T oldDamage = 0; - esm.getHNOT(oldDamage, "STDA"); - mDamage = static_cast(oldDamage); - - esm.getHNOT (mDamage, "STDF"); - - mProgress = 0; - esm.getHNOT (mProgress, "STPR"); - } - - template - void StatState::save (ESMWriter &esm) const - { - esm.writeHNT ("STBA", mBase); - - if (mMod != 0) - esm.writeHNT ("STMO", mMod); - - if (mCurrent) - esm.writeHNT ("STCU", mCurrent); - - if (mDamage) - esm.writeHNT ("STDF", mDamage); - - if (mProgress) - esm.writeHNT ("STPR", mProgress); - } } #endif diff --git a/components/esm/util.hpp b/components/esm/util.hpp index bb7f3cf7cd..a80df2456f 100644 --- a/components/esm/util.hpp +++ b/components/esm/util.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_ESM_UTIL_H #define OPENMW_ESM_UTIL_H -#include -#include +#include +#include namespace ESM { @@ -14,17 +14,18 @@ struct Quaternion float mValues[4]; Quaternion() {} - Quaternion (Ogre::Quaternion q) + + Quaternion(const osg::Quat& q) { - mValues[0] = q.w; - mValues[1] = q.x; - mValues[2] = q.y; - mValues[3] = q.z; + mValues[0] = q.w(); + mValues[1] = q.x(); + mValues[2] = q.y(); + mValues[3] = q.z(); } - operator Ogre::Quaternion () const + operator osg::Quat () const { - return Ogre::Quaternion(mValues[0], mValues[1], mValues[2], mValues[3]); + return osg::Quat(mValues[1], mValues[2], mValues[3], mValues[0]); } }; @@ -33,16 +34,17 @@ struct Vector3 float mValues[3]; Vector3() {} - Vector3 (Ogre::Vector3 v) + + Vector3(const osg::Vec3f& v) { - mValues[0] = v.x; - mValues[1] = v.y; - mValues[2] = v.z; + mValues[0] = v.x(); + mValues[1] = v.y(); + mValues[2] = v.z(); } - operator Ogre::Vector3 () const + operator osg::Vec3f () const { - return Ogre::Vector3(&mValues[0]); + return osg::Vec3f(mValues[0], mValues[1], mValues[2]); } }; diff --git a/components/esm/variantimp.cpp b/components/esm/variantimp.cpp index eeb0bf04f3..aeea5017e1 100644 --- a/components/esm/variantimp.cpp +++ b/components/esm/variantimp.cpp @@ -1,4 +1,3 @@ - #include "variantimp.hpp" #include diff --git a/components/esm/weatherstate.cpp b/components/esm/weatherstate.cpp index 48cf55a600..ff2528e58f 100644 --- a/components/esm/weatherstate.cpp +++ b/components/esm/weatherstate.cpp @@ -1,59 +1,72 @@ -#include "weatherstate.hpp" - -#include "esmreader.hpp" -#include "esmwriter.hpp" - -namespace -{ - const char* hourRecord = "HOUR"; - const char* windSpeedRecord = "WNSP"; - const char* currentWeatherRecord = "CWTH"; - const char* nextWeatherRecord = "NWTH"; - const char* currentRegionRecord = "CREG"; - const char* firstUpdateRecord = "FUPD"; - const char* remainingTransitionTimeRecord = "RTTM"; - const char* timePassedRecord = "TMPS"; -} - -namespace ESM -{ - void WeatherState::load(ESMReader& esm) - { - // store values locally so that a failed load can't leave the state half set - float newHour = 0.0; - esm.getHNT(newHour, hourRecord); - float newWindSpeed = 0.0; - esm.getHNT(newWindSpeed, windSpeedRecord); - std::string newCurrentWeather = esm.getHNString(currentWeatherRecord); - std::string newNextWeather = esm.getHNString(nextWeatherRecord); - std::string newCurrentRegion = esm.getHNString(currentRegionRecord); - bool newFirstUpdate = false; - esm.getHNT(newFirstUpdate, firstUpdateRecord); - float newRemainingTransitionTime = 0.0; - esm.getHNT(newRemainingTransitionTime, remainingTransitionTimeRecord); - double newTimePassed = 0.0; - esm.getHNT(newTimePassed, timePassedRecord); - - // swap values now that it is safe to do so - mHour = newHour; - mWindSpeed = newWindSpeed; - mCurrentWeather.swap(newCurrentWeather); - mNextWeather.swap(newNextWeather); - mCurrentRegion.swap(newCurrentRegion); - mFirstUpdate = newFirstUpdate; - mRemainingTransitionTime = newRemainingTransitionTime; - mTimePassed = newTimePassed; - } - - void WeatherState::save(ESMWriter& esm) const - { - esm.writeHNT(hourRecord, mHour); - esm.writeHNT(windSpeedRecord, mWindSpeed); - esm.writeHNCString(currentWeatherRecord, mCurrentWeather.c_str()); - esm.writeHNCString(nextWeatherRecord, mNextWeather.c_str()); - esm.writeHNCString(currentRegionRecord, mCurrentRegion.c_str()); - esm.writeHNT(firstUpdateRecord, mFirstUpdate); - esm.writeHNT(remainingTransitionTimeRecord, mRemainingTransitionTime); - esm.writeHNT(timePassedRecord, mTimePassed); - } -} +#include "weatherstate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +namespace +{ + const char* currentRegionRecord = "CREG"; + const char* timePassedRecord = "TMPS"; + const char* fastForwardRecord = "FAST"; + const char* weatherUpdateTimeRecord = "WUPD"; + const char* transitionFactorRecord = "TRFC"; + const char* currentWeatherRecord = "CWTH"; + const char* nextWeatherRecord = "NWTH"; + const char* queuedWeatherRecord = "QWTH"; + const char* regionNameRecord = "RGNN"; + const char* regionWeatherRecord = "RGNW"; + const char* regionChanceRecord = "RGNC"; +} + +namespace ESM +{ + void WeatherState::load(ESMReader& esm) + { + mCurrentRegion = esm.getHNString(currentRegionRecord); + esm.getHNT(mTimePassed, timePassedRecord); + esm.getHNT(mFastForward, fastForwardRecord); + esm.getHNT(mWeatherUpdateTime, weatherUpdateTimeRecord); + esm.getHNT(mTransitionFactor, transitionFactorRecord); + esm.getHNT(mCurrentWeather, currentWeatherRecord); + esm.getHNT(mNextWeather, nextWeatherRecord); + esm.getHNT(mQueuedWeather, queuedWeatherRecord); + + while(esm.peekNextSub(regionNameRecord)) + { + std::string regionID = esm.getHNString(regionNameRecord); + RegionWeatherState region; + esm.getHNT(region.mWeather, regionWeatherRecord); + while(esm.peekNextSub(regionChanceRecord)) + { + char chance; + esm.getHNT(chance, regionChanceRecord); + region.mChances.push_back(chance); + } + + mRegions.insert(std::make_pair(regionID, region)); + } + } + + void WeatherState::save(ESMWriter& esm) const + { + esm.writeHNCString(currentRegionRecord, mCurrentRegion.c_str()); + esm.writeHNT(timePassedRecord, mTimePassed); + esm.writeHNT(fastForwardRecord, mFastForward); + esm.writeHNT(weatherUpdateTimeRecord, mWeatherUpdateTime); + esm.writeHNT(transitionFactorRecord, mTransitionFactor); + esm.writeHNT(currentWeatherRecord, mCurrentWeather); + esm.writeHNT(nextWeatherRecord, mNextWeather); + esm.writeHNT(queuedWeatherRecord, mQueuedWeather); + + std::map::const_iterator it = mRegions.begin(); + for(; it != mRegions.end(); ++it) + { + esm.writeHNCString(regionNameRecord, it->first.c_str()); + esm.writeHNT(regionWeatherRecord, it->second.mWeather); + for(size_t i = 0; i < it->second.mChances.size(); ++i) + { + esm.writeHNT(regionChanceRecord, it->second.mChances[i]); + } + } + } +} diff --git a/components/esm/weatherstate.hpp b/components/esm/weatherstate.hpp index d0cd59c160..532a056ac0 100644 --- a/components/esm/weatherstate.hpp +++ b/components/esm/weatherstate.hpp @@ -1,27 +1,36 @@ -#ifndef OPENMW_ESM_WEATHERSTATE_H -#define OPENMW_ESM_WEATHERSTATE_H - -#include - -namespace ESM -{ - class ESMReader; - class ESMWriter; - - struct WeatherState - { - float mHour; - float mWindSpeed; - std::string mCurrentWeather; - std::string mNextWeather; - std::string mCurrentRegion; - bool mFirstUpdate; - float mRemainingTransitionTime; - double mTimePassed; - - void load(ESMReader& esm); - void save(ESMWriter& esm) const; - }; -} - -#endif +#ifndef OPENMW_ESM_WEATHERSTATE_H +#define OPENMW_ESM_WEATHERSTATE_H + +#include +#include +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + struct RegionWeatherState + { + int mWeather; + std::vector mChances; + }; + + struct WeatherState + { + std::string mCurrentRegion; + float mTimePassed; + bool mFastForward; + float mWeatherUpdateTime; + float mTransitionFactor; + int mCurrentWeather; + int mNextWeather; + int mQueuedWeather; + std::map mRegions; + + void load(ESMReader& esm); + void save(ESMWriter& esm) const; + }; +} + +#endif diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 763f0f4e05..f0865a0a98 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -1,56 +1,71 @@ #include "storage.hpp" -#include -#include -#include -#include -#include -#include -#include +#include +#include + +#include +#include #include -#include #include +#include namespace ESMTerrain { - bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 ¢er, float &min, float &max) + Storage::Storage(const VFS::Manager *vfs) + : mVFS(vfs) + { + } + + const ESM::Land::LandData *Storage::getLandData (int cellX, int cellY, int flags) + { + if (const ESM::Land *land = getLand (cellX, cellY)) + return land->getLandData (flags); + + return 0; + } + + bool Storage::getMinMaxHeights(float size, const osg::Vec2f ¢er, float &min, float &max) { assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell"); /// \todo investigate if min/max heights should be stored at load time in ESM::Land instead - Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); + osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f); - assert(origin.x == (int) origin.x); - assert(origin.y == (int) origin.y); + int cellX = static_cast(std::floor(origin.x())); + int cellY = static_cast(std::floor(origin.y())); - int cellX = static_cast(origin.x); - int cellY = static_cast(origin.y); + int startRow = (origin.x() - cellX) * ESM::Land::LAND_SIZE; + int startColumn = (origin.y() - cellY) * ESM::Land::LAND_SIZE; - const ESM::Land* land = getLand(cellX, cellY); - if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT)) - return false; + int endRow = startRow + size * (ESM::Land::LAND_SIZE-1) + 1; + int endColumn = startColumn + size * (ESM::Land::LAND_SIZE-1) + 1; - min = std::numeric_limits::max(); - max = -std::numeric_limits::max(); - for (int row=0; row::max(); + max = -std::numeric_limits::max(); + for (int row=startRow; rowmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; - if (h > max) - max = h; - if (h < min) - min = h; + for (int col=startColumn; colmHeights[col*ESM::Land::LAND_SIZE+row]; + if (h > max) + max = h; + if (h < min) + min = h; + } } + return true; } - return true; + + return false; } - void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row) + void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row) { while (col >= ESM::Land::LAND_SIZE-1) { @@ -72,30 +87,30 @@ namespace ESMTerrain --cellX; row += ESM::Land::LAND_SIZE-1; } - ESM::Land* land = getLand(cellX, cellY); - if (land && land->mDataTypes&ESM::Land::DATA_VNML) + + if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VNML)) { - normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; - normal.normalise(); + normal.x() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.normalize(); } else - normal = Ogre::Vector3(0,0,1); + normal = osg::Vec3f(0,0,1); } - void Storage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row) + void Storage::averageNormal(osg::Vec3f &normal, int cellX, int cellY, int col, int row) { - Ogre::Vector3 n1,n2,n3,n4; + osg::Vec3f n1,n2,n3,n4; fixNormal(n1, cellX, cellY, col+1, row); fixNormal(n2, cellX, cellY, col-1, row); fixNormal(n3, cellX, cellY, col, row+1); fixNormal(n4, cellX, cellY, col, row-1); normal = (n1+n2+n3+n4); - normal.normalise(); + normal.normalize(); } - void Storage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row) + void Storage::fixColour (osg::Vec4f& color, int cellX, int cellY, int col, int row) { if (col == ESM::Land::LAND_SIZE-1) { @@ -107,90 +122,105 @@ namespace ESMTerrain ++cellX; row = 0; } - ESM::Land* land = getLand(cellX, cellY); - if (land && land->mDataTypes&ESM::Land::DATA_VCLR) + + if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VCLR)) { - color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; - color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; - color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; } else { - color.r = 1; - color.g = 1; - color.b = 1; + color.r() = 1; + color.g() = 1; + color.b() = 1; } } - void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, - std::vector& positions, - std::vector& normals, - std::vector& colours) + void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, + osg::ref_ptr positions, + osg::ref_ptr normals, + osg::ref_ptr colours) { // LOD level n means every 2^n-th vertex is kept size_t increment = 1 << lodLevel; - Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); - assert(origin.x == (int) origin.x); - assert(origin.y == (int) origin.y); + osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f); - int startX = static_cast(origin.x); - int startY = static_cast(origin.y); + int startCellX = static_cast(std::floor(origin.x())); + int startCellY = static_cast(std::floor(origin.y())); size_t numVerts = static_cast(size*(ESM::Land::LAND_SIZE - 1) / increment + 1); - colours.resize(numVerts*numVerts*4); - positions.resize(numVerts*numVerts*3); - normals.resize(numVerts*numVerts*3); + positions->resize(numVerts*numVerts); + normals->resize(numVerts*numVerts); + colours->resize(numVerts*numVerts); - Ogre::Vector3 normal; - Ogre::ColourValue color; + osg::Vec3f normal; + osg::Vec4f color; float vertY = 0; float vertX = 0; float vertY_ = 0; // of current cell corner - for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY) + for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) { float vertX_ = 0; // of current cell corner - for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) + for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) { - ESM::Land* land = getLand(cellX, cellY); - if (land && !(land->mDataTypes&ESM::Land::DATA_VHGT)) - land = NULL; + const ESM::Land::LandData *heightData = getLandData (cellX, cellY, ESM::Land::DATA_VHGT); + const ESM::Land::LandData *normalData = getLandData (cellX, cellY, ESM::Land::DATA_VNML); + const ESM::Land::LandData *colourData = getLandData (cellX, cellY, ESM::Land::DATA_VCLR); int rowStart = 0; int colStart = 0; // Skip the first row / column unless we're at a chunk edge, // since this row / column is already contained in a previous cell + // This is only relevant if we're creating a chunk spanning multiple cells if (colStart == 0 && vertY_ != 0) colStart += increment; if (rowStart == 0 && vertX_ != 0) rowStart += increment; + // Only relevant for chunks smaller than (contained in) one cell + rowStart += (origin.x() - startCellX) * ESM::Land::LAND_SIZE; + colStart += (origin.y() - startCellY) * ESM::Land::LAND_SIZE; + int rowEnd = rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1; + int colEnd = colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1; + vertY = vertY_; - for (int col=colStart; col(vertX*numVerts * 3 + vertY * 3)] = ((vertX / float(numVerts - 1) - 0.5f) * size * 8192); - positions[static_cast(vertX*numVerts * 3 + vertY * 3 + 1)] = ((vertY / float(numVerts - 1) - 0.5f) * size * 8192); - if (land) - positions[static_cast(vertX*numVerts * 3 + vertY * 3 + 2)] = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE + row]; - else - positions[static_cast(vertX*numVerts * 3 + vertY * 3 + 2)] = -2048; + int srcArrayIndex = col*ESM::Land::LAND_SIZE*3+row*3; - if (land && land->mDataTypes&ESM::Land::DATA_VNML) + assert(row >= 0 && row < ESM::Land::LAND_SIZE); + assert(col >= 0 && col < ESM::Land::LAND_SIZE); + + assert (vertX < numVerts); + assert (vertY < numVerts); + + float height = -2048; + if (heightData) + height = heightData->mHeights[col*ESM::Land::LAND_SIZE + row]; + + (*positions)[static_cast(vertX*numVerts + vertY)] + = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * 8192, + (vertY / float(numVerts - 1) - 0.5f) * size * 8192, + height); + + if (normalData) { - normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; - normal.normalise(); + for (int i=0; i<3; ++i) + normal[i] = normalData->mNormals[srcArrayIndex+i]; + + normal.normalize(); } else - normal = Ogre::Vector3(0,0,1); + normal = osg::Vec3f(0,0,1); // Normals apparently don't connect seamlessly between cells if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) @@ -200,33 +230,29 @@ namespace ESMTerrain if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) averageNormal(normal, cellX, cellY, col, row); - assert(normal.z > 0); + assert(normal.z() > 0); - normals[static_cast(vertX*numVerts * 3 + vertY * 3)] = normal.x; - normals[static_cast(vertX*numVerts * 3 + vertY * 3 + 1)] = normal.y; - normals[static_cast(vertX*numVerts * 3 + vertY * 3 + 2)] = normal.z; + (*normals)[static_cast(vertX*numVerts + vertY)] = normal; - if (land && land->mDataTypes&ESM::Land::DATA_VCLR) + if (colourData) { - color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; - color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; - color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + for (int i=0; i<3; ++i) + color[i] = colourData->mColours[srcArrayIndex+i] / 255.f; } else { - color.r = 1; - color.g = 1; - color.b = 1; + color.r() = 1; + color.g() = 1; + color.b() = 1; } // Unlike normals, colors mostly connect seamlessly between cells, but not always... if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) fixColour(color, cellX, cellY, col, row); - color.a = 1; - Ogre::uint32 rsColor; - Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor); - memcpy(&colours[static_cast(vertX*numVerts * 4 + vertY * 4)], &rsColor, sizeof(Ogre::uint32)); + color.a() = 1; + + (*colours)[static_cast(vertX*numVerts + vertY)] = color; ++vertX; } @@ -261,13 +287,12 @@ namespace ESMTerrain assert(xmDataTypes&ESM::Land::DATA_VTEX)) + if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VTEX)) { - int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; + int tex = data->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; if (tex == 0) return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin - return std::make_pair(tex, land->mPlugin); + return std::make_pair(tex, getLand (cellX, cellY)->mPlugin); } else return std::make_pair(0,0); @@ -275,44 +300,45 @@ namespace ESMTerrain std::string Storage::getTextureName(UniqueTextureId id) { + static const std::string defaultTexture = "textures\\_land_default.dds"; if (id.first == 0) - return "textures\\_land_default.dds"; // Not sure if the default texture really is hardcoded? + return defaultTexture; // Not sure if the default texture really is hardcoded? // NB: All vtex ids are +1 compared to the ltex ids const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); + if (!ltex) + { + std::cerr << "Unable to find land texture index " << id.first-1 << " in plugin " << id.second << ", using default texture instead" << std::endl; + return defaultTexture; + } - //TODO this is needed due to MWs messed up texture handling - std::string texture = Misc::ResourceHelpers::correctTexturePath(ltex->mTexture); + // this is needed due to MWs messed up texture handling + std::string texture = Misc::ResourceHelpers::correctTexturePath(ltex->mTexture, mVFS); return texture; } - void Storage::getBlendmaps (const std::vector& nodes, std::vector& out, bool pack) - { - for (std::vector::const_iterator it = nodes.begin(); it != nodes.end(); ++it) - { - out.push_back(Terrain::LayerCollection()); - out.back().mTarget = *it; - getBlendmapsImpl(static_cast((*it)->getSize()), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers); - } - } - - void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, - bool pack, std::vector &blendmaps, std::vector &layerList) - { - getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList); - } - - void Storage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter, - bool pack, std::vector &blendmaps, std::vector &layerList) + void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter, + bool pack, ImageVector &blendmaps, std::vector &layerList) { // TODO - blending isn't completely right yet; the blending radius appears to be // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap // and interpolate the rest of the cell by hand? :/ - Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f); - int cellX = static_cast(origin.x); - int cellY = static_cast(origin.y); + osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize/2.f, chunkSize/2.f); + int cellX = static_cast(std::floor(origin.x())); + int cellY = static_cast(std::floor(origin.y())); + + int realTextureSize = ESM::Land::LAND_TEXTURE_SIZE+1; // add 1 to wrap around next cell + + int rowStart = (origin.x() - cellX) * realTextureSize; + int colStart = (origin.y() - cellY) * realTextureSize; + int rowEnd = rowStart + chunkSize * (realTextureSize-1) + 1; + int colEnd = colStart + chunkSize * (realTextureSize-1) + 1; + + assert (rowStart >= 0 && colStart >= 0); + assert (rowEnd <= realTextureSize); + assert (colEnd <= realTextureSize); // Save the used texture indices so we know the total number of textures // and number of required blend maps @@ -323,8 +349,8 @@ namespace ESMTerrain // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. textureIndices.insert(std::make_pair(0,0)); - for (int y=0; y image (new osg::Image); + image->allocateImage(blendmapSize, blendmapSize, 1, format, GL_UNSIGNED_BYTE); + unsigned char* pData = image->data(); for (int y=0; ysecond; int blendIndex = (pack ? static_cast(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1); int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; @@ -373,24 +400,25 @@ namespace ESMTerrain pData[y*blendmapSize*channels + x*channels + channel] = 0; } } - blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData)); + + blendmaps.push_back(image); } } - float Storage::getHeightAt(const Ogre::Vector3 &worldPos) + float Storage::getHeightAt(const osg::Vec3f &worldPos) { - int cellX = static_cast(std::floor(worldPos.x / 8192.f)); - int cellY = static_cast(std::floor(worldPos.y / 8192.f)); + int cellX = static_cast(std::floor(worldPos.x() / 8192.f)); + int cellY = static_cast(std::floor(worldPos.y() / 8192.f)); - ESM::Land* land = getLand(cellX, cellY); + const ESM::Land* land = getLand(cellX, cellY); if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT)) return -2048; // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition // Normalized position in the cell - float nX = (worldPos.x - (cellX * 8192))/8192.f; - float nY = (worldPos.y - (cellY * 8192))/8192.f; + float nX = (worldPos.x() - (cellX * 8192))/8192.f; + float nY = (worldPos.y() - (cellY * 8192))/8192.f; // get left / bottom points (rounded down) float factor = ESM::Land::LAND_SIZE - 1.0f; @@ -422,22 +450,23 @@ namespace ESMTerrain */ // Build all 4 positions in normalized cell space, using point-sampled height - Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); - Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); - Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); - Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f); + osg::Vec3f v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); + osg::Vec3f v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); + osg::Vec3f v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); + osg::Vec3f v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f); // define this plane in terrain space - Ogre::Plane plane; - // (At the moment, all rows have the same triangle alignment) + osg::Plane plane; + // FIXME: deal with differing triangle alignment if (true) { // odd row bool secondTri = ((1.0 - yParam) > xParam); if (secondTri) - plane.redefine(v0, v1, v3); + plane = osg::Plane(v0, v1, v3); else - plane.redefine(v1, v2, v3); + plane = osg::Plane(v1, v2, v3); } + /* else { // even row @@ -447,11 +476,12 @@ namespace ESMTerrain else plane.redefine(v0, v1, v2); } + */ // Solve plane equation for z - return (-plane.normal.x * nX - -plane.normal.y * nY - - plane.d) / plane.normal.z * 8192; + return (-plane.getNormal().x() * nX + -plane.getNormal().y() * nY + - plane[3]) / plane.getNormal().z() * 8192; } @@ -459,7 +489,7 @@ namespace ESMTerrain { assert(x < ESM::Land::LAND_SIZE); assert(y < ESM::Land::LAND_SIZE); - return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; + return land->getLandData()->mHeights[y * ESM::Land::LAND_SIZE + x]; } Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture) @@ -476,7 +506,7 @@ namespace ESMTerrain std::string texture_ = texture; boost::replace_last(texture_, ".", "_nh."); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_)) + if (mVFS->exists(texture_)) { info.mNormalMap = texture_; info.mParallax = true; @@ -485,24 +515,18 @@ namespace ESMTerrain { texture_ = texture; boost::replace_last(texture_, ".", "_n."); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_)) + if (mVFS->exists(texture_)) info.mNormalMap = texture_; } texture_ = texture; boost::replace_last(texture_, ".", "_diffusespec."); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_)) + if (mVFS->exists(texture_)) { info.mDiffuseMap = texture_; info.mSpecular = true; } - // This wasn't cached, so the textures are probably not loaded either. - // Background load them so they are hopefully already loaded once we need them! - Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General"); - if (!info.mNormalMap.empty()) - Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General"); - mLayerInfoMap[texture] = info; return info; diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index e184cbc4cd..5b8ca953d4 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -6,6 +6,11 @@ #include #include +namespace VFS +{ + class Manager; +} + namespace ESMTerrain { @@ -16,10 +21,16 @@ namespace ESMTerrain private: // Not implemented in this class, because we need different Store implementations for game and editor - virtual ESM::Land* getLand (int cellX, int cellY) = 0; + virtual const ESM::Land* getLand (int cellX, int cellY)= 0; virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; public: + Storage(const VFS::Manager* vfs); + + /// Data is loaded first, if necessary. Will return a 0-pointer if there is no data for + /// any of the data types specified via \a flags. Will also return a 0-pointer if there + /// is no land record for the coordinates \a cellX / \a cellY. + const ESM::Land::LandData *getLandData (int cellX, int cellY, int flags); // Not implemented in this class, because we need different Store implementations for game and editor /// Get bounds of the whole terrain in cell units @@ -33,11 +44,10 @@ namespace ESMTerrain /// @param min min height will be stored here /// @param max max height will be stored here /// @return true if there was data available for this terrain chunk - virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max); + virtual bool getMinMaxHeights (float size, const osg::Vec2f& center, float& min, float& max); /// Fill vertex buffers for a terrain chunk. /// @note May be called from background threads. Make sure to only call thread-safe functions from here! - /// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue. /// @note Vertices should be written in row-major order (a row is defined as parallel to the x-axis). /// The specified positions should be in local space, i.e. relative to the center of the terrain chunk. /// @param lodLevel LOD level, 0 = most detailed @@ -46,10 +56,10 @@ namespace ESMTerrain /// @param positions buffer to write vertices /// @param normals buffer to write vertex normals /// @param colours buffer to write vertex colours - virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, - std::vector& positions, - std::vector& normals, - std::vector& colours); + virtual void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, + osg::ref_ptr positions, + osg::ref_ptr normals, + osg::ref_ptr colours); /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might @@ -62,23 +72,11 @@ namespace ESMTerrain /// can utilize packing, FFP can't. /// @param blendmaps created blendmaps will be written here /// @param layerList names of the layer textures used will be written here - virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, - std::vector& blendmaps, + virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, bool pack, + ImageVector& blendmaps, std::vector& layerList); - /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. - /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. - /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might - /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. - /// @note May be called from background threads. - /// @param nodes A collection of nodes for which to retrieve the aforementioned data - /// @param out Output vector - /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - - /// otherwise, each texture contains blend values for one layer only. Shader-based rendering - /// can utilize packing, FFP can't. - virtual void getBlendmaps (const std::vector& nodes, std::vector& out, bool pack); - - virtual float getHeightAt (const Ogre::Vector3& worldPos); + virtual float getHeightAt (const osg::Vec3f& worldPos); virtual Terrain::LayerInfo getDefaultLayer(); @@ -89,9 +87,11 @@ namespace ESMTerrain virtual int getCellVertices(); private: - void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); - void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row); - void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); + const VFS::Manager* mVFS; + + void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row); + void fixColour (osg::Vec4f& colour, int cellX, int cellY, int col, int row); + void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row); float getVertexHeight (const ESM::Land* land, int x, int y); @@ -107,11 +107,6 @@ namespace ESMTerrain std::map mLayerInfoMap; Terrain::LayerInfo getLayerInfo(const std::string& texture); - - // Non-virtual - void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, - std::vector& blendmaps, - std::vector& layerList); }; } diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index dc6f02b608..9c9cebe96f 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -60,10 +60,14 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m loadConfig(mFixedPath.getUserConfigPath(), variables, description); boost::program_options::notify(variables); - loadConfig(mFixedPath.getLocalPath(), variables, description); - boost::program_options::notify(variables); - loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); + // read either local or global config depending on type of installation + bool loaded = loadConfig(mFixedPath.getLocalPath(), variables, description); boost::program_options::notify(variables); + if (!loaded) + { + loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); + boost::program_options::notify(variables); + } mSilent = silent; } @@ -126,7 +130,7 @@ void ConfigurationManager::processPaths(Files::PathContainer& dataDirs, bool cre boost::bind(&boost::filesystem::path::empty, _1)), dataDirs.end()); } -void ConfigurationManager::loadConfig(const boost::filesystem::path& path, +bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, boost::program_options::variables_map& variables, boost::program_options::options_description& description) { @@ -145,13 +149,16 @@ void ConfigurationManager::loadConfig(const boost::filesystem::path& path, if (!mSilent) std::cout << "done." << std::endl; + return true; } else { if (!mSilent) std::cout << "failed." << std::endl; + return false; } } + return false; } const boost::filesystem::path& ConfigurationManager::getGlobalPath() const diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 5f0062c2e9..58ee5c1aef 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -52,13 +52,13 @@ struct ConfigurationManager typedef Files::FixedPath<> FixedPathType; typedef const boost::filesystem::path& (FixedPathType::*path_type_f)() const; - #if defined HAVE_UNORDERED_MAP - typedef std::unordered_map TokensMappingContainer; - #else - typedef std::tr1::unordered_map TokensMappingContainer; - #endif + #if defined HAVE_UNORDERED_MAP + typedef std::unordered_map TokensMappingContainer; + #else + typedef std::tr1::unordered_map TokensMappingContainer; + #endif - void loadConfig(const boost::filesystem::path& path, + bool loadConfig(const boost::filesystem::path& path, boost::program_options::variables_map& variables, boost::program_options::options_description& description); diff --git a/components/files/constrainedfiledatastream.cpp b/components/files/constrainedfiledatastream.cpp deleted file mode 100644 index d4d7c231a4..0000000000 --- a/components/files/constrainedfiledatastream.cpp +++ /dev/null @@ -1,182 +0,0 @@ -#include "constrainedfiledatastream.hpp" -#include "lowlevelfile.hpp" - -#include -#include - -#include - -namespace { - -class ConstrainedDataStream : public Ogre::DataStream { -public: - - static const size_t sBufferSize = 4096; // somewhat arbitrary though 64KB buffers didn't seem to improve performance any - static const size_t sBufferThreshold = 1024; // reads larger than this bypass buffering as cost of memcpy outweighs cost of system call - - ConstrainedDataStream(const Ogre::String &fname, size_t start, size_t length) - : Ogre::DataStream(fname) - { - mFile.open (fname.c_str ()); - mSize = length != 0xFFFFFFFF ? length : mFile.size () - start; - - mPos = 0; - mOrigin = start; - mExtent = start + mSize; - - mBufferOrigin = 0; - mBufferExtent = 0; - } - - - size_t read(void* buf, size_t count) - { - try - { - assert (mPos <= mSize); - - uint8_t * out = reinterpret_cast (buf); - - size_t posBeg = mOrigin + mPos; - size_t posEnd = posBeg + count; - - if (posEnd > mExtent) - posEnd = mExtent; - - size_t posCur = posBeg; - - while (posCur != posEnd) - { - size_t readLeft = posEnd - posCur; - - if (posCur < mBufferOrigin || posCur >= mBufferExtent) - { - if (readLeft >= sBufferThreshold || (posCur == mOrigin && posEnd == mExtent)) - { - assert (mFile.tell () == mBufferExtent); - - if (posCur != mBufferExtent) - mFile.seek (posCur); - - posCur += mFile.read (out, readLeft); - - mBufferOrigin = mBufferExtent = posCur; - - mPos = posCur - mOrigin; - - return posCur - posBeg; - } - else - { - size_t newBufferOrigin; - - if ((posCur < mBufferOrigin) && (mBufferOrigin - posCur < sBufferSize)) - newBufferOrigin = std::max (mOrigin, mBufferOrigin > sBufferSize ? mBufferOrigin - sBufferSize : 0); - else - newBufferOrigin = posCur; - - fill (newBufferOrigin); - } - } - - size_t xfer = std::min (readLeft, mBufferExtent - posCur); - - memcpy (out, mBuffer + (posCur - mBufferOrigin), xfer); - - posCur += xfer; - out += xfer; - } - - count = posEnd - posBeg; - mPos += count; - return count; - } - catch (std::exception& e) - { - std::stringstream error; - error << "Failed to read '" << mName << "': " << e.what(); - throw std::runtime_error(error.str()); - } - } - - void skip(long count) - { - assert (mPos <= mSize); - - if((count >= 0 && (size_t)count <= mSize-mPos) || - (count < 0 && (size_t)-count <= mPos)) - mPos += count; - } - - void seek(size_t pos) - { - assert (mPos <= mSize); - - if (pos < mSize) - mPos = pos; - } - - virtual size_t tell() const - { - assert (mPos <= mSize); - - return mPos; - } - - virtual bool eof() const - { - assert (mPos <= mSize); - - return mPos == mSize; - } - - virtual void close() - { - mFile.close(); - } - -private: - - void fill (size_t newOrigin) - { - assert (mFile.tell () == mBufferExtent); - - size_t newExtent = newOrigin + sBufferSize; - - if (newExtent > mExtent) - newExtent = mExtent; - - size_t oldExtent = mBufferExtent; - - if (newOrigin != oldExtent) - mFile.seek (newOrigin); - - mBufferOrigin = mBufferExtent = newOrigin; - - size_t amountRequested = newExtent - newOrigin; - - size_t amountRead = mFile.read (mBuffer, amountRequested); - - if (amountRead != amountRequested) - throw std::runtime_error ("An unexpected condition occurred while reading from a file."); - - mBufferExtent = newExtent; - } - - LowLevelFile mFile; - - size_t mOrigin; - size_t mExtent; - size_t mPos; - - uint8_t mBuffer [sBufferSize]; - size_t mBufferOrigin; - size_t mBufferExtent; -}; - -} // end of unnamed namespace - -Ogre::DataStreamPtr openConstrainedFileDataStream (char const * filename, size_t offset, size_t length) -{ - return Ogre::DataStreamPtr(new ConstrainedDataStream(filename, offset, length)); -} diff --git a/components/files/constrainedfiledatastream.hpp b/components/files/constrainedfiledatastream.hpp deleted file mode 100644 index 367defcbcb..0000000000 --- a/components/files/constrainedfiledatastream.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef COMPONENTS_FILES_CONSTRAINEDFILEDATASTREAM_HPP -#define COMPONENTS_FILES_CONSTRAINEDFILEDATASTREAM_HPP - -#include - -Ogre::DataStreamPtr openConstrainedFileDataStream (char const * filename, size_t offset = 0, size_t length = 0xFFFFFFFF); - -#endif // COMPONENTS_FILES_CONSTRAINEDFILEDATASTREAM_HPP diff --git a/components/files/constrainedfilestream.cpp b/components/files/constrainedfilestream.cpp new file mode 100644 index 0000000000..cd9a3c8f57 --- /dev/null +++ b/components/files/constrainedfilestream.cpp @@ -0,0 +1,122 @@ +#include "constrainedfilestream.hpp" + +#include + +#include "lowlevelfile.hpp" + +namespace +{ +// somewhat arbitrary though 64KB buffers didn't seem to improve performance any +const size_t sBufferSize = 4096; +} + +namespace Files +{ + class ConstrainedFileStreamBuf : public std::streambuf + { + + size_t mOrigin; + size_t mSize; + + LowLevelFile mFile; + + char mBuffer[sBufferSize]; + + public: + ConstrainedFileStreamBuf(const std::string &fname, size_t start, size_t length) + { + mFile.open (fname.c_str ()); + mSize = length != 0xFFFFFFFF ? length : mFile.size () - start; + + if (start != 0) + mFile.seek(start); + + setg(0,0,0); + + mOrigin = start; + } + + virtual int_type underflow() + { + if(gptr() == egptr()) + { + size_t toRead = std::min((mOrigin+mSize)-(mFile.tell()), sBufferSize); + // Read in the next chunk of data, and set the read pointers on success + // Failure will throw exception in LowLevelFile + size_t got = mFile.read(mBuffer, toRead); + setg(&mBuffer[0], &mBuffer[0], &mBuffer[0]+got); + } + if(gptr() == egptr()) + return traits_type::eof(); + + return traits_type::to_int_type(*gptr()); + } + + virtual pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) + { + if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) + return traits_type::eof(); + + // new file position, relative to mOrigin + size_t newPos; + switch (whence) + { + case std::ios_base::beg: + newPos = offset; + break; + case std::ios_base::cur: + newPos = (mFile.tell() - mOrigin - (egptr() - gptr())) + offset; + break; + case std::ios_base::end: + newPos = mSize + offset; + break; + default: + return traits_type::eof(); + } + + if (newPos > mSize) + return traits_type::eof(); + + mFile.seek(mOrigin+newPos); + + // Clear read pointers so underflow() gets called on the next read attempt. + setg(0, 0, 0); + + return newPos; + } + + virtual pos_type seekpos(pos_type pos, std::ios_base::openmode mode) + { + if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) + return traits_type::eof(); + + if ((size_t)pos > mSize) + return traits_type::eof(); + + mFile.seek(mOrigin + pos); + + // Clear read pointers so underflow() gets called on the next read attempt. + setg(0, 0, 0); + return pos; + } + + }; + + ConstrainedFileStream::ConstrainedFileStream(const char *filename, size_t start, size_t length) + : std::istream(new ConstrainedFileStreamBuf(filename, start, length)) + { + + } + + ConstrainedFileStream::~ConstrainedFileStream() + { + delete rdbuf(); + } + + + IStreamPtr openConstrainedFileStream(const char *filename, + size_t start, size_t length) + { + return IStreamPtr(new ConstrainedFileStream(filename, start, length)); + } +} diff --git a/components/files/constrainedfilestream.hpp b/components/files/constrainedfilestream.hpp new file mode 100644 index 0000000000..069ceec587 --- /dev/null +++ b/components/files/constrainedfilestream.hpp @@ -0,0 +1,26 @@ +#ifndef OPENMW_CONSTRAINEDFILESTREAM_H +#define OPENMW_CONSTRAINEDFILESTREAM_H + +#include + +#include + +namespace Files +{ + +/// A file stream constrained to a specific region in the file, specified by the 'start' and 'length' parameters. +class ConstrainedFileStream : public std::istream +{ +public: + ConstrainedFileStream(const char *filename, + size_t start=0, size_t length=0xFFFFFFFF); + virtual ~ConstrainedFileStream(); +}; + +typedef boost::shared_ptr IStreamPtr; + +IStreamPtr openConstrainedFileStream(const char *filename, size_t start=0, size_t length=0xFFFFFFFF); + +} + +#endif diff --git a/components/files/linuxpath.cpp b/components/files/linuxpath.cpp index a105bb928d..212db562c0 100644 --- a/components/files/linuxpath.cpp +++ b/components/files/linuxpath.cpp @@ -8,6 +8,8 @@ #include #include +#include + namespace { @@ -139,7 +141,7 @@ boost::filesystem::path LinuxPath::getInstallPath() const { // Change drive letter to lowercase, so we could use // ~/.wine/dosdevices symlinks - mwpath[0] = tolower(mwpath[0]); + mwpath[0] = Misc::StringUtils::toLower(mwpath[0]); installPath /= homePath; installPath /= ".wine/dosdevices/"; installPath /= mwpath; diff --git a/components/files/lowlevelfile.cpp b/components/files/lowlevelfile.cpp index 8456c7a26d..169a6f8131 100644 --- a/components/files/lowlevelfile.cpp +++ b/components/files/lowlevelfile.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #endif #if FILE_API == FILE_API_STDIO @@ -139,7 +141,7 @@ void LowLevelFile::open (char const * filename) if (mHandle == -1) { std::ostringstream os; - os << "Failed to open '" << filename << "' for reading."; + os << "Failed to open '" << filename << "' for reading: " << strerror(errno); throw std::runtime_error (os.str ()); } } @@ -160,15 +162,27 @@ size_t LowLevelFile::size () size_t oldPosition = ::lseek (mHandle, 0, SEEK_CUR); if (oldPosition == size_t (-1)) - throw std::runtime_error ("A query operation on a file failed."); + { + std::ostringstream os; + os << "An lseek() call failed:" << strerror(errno); + throw std::runtime_error (os.str ()); + } size_t Size = ::lseek (mHandle, 0, SEEK_END); if (Size == size_t (-1)) - throw std::runtime_error ("A query operation on a file failed."); + { + std::ostringstream os; + os << "An lseek() call failed:" << strerror(errno); + throw std::runtime_error (os.str ()); + } if (lseek (mHandle, oldPosition, SEEK_SET) == -1) - throw std::runtime_error ("A query operation on a file failed."); + { + std::ostringstream os; + os << "An lseek() call failed:" << strerror(errno); + throw std::runtime_error (os.str ()); + } return Size; } @@ -178,7 +192,11 @@ void LowLevelFile::seek (size_t Position) assert (mHandle != -1); if (::lseek (mHandle, Position, SEEK_SET) == -1) - throw std::runtime_error ("A seek operation on a file failed."); + { + std::ostringstream os; + os << "An lseek() call failed:" << strerror(errno); + throw std::runtime_error (os.str ()); + } } size_t LowLevelFile::tell () @@ -188,7 +206,11 @@ size_t LowLevelFile::tell () size_t Position = ::lseek (mHandle, 0, SEEK_CUR); if (Position == size_t (-1)) - throw std::runtime_error ("A query operation on a file failed."); + { + std::ostringstream os; + os << "An lseek() call failed:" << strerror(errno); + throw std::runtime_error (os.str ()); + } return Position; } @@ -200,7 +222,11 @@ size_t LowLevelFile::read (void * data, size_t size) int amount = ::read (mHandle, data, size); if (amount == -1) - throw std::runtime_error ("A read operation on a file failed."); + { + std::ostringstream os; + os << "An attempt to read " << size << "bytes failed:" << strerror(errno); + throw std::runtime_error (os.str ()); + } return amount; } diff --git a/components/files/lowlevelfile.hpp b/components/files/lowlevelfile.hpp index d94238ad6e..b2634d8c76 100644 --- a/components/files/lowlevelfile.hpp +++ b/components/files/lowlevelfile.hpp @@ -1,17 +1,15 @@ #ifndef COMPONENTS_FILES_LOWLEVELFILE_HPP #define COMPONENTS_FILES_LOWLEVELFILE_HPP -#include - #include #define FILE_API_STDIO 0 #define FILE_API_POSIX 1 #define FILE_API_WIN32 2 -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX +#if defined(__linux) || defined(__unix) || defined(__posix) #define FILE_API FILE_API_POSIX -#elif OGRE_PLATFORM == OGRE_PLATFORM_WIN32 +#elif defined(_WIN32) #define FILE_API FILE_API_WIN32 #else #define FILE_API FILE_API_STDIO diff --git a/components/files/macospath.cpp b/components/files/macospath.cpp index 6e794796f9..2371419605 100644 --- a/components/files/macospath.cpp +++ b/components/files/macospath.cpp @@ -7,6 +7,8 @@ #include #include +#include + namespace { boost::filesystem::path getUserHome() @@ -129,7 +131,7 @@ boost::filesystem::path MacOsPath::getInstallPath() const if (!mwpath.empty()) { // Change drive letter to lowercase, so we could use ~/.wine/dosdevice symlinks - mwpath[0] = tolower(mwpath[0]); + mwpath[0] = Misc::StringUtils::toLower(mwpath[0]); installPath /= homePath; installPath /= ".wine/dosdevices/"; installPath /= mwpath; diff --git a/components/files/memorystream.hpp b/components/files/memorystream.hpp new file mode 100644 index 0000000000..9a35100440 --- /dev/null +++ b/components/files/memorystream.hpp @@ -0,0 +1,31 @@ +#ifndef OPENMW_COMPONENTS_FILES_MEMORYSTREAM_H +#define OPENMW_COMPONENTS_FILES_MEMORYSTREAM_H + +#include + +namespace Files +{ + + struct MemBuf : std::streambuf + { + MemBuf(char const* buffer, size_t size) + { + // a streambuf isn't specific to istreams, so we need a non-const pointer :/ + char* nonconstBuffer = (const_cast(buffer)); + this->setg(nonconstBuffer, nonconstBuffer, nonconstBuffer + size); + } + }; + + /// @brief A variant of std::istream that reads from a constant in-memory buffer. + struct IMemStream: virtual MemBuf, std::istream + { + IMemStream(char const* buffer, size_t size) + : MemBuf(buffer, size) + , std::istream(static_cast(this)) + { + } + }; + +} + +#endif diff --git a/components/files/multidircollection.cpp b/components/files/multidircollection.cpp index 1abbae3ae8..b37a95b2fc 100644 --- a/components/files/multidircollection.cpp +++ b/components/files/multidircollection.cpp @@ -1,4 +1,3 @@ - #include "multidircollection.hpp" #include @@ -9,6 +8,8 @@ #include +#include + namespace Files { struct NameEqual @@ -29,8 +30,8 @@ namespace Files for (std::size_t i=0; i #include #include -#include #include #include +#include + namespace Files { typedef std::vector PathContainer; @@ -25,12 +26,11 @@ namespace Files return left -#include #include #include +#include #include namespace bconv = boost::locale::conv; diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index eb7dd901d7..b66fbbb204 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -2,18 +2,24 @@ #include -#include -#include +#include +#include + +#include #include #include #include #include #include +#include +#include #include +#include + namespace { unsigned long utf8ToUnicode(const std::string& utf8) @@ -123,12 +129,12 @@ namespace return encoder.getUtf8(std::string(1, c)); } - void fail (Ogre::DataStreamPtr file, const std::string& fileName, const std::string& message) + void fail (Files::IStreamPtr file, const std::string& fileName, const std::string& message) { std::stringstream error; error << "Font loading error: " << message; error << "\n File: " << fileName; - error << "\n Offset: 0x" << std::hex << file->tell(); + error << "\n Offset: 0x" << std::hex << file->tellg(); throw std::runtime_error(error.str()); } @@ -137,7 +143,8 @@ namespace namespace Gui { - FontLoader::FontLoader(ToUTF8::FromType encoding) + FontLoader::FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs) + : mVFS(vfs) { if (encoding == ToUTF8::WINDOWS_1252) mEncoding = ToUTF8::CP437; @@ -145,16 +152,37 @@ namespace Gui mEncoding = encoding; } + FontLoader::~FontLoader() + { + for (std::vector::iterator it = mTextures.begin(); it != mTextures.end(); ++it) + delete *it; + mTextures.clear(); + + for (std::vector::iterator it = mFonts.begin(); it != mFonts.end(); ++it) + MyGUI::ResourceManager::getInstance().removeByName((*it)->getResourceName()); + mFonts.clear(); + } + void FontLoader::loadAllFonts(bool exportToFile) { - Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups (); - for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it) + const std::map& index = mVFS->getIndex(); + + std::string pattern = "Fonts/"; + mVFS->normalizeFilename(pattern); + + std::map::const_iterator found = index.lower_bound(pattern); + while (found != index.end()) { - Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "*.fnt"); - for (Ogre::StringVector::iterator resource = resourcesInThisGroup->begin(); resource != resourcesInThisGroup->end(); ++resource) + const std::string& name = found->first; + if (name.size() >= pattern.size() && name.substr(0, pattern.size()) == pattern) { - loadFont(*resource, exportToFile); + size_t pos = name.find_last_of('.'); + if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".fnt") == 0) + loadFont(name, exportToFile); } + else + break; + ++found; } } @@ -181,54 +209,62 @@ namespace Gui void FontLoader::loadFont(const std::string &fileName, bool exportToFile) { - Ogre::DataStreamPtr file = Ogre::ResourceGroupManager::getSingleton().openResource(fileName); + Files::IStreamPtr file = mVFS->get(fileName); float fontSize; - if (file->read(&fontSize, sizeof(fontSize)) < sizeof(fontSize)) + file->read((char*)&fontSize, sizeof(fontSize)); + if (!file->good()) fail(file, fileName, "File too small to be a valid font"); int one; - if (file->read(&one, sizeof(int)) < sizeof(int)) + file->read((char*)&one, sizeof(one)); + if (!file->good()) fail(file, fileName, "File too small to be a valid font"); if (one != 1) fail(file, fileName, "Unexpected value"); - if (file->read(&one, sizeof(int)) < sizeof(int)) + file->read((char*)&one, sizeof(one)); + if (!file->good()) fail(file, fileName, "File too small to be a valid font"); if (one != 1) fail(file, fileName, "Unexpected value"); char name_[284]; - if (file->read(name_, sizeof(name_)) < sizeof(name_)) + file->read(name_, sizeof(name_)); + if (!file->good()) fail(file, fileName, "File too small to be a valid font"); std::string name(name_); GlyphInfo data[256]; - if (file->read(data, sizeof(data)) < sizeof(data)) + file->read((char*)data, sizeof(data)); + if (!file->good()) fail(file, fileName, "File too small to be a valid font"); - file->close(); + + file.reset(); // Create the font texture std::string bitmapFilename = "Fonts/" + std::string(name) + ".tex"; - Ogre::DataStreamPtr bitmapFile = Ogre::ResourceGroupManager::getSingleton().openResource(bitmapFilename); + + Files::IStreamPtr bitmapFile = mVFS->get(bitmapFilename); int width, height; - if (bitmapFile->read(&width, sizeof(int)) < sizeof(int)) - fail(bitmapFile, bitmapFilename, "File too small to be a valid bitmap"); + bitmapFile->read((char*)&width, sizeof(int)); + bitmapFile->read((char*)&height, sizeof(int)); - if (bitmapFile->read(&height, sizeof(int)) < sizeof(int)) + if (!bitmapFile->good()) fail(bitmapFile, bitmapFilename, "File too small to be a valid bitmap"); if (width <= 0 || height <= 0) fail(bitmapFile, bitmapFilename, "Width and height must be positive"); - std::vector textureData; + std::vector textureData; textureData.resize(width*height*4); - if (bitmapFile->read(&textureData[0], width*height*4) < (size_t)(width*height*4)) - fail(bitmapFile, bitmapFilename, "Bitmap does not contain the specified number of pixels"); - bitmapFile->close(); + bitmapFile->read(&textureData[0], width*height*4); + if (!bitmapFile->good()) + fail(bitmapFile, bitmapFilename, "File too small to be a valid bitmap"); + bitmapFile.reset(); std::string resourceName; if (name.size() >= 5 && Misc::StringUtils::ciEqual(name.substr(0, 5), "magic")) @@ -237,24 +273,37 @@ namespace Gui resourceName = "Century Gothic"; else if (name.size() >= 7 && Misc::StringUtils::ciEqual(name.substr(0, 7), "daedric")) resourceName = "Daedric"; - else - return; // no point in loading it, since there is no way of using additional fonts - - std::string textureName = name; - Ogre::Image image; - image.loadDynamicImage(&textureData[0], width, height, Ogre::PF_BYTE_RGBA); - Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().createManual(textureName, - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, - width, height, 0, Ogre::PF_BYTE_RGBA); - texture->loadImage(image); if (exportToFile) - image.save(resourceName + ".png"); + { + osg::ref_ptr image = new osg::Image; + image->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE); + assert (image->isDataContiguous()); + memcpy(image->data(), &textureData[0], textureData.size()); + + std::cout << "Writing " << resourceName + ".png" << std::endl; + osgDB::writeImageFile(*image, resourceName + ".png"); + } // Register the font with MyGUI MyGUI::ResourceManualFont* font = static_cast( MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont")); + mFonts.push_back(font); + + MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().createTexture(bitmapFilename); + tex->createManual(width, height, MyGUI::TextureUsage::Write, MyGUI::PixelFormat::R8G8B8A8); + unsigned char* texData = reinterpret_cast(tex->lock(MyGUI::TextureUsage::Write)); + memcpy(texData, &textureData[0], textureData.size()); + tex->unlock(); + + // Using ResourceManualFont::setTexture, enable for MyGUI 3.2.3 + /* + osg::ref_ptr texture = new osg::Texture2D; + texture->setImage(image); + osgMyGUI::OSGTexture* myguiTex = new osgMyGUI::OSGTexture(texture); + mTextures.push_back(myguiTex); + font->setTexture(myguiTex); + */ // We need to emulate loading from XML because the data members are private as of mygui 3.2.0 MyGUI::xml::Document xmlDocument; @@ -266,7 +315,7 @@ namespace Gui defaultHeight->addAttribute("value", fontSize); MyGUI::xml::ElementPtr source = root->createChild("Property"); source->addAttribute("key", "Source"); - source->addAttribute("value", std::string(textureName)); + source->addAttribute("value", std::string(bitmapFilename)); MyGUI::xml::ElementPtr codes = root->createChild("Codes"); for(int i = 0; i < 256; i++) @@ -388,13 +437,23 @@ namespace Gui if (exportToFile) { + std::cout << "Writing " << resourceName + ".xml" << std::endl; xmlDocument.createDeclaration(); xmlDocument.save(resourceName + ".xml"); } font->deserialization(root, MyGUI::Version(3,2,0)); - MyGUI::ResourceManager::getInstance().removeByName(font->getResourceName()); + for (std::vector::iterator it = mFonts.begin(); it != mFonts.end();) + { + if ((*it)->getResourceName() == font->getResourceName()) + { + MyGUI::ResourceManager::getInstance().removeByName(font->getResourceName()); + it = mFonts.erase(it); + } + else + ++it; + } MyGUI::ResourceManager::getInstance().addResource(font); } diff --git a/components/fontloader/fontloader.hpp b/components/fontloader/fontloader.hpp index a41506dbb4..b92815f136 100644 --- a/components/fontloader/fontloader.hpp +++ b/components/fontloader/fontloader.hpp @@ -1,26 +1,46 @@ -#ifndef MWGUI_FONTLOADER_H -#define MWGUI_FONTLOADER_H +#ifndef OPENMW_COMPONENTS_FONTLOADER_H +#define OPENMW_COMPONENTS_FONTLOADER_H #include +namespace VFS +{ + class Manager; +} + +namespace MyGUI +{ + class ITexture; + class ResourceManualFont; +} + namespace Gui { - /// @brief loads Morrowind's .fnt/.tex fonts for use with MyGUI and Ogre + /// @brief loads Morrowind's .fnt/.tex fonts for use with MyGUI and OSG + /// @note The FontLoader needs to remain in scope as long as you want to use the loaded fonts. class FontLoader { public: - FontLoader (ToUTF8::FromType encoding); + FontLoader (ToUTF8::FromType encoding, const VFS::Manager* vfs); + ~FontLoader(); /// @param exportToFile export the converted fonts (Images and XML with glyph metrics) to files? void loadAllFonts (bool exportToFile); private: ToUTF8::FromType mEncoding; + const VFS::Manager* mVFS; + + std::vector mTextures; + std::vector mFonts; /// @param exportToFile export the converted font (Image and XML with glyph metrics) to files? void loadFont (const std::string& fileName, bool exportToFile); + + FontLoader(const FontLoader&); + void operator=(const FontLoader&); }; } diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp index 4881274f03..2ceb857c40 100644 --- a/components/interpreter/defines.cpp +++ b/components/interpreter/defines.cpp @@ -1,11 +1,12 @@ #include "defines.hpp" -#include #include #include #include #include +#include + namespace Interpreter{ bool check(const std::string& str, const std::string& escword, unsigned int* i, unsigned int* start) @@ -34,8 +35,7 @@ namespace Interpreter{ if(text[i] == eschar) { retval << text.substr(start, i - start); - std::string temp = text.substr(i+1, 100); - transform(temp.begin(), temp.end(), temp.begin(), ::tolower); + std::string temp = Misc::StringUtils::lowerCase(text.substr(i+1, 100)); bool found = false; try diff --git a/components/interpreter/installopcodes.cpp b/components/interpreter/installopcodes.cpp index d705a109c7..31e911f8ba 100644 --- a/components/interpreter/installopcodes.cpp +++ b/components/interpreter/installopcodes.cpp @@ -1,4 +1,3 @@ - #include "installopcodes.hpp" #include diff --git a/components/interpreter/interpreter.cpp b/components/interpreter/interpreter.cpp index ea1e9fc912..1892e4a6f5 100644 --- a/components/interpreter/interpreter.cpp +++ b/components/interpreter/interpreter.cpp @@ -1,8 +1,6 @@ - #include "interpreter.hpp" #include -#include #include #include @@ -134,7 +132,34 @@ namespace Interpreter throw std::runtime_error (error.str()); } - Interpreter::Interpreter() + void Interpreter::begin() + { + if (mRunning) + { + mCallstack.push (mRuntime); + mRuntime.clear(); + } + else + { + mRunning = true; + } + } + + void Interpreter::end() + { + if (mCallstack.empty()) + { + mRuntime.clear(); + mRunning = false; + } + else + { + mRuntime = mCallstack.top(); + mCallstack.pop(); + } + } + + Interpreter::Interpreter() : mRunning (false) {} Interpreter::~Interpreter() @@ -204,19 +229,29 @@ namespace Interpreter { assert (codeSize>=4); - mRuntime.configure (code, codeSize, context); + begin(); - int opcodes = static_cast (code[0]); - - const Type_Code *codeBlock = code + 4; - - while (mRuntime.getPC()>=0 && mRuntime.getPC() (code[0]); + + const Type_Code *codeBlock = code + 4; + + while (mRuntime.getPC()>=0 && mRuntime.getPC() +#include #include "runtime.hpp" #include "types.hpp" @@ -14,6 +15,8 @@ namespace Interpreter class Interpreter { + std::stack mCallstack; + bool mRunning; Runtime mRuntime; std::map mSegment0; std::map mSegment1; @@ -32,6 +35,10 @@ namespace Interpreter void abortUnknownSegment (Type_Code code); + void begin(); + + void end(); + public: Interpreter(); diff --git a/components/interpreter/miscopcodes.hpp b/components/interpreter/miscopcodes.hpp index f566a54997..c49bbeb013 100644 --- a/components/interpreter/miscopcodes.hpp +++ b/components/interpreter/miscopcodes.hpp @@ -12,7 +12,7 @@ #include "runtime.hpp" #include "defines.hpp" -#include +#include namespace Interpreter { @@ -148,7 +148,7 @@ namespace Interpreter throw std::runtime_error ( "random: argument out of range (Don't be so negative!)"); - Type_Integer value = OEngine::Misc::Rng::rollDice(limit); // [o, limit) + Type_Integer value = Misc::Rng::rollDice(limit); // [o, limit) runtime[0].mInteger = value; } diff --git a/components/interpreter/runtime.cpp b/components/interpreter/runtime.cpp index dc3da07a8d..6599882f12 100644 --- a/components/interpreter/runtime.cpp +++ b/components/interpreter/runtime.cpp @@ -1,4 +1,3 @@ - #include "runtime.hpp" #include diff --git a/components/loadinglistener/loadinglistener.hpp b/components/loadinglistener/loadinglistener.hpp index 47962015c2..1d48cce0b9 100644 --- a/components/loadinglistener/loadinglistener.hpp +++ b/components/loadinglistener/loadinglistener.hpp @@ -1,26 +1,37 @@ #ifndef COMPONENTS_LOADINGLISTENER_H #define COMPONENTS_LOADINGLISTENER_H +#include + namespace Loading { class Listener { public: - virtual void setLabel (const std::string& label) = 0; + /// Set a text label to show on the loading screen. + /// @param label The label + /// @param important Is the label considered important to show? + /// @note "non-important" labels may not show on screen if the loading process went so fast + /// that the implementation decided not to show a loading screen at all. "important" labels + /// will show in a separate message-box if the loading screen was not shown. + virtual void setLabel (const std::string& label, bool important=false) {} - // Use ScopedLoad instead of using these directly - virtual void loadingOn() = 0; - virtual void loadingOff() = 0; + /// Start a loading sequence. Must call loadingOff() when done. + /// @note To get the loading screen to actually update, you must call setProgress / increaseProgress periodically. + /// @note It is best to use the ScopedLoad object instead of using loadingOn()/loadingOff() directly, + /// so that the loading is exception safe. + virtual void loadingOn() {} + virtual void loadingOff() {} - /// Indicate that some progress has been made, without specifying how much - virtual void indicateProgress () = 0; - - virtual void setProgressRange (size_t range) = 0; - virtual void setProgress (size_t value) = 0; - virtual void increaseProgress (size_t increase = 1) = 0; + /// Set the total range of progress (e.g. the number of objects to load). + virtual void setProgressRange (size_t range) {} + /// Set current progress. Valid range is [0, progressRange) + virtual void setProgress (size_t value) {} + /// Increase current progress, default by 1. + virtual void increaseProgress (size_t increase = 1) {} }; - // Used for stopping a loading sequence when the object goes out of scope + /// @brief Used for stopping a loading sequence when the object goes out of scope struct ScopedLoad { ScopedLoad(Listener* l) : mListener(l) { mListener->loadingOn(); } diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index dc08b352a6..6fe13abf31 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -1,8 +1,10 @@ #include "resourcehelpers.hpp" +#include + #include -#include +#include namespace { @@ -29,8 +31,8 @@ namespace bool Misc::ResourceHelpers::changeExtensionToDds(std::string &path) { - Ogre::String::size_type pos = path.rfind('.'); - if(pos != Ogre::String::npos && path.compare(pos, path.length() - pos, ".dds") != 0) + std::string::size_type pos = path.rfind('.'); + if(pos != std::string::npos && path.compare(pos, path.length() - pos, ".dds") != 0) { path.replace(pos, path.length(), ".dds"); return true; @@ -38,7 +40,7 @@ bool Misc::ResourceHelpers::changeExtensionToDds(std::string &path) return false; } -std::string Misc::ResourceHelpers::correctResourcePath(const std::string &topLevelDirectory, const std::string &resPath) +std::string Misc::ResourceHelpers::correctResourcePath(const std::string &topLevelDirectory, const std::string &resPath, const VFS::Manager* vfs) { /* Bethesda at some point converted all their BSA * textures from tga to dds for increased load speed, but all @@ -49,7 +51,7 @@ std::string Misc::ResourceHelpers::correctResourcePath(const std::string &topLev std::string prefix2 = topLevelDirectory + '/'; std::string correctedPath = resPath; - Misc::StringUtils::toLower(correctedPath); + Misc::StringUtils::lowerCaseInPlace(correctedPath); // Apparently, leading separators are allowed while (correctedPath.size() && (correctedPath[0] == '/' || correctedPath[0] == '\\')) @@ -64,65 +66,65 @@ std::string Misc::ResourceHelpers::correctResourcePath(const std::string &topLev // since we know all (GOTY edition or less) textures end // in .dds, we change the extension bool changedToDds = changeExtensionToDds(correctedPath); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(correctedPath)) + if (vfs->exists(correctedPath)) return correctedPath; // if it turns out that the above wasn't true in all cases (not for vanilla, but maybe mods) // verify, and revert if false (this call succeeds quickly, but fails slowly) - if (changedToDds && Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(origExt)) + if (changedToDds && vfs->exists(origExt)) return origExt; // fall back to a resource in the top level directory if it exists std::string fallback = topLevelDirectory + "\\" + getBasename(correctedPath); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(fallback)) + if (vfs->exists(fallback)) return fallback; if (changedToDds) { fallback = topLevelDirectory + "\\" + getBasename(origExt); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(fallback)) + if (vfs->exists(fallback)) return fallback; } return correctedPath; } -std::string Misc::ResourceHelpers::correctTexturePath(const std::string &resPath) +std::string Misc::ResourceHelpers::correctTexturePath(const std::string &resPath, const VFS::Manager* vfs) { static const std::string dir = "textures"; - return correctResourcePath(dir, resPath); + return correctResourcePath(dir, resPath, vfs); } -std::string Misc::ResourceHelpers::correctIconPath(const std::string &resPath) +std::string Misc::ResourceHelpers::correctIconPath(const std::string &resPath, const VFS::Manager* vfs) { static const std::string dir = "icons"; - return correctResourcePath(dir, resPath); + return correctResourcePath(dir, resPath, vfs); } -std::string Misc::ResourceHelpers::correctBookartPath(const std::string &resPath) +std::string Misc::ResourceHelpers::correctBookartPath(const std::string &resPath, const VFS::Manager* vfs) { static const std::string dir = "bookart"; - std::string image = correctResourcePath(dir, resPath); + std::string image = correctResourcePath(dir, resPath, vfs); return image; } -std::string Misc::ResourceHelpers::correctBookartPath(const std::string &resPath, int width, int height) +std::string Misc::ResourceHelpers::correctBookartPath(const std::string &resPath, int width, int height, const VFS::Manager* vfs) { - std::string image = correctBookartPath(resPath); + std::string image = correctBookartPath(resPath, vfs); // Apparently a bug with some morrowind versions, they reference the image without the size suffix. // So if the image isn't found, try appending the size. - if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(image)) + if (!vfs->exists(image)) { std::stringstream str; str << image.substr(0, image.rfind('.')) << "_" << width << "_" << height << image.substr(image.rfind('.')); - image = Misc::ResourceHelpers::correctBookartPath(str.str()); + image = Misc::ResourceHelpers::correctBookartPath(str.str(), vfs); } return image; } -std::string Misc::ResourceHelpers::correctActorModelPath(const std::string &resPath) +std::string Misc::ResourceHelpers::correctActorModelPath(const std::string &resPath, const VFS::Manager* vfs) { std::string mdlname = resPath; std::string::size_type p = mdlname.rfind('\\'); @@ -132,7 +134,7 @@ std::string Misc::ResourceHelpers::correctActorModelPath(const std::string &resP mdlname.insert(mdlname.begin()+p+1, 'x'); else mdlname.insert(mdlname.begin(), 'x'); - if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(mdlname)) + if(!vfs->exists(mdlname)) { return resPath; } diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index 2ce3dce1e9..fa50cce228 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -3,18 +3,26 @@ #include +namespace VFS +{ + class Manager; +} + namespace Misc { + // Workarounds for messy resource handling in vanilla morrowind + // The below functions are provided on a opt-in basis, instead of built into the VFS, + // so we have the opportunity to use proper resource handling for content created in OpenMW-CS. namespace ResourceHelpers { bool changeExtensionToDds(std::string &path); - std::string correctResourcePath(const std::string &topLevelDirectory, const std::string &resPath); - std::string correctTexturePath(const std::string &resPath); - std::string correctIconPath(const std::string &resPath); - std::string correctBookartPath(const std::string &resPath); - std::string correctBookartPath(const std::string &resPath, int width, int height); - /// Uses "xfoo.nif" instead of "foo.nif" if available - std::string correctActorModelPath(const std::string &resPath); + std::string correctResourcePath(const std::string &topLevelDirectory, const std::string &resPath, const VFS::Manager* vfs); + std::string correctTexturePath(const std::string &resPath, const VFS::Manager* vfs); + std::string correctIconPath(const std::string &resPath, const VFS::Manager* vfs); + std::string correctBookartPath(const std::string &resPath, const VFS::Manager* vfs); + std::string correctBookartPath(const std::string &resPath, int width, int height, const VFS::Manager* vfs); + /// Use "xfoo.nif" instead of "foo.nif" if available + std::string correctActorModelPath(const std::string &resPath, const VFS::Manager* vfs); } } diff --git a/libs/openengine/misc/rng.cpp b/components/misc/rng.cpp similarity index 93% rename from libs/openengine/misc/rng.cpp rename to components/misc/rng.cpp index 3d50400df0..df0fc687e8 100644 --- a/libs/openengine/misc/rng.cpp +++ b/components/misc/rng.cpp @@ -2,8 +2,8 @@ #include #include -namespace OEngine { -namespace Misc { +namespace Misc +{ void Rng::init() { @@ -26,4 +26,3 @@ namespace Misc { } } -} diff --git a/libs/openengine/misc/rng.hpp b/components/misc/rng.hpp similarity index 88% rename from libs/openengine/misc/rng.hpp rename to components/misc/rng.hpp index 4e1da17e10..01fcdd763d 100644 --- a/libs/openengine/misc/rng.hpp +++ b/components/misc/rng.hpp @@ -1,9 +1,8 @@ -#ifndef MISC_RNG_H -#define MISC_RNG_H +#ifndef OPENMW_COMPONENTS_MISC_RNG_H +#define OPENMW_COMPONENTS_MISC_RNG_H #include -namespace OEngine { namespace Misc { @@ -30,7 +29,6 @@ public: static int roll0to99() { return rollDice(100); } }; -} } #endif diff --git a/components/misc/stringops.cpp b/components/misc/stringops.cpp deleted file mode 100644 index 723c1c1e0b..0000000000 --- a/components/misc/stringops.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "stringops.hpp" - -namespace Misc -{ - -std::locale StringUtils::mLocale = std::locale::classic(); - -} diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 04dedb0721..2723527f1d 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -4,22 +4,57 @@ #include #include #include -#include namespace Misc { class StringUtils { - - static std::locale mLocale; struct ci { bool operator()(char x, char y) const { - return std::tolower(x, StringUtils::mLocale) < std::tolower(y, StringUtils::mLocale); + return toLower(x) < toLower(y); } }; public: + + /// Plain and simple locale-unaware toLower. Anything from A to Z is lower-cased, multibyte characters are unchanged. + /// Don't use std::tolower(char, locale&) because that is abysmally slow. + /// Don't use tolower(int) because that depends on global locale. + static char toLower(char c) + { + switch(c) + { + case 'A':return 'a'; + case 'B':return 'b'; + case 'C':return 'c'; + case 'D':return 'd'; + case 'E':return 'e'; + case 'F':return 'f'; + case 'G':return 'g'; + case 'H':return 'h'; + case 'I':return 'i'; + case 'J':return 'j'; + case 'K':return 'k'; + case 'L':return 'l'; + case 'M':return 'm'; + case 'N':return 'n'; + case 'O':return 'o'; + case 'P':return 'p'; + case 'Q':return 'q'; + case 'R':return 'r'; + case 'S':return 's'; + case 'T':return 't'; + case 'U':return 'u'; + case 'V':return 'v'; + case 'W':return 'w'; + case 'X':return 'x'; + case 'Y':return 'y'; + case 'Z':return 'z'; + default:return c; + }; + } + static bool ciLess(const std::string &x, const std::string &y) { return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), ci()); } @@ -31,7 +66,7 @@ public: std::string::const_iterator xit = x.begin(); std::string::const_iterator yit = y.begin(); for (; xit != x.end(); ++xit, ++yit) { - if (std::tolower(*xit, mLocale) != std::tolower(*yit, mLocale)) { + if (toLower(*xit) != toLower(*yit)) { return false; } } @@ -45,7 +80,7 @@ public: for(;xit != x.end() && yit != y.end() && len > 0;++xit,++yit,--len) { int res = *xit - *yit; - if(res != 0 && std::tolower(*xit, mLocale) != std::tolower(*yit, mLocale)) + if(res != 0 && toLower(*xit) != toLower(*yit)) return (res > 0) ? 1 : -1; } if(len > 0) @@ -59,17 +94,17 @@ public: } /// Transforms input string to lower case w/o copy - static std::string &toLower(std::string &inout) { + static void lowerCaseInPlace(std::string &inout) { for (unsigned int i=0; i - -using namespace std; - -#include "../slice_array.hpp" - -int main() -{ - Misc::SString s, t; - s = Misc::SString("hello"); - cout << s.toString() << ", len=" << s.length << endl; - cout << (s=="hel") << (s=="hell") << (s=="hello") << endl; - t = s; - - s = Misc::SString("othello"+2, 4); - cout << s.toString() << ", len=" << s.length << endl; - cout << (s=="hel") << (s=="hell") << (s=="hello") << endl; - - cout << (s==t) << (Misc::SString("hello")==t) << endl; - - const int arr[4] = {1,2,3,4}; - - Misc::IntArray ia(arr,4); - - cout << ia.length << " " << ia.ptr[2] << endl; - - return 0; -} diff --git a/components/misc/tests/strops_test.cpp b/components/misc/tests/strops_test.cpp deleted file mode 100644 index 24ab8a298a..0000000000 --- a/components/misc/tests/strops_test.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include - -#include "../stringops.hpp" - -int main() -{ - assert(Misc::begins("abc", "a")); - assert(Misc::begins("abc", "ab")); - assert(Misc::begins("abc", "abc")); - assert(Misc::begins("abcd", "abc")); - - assert(!Misc::begins("abc", "b")); - assert(!Misc::begins("abc", "bc")); - assert(!Misc::begins("abc", "bcd")); - assert(!Misc::begins("abc", "abcd")); - - assert(Misc::ibegins("Abc", "a")); - assert(Misc::ibegins("aBc", "ab")); - assert(Misc::ibegins("abC", "abc")); - assert(Misc::ibegins("abcD", "abc")); - - assert(!Misc::ibegins("abc", "b")); - assert(!Misc::ibegins("abc", "bc")); - assert(!Misc::ibegins("abc", "bcd")); - assert(!Misc::ibegins("abc", "abcd")); - - assert(Misc::ends("abc", "c")); - assert(Misc::ends("abc", "bc")); - assert(Misc::ends("abc", "abc")); - assert(Misc::ends("abcd", "abcd")); - - assert(!Misc::ends("abc", "b")); - assert(!Misc::ends("abc", "ab")); - assert(!Misc::ends("abc", "bcd")); - assert(!Misc::ends("abc", "abcd")); - - assert(Misc::iends("Abc", "c")); - assert(Misc::iends("aBc", "bc")); - assert(Misc::iends("abC", "abc")); - assert(Misc::iends("abcD", "abcd")); - - assert(!Misc::iends("abc", "b")); - assert(!Misc::iends("abc", "ab")); - assert(!Misc::iends("abc", "bcd")); - assert(!Misc::iends("abc", "abcd")); - - return 0; -} diff --git a/components/misc/tests/test.sh b/components/misc/tests/test.sh deleted file mode 100755 index 2d07708adc..0000000000 --- a/components/misc/tests/test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -make || exit - -mkdir -p output - -PROGS=*_test - -for a in $PROGS; do - if [ -f "output/$a.out" ]; then - echo "Running $a:" - ./$a | diff output/$a.out - - else - echo "Creating $a.out" - ./$a > "output/$a.out" - git add "output/$a.out" - fi -done diff --git a/components/myguiplatform/additivelayer.cpp b/components/myguiplatform/additivelayer.cpp new file mode 100644 index 0000000000..aa2ef08b3b --- /dev/null +++ b/components/myguiplatform/additivelayer.cpp @@ -0,0 +1,33 @@ +#include "additivelayer.hpp" + +#include +#include + +#include "myguirendermanager.hpp" + +namespace osgMyGUI +{ + + AdditiveLayer::AdditiveLayer() + { + mStateSet = new osg::StateSet; + mStateSet->setAttributeAndModes(new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE)); + } + + AdditiveLayer::~AdditiveLayer() + { + // defined in .cpp file since we can't delete incomplete types + } + + void AdditiveLayer::renderToTarget(MyGUI::IRenderTarget *_target, bool _update) + { + RenderManager& renderManager = static_cast(MyGUI::RenderManager::getInstance()); + + renderManager.setInjectState(mStateSet.get()); + + MyGUI::OverlappedLayer::renderToTarget(_target, _update); + + renderManager.setInjectState(NULL); + } + +} diff --git a/components/myguiplatform/additivelayer.hpp b/components/myguiplatform/additivelayer.hpp new file mode 100644 index 0000000000..f3d47bc826 --- /dev/null +++ b/components/myguiplatform/additivelayer.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_ADDITIVELAYER +#define OPENMW_COMPONENTS_MYGUIPLATFORM_ADDITIVELAYER + +#include + +#include + +namespace osg +{ + class StateSet; +} + +namespace osgMyGUI +{ + + /// @brief A Layer rendering with additive blend mode. + class AdditiveLayer : public MyGUI::OverlappedLayer + { + public: + MYGUI_RTTI_DERIVED( AdditiveLayer ) + + AdditiveLayer(); + ~AdditiveLayer(); + + virtual void renderToTarget(MyGUI::IRenderTarget* _target, bool _update); + + private: + osg::ref_ptr mStateSet; + }; + +} + +#endif diff --git a/components/myguiplatform/myguidatamanager.cpp b/components/myguiplatform/myguidatamanager.cpp new file mode 100644 index 0000000000..69bc3878df --- /dev/null +++ b/components/myguiplatform/myguidatamanager.cpp @@ -0,0 +1,64 @@ +#include "myguidatamanager.hpp" + +#include + +#include +#include + +#include + +namespace osgMyGUI +{ + +void DataManager::setResourcePath(const std::string &path) +{ + mResourcePath = path; +} + +MyGUI::IDataStream *DataManager::getData(const std::string &name) +{ + std::string fullpath = getDataPath(name); + std::auto_ptr stream; + stream.reset(new boost::filesystem::ifstream); + stream->open(fullpath, std::ios::binary); + if (stream->fail()) + { + std::cerr << "DataManager::getData: Failed to open '" << name << "'" << std::endl; + return NULL; + } + return new MyGUI::DataFileStream(stream.release()); +} + +void DataManager::freeData(MyGUI::IDataStream *data) +{ + delete data; +} + +bool DataManager::isDataExist(const std::string &name) +{ + std::string fullpath = mResourcePath + "/" + name; + return boost::filesystem::exists(fullpath); +} + +const MyGUI::VectorString &DataManager::getDataListNames(const std::string &pattern) +{ + // TODO: pattern matching (unused?) + static MyGUI::VectorString strings; + strings.clear(); + strings.push_back(getDataPath(pattern)); + return strings; +} + +const std::string &DataManager::getDataPath(const std::string &name) +{ + static std::string result; + result.clear(); + if (!isDataExist(name)) + { + return result; + } + result = mResourcePath + "/" + name; + return result; +} + +} diff --git a/components/myguiplatform/myguidatamanager.hpp b/components/myguiplatform/myguidatamanager.hpp new file mode 100644 index 0000000000..5002f0fb75 --- /dev/null +++ b/components/myguiplatform/myguidatamanager.hpp @@ -0,0 +1,50 @@ +#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIDATAMANAGER_H +#define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIDATAMANAGER_H + +#include + +namespace osgMyGUI +{ + + +class DataManager : public MyGUI::DataManager +{ +public: + void initialise() {} + void shutdown() {} + + void setResourcePath(const std::string& path); + + /** Get data stream from specified resource name. + @param _name Resource name (usually file name). + */ + virtual MyGUI::IDataStream* getData(const std::string& _name); + + /** Free data stream. + @param _data Data stream. + */ + virtual void freeData(MyGUI::IDataStream* _data); + + /** Is data with specified name exist. + @param _name Resource name. + */ + virtual bool isDataExist(const std::string& _name); + + /** Get all data names with names that matches pattern. + @param _pattern Pattern to match (for example "*.layout"). + */ + virtual const MyGUI::VectorString& getDataListNames(const std::string& _pattern); + + /** Get full path to data. + @param _name Resource name. + @return Return full path to specified data. + */ + virtual const std::string& getDataPath(const std::string& _name); + +private: + std::string mResourcePath; +}; + +} + +#endif diff --git a/libs/openengine/gui/loglistener.cpp b/components/myguiplatform/myguiloglistener.cpp similarity index 78% rename from libs/openengine/gui/loglistener.cpp rename to components/myguiplatform/myguiloglistener.cpp index da36b90a2f..b36e0d852c 100644 --- a/libs/openengine/gui/loglistener.cpp +++ b/components/myguiplatform/myguiloglistener.cpp @@ -1,11 +1,11 @@ -#include "loglistener.hpp" +#include "myguiloglistener.hpp" #include #include #include -namespace MyGUI +namespace osgMyGUI { void CustomLogListener::open() { @@ -24,7 +24,7 @@ namespace MyGUI mStream.flush(); } - void CustomLogListener::log(const std::string& _section, LogLevel _level, const struct tm* _time, const std::string& _message, const char* _file, int _line) + void CustomLogListener::log(const std::string& _section, MyGUI::LogLevel _level, const struct tm* _time, const std::string& _message, const char* _file, int _line) { if (mStream.is_open()) { diff --git a/components/myguiplatform/myguiloglistener.hpp b/components/myguiplatform/myguiloglistener.hpp new file mode 100644 index 0000000000..70dfc4ecfd --- /dev/null +++ b/components/myguiplatform/myguiloglistener.hpp @@ -0,0 +1,69 @@ +#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_LOGLISTENER_H +#define OPENMW_COMPONENTS_MYGUIPLATFORM_LOGLISTENER_H + +#include +#include + +#include +#include +#include +#include + +namespace osgMyGUI +{ + + /// \brief Custom MyGUI::ILogListener interface implementation + /// being able to portably handle UTF-8 encoded path. + /// \todo try patching MyGUI to make this easier + class CustomLogListener : public MyGUI::ILogListener + { + public: + CustomLogListener(const std::string &name) + : mFileName(name) + {} + + ~CustomLogListener() {} + + virtual void open(); + virtual void close(); + virtual void flush(); + + virtual void log(const std::string& _section, MyGUI::LogLevel _level, const struct tm* _time, const std::string& _message, const char* _file, int _line); + + const std::string& getFileName() const { return mFileName; } + + private: + boost::filesystem::ofstream mStream; + std::string mFileName; + }; + + /// \brief Helper class holding data that required during + /// MyGUI log creation + class LogFacility + { + MyGUI::ConsoleLogListener mConsole; + CustomLogListener mFile; + MyGUI::LevelLogFilter mFilter; + MyGUI::LogSource mSource; + + public: + + LogFacility(const std::string &output, bool console) + : mFile(output) + { + mConsole.setEnabled(console); + mFilter.setLoggingLevel(MyGUI::LogLevel::Info); + + mSource.addLogListener(&mFile); + mSource.addLogListener(&mConsole); + mSource.setLogFilter(&mFilter); + + mSource.open(); + } + + MyGUI::LogSource *getSource() { return &mSource; } + }; + +} + +#endif diff --git a/components/myguiplatform/myguiplatform.cpp b/components/myguiplatform/myguiplatform.cpp new file mode 100644 index 0000000000..22b88438f6 --- /dev/null +++ b/components/myguiplatform/myguiplatform.cpp @@ -0,0 +1,64 @@ +#include "myguiplatform.hpp" + +#include "myguirendermanager.hpp" +#include "myguidatamanager.hpp" +#include "myguiloglistener.hpp" + +namespace osgMyGUI +{ + +Platform::Platform(osgViewer::Viewer *viewer, osg::Group *guiRoot, Resource::TextureManager *textureManager, float uiScalingFactor) + : mRenderManager(nullptr) + , mDataManager(nullptr) + , mLogManager(nullptr) + , mLogFacility(nullptr) +{ + mLogManager = new MyGUI::LogManager(); + mRenderManager = new RenderManager(viewer, guiRoot, textureManager, uiScalingFactor); + mDataManager = new DataManager(); +} + +Platform::~Platform() +{ + delete mRenderManager; + mRenderManager = nullptr; + delete mDataManager; + mDataManager = nullptr; + delete mLogManager; + mLogManager = nullptr; + delete mLogFacility; + mLogFacility = nullptr; +} + +void Platform::initialise(const std::string &resourcePath, const std::string &_logName) +{ + if (!_logName.empty() && !mLogFacility) + { + mLogFacility = new LogFacility(_logName, false); + mLogManager->addLogSource(mLogFacility->getSource()); + } + + mDataManager->setResourcePath(resourcePath); + + mRenderManager->initialise(); + mDataManager->initialise(); +} + +void Platform::shutdown() +{ + mRenderManager->shutdown(); + mDataManager->shutdown(); +} + +RenderManager *Platform::getRenderManagerPtr() +{ + return mRenderManager; +} + +DataManager *Platform::getDataManagerPtr() +{ + return mDataManager; +} + + +} diff --git a/components/myguiplatform/myguiplatform.hpp b/components/myguiplatform/myguiplatform.hpp new file mode 100644 index 0000000000..56562e12a6 --- /dev/null +++ b/components/myguiplatform/myguiplatform.hpp @@ -0,0 +1,54 @@ +#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIPLATFORM_H +#define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIPLATFORM_H + +#include + +namespace osgViewer +{ + class Viewer; +} +namespace osg +{ + class Group; +} +namespace Resource +{ + class TextureManager; +} +namespace MyGUI +{ + class LogManager; +} + +namespace osgMyGUI +{ + + class RenderManager; + class DataManager; + class LogFacility; + + class Platform + { + public: + Platform(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::TextureManager* textureManager, float uiScalingFactor); + + ~Platform(); + + void initialise(const std::string& resourcePath, const std::string& _logName = "MyGUI.log"); + + void shutdown(); + + RenderManager* getRenderManagerPtr(); + + DataManager* getDataManagerPtr(); + + private: + RenderManager* mRenderManager; + DataManager* mDataManager; + MyGUI::LogManager* mLogManager; + LogFacility* mLogFacility; + }; + +} + +#endif diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp new file mode 100644 index 0000000000..5bd56dc8f4 --- /dev/null +++ b/components/myguiplatform/myguirendermanager.cpp @@ -0,0 +1,564 @@ +#include "myguirendermanager.hpp" + +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include + +#include "myguitexture.hpp" + +#define MYGUI_PLATFORM_LOG_SECTION "Platform" +#define MYGUI_PLATFORM_LOG(level, text) MYGUI_LOGGING(MYGUI_PLATFORM_LOG_SECTION, level, text) + +#define MYGUI_PLATFORM_EXCEPT(dest) do { \ + MYGUI_PLATFORM_LOG(Critical, dest); \ + MYGUI_DBG_BREAK;\ + std::ostringstream stream; \ + stream << dest << "\n"; \ + MYGUI_BASE_EXCEPT(stream.str().c_str(), "MyGUI"); \ +} while(0) + +#define MYGUI_PLATFORM_ASSERT(exp, dest) do { \ + if ( ! (exp) ) \ + { \ + MYGUI_PLATFORM_LOG(Critical, dest); \ + MYGUI_DBG_BREAK;\ + std::ostringstream stream; \ + stream << dest << "\n"; \ + MYGUI_BASE_EXCEPT(stream.str().c_str(), "MyGUI"); \ + } \ +} while(0) + +namespace osgMyGUI +{ + +class Drawable : public osg::Drawable { + osgMyGUI::RenderManager *mParent; + osg::ref_ptr mStateSet; + +public: + + // Stage 0: update widget animations and controllers. Run during the Update traversal. + class FrameUpdate : public osg::Drawable::UpdateCallback + { + public: + FrameUpdate() + : mRenderManager(NULL) + { + } + + void setRenderManager(osgMyGUI::RenderManager* renderManager) + { + mRenderManager = renderManager; + } + + virtual void update(osg::NodeVisitor*, osg::Drawable*) + { + if (mRenderManager) + mRenderManager->update(); + } + + private: + osgMyGUI::RenderManager* mRenderManager; + }; + + // Stage 1: collect draw calls. Run during the Cull traversal. + class CollectDrawCalls : public osg::Drawable::CullCallback + { + public: + CollectDrawCalls() + : mRenderManager(NULL) + { + } + + void setRenderManager(osgMyGUI::RenderManager* renderManager) + { + mRenderManager = renderManager; + } + + virtual bool cull(osg::NodeVisitor*, osg::Drawable*, osg::State*) const + { + if (!mRenderManager) + return false; + + mRenderManager->collectDrawCalls(); + return false; + } + + private: + osgMyGUI::RenderManager* mRenderManager; + }; + + // Stage 2: execute the draw calls. Run during the Draw traversal. May run in parallel with the update traversal of the next frame. + virtual void drawImplementation(osg::RenderInfo &renderInfo) const + { + osg::State *state = renderInfo.getState(); + + state->pushStateSet(mStateSet); + state->apply(); + + state->disableAllVertexArrays(); + state->setClientActiveTextureUnit(0); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + mReadFrom = (mReadFrom+1)%sNumBuffers; + const std::vector& vec = mBatchVector[mReadFrom]; + for (std::vector::const_iterator it = vec.begin(); it != vec.end(); ++it) + { + const Batch& batch = *it; + osg::VertexBufferObject *vbo = batch.mVertexBuffer; + + if (batch.mStateSet) + { + state->pushStateSet(batch.mStateSet); + state->apply(); + } + + osg::Texture2D* texture = batch.mTexture; + if(texture) + state->applyTextureAttribute(0, texture); + + // VBOs disabled due to crash in OSG: http://forum.openscenegraph.org/viewtopic.php?t=14909 + osg::GLBufferObject* bufferobject = 0;//state->isVertexBufferObjectSupported() ? vbo->getOrCreateGLBufferObject(state->getContextID()) : 0; + if (0)//bufferobject) + { + state->bindVertexBufferObject(bufferobject); + + glVertexPointer(3, GL_FLOAT, sizeof(MyGUI::Vertex), (char*)NULL); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(MyGUI::Vertex), (char*)NULL + 12); + glTexCoordPointer(2, GL_FLOAT, sizeof(MyGUI::Vertex), (char*)NULL + 16); + } + else + { + glVertexPointer(3, GL_FLOAT, sizeof(MyGUI::Vertex), (char*)vbo->getArray(0)->getDataPointer()); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(MyGUI::Vertex), (char*)vbo->getArray(0)->getDataPointer() + 12); + glTexCoordPointer(2, GL_FLOAT, sizeof(MyGUI::Vertex), (char*)vbo->getArray(0)->getDataPointer() + 16); + } + + glDrawArrays(GL_TRIANGLES, 0, batch.mVertexCount); + + if (batch.mStateSet) + { + state->popStateSet(); + state->apply(); + } + } + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + state->popStateSet(); + + state->unbindVertexBufferObject(); + state->dirtyAllVertexArrays(); + state->disableAllVertexArrays(); + } + +public: + Drawable(osgMyGUI::RenderManager *parent = nullptr) + : mParent(parent) + , mWriteTo(0) + , mReadFrom(0) + { + setSupportsDisplayList(false); + + osg::ref_ptr collectDrawCalls = new CollectDrawCalls; + collectDrawCalls->setRenderManager(mParent); + setCullCallback(collectDrawCalls); + + osg::ref_ptr frameUpdate = new FrameUpdate; + frameUpdate->setRenderManager(mParent); + setUpdateCallback(frameUpdate); + + mStateSet = new osg::StateSet; + mStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + mStateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON); + mStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + mStateSet->setMode(GL_BLEND, osg::StateAttribute::ON); + } + Drawable(const Drawable ©, const osg::CopyOp ©op=osg::CopyOp::SHALLOW_COPY) + : osg::Drawable(copy, copyop) + , mParent(copy.mParent) + , mStateSet(copy.mStateSet) + , mWriteTo(0) + , mReadFrom(0) + { + } + + // Defines the necessary information for a draw call + struct Batch + { + // May be empty + osg::ref_ptr mTexture; + + osg::ref_ptr mVertexBuffer; + // need to hold on to this too as the mVertexBuffer does not hold a ref to its own array + osg::ref_ptr mArray; + + // optional + osg::ref_ptr mStateSet; + + size_t mVertexCount; + }; + + void addBatch(const Batch& batch) + { + mBatchVector[mWriteTo].push_back(batch); + } + + void clear() + { + mWriteTo = (mWriteTo+1)%sNumBuffers; + mBatchVector[mWriteTo].clear(); + } + + META_Object(osgMyGUI, Drawable) + +private: + // 2 would be enough in most cases, use 4 to get stereo working + static const int sNumBuffers = 4; + + // double buffering approach, to avoid the need for synchronization with the draw thread + std::vector mBatchVector[sNumBuffers]; + + int mWriteTo; + mutable int mReadFrom; +}; + +class OSGVertexBuffer : public MyGUI::IVertexBuffer +{ + osg::ref_ptr mBuffer; + osg::ref_ptr mVertexArray; + + size_t mNeedVertexCount; + + bool mQueuedForDrawing; + + void destroy(); + void create(); + +public: + OSGVertexBuffer(); + virtual ~OSGVertexBuffer(); + + void markAsQueuedForDrawing(); + + virtual void setVertexCount(size_t count); + virtual size_t getVertexCount(); + + virtual MyGUI::Vertex *lock(); + virtual void unlock(); + +/*internal:*/ + + osg::VertexBufferObject *getBuffer() const { return mBuffer.get(); } + osg::UByteArray *getArray() const { return mVertexArray.get(); } +}; + +OSGVertexBuffer::OSGVertexBuffer() + : mNeedVertexCount(0) + , mQueuedForDrawing(false) +{ +} + +OSGVertexBuffer::~OSGVertexBuffer() +{ + destroy(); +} + +void OSGVertexBuffer::markAsQueuedForDrawing() +{ + mQueuedForDrawing = true; +} + +void OSGVertexBuffer::setVertexCount(size_t count) +{ + if(count == mNeedVertexCount) + return; + + mNeedVertexCount = count; +} + +size_t OSGVertexBuffer::getVertexCount() +{ + return mNeedVertexCount; +} + +MyGUI::Vertex *OSGVertexBuffer::lock() +{ + if (mQueuedForDrawing || !mVertexArray) + { + // Force recreating the buffer, to make sure we are not modifying a buffer currently + // queued for rendering in the last frame's draw thread. + // a more efficient solution might be double buffering + destroy(); + create(); + mQueuedForDrawing = false; + } + else + { + mVertexArray->resize(mNeedVertexCount * sizeof(MyGUI::Vertex)); + } + + MYGUI_PLATFORM_ASSERT(mBuffer.valid(), "Vertex buffer is not created"); + + return (MyGUI::Vertex*)&(*mVertexArray)[0]; +} + +void OSGVertexBuffer::unlock() +{ + mVertexArray->dirty(); + mBuffer->dirty(); +} + +void OSGVertexBuffer::destroy() +{ + mBuffer = nullptr; + mVertexArray = nullptr; +} + +void OSGVertexBuffer::create() +{ + MYGUI_PLATFORM_ASSERT(!mBuffer.valid(), "Vertex buffer already exist"); + + mVertexArray = new osg::UByteArray(mNeedVertexCount*sizeof(MyGUI::Vertex)); + + mBuffer = new osg::VertexBufferObject; + mBuffer->setDataVariance(osg::Object::DYNAMIC); + mBuffer->setUsage(GL_DYNAMIC_DRAW); + // NB mBuffer does not own the array + mBuffer->setArray(0, mVertexArray.get()); +} + +// --------------------------------------------------------------------------- + +RenderManager::RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::TextureManager* textureManager, float scalingFactor) + : mViewer(viewer) + , mSceneRoot(sceneroot) + , mTextureManager(textureManager) + , mUpdate(false) + , mIsInitialise(false) + , mInvScalingFactor(1.f) + , mInjectState(NULL) +{ + if (scalingFactor != 0.f) + mInvScalingFactor = 1.f / scalingFactor; +} + +RenderManager::~RenderManager() +{ + MYGUI_PLATFORM_LOG(Info, "* Shutdown: "<removeChild(mGuiRoot.get()); + mGuiRoot = nullptr; + mSceneRoot = nullptr; + mViewer = nullptr; + + destroyAllResources(); + + MYGUI_PLATFORM_LOG(Info, getClassTypeName()<<" successfully shutdown"); + mIsInitialise = false; +} + + +void RenderManager::initialise() +{ + MYGUI_PLATFORM_ASSERT(!mIsInitialise, getClassTypeName()<<" initialised twice"); + MYGUI_PLATFORM_LOG(Info, "* Initialise: "< geode = new osg::Geode; + geode->addDrawable(mDrawable.get()); + + osg::ref_ptr camera = new osg::Camera(); + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + camera->setProjectionResizePolicy(osg::Camera::FIXED); + camera->setProjectionMatrix(osg::Matrix::identity()); + camera->setViewMatrix(osg::Matrix::identity()); + camera->setRenderOrder(osg::Camera::POST_RENDER); + camera->setClearMask(GL_NONE); + geode->setCullingActive(false); + camera->addChild(geode.get()); + + mGuiRoot = camera; + mSceneRoot->addChild(mGuiRoot.get()); + + osg::ref_ptr vp = mViewer->getCamera()->getViewport(); + setViewSize(vp->width(), vp->height()); + + MYGUI_PLATFORM_LOG(Info, getClassTypeName()<<" successfully initialized"); + mIsInitialise = true; +} + +void RenderManager::shutdown() +{ +} + +MyGUI::IVertexBuffer* RenderManager::createVertexBuffer() +{ + return new OSGVertexBuffer(); +} + +void RenderManager::destroyVertexBuffer(MyGUI::IVertexBuffer *buffer) +{ + delete buffer; +} + + +void RenderManager::begin() +{ + mDrawable->clear(); + // variance will be recomputed based on textures being rendered in this frame + mDrawable->setDataVariance(osg::Object::STATIC); +} + +void RenderManager::doRender(MyGUI::IVertexBuffer *buffer, MyGUI::ITexture *texture, size_t count) +{ + Drawable::Batch batch; + batch.mVertexCount = count; + batch.mVertexBuffer = static_cast(buffer)->getBuffer(); + static_cast(buffer)->markAsQueuedForDrawing(); + batch.mArray = static_cast(buffer)->getArray(); + if (texture) + { + batch.mTexture = static_cast(texture)->getTexture(); + if (batch.mTexture->getDataVariance() == osg::Object::DYNAMIC) + mDrawable->setDataVariance(osg::Object::DYNAMIC); // only for this frame, reset in begin() + } + if (mInjectState) + batch.mStateSet = mInjectState; + + mDrawable->addBatch(batch); +} + +void RenderManager::setInjectState(osg::StateSet* stateSet) +{ + mInjectState = stateSet; +} + +void RenderManager::end() +{ +} + +void RenderManager::update() +{ + static MyGUI::Timer timer; + static unsigned long last_time = timer.getMilliseconds(); + unsigned long now_time = timer.getMilliseconds(); + unsigned long time = now_time - last_time; + + onFrameEvent((float)((double)(time) / (double)1000)); + + last_time = now_time; +} + +void RenderManager::collectDrawCalls() +{ + begin(); + onRenderToTarget(this, mUpdate); + end(); + + mUpdate = false; +} + +void RenderManager::setViewSize(int width, int height) +{ + if(width < 1) width = 1; + if(height < 1) height = 1; + + mGuiRoot->setViewport(0, 0, width, height); + + mViewSize.set(width * mInvScalingFactor, height * mInvScalingFactor); + + mInfo.maximumDepth = 1; + mInfo.hOffset = 0; + mInfo.vOffset = 0; + mInfo.aspectCoef = float(mViewSize.height) / float(mViewSize.width); + mInfo.pixScaleX = 1.0f / float(mViewSize.width); + mInfo.pixScaleY = 1.0f / float(mViewSize.height); + + onResizeView(mViewSize); + mUpdate = true; +} + + +bool RenderManager::isFormatSupported(MyGUI::PixelFormat /*format*/, MyGUI::TextureUsage /*usage*/) +{ + return true; +} + +MyGUI::ITexture* RenderManager::createTexture(const std::string &name) +{ + MapTexture::iterator item = mTextures.find(name); + if (item != mTextures.end()) + { + delete item->second; + mTextures.erase(item); + } + + OSGTexture* texture = new OSGTexture(name, mTextureManager); + mTextures.insert(std::make_pair(name, texture)); + return texture; +} + +void RenderManager::destroyTexture(MyGUI::ITexture *texture) +{ + if(texture == nullptr) + return; + + MapTexture::iterator item = mTextures.find(texture->getName()); + MYGUI_PLATFORM_ASSERT(item != mTextures.end(), "Texture '"<getName()<<"' not found"); + + mTextures.erase(item); + delete texture; +} + +MyGUI::ITexture* RenderManager::getTexture(const std::string &name) +{ + if (name.empty()) + return NULL; + + MapTexture::const_iterator item = mTextures.find(name); + if(item == mTextures.end()) + { + MyGUI::ITexture* tex = createTexture(name); + tex->loadFromFile(name); + return tex; + } + return item->second; +} + +void RenderManager::destroyAllResources() +{ + for (MapTexture::iterator it = mTextures.begin(); it != mTextures.end(); ++it) + delete it->second; + mTextures.clear(); +} + +bool RenderManager::checkTexture(MyGUI::ITexture* _texture) +{ + // We support external textures that aren't registered via this manager, so can't implement this method sensibly. + return true; +} + +} diff --git a/components/myguiplatform/myguirendermanager.hpp b/components/myguiplatform/myguirendermanager.hpp new file mode 100644 index 0000000000..f2251cdb04 --- /dev/null +++ b/components/myguiplatform/myguirendermanager.hpp @@ -0,0 +1,117 @@ +#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIRENDERMANAGER_H +#define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUIRENDERMANAGER_H + +#include + +#include + +namespace Resource +{ + class TextureManager; +} + +namespace osgViewer +{ + class Viewer; +} + +namespace osg +{ + class Group; + class Camera; + class RenderInfo; + class StateSet; +} + +namespace osgMyGUI +{ + +class Drawable; + +class RenderManager : public MyGUI::RenderManager, public MyGUI::IRenderTarget +{ + osg::ref_ptr mViewer; + osg::ref_ptr mSceneRoot; + osg::ref_ptr mDrawable; + Resource::TextureManager* mTextureManager; + + MyGUI::IntSize mViewSize; + bool mUpdate; + MyGUI::VertexColourType mVertexFormat; + MyGUI::RenderTargetInfo mInfo; + + typedef std::map MapTexture; + MapTexture mTextures; + + bool mIsInitialise; + + osg::ref_ptr mGuiRoot; + + float mInvScalingFactor; + + osg::StateSet* mInjectState; + + void destroyAllResources(); + +public: + RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, Resource::TextureManager* textureManager, float scalingFactor); + virtual ~RenderManager(); + + void initialise(); + void shutdown(); + + void setScalingFactor(float factor); + + static RenderManager& getInstance() { return *getInstancePtr(); } + static RenderManager* getInstancePtr() + { return static_cast(MyGUI::RenderManager::getInstancePtr()); } + + /** @see RenderManager::getViewSize */ + virtual const MyGUI::IntSize& getViewSize() const { return mViewSize; } + + /** @see RenderManager::getVertexFormat */ + virtual MyGUI::VertexColourType getVertexFormat() { return mVertexFormat; } + + /** @see RenderManager::isFormatSupported */ + virtual bool isFormatSupported(MyGUI::PixelFormat format, MyGUI::TextureUsage usage); + + /** @see RenderManager::createVertexBuffer */ + virtual MyGUI::IVertexBuffer* createVertexBuffer(); + /** @see RenderManager::destroyVertexBuffer */ + virtual void destroyVertexBuffer(MyGUI::IVertexBuffer *buffer); + + /** @see RenderManager::createTexture */ + virtual MyGUI::ITexture* createTexture(const std::string &name); + /** @see RenderManager::destroyTexture */ + virtual void destroyTexture(MyGUI::ITexture* _texture); + /** @see RenderManager::getTexture */ + virtual MyGUI::ITexture* getTexture(const std::string &name); + + // Called by the update traversal + void update(); + + // Called by the cull traversal + /** @see IRenderTarget::begin */ + virtual void begin(); + /** @see IRenderTarget::end */ + virtual void end(); + /** @see IRenderTarget::doRender */ + virtual void doRender(MyGUI::IVertexBuffer *buffer, MyGUI::ITexture *texture, size_t count); + + /** specify a StateSet to inject for rendering. The StateSet will be used by future doRender calls until you reset it to NULL again. */ + void setInjectState(osg::StateSet* stateSet); + + /** @see IRenderTarget::getInfo */ + virtual const MyGUI::RenderTargetInfo& getInfo() { return mInfo; } + + bool checkTexture(MyGUI::ITexture* _texture); + +/*internal:*/ + + void collectDrawCalls(); + void setViewSize(int width, int height); +}; + +} + +#endif diff --git a/components/myguiplatform/myguitexture.cpp b/components/myguiplatform/myguitexture.cpp new file mode 100644 index 0000000000..50ac5c1f34 --- /dev/null +++ b/components/myguiplatform/myguitexture.cpp @@ -0,0 +1,176 @@ +#include "myguitexture.hpp" + +#include +#include + +#include + +#include + +namespace osgMyGUI +{ + + OSGTexture::OSGTexture(const std::string &name, Resource::TextureManager* textureManager) + : mName(name) + , mTextureManager(textureManager) + , mFormat(MyGUI::PixelFormat::Unknow) + , mUsage(MyGUI::TextureUsage::Default) + , mNumElemBytes(0) + { + } + + OSGTexture::OSGTexture(osg::Texture2D *texture) + : mTextureManager(NULL) + , mTexture(texture) + , mFormat(MyGUI::PixelFormat::Unknow) + , mUsage(MyGUI::TextureUsage::Default) + , mNumElemBytes(0) + { + } + + OSGTexture::~OSGTexture() + { + } + + void OSGTexture::createManual(int width, int height, MyGUI::TextureUsage usage, MyGUI::PixelFormat format) + { + GLenum glfmt = GL_NONE; + size_t numelems = 0; + switch(format.getValue()) + { + case MyGUI::PixelFormat::L8: + glfmt = GL_LUMINANCE; + numelems = 1; + break; + case MyGUI::PixelFormat::L8A8: + glfmt = GL_LUMINANCE_ALPHA; + numelems = 2; + break; + case MyGUI::PixelFormat::R8G8B8: + glfmt = GL_RGB; + numelems = 3; + break; + case MyGUI::PixelFormat::R8G8B8A8: + glfmt = GL_RGBA; + numelems = 4; + break; + } + if(glfmt == GL_NONE) + throw std::runtime_error("Texture format not supported"); + + mTexture = new osg::Texture2D(); + mTexture->setTextureSize(width, height); + mTexture->setSourceFormat(glfmt); + mTexture->setSourceType(GL_UNSIGNED_BYTE); + + mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + mTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + mFormat = format; + mUsage = usage; + mNumElemBytes = numelems; + } + + void OSGTexture::destroy() + { + mTexture = nullptr; + mFormat = MyGUI::PixelFormat::Unknow; + mUsage = MyGUI::TextureUsage::Default; + mNumElemBytes = 0; + } + + void OSGTexture::loadFromFile(const std::string &fname) + { + if (!mTextureManager) + throw std::runtime_error("No texturemanager set"); + + mTexture = mTextureManager->getTexture2D(fname, osg::Texture2D::CLAMP_TO_EDGE, osg::Texture2D::CLAMP_TO_EDGE); + // disable mip-maps + mTexture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR); + + // FIXME + mFormat = MyGUI::PixelFormat::R8G8B8; + mUsage = MyGUI::TextureUsage::Static | MyGUI::TextureUsage::Write; + mNumElemBytes = 3; // FIXME + } + + void OSGTexture::saveToFile(const std::string &fname) + { + std::cerr << "Would save image to file " << fname << std::endl; + } + + int OSGTexture::getWidth() + { + if(!mTexture.valid()) + return 0; + osg::Image *image = mTexture->getImage(); + if(image) return image->s(); + return mTexture->getTextureWidth(); + } + + int OSGTexture::getHeight() + { + if(!mTexture.valid()) + return 0; + osg::Image *image = mTexture->getImage(); + if(image) return image->t(); + return mTexture->getTextureHeight(); + } + + void *OSGTexture::lock(MyGUI::TextureUsage /*access*/) + { + if (!mTexture.valid()) + throw std::runtime_error("Texture is not created"); + if (mLockedImage.valid()) + throw std::runtime_error("Texture already locked"); + + mLockedImage = mTexture->getImage(); + if(!mLockedImage.valid()) + { + mLockedImage = new osg::Image(); + mLockedImage->allocateImage( + mTexture->getTextureWidth(), mTexture->getTextureHeight(), mTexture->getTextureDepth(), + mTexture->getSourceFormat(), mTexture->getSourceType() + ); + } + return mLockedImage->data(); + } + + void OSGTexture::unlock() + { + if (!mLockedImage.valid()) + throw std::runtime_error("Texture not locked"); + + // mTexture might be in use by the draw thread, so create a new texture instead and use that. + osg::ref_ptr newTexture = new osg::Texture2D; + newTexture->setTextureSize(getWidth(), getHeight()); + newTexture->setSourceFormat(mTexture->getSourceFormat()); + newTexture->setSourceType(mTexture->getSourceType()); + newTexture->setFilter(osg::Texture::MIN_FILTER, mTexture->getFilter(osg::Texture::MIN_FILTER)); + newTexture->setFilter(osg::Texture::MAG_FILTER, mTexture->getFilter(osg::Texture::MAG_FILTER)); + newTexture->setWrap(osg::Texture::WRAP_S, mTexture->getWrap(osg::Texture::WRAP_S)); + newTexture->setWrap(osg::Texture::WRAP_T, mTexture->getWrap(osg::Texture::WRAP_T)); + newTexture->setImage(mLockedImage.get()); + // Tell the texture it can get rid of the image for static textures (since + // they aren't expected to update much at all). + newTexture->setUnRefImageDataAfterApply(mUsage.isValue(MyGUI::TextureUsage::Static) ? true : false); + + mTexture = newTexture; + + mLockedImage = nullptr; + } + + bool OSGTexture::isLocked() + { + return mLockedImage.valid(); + } + + // Render-to-texture not currently implemented. + MyGUI::IRenderTarget* OSGTexture::getRenderTarget() + { + return nullptr; + } + +} diff --git a/components/myguiplatform/myguitexture.hpp b/components/myguiplatform/myguitexture.hpp new file mode 100644 index 0000000000..de385e94d6 --- /dev/null +++ b/components/myguiplatform/myguitexture.hpp @@ -0,0 +1,64 @@ +#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUITEXTURE_H +#define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUITEXTURE_H + +#include + +#include + +namespace osg +{ + class Image; + class Texture2D; +} + +namespace Resource +{ + class TextureManager; +} + +namespace osgMyGUI +{ + + class OSGTexture : public MyGUI::ITexture { + std::string mName; + Resource::TextureManager* mTextureManager; + + osg::ref_ptr mLockedImage; + osg::ref_ptr mTexture; + MyGUI::PixelFormat mFormat; + MyGUI::TextureUsage mUsage; + size_t mNumElemBytes; + + public: + OSGTexture(const std::string &name, Resource::TextureManager* textureManager); + OSGTexture(osg::Texture2D* texture); + virtual ~OSGTexture(); + + virtual const std::string& getName() const { return mName; } + + virtual void createManual(int width, int height, MyGUI::TextureUsage usage, MyGUI::PixelFormat format); + virtual void loadFromFile(const std::string &fname); + virtual void saveToFile(const std::string &fname); + + virtual void destroy(); + + virtual void* lock(MyGUI::TextureUsage access); + virtual void unlock(); + virtual bool isLocked(); + + virtual int getWidth(); + virtual int getHeight(); + + virtual MyGUI::PixelFormat getFormat() { return mFormat; } + virtual MyGUI::TextureUsage getUsage() { return mUsage; } + virtual size_t getNumElemBytes() { return mNumElemBytes; } + + virtual MyGUI::IRenderTarget *getRenderTarget(); + + /*internal:*/ + osg::Texture2D *getTexture() const { return mTexture.get(); } + }; + +} + +#endif diff --git a/components/myguiplatform/scalinglayer.cpp b/components/myguiplatform/scalinglayer.cpp new file mode 100644 index 0000000000..ee9de349ed --- /dev/null +++ b/components/myguiplatform/scalinglayer.cpp @@ -0,0 +1,140 @@ +#include "scalinglayer.hpp" + +#include +#include + +namespace osgMyGUI +{ + + /// @brief the ProxyRenderTarget allows to adjust the pixel scale and offset for a "source" render target. + class ProxyRenderTarget : public MyGUI::IRenderTarget + { + public: + /// @param target The target to render to. + /// @param viewSize The size of the underlying layer node to render. + /// @param hoffset The horizontal rendering offset, specified as an offset from the left screen edge in range 0-1. + /// @param voffset The vertical rendering offset, specified as an offset from the top screen edge in range 0-1. + ProxyRenderTarget(MyGUI::IRenderTarget* target, MyGUI::IntSize viewSize, float hoffset, float voffset) + : mTarget(target) + , mViewSize(viewSize) + , mHOffset(hoffset) + , mVOffset(voffset) + { + } + + virtual void begin() + { + mTarget->begin(); + } + + virtual void end() + { + mTarget->end(); + } + + virtual void doRender(MyGUI::IVertexBuffer* _buffer, MyGUI::ITexture* _texture, size_t _count) + { + mTarget->doRender(_buffer, _texture, _count); + } + + virtual const MyGUI::RenderTargetInfo& getInfo() + { + mInfo = mTarget->getInfo(); + mInfo.hOffset = mHOffset; + mInfo.vOffset = mVOffset; + mInfo.pixScaleX = 1.f / mViewSize.width; + mInfo.pixScaleY = 1.f / mViewSize.height; + return mInfo; + } + + private: + MyGUI::IRenderTarget* mTarget; + MyGUI::IntSize mViewSize; + float mHOffset, mVOffset; + MyGUI::RenderTargetInfo mInfo; + }; + + MyGUI::ILayerItem *ScalingLayer::getLayerItemByPoint(int _left, int _top) const + { + screenToLayerCoords(_left, _top); + + return OverlappedLayer::getLayerItemByPoint(_left, _top); + } + + void ScalingLayer::screenToLayerCoords(int& _left, int& _top) const + { + float scale = getScaleFactor(); + if (scale <= 0.f) + return; + + MyGUI::IntSize globalViewSize = MyGUI::RenderManager::getInstance().getViewSize(); + + _left -= globalViewSize.width/2; + _top -= globalViewSize.height/2; + + _left /= scale; + _top /= scale; + + _left += mViewSize.width/2; + _top += mViewSize.height/2; + } + + float ScalingLayer::getScaleFactor() const + { + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + float w = viewSize.width; + float h = viewSize.height; + + float heightScale = (h / mViewSize.height); + float widthScale = (w / mViewSize.width); + return std::min(widthScale, heightScale); + } + + MyGUI::IntPoint ScalingLayer::getPosition(int _left, int _top) const + { + screenToLayerCoords(_left, _top); + return MyGUI::IntPoint(_left, _top); + } + + void ScalingLayer::renderToTarget(MyGUI::IRenderTarget *_target, bool _update) + { + MyGUI::IntSize globalViewSize = MyGUI::RenderManager::getInstance().getViewSize(); + MyGUI::IntSize viewSize = globalViewSize; + float scale = getScaleFactor(); + viewSize.width /= scale; + viewSize.height /= scale; + + float hoffset = (globalViewSize.width - mViewSize.width*getScaleFactor())/2.f / static_cast(globalViewSize.width); + float voffset = (globalViewSize.height - mViewSize.height*getScaleFactor())/2.f / static_cast(globalViewSize.height); + + ProxyRenderTarget proxy(_target, viewSize, hoffset, voffset); + + MyGUI::OverlappedLayer::renderToTarget(&proxy, _update); + } + + void ScalingLayer::resizeView(const MyGUI::IntSize &_viewSize) + { + // do nothing + } + + void ScalingLayer::deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version) + { + MyGUI::OverlappedLayer::deserialization(_node, _version); + + MyGUI::xml::ElementEnumerator info = _node->getElementEnumerator(); + while (info.next()) + { + if (info->getName() == "Property") + { + const std::string& key = info->findAttribute("key"); + const std::string& value = info->findAttribute("value"); + + if (key == "Size") + { + mViewSize = MyGUI::IntSize::parse(value); + } + } + } + } + +} diff --git a/components/myguiplatform/scalinglayer.hpp b/components/myguiplatform/scalinglayer.hpp new file mode 100644 index 0000000000..3ee1489f19 --- /dev/null +++ b/components/myguiplatform/scalinglayer.hpp @@ -0,0 +1,31 @@ +#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_SCALINGLAYER +#define OPENMW_COMPONENTS_MYGUIPLATFORM_SCALINGLAYER + +#include + +namespace osgMyGUI +{ + + ///@brief A Layer that lays out and renders widgets in screen-relative coordinates. The "Size" property determines the size of the virtual screen, + /// which is then upscaled to the real screen size during rendering. The aspect ratio is kept intact, adding blanks to the sides when necessary. + class ScalingLayer : public MyGUI::OverlappedLayer + { + public: + MYGUI_RTTI_DERIVED(ScalingLayer) + + virtual void deserialization(MyGUI::xml::ElementPtr _node, MyGUI::Version _version); + + virtual MyGUI::ILayerItem* getLayerItemByPoint(int _left, int _top) const; + virtual MyGUI::IntPoint getPosition(int _left, int _top) const; + virtual void renderToTarget(MyGUI::IRenderTarget* _target, bool _update); + + virtual void resizeView(const MyGUI::IntSize& _viewSize); + + private: + void screenToLayerCoords(int& _left, int& _top) const; + float getScaleFactor() const; + }; + +} + +#endif diff --git a/components/nif/base.hpp b/components/nif/base.hpp index 30c652b64b..4b2e40dec0 100644 --- a/components/nif/base.hpp +++ b/components/nif/base.hpp @@ -32,26 +32,8 @@ public: float timeStart, timeStop; ControlledPtr target; - void read(NIFStream *nif) - { - next.read(nif); - - flags = nif->getUShort(); - - frequency = nif->getFloat(); - phase = nif->getFloat(); - timeStart = nif->getFloat(); - timeStop = nif->getFloat(); - - target.read(nif); - } - - void post(NIFFile *nif) - { - Record::post(nif); - next.post(nif); - target.post(nif); - } + void read(NIFStream *nif); + void post(NIFFile *nif); }; /// Anything that has a controller diff --git a/components/nif/controlled.cpp b/components/nif/controlled.cpp new file mode 100644 index 0000000000..5c63094ce1 --- /dev/null +++ b/components/nif/controlled.cpp @@ -0,0 +1,94 @@ +#include "controlled.hpp" + +#include "data.hpp" + +namespace Nif +{ + + void NiSourceTexture::read(NIFStream *nif) + { + Named::read(nif); + + external = !!nif->getChar(); + if(external) + filename = nif->getString(); + else + { + nif->getChar(); // always 1 + data.read(nif); + } + + pixel = nif->getInt(); + mipmap = nif->getInt(); + alpha = nif->getInt(); + + nif->getChar(); // always 1 + } + + void NiSourceTexture::post(NIFFile *nif) + { + Named::post(nif); + data.post(nif); + } + + void NiParticleGrowFade::read(NIFStream *nif) + { + Controlled::read(nif); + growTime = nif->getFloat(); + fadeTime = nif->getFloat(); + } + + void NiParticleColorModifier::read(NIFStream *nif) + { + Controlled::read(nif); + data.read(nif); + } + + void NiParticleColorModifier::post(NIFFile *nif) + { + Controlled::post(nif); + data.post(nif); + } + + void NiGravity::read(NIFStream *nif) + { + Controlled::read(nif); + + mDecay = nif->getFloat(); + mForce = nif->getFloat(); + mType = nif->getUInt(); + mPosition = nif->getVector3(); + mDirection = nif->getVector3(); + } + + void NiPlanarCollider::read(NIFStream *nif) + { + Controlled::read(nif); + + mBounceFactor = nif->getFloat(); + /*unknown*/nif->getFloat(); + + for (int i=0;i<10;++i) + /*unknown*/nif->getFloat(); + + mPlaneNormal = nif->getVector3(); + mPlaneDistance = nif->getFloat(); + } + + void NiParticleRotation::read(NIFStream *nif) + { + Controlled::read(nif); + + /* + byte (0 or 1) + float (1) + float*3 + */ + nif->skip(17); + } + + + + + +} diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp index 815aa7d3f6..4bd7ce1f9d 100644 --- a/components/nif/controlled.hpp +++ b/components/nif/controlled.hpp @@ -62,31 +62,8 @@ public: */ int alpha; - void read(NIFStream *nif) - { - Named::read(nif); - - external = !!nif->getChar(); - if(external) - filename = nif->getString(); - else - { - nif->getChar(); // always 1 - data.read(nif); - } - - pixel = nif->getInt(); - mipmap = nif->getInt(); - alpha = nif->getInt(); - - nif->getChar(); // always 1 - } - - void post(NIFFile *nif) - { - Named::post(nif); - data.post(nif); - } + void read(NIFStream *nif); + void post(NIFFile *nif); }; class NiParticleGrowFade : public Controlled @@ -95,12 +72,7 @@ public: float growTime; float fadeTime; - void read(NIFStream *nif) - { - Controlled::read(nif); - growTime = nif->getFloat(); - fadeTime = nif->getFloat(); - } + void read(NIFStream *nif); }; class NiParticleColorModifier : public Controlled @@ -108,17 +80,8 @@ class NiParticleColorModifier : public Controlled public: NiColorDataPtr data; - void read(NIFStream *nif) - { - Controlled::read(nif); - data.read(nif); - } - - void post(NIFFile *nif) - { - Controlled::post(nif); - data.post(nif); - } + void read(NIFStream *nif); + void post(NIFFile *nif); }; class NiGravity : public Controlled @@ -129,48 +92,29 @@ public: * 1 - Point (fixed origin) */ int mType; - Ogre::Vector3 mPosition; - Ogre::Vector3 mDirection; + float mDecay; + osg::Vec3f mPosition; + osg::Vec3f mDirection; - void read(NIFStream *nif) - { - Controlled::read(nif); - - /*unknown*/nif->getFloat(); - mForce = nif->getFloat(); - mType = nif->getUInt(); - mPosition = nif->getVector3(); - mDirection = nif->getVector3(); - } + void read(NIFStream *nif); }; // NiPinaColada class NiPlanarCollider : public Controlled { public: - void read(NIFStream *nif) - { - Controlled::read(nif); + void read(NIFStream *nif); - // (I think) 4 floats + 4 vectors - nif->skip(4*16); - } + float mBounceFactor; + + osg::Vec3f mPlaneNormal; + float mPlaneDistance; }; class NiParticleRotation : public Controlled { public: - void read(NIFStream *nif) - { - Controlled::read(nif); - - /* - byte (0 or 1) - float (1) - float*3 - */ - nif->skip(17); - } + void read(NIFStream *nif); }; diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp new file mode 100644 index 0000000000..f391325439 --- /dev/null +++ b/components/nif/controller.cpp @@ -0,0 +1,204 @@ +#include "controller.hpp" + +#include "node.hpp" +#include "data.hpp" + +namespace Nif +{ + + void Controller::read(NIFStream *nif) + { + next.read(nif); + + flags = nif->getUShort(); + + frequency = nif->getFloat(); + phase = nif->getFloat(); + timeStart = nif->getFloat(); + timeStop = nif->getFloat(); + + target.read(nif); + } + + void Controller::post(NIFFile *nif) + { + Record::post(nif); + next.post(nif); + target.post(nif); + } + + void NiParticleSystemController::read(NIFStream *nif) + { + Controller::read(nif); + + velocity = nif->getFloat(); + velocityRandom = nif->getFloat(); + verticalDir = nif->getFloat(); + verticalAngle = nif->getFloat(); + horizontalDir = nif->getFloat(); + horizontalAngle = nif->getFloat(); + /*normal?*/ nif->getVector3(); + /*color?*/ nif->getVector4(); + size = nif->getFloat(); + startTime = nif->getFloat(); + stopTime = nif->getFloat(); + nif->getChar(); + emitRate = nif->getFloat(); + lifetime = nif->getFloat(); + lifetimeRandom = nif->getFloat(); + + emitFlags = nif->getUShort(); + offsetRandom = nif->getVector3(); + + emitter.read(nif); + + /* Unknown Short, 0? + * Unknown Float, 1.0? + * Unknown Int, 1? + * Unknown Int, 0? + * Unknown Short, 0? + */ + nif->skip(16); + + numParticles = nif->getUShort(); + activeCount = nif->getUShort(); + + particles.resize(numParticles); + for(size_t i = 0;i < particles.size();i++) + { + particles[i].velocity = nif->getVector3(); + nif->getVector3(); /* unknown */ + particles[i].lifetime = nif->getFloat(); + particles[i].lifespan = nif->getFloat(); + particles[i].timestamp = nif->getFloat(); + nif->getUShort(); /* unknown */ + particles[i].vertex = nif->getUShort(); + } + + nif->getUInt(); /* -1? */ + affectors.read(nif); + colliders.read(nif); + nif->getChar(); + } + + void NiParticleSystemController::post(NIFFile *nif) + { + Controller::post(nif); + emitter.post(nif); + affectors.post(nif); + colliders.post(nif); + } + + void NiMaterialColorController::read(NIFStream *nif) + { + Controller::read(nif); + data.read(nif); + } + + void NiMaterialColorController::post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } + + void NiPathController::read(NIFStream *nif) + { + Controller::read(nif); + + /* + int = 1 + 2xfloat + short = 0 or 1 + */ + nif->skip(14); + posData.read(nif); + floatData.read(nif); + } + + void NiPathController::post(NIFFile *nif) + { + Controller::post(nif); + + posData.post(nif); + floatData.post(nif); + } + + void NiUVController::read(NIFStream *nif) + { + Controller::read(nif); + + nif->getUShort(); // always 0 + data.read(nif); + } + + void NiUVController::post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } + + void NiKeyframeController::read(NIFStream *nif) + { + Controller::read(nif); + data.read(nif); + } + + void NiKeyframeController::post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } + + void NiAlphaController::read(NIFStream *nif) + { + Controller::read(nif); + data.read(nif); + } + + void NiAlphaController::post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } + + void NiGeomMorpherController::read(NIFStream *nif) + { + Controller::read(nif); + data.read(nif); + nif->getChar(); // always 0 + } + + void NiGeomMorpherController::post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } + + void NiVisController::read(NIFStream *nif) + { + Controller::read(nif); + data.read(nif); + } + + void NiVisController::post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } + + void NiFlipController::read(NIFStream *nif) + { + Controller::read(nif); + mTexSlot = nif->getUInt(); + /*unknown=*/nif->getUInt();/*0?*/ + mDelta = nif->getFloat(); + mSources.read(nif); + } + + void NiFlipController::post(NIFFile *nif) + { + Controller::post(nif); + mSources.post(nif); + } + +} diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 9ae527e5a0..0861dfa6b6 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -33,7 +33,7 @@ class NiParticleSystemController : public Controller { public: struct Particle { - Ogre::Vector3 velocity; + osg::Vec3f velocity; float lifetime; float lifespan; float timestamp; @@ -64,7 +64,7 @@ public: }; int emitFlags; - Ogre::Vector3 offsetRandom; + osg::Vec3f offsetRandom; NodePtr emitter; @@ -72,68 +72,11 @@ public: int activeCount; std::vector particles; - ExtraPtr extra; + ExtraPtr affectors; + ExtraPtr colliders; - void read(NIFStream *nif) - { - Controller::read(nif); - - velocity = nif->getFloat(); - velocityRandom = nif->getFloat(); - verticalDir = nif->getFloat(); - verticalAngle = nif->getFloat(); - horizontalDir = nif->getFloat(); - horizontalAngle = nif->getFloat(); - /*normal?*/ nif->getVector3(); - /*color?*/ nif->getVector4(); - size = nif->getFloat(); - startTime = nif->getFloat(); - stopTime = nif->getFloat(); - nif->getChar(); - emitRate = nif->getFloat(); - lifetime = nif->getFloat(); - lifetimeRandom = nif->getFloat(); - - emitFlags = nif->getUShort(); - offsetRandom = nif->getVector3(); - - emitter.read(nif); - - /* Unknown Short, 0? - * Unknown Float, 1.0? - * Unknown Int, 1? - * Unknown Int, 0? - * Unknown Short, 0? - */ - nif->skip(16); - - numParticles = nif->getUShort(); - activeCount = nif->getUShort(); - - particles.resize(numParticles); - for(size_t i = 0;i < particles.size();i++) - { - particles[i].velocity = nif->getVector3(); - nif->getVector3(); /* unknown */ - particles[i].lifetime = nif->getFloat(); - particles[i].lifespan = nif->getFloat(); - particles[i].timestamp = nif->getFloat(); - nif->getUShort(); /* unknown */ - particles[i].vertex = nif->getUShort(); - } - - nif->getUInt(); /* -1? */ - extra.read(nif); - nif->getUInt(); /* -1? */ - nif->getChar(); - } - - void post(NIFFile *nif) - { - Controller::post(nif); - emitter.post(nif); - extra.post(nif); - } + void read(NIFStream *nif); + void post(NIFFile *nif); }; typedef NiParticleSystemController NiBSPArrayController; @@ -142,17 +85,8 @@ class NiMaterialColorController : public Controller public: NiPosDataPtr data; - void read(NIFStream *nif) - { - Controller::read(nif); - data.read(nif); - } - - void post(NIFFile *nif) - { - Controller::post(nif); - data.post(nif); - } + void read(NIFStream *nif); + void post(NIFFile *nif); }; class NiPathController : public Controller @@ -161,27 +95,8 @@ public: NiPosDataPtr posData; NiFloatDataPtr floatData; - void read(NIFStream *nif) - { - Controller::read(nif); - - /* - int = 1 - 2xfloat - short = 0 or 1 - */ - nif->skip(14); - posData.read(nif); - floatData.read(nif); - } - - void post(NIFFile *nif) - { - Controller::post(nif); - - posData.post(nif); - floatData.post(nif); - } + void read(NIFStream *nif); + void post(NIFFile *nif); }; class NiUVController : public Controller @@ -189,19 +104,8 @@ class NiUVController : public Controller public: NiUVDataPtr data; - void read(NIFStream *nif) - { - Controller::read(nif); - - nif->getUShort(); // always 0 - data.read(nif); - } - - void post(NIFFile *nif) - { - Controller::post(nif); - data.post(nif); - } + void read(NIFStream *nif); + void post(NIFFile *nif); }; class NiKeyframeController : public Controller @@ -209,17 +113,8 @@ class NiKeyframeController : public Controller public: NiKeyframeDataPtr data; - void read(NIFStream *nif) - { - Controller::read(nif); - data.read(nif); - } - - void post(NIFFile *nif) - { - Controller::post(nif); - data.post(nif); - } + void read(NIFStream *nif); + void post(NIFFile *nif); }; class NiAlphaController : public Controller @@ -227,17 +122,8 @@ class NiAlphaController : public Controller public: NiFloatDataPtr data; - void read(NIFStream *nif) - { - Controller::read(nif); - data.read(nif); - } - - void post(NIFFile *nif) - { - Controller::post(nif); - data.post(nif); - } + void read(NIFStream *nif); + void post(NIFFile *nif); }; class NiGeomMorpherController : public Controller @@ -245,18 +131,8 @@ class NiGeomMorpherController : public Controller public: NiMorphDataPtr data; - void read(NIFStream *nif) - { - Controller::read(nif); - data.read(nif); - nif->getChar(); // always 0 - } - - void post(NIFFile *nif) - { - Controller::post(nif); - data.post(nif); - } + void read(NIFStream *nif); + void post(NIFFile *nif); }; class NiVisController : public Controller @@ -264,17 +140,8 @@ class NiVisController : public Controller public: NiVisDataPtr data; - void read(NIFStream *nif) - { - Controller::read(nif); - data.read(nif); - } - - void post(NIFFile *nif) - { - Controller::post(nif); - data.post(nif); - } + void read(NIFStream *nif); + void post(NIFFile *nif); }; class NiFlipController : public Controller @@ -284,20 +151,8 @@ public: float mDelta; // Time between two flips. delta = (start_time - stop_time) / num_sources NiSourceTextureList mSources; - void read(NIFStream *nif) - { - Controller::read(nif); - mTexSlot = nif->getUInt(); - /*unknown=*/nif->getUInt();/*0?*/ - mDelta = nif->getFloat(); - mSources.read(nif); - } - - void post(NIFFile *nif) - { - Controller::post(nif); - mSources.post(nif); - } + void read(NIFStream *nif); + void post(NIFFile *nif); }; } // Namespace diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 4248b93d2f..5a60ab8a5e 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -1,8 +1,18 @@ #include "data.hpp" #include "node.hpp" +#include +#include + namespace Nif { +void NiSkinInstance::read(NIFStream *nif) +{ + data.read(nif); + root.read(nif); + bones.read(nif); +} + void NiSkinInstance::post(NIFFile *nif) { data.post(nif); @@ -26,4 +36,230 @@ void NiSkinInstance::post(NIFFile *nif) } } +void ShapeData::read(NIFStream *nif) +{ + int verts = nif->getUShort(); + + vertices = new osg::Vec3Array; + if(nif->getInt()) + nif->getVector3s(vertices, verts); + + normals = new osg::Vec3Array(osg::Array::BIND_PER_VERTEX); + if(nif->getInt()) + nif->getVector3s(normals, verts); + + center = nif->getVector3(); + radius = nif->getFloat(); + + colors = new osg::Vec4Array(osg::Array::BIND_PER_VERTEX); + if(nif->getInt()) + nif->getVector4s(colors, verts); + + // Only the first 6 bits are used as a count. I think the rest are + // flags of some sort. + int uvs = nif->getUShort(); + uvs &= 0x3f; + + if(nif->getInt()) + { + uvlist.resize(uvs); + for(int i = 0;i < uvs;i++) + { + uvlist[i] = new osg::Vec2Array(osg::Array::BIND_PER_VERTEX); + nif->getVector2s(uvlist[i], verts); + } + } +} + +void NiTriShapeData::read(NIFStream *nif) +{ + ShapeData::read(nif); + + /*int tris =*/ nif->getUShort(); + + // We have three times as many vertices as triangles, so this + // is always equal to tris*3. + int cnt = nif->getInt(); + triangles = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES); + nif->getUShorts(triangles, cnt); + + // Read the match list, which lists the vertices that are equal to + // vertices. We don't actually need need this for anything, so + // just skip it. + int verts = nif->getUShort(); + for(int i=0;i < verts;i++) + { + // Number of vertices matching vertex 'i' + int num = nif->getUShort(); + nif->skip(num * sizeof(short)); + } +} + +void NiAutoNormalParticlesData::read(NIFStream *nif) +{ + ShapeData::read(nif); + + // Should always match the number of vertices + numParticles = nif->getUShort(); + + particleRadius = nif->getFloat(); + activeCount = nif->getUShort(); + + if(nif->getInt()) + { + int numVerts = vertices->size(); + // Particle sizes + nif->getFloats(sizes, numVerts); + } +} + +void NiRotatingParticlesData::read(NIFStream *nif) +{ + NiAutoNormalParticlesData::read(nif); + + if(nif->getInt()) + { + int numVerts = vertices->size(); + // Rotation quaternions. + nif->getQuaternions(rotations, numVerts); + } +} + +void NiPosData::read(NIFStream *nif) +{ + mKeyList.reset(new Vector3KeyMap); + mKeyList->read(nif); +} + +void NiUVData::read(NIFStream *nif) +{ + for(int i = 0;i < 4;i++) + { + mKeyList[i].reset(new FloatKeyMap); + mKeyList[i]->read(nif); + } +} + +void NiFloatData::read(NIFStream *nif) +{ + mKeyList.reset(new FloatKeyMap); + mKeyList->read(nif); +} + +void NiPixelData::read(NIFStream *nif) +{ + nif->getInt(); // always 0 or 1 + + rmask = nif->getInt(); // usually 0xff + gmask = nif->getInt(); // usually 0xff00 + bmask = nif->getInt(); // usually 0xff0000 + amask = nif->getInt(); // usually 0xff000000 or zero + + bpp = nif->getInt(); + + // Unknown + nif->skip(12); + + mips = nif->getInt(); + + // Bytes per pixel, should be bpp * 8 + /*int bytes =*/ nif->getInt(); + + for(int i=0; igetInt(); + /*int y =*/ nif->getInt(); + /*int offset =*/ nif->getInt(); + } + + // Skip the data + unsigned int dataSize = nif->getInt(); + nif->skip(dataSize); +} + +void NiColorData::read(NIFStream *nif) +{ + mKeyMap.reset(new Vector4KeyMap); + mKeyMap->read(nif); +} + +void NiVisData::read(NIFStream *nif) +{ + int count = nif->getInt(); + mVis.resize(count); + for(size_t i = 0;i < mVis.size();i++) + { + mVis[i].time = nif->getFloat(); + mVis[i].isSet = (nif->getChar() != 0); + } +} + +void NiSkinData::read(NIFStream *nif) +{ + trafo.rotation = nif->getMatrix3(); + trafo.pos = nif->getVector3(); + trafo.scale = nif->getFloat(); + + int boneNum = nif->getInt(); + nif->getInt(); // -1 + + bones.resize(boneNum); + for(int i=0;igetMatrix3(); + bi.trafo.pos = nif->getVector3(); + bi.trafo.scale = nif->getFloat(); + bi.boundSphereCenter = nif->getVector3(); + bi.boundSphereRadius = nif->getFloat(); + + // Number of vertex weights + bi.weights.resize(nif->getUShort()); + for(size_t j = 0;j < bi.weights.size();j++) + { + bi.weights[j].vertex = nif->getUShort(); + bi.weights[j].weight = nif->getFloat(); + } + } +} + +void NiMorphData::read(NIFStream *nif) +{ + int morphCount = nif->getInt(); + int vertCount = nif->getInt(); + /*relative targets?*/nif->getChar(); + + mMorphs.resize(morphCount); + for(int i = 0;i < morphCount;i++) + { + mMorphs[i].mKeyFrames.reset(new FloatKeyMap); + mMorphs[i].mKeyFrames->read(nif, true); + mMorphs[i].mVertices = new osg::Vec3Array; + nif->getVector3s(mMorphs[i].mVertices, vertCount); + } +} + +void NiKeyframeData::read(NIFStream *nif) +{ + mRotations.reset(new QuaternionKeyMap); + mRotations->read(nif); + if(mRotations->mInterpolationType == Vector3KeyMap::sXYZInterpolation) + { + //Chomp unused float + nif->getFloat(); + mXRotations.reset(new FloatKeyMap); + mYRotations.reset(new FloatKeyMap); + mZRotations.reset(new FloatKeyMap); + mXRotations->read(nif, true); + mYRotations->read(nif, true); + mZRotations->read(nif, true); + } + mTranslations.reset(new Vector3KeyMap); + mTranslations->read(nif); + mScales.reset(new FloatKeyMap); + mScales->read(nif); +} + } // Namespace diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 1fa94d9c2a..95f2441295 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -26,6 +26,10 @@ #include "base.hpp" +#include "niftypes.hpp" // Transformation + +#include + namespace Nif { @@ -33,70 +37,23 @@ namespace Nif class ShapeData : public Record { public: - std::vector vertices, normals; - std::vector colors; - std::vector< std::vector > uvlist; - Ogre::Vector3 center; + osg::ref_ptr vertices, normals; + osg::ref_ptr colors; + + std::vector< osg::ref_ptr > uvlist; + osg::Vec3f center; float radius; - void read(NIFStream *nif) - { - int verts = nif->getUShort(); - - if(nif->getInt()) - nif->getVector3s(vertices, verts); - - if(nif->getInt()) - nif->getVector3s(normals, verts); - - center = nif->getVector3(); - radius = nif->getFloat(); - - if(nif->getInt()) - nif->getVector4s(colors, verts); - - // Only the first 6 bits are used as a count. I think the rest are - // flags of some sort. - int uvs = nif->getUShort(); - uvs &= 0x3f; - - if(nif->getInt()) - { - uvlist.resize(uvs); - for(int i = 0;i < uvs;i++) - nif->getVector2s(uvlist[i], verts); - } - } + void read(NIFStream *nif); }; class NiTriShapeData : public ShapeData { public: // Triangles, three vertex indices per triangle - std::vector triangles; + osg::ref_ptr triangles; - void read(NIFStream *nif) - { - ShapeData::read(nif); - - /*int tris =*/ nif->getUShort(); - - // We have three times as many vertices as triangles, so this - // is always equal to tris*3. - int cnt = nif->getInt(); - nif->getShorts(triangles, cnt); - - // Read the match list, which lists the vertices that are equal to - // vertices. We don't actually need need this for anything, so - // just skip it. - int verts = nif->getUShort(); - for(int i=0;i < verts;i++) - { - // Number of vertices matching vertex 'i' - int num = nif->getUShort(); - nif->skip(num * sizeof(short)); - } - } + void read(NIFStream *nif); }; class NiAutoNormalParticlesData : public ShapeData @@ -110,73 +67,39 @@ public: std::vector sizes; - void read(NIFStream *nif) - { - ShapeData::read(nif); - - // Should always match the number of vertices - numParticles = nif->getUShort(); - - particleRadius = nif->getFloat(); - activeCount = nif->getUShort(); - - if(nif->getInt()) - { - // Particle sizes - nif->getFloats(sizes, vertices.size()); - } - } + void read(NIFStream *nif); }; class NiRotatingParticlesData : public NiAutoNormalParticlesData { public: - std::vector rotations; + std::vector rotations; - void read(NIFStream *nif) - { - NiAutoNormalParticlesData::read(nif); - - if(nif->getInt()) - { - // Rotation quaternions. - nif->getQuaternions(rotations, vertices.size()); - } - } + void read(NIFStream *nif); }; class NiPosData : public Record { public: - Vector3KeyMap mKeyList; + Vector3KeyMapPtr mKeyList; - void read(NIFStream *nif) - { - mKeyList.read(nif); - } + void read(NIFStream *nif); }; class NiUVData : public Record { public: - FloatKeyMap mKeyList[4]; + FloatKeyMapPtr mKeyList[4]; - void read(NIFStream *nif) - { - for(int i = 0;i < 4;i++) - mKeyList[i].read(nif); - } + void read(NIFStream *nif); }; class NiFloatData : public Record { public: - FloatKeyMap mKeyList; + FloatKeyMapPtr mKeyList; - void read(NIFStream *nif) - { - mKeyList.read(nif); - } + void read(NIFStream *nif); }; class NiPixelData : public Record @@ -185,48 +108,15 @@ public: unsigned int rmask, gmask, bmask, amask; int bpp, mips; - void read(NIFStream *nif) - { - nif->getInt(); // always 0 or 1 - - rmask = nif->getInt(); // usually 0xff - gmask = nif->getInt(); // usually 0xff00 - bmask = nif->getInt(); // usually 0xff0000 - amask = nif->getInt(); // usually 0xff000000 or zero - - bpp = nif->getInt(); - - // Unknown - nif->skip(12); - - mips = nif->getInt(); - - // Bytes per pixel, should be bpp * 8 - /*int bytes =*/ nif->getInt(); - - for(int i=0; igetInt(); - /*int y =*/ nif->getInt(); - /*int offset =*/ nif->getInt(); - } - - // Skip the data - unsigned int dataSize = nif->getInt(); - nif->skip(dataSize); - } + void read(NIFStream *nif); }; class NiColorData : public Record { public: - Vector4KeyMap mKeyMap; + Vector4KeyMapPtr mKeyMap; - void read(NIFStream *nif) - { - mKeyMap.read(nif); - } + void read(NIFStream *nif); }; class NiVisData : public Record @@ -238,16 +128,7 @@ public: }; std::vector mVis; - void read(NIFStream *nif) - { - int count = nif->getInt(); - mVis.resize(count); - for(size_t i = 0;i < mVis.size();i++) - { - mVis[i].time = nif->getFloat(); - mVis[i].isSet = nif->getChar() != 0; - } - } + void read(NIFStream *nif); }; class NiSkinInstance : public Record @@ -257,121 +138,58 @@ public: NodePtr root; NodeList bones; - void read(NIFStream *nif) - { - data.read(nif); - root.read(nif); - bones.read(nif); - } - + void read(NIFStream *nif); void post(NIFFile *nif); }; class NiSkinData : public Record { public: - struct BoneTrafo - { - Ogre::Matrix3 rotation; // Rotation offset from bone? - Ogre::Vector3 trans; // Translation - float scale; // Probably scale (always 1) - }; - struct VertWeight { - short vertex; + unsigned short vertex; float weight; }; struct BoneInfo { - BoneTrafo trafo; - Ogre::Vector4 unknown; + Transformation trafo; + osg::Vec3f boundSphereCenter; + float boundSphereRadius; std::vector weights; }; - BoneTrafo trafo; + Transformation trafo; std::vector bones; - void read(NIFStream *nif) - { - trafo.rotation = nif->getMatrix3(); - trafo.trans = nif->getVector3(); - trafo.scale = nif->getFloat(); - - int boneNum = nif->getInt(); - nif->getInt(); // -1 - - bones.resize(boneNum); - for(int i=0;igetMatrix3(); - bi.trafo.trans = nif->getVector3(); - bi.trafo.scale = nif->getFloat(); - bi.unknown = nif->getVector4(); - - // Number of vertex weights - bi.weights.resize(nif->getUShort()); - for(size_t j = 0;j < bi.weights.size();j++) - { - bi.weights[j].vertex = nif->getUShort(); - bi.weights[j].weight = nif->getFloat(); - } - } - } + void read(NIFStream *nif); }; struct NiMorphData : public Record { struct MorphData { - FloatKeyMap mData; - std::vector mVertices; + FloatKeyMapPtr mKeyFrames; + osg::ref_ptr mVertices; }; std::vector mMorphs; - void read(NIFStream *nif) - { - int morphCount = nif->getInt(); - int vertCount = nif->getInt(); - /*relative targets?*/nif->getChar(); - - mMorphs.resize(morphCount); - for(int i = 0;i < morphCount;i++) - { - mMorphs[i].mData.read(nif, true); - nif->getVector3s(mMorphs[i].mVertices, vertCount); - } - } + void read(NIFStream *nif); }; struct NiKeyframeData : public Record { - QuaternionKeyMap mRotations; + QuaternionKeyMapPtr mRotations; - FloatKeyMap mXRotations; - FloatKeyMap mYRotations; - FloatKeyMap mZRotations; + // may be NULL + FloatKeyMapPtr mXRotations; + FloatKeyMapPtr mYRotations; + FloatKeyMapPtr mZRotations; - Vector3KeyMap mTranslations; - FloatKeyMap mScales; + Vector3KeyMapPtr mTranslations; + FloatKeyMapPtr mScales; - void read(NIFStream *nif) - { - mRotations.read(nif); - if(mRotations.mInterpolationType == mRotations.sXYZInterpolation) - { - //Chomp unused float - nif->getFloat(); - mXRotations.read(nif, true); - mYRotations.read(nif, true); - mZRotations.read(nif, true); - } - mTranslations.read(nif); - mScales.read(nif); - } + void read(NIFStream *nif); }; } // Namespace diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp new file mode 100644 index 0000000000..79cd104312 --- /dev/null +++ b/components/nif/effect.cpp @@ -0,0 +1,66 @@ +#include "effect.hpp" + +#include "node.hpp" + +namespace Nif +{ + +void NiLight::read(NIFStream *nif) +{ + NiDynamicEffect::read(nif); + + dimmer = nif->getFloat(); + ambient = nif->getVector3(); + diffuse = nif->getVector3(); + specular = nif->getVector3(); +} + +void NiTextureEffect::read(NIFStream *nif) +{ + NiDynamicEffect::read(nif); + + /* + 3 x Vector4 = [1,0,0,0] + int = 2 + int = 0 or 3 + int = 2 + int = 2 + */ + nif->skip(16*4); + + texture.read(nif); + + /* + byte = 0 + vector4 = [1,0,0,0] + short = 0 + short = -75 + short = 0 + */ + nif->skip(23); +} + +void NiTextureEffect::post(NIFFile *nif) +{ + NiDynamicEffect::post(nif); + texture.post(nif); +} + +void NiPointLight::read(NIFStream *nif) +{ + NiLight::read(nif); + + constantAttenuation = nif->getFloat(); + linearAttenuation = nif->getFloat(); + quadraticAttenuation = nif->getFloat(); +} + +void NiSpotLight::read(NIFStream *nif) +{ + NiPointLight::read(nif); + + cutoff = nif->getFloat(); + exponent = nif->getFloat(); +} + +} diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index cc1b0f41c1..02647e4448 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -29,76 +29,50 @@ namespace Nif { -typedef Node Effect; - -// Used for NiAmbientLight and NiDirectionalLight. Might also work for -// NiPointLight and NiSpotLight? -struct NiLight : Effect +struct NiDynamicEffect : public Node { - struct SLight - { - float dimmer; - Ogre::Vector3 ambient; - Ogre::Vector3 diffuse; - Ogre::Vector3 specular; - - void read(NIFStream *nif) - { - dimmer = nif->getFloat(); - ambient = nif->getVector3(); - diffuse = nif->getVector3(); - specular = nif->getVector3(); - } - }; - SLight light; - void read(NIFStream *nif) { - Effect::read(nif); - - nif->getInt(); // 1 - nif->getInt(); // 1? - light.read(nif); + Node::read(nif); + unsigned int numAffectedNodes = nif->getUInt(); + for (unsigned int i=0; igetUInt(); // ref to another Node } }; -struct NiTextureEffect : Effect +// Used as base for NiAmbientLight, NiDirectionalLight, NiPointLight and NiSpotLight. +struct NiLight : NiDynamicEffect +{ + float dimmer; + osg::Vec3f ambient; + osg::Vec3f diffuse; + osg::Vec3f specular; + + void read(NIFStream *nif); +}; + +struct NiPointLight : public NiLight +{ + float constantAttenuation; + float linearAttenuation; + float quadraticAttenuation; + + void read(NIFStream *nif); +}; + +struct NiSpotLight : public NiPointLight +{ + float cutoff; + float exponent; + void read(NIFStream *nif); +}; + +struct NiTextureEffect : NiDynamicEffect { NiSourceTexturePtr texture; - void read(NIFStream *nif) - { - Effect::read(nif); - - int tmp = nif->getInt(); - if(tmp) nif->getInt(); // always 1? - - /* - 3 x Vector4 = [1,0,0,0] - int = 2 - int = 0 or 3 - int = 2 - int = 2 - */ - nif->skip(16*4); - - texture.read(nif); - - /* - byte = 0 - vector4 = [1,0,0,0] - short = 0 - short = -75 - short = 0 - */ - nif->skip(23); - } - - void post(NIFFile *nif) - { - Effect::post(nif); - texture.post(nif); - } + void read(NIFStream *nif); + void post(NIFFile *nif); }; } // Namespace diff --git a/components/nif/extra.cpp b/components/nif/extra.cpp new file mode 100644 index 0000000000..b7e2216688 --- /dev/null +++ b/components/nif/extra.cpp @@ -0,0 +1,43 @@ +#include "extra.hpp" + +namespace Nif +{ + +void NiStringExtraData::read(NIFStream *nif) +{ + Extra::read(nif); + + nif->getInt(); // size of string + 4. Really useful... + string = nif->getString(); +} + +void NiTextKeyExtraData::read(NIFStream *nif) +{ + Extra::read(nif); + + nif->getInt(); // 0 + + int keynum = nif->getInt(); + list.resize(keynum); + for(int i=0; igetFloat(); + list[i].text = nif->getString(); + } +} + +void NiVertWeightsExtraData::read(NIFStream *nif) +{ + Extra::read(nif); + + // We should have s*4+2 == i, for some reason. Might simply be the + // size of the rest of the record, unhelpful as that may be. + /*int i =*/ nif->getInt(); + int s = nif->getUShort(); + + nif->skip(s * sizeof(float)); // vertex weights I guess +} + + + +} diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index 2913c62b0b..1e5a8616de 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -32,17 +32,7 @@ namespace Nif class NiVertWeightsExtraData : public Extra { public: - void read(NIFStream *nif) - { - Extra::read(nif); - - // We should have s*4+2 == i, for some reason. Might simply be the - // size of the rest of the record, unhelpful as that may be. - /*int i =*/ nif->getInt(); - int s = nif->getUShort(); - - nif->skip(s * sizeof(float)); // vertex weights I guess - } + void read(NIFStream *nif); }; class NiTextKeyExtraData : public Extra @@ -55,20 +45,7 @@ public: }; std::vector list; - void read(NIFStream *nif) - { - Extra::read(nif); - - nif->getInt(); // 0 - - int keynum = nif->getInt(); - list.resize(keynum); - for(int i=0; igetFloat(); - list[i].text = nif->getString(); - } - } + void read(NIFStream *nif); }; class NiStringExtraData : public Extra @@ -80,13 +57,7 @@ public: */ std::string string; - void read(NIFStream *nif) - { - Extra::read(nif); - - nif->getInt(); // size of string + 4. Really useful... - string = nif->getString(); - } + void read(NIFStream *nif); }; } // Namespace diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 4f3ee95cbe..f6e489527e 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -2,18 +2,18 @@ #include "effect.hpp" #include - -#include +#include namespace Nif { /// Open a NIF stream. The name is used for error messages. -NIFFile::NIFFile(const std::string &name) +NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name) : ver(0) , filename(name) + , mUseSkinning(false) { - parse(); + parse(stream); } NIFFile::~NIFFile() @@ -47,6 +47,8 @@ static std::map makeFactory() { std::map newFactory; newFactory.insert(makeEntry("NiNode", &construct , RC_NiNode )); + newFactory.insert(makeEntry("NiSwitchNode", &construct , RC_NiSwitchNode )); + newFactory.insert(makeEntry("NiLODNode", &construct , RC_NiLODNode )); newFactory.insert(makeEntry("AvoidNode", &construct , RC_AvoidNode )); newFactory.insert(makeEntry("NiBSParticleNode", &construct , RC_NiBSParticleNode )); newFactory.insert(makeEntry("NiBSAnimationNode", &construct , RC_NiBSAnimationNode )); @@ -79,6 +81,8 @@ static std::map makeFactory() newFactory.insert(makeEntry("NiFlipController", &construct , RC_NiFlipController )); newFactory.insert(makeEntry("NiAmbientLight", &construct , RC_NiLight )); newFactory.insert(makeEntry("NiDirectionalLight", &construct , RC_NiLight )); + newFactory.insert(makeEntry("NiPointLight", &construct , RC_NiLight )); + newFactory.insert(makeEntry("NiSpotLight", &construct , RC_NiLight )); newFactory.insert(makeEntry("NiTextureEffect", &construct , RC_NiTextureEffect )); newFactory.insert(makeEntry("NiVertWeightsExtraData", &construct , RC_NiVertWeightsExtraData )); newFactory.insert(makeEntry("NiTextKeyExtraData", &construct , RC_NiTextKeyExtraData )); @@ -110,7 +114,6 @@ static std::map makeFactory() ///Make the factory map used for parsing the file static const std::map factories = makeFactory(); -/// Get the file's version in a human readable form std::string NIFFile::printVersion(unsigned int version) { union ver_quad @@ -121,63 +124,68 @@ std::string NIFFile::printVersion(unsigned int version) version_out.full = version; - return Ogre::StringConverter::toString(version_out.quad[3]) - +"." + Ogre::StringConverter::toString(version_out.quad[2]) - +"." + Ogre::StringConverter::toString(version_out.quad[1]) - +"." + Ogre::StringConverter::toString(version_out.quad[0]); + std::stringstream stream; + stream << version_out.quad[3] << "." + << version_out.quad[2] << "." + << version_out.quad[1] << "." + << version_out.quad[0]; + return stream.str(); } -void NIFFile::parse() +void NIFFile::parse(Files::IStreamPtr stream) { - NIFStream nif (this, Ogre::ResourceGroupManager::getSingleton().openResource(filename)); + NIFStream nif (this, stream); - // Check the header string - std::string head = nif.getVersionString(); - if(head.compare(0, 22, "NetImmerse File Format") != 0) - fail("Invalid NIF header: " + head); + // Check the header string + std::string head = nif.getVersionString(); + if(head.compare(0, 22, "NetImmerse File Format") != 0) + fail("Invalid NIF header: " + head); - // Get BCD version - ver = nif.getUInt(); - if(ver != VER_MW) - fail("Unsupported NIF version: " + printVersion(ver)); - // Number of records - size_t recNum = nif.getInt(); - records.resize(recNum); + // Get BCD version + ver = nif.getUInt(); + if(ver != VER_MW) + fail("Unsupported NIF version: " + printVersion(ver)); + // Number of records + size_t recNum = nif.getInt(); + records.resize(recNum); - /* The format for 10.0.1.0 seems to be a bit different. After the + /* The format for 10.0.1.0 seems to be a bit different. After the header, it contains the number of records, r (int), just like 4.0.0.2, but following that it contains a short x, followed by x strings. Then again by r shorts, one for each record, giving which of the above strings to use to identify the record. After this follows two ints (zero?) and then the record data. However we do not support or plan to support other versions yet. - */ + */ - for(size_t i = 0;i < recNum;i++) + for(size_t i = 0;i < recNum;i++) { - Record *r = NULL; + Record *r = NULL; - std::string rec = nif.getString(); - if(rec.empty()) - fail("Record number " + Ogre::StringConverter::toString(i) + " out of " + Ogre::StringConverter::toString(recNum) + " is blank."); + std::string rec = nif.getString(); + if(rec.empty()) + { + std::stringstream error; + error << "Record number " << i << " out of " << recNum << " is blank."; + fail(error.str()); + } + std::map::const_iterator entry = factories.find(rec); - std::map::const_iterator entry = factories.find(rec); + if (entry != factories.end()) + { + r = entry->second.mCreate (); + r->recType = entry->second.mType; + } + else + fail("Unknown record type " + rec); - if (entry != factories.end()) - { - r = entry->second.mCreate (); - r->recType = entry->second.mType; - } - else - fail("Unknown record type " + rec); - - assert(r != NULL); - assert(r->recType != RC_MISSING); - r->recName = rec; - r->recIndex = i; - records[i] = r; - r->read(&nif); + assert(r != NULL); + assert(r->recType != RC_MISSING); + r->recName = rec; + r->recIndex = i; + records[i] = r; + r->read(&nif); } size_t rootNum = nif.getUInt(); @@ -187,9 +195,9 @@ void NIFFile::parse() for(size_t i = 0;i < rootNum;i++) { int idx = nif.getInt(); - if (idx >= 0) + if (idx >= 0 && idx < int(records.size())) { - roots[i] = records.at(idx); + roots[i] = records[idx]; } else { @@ -203,4 +211,14 @@ void NIFFile::parse() records[i]->post(this); } +void NIFFile::setUseSkinning(bool skinning) +{ + mUseSkinning = skinning; +} + +bool NIFFile::getUseSkinning() const +{ + return mUseSkinning; +} + } diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index ceb9984fb8..900c360bb8 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -7,6 +7,8 @@ #include #include +#include + #include "record.hpp" namespace Nif @@ -30,8 +32,10 @@ class NIFFile /// Root list. This is a select portion of the pointers from records std::vector roots; + bool mUseSkinning; + /// Parse the file - void parse(); + void parse(Files::IStreamPtr stream); /// Get the file's version in a human readable form ///\returns A string containing a human readable NIF version number @@ -57,8 +61,8 @@ public: << "File: " << filename < NIFFilePtr; diff --git a/components/nif/nifkey.hpp b/components/nif/nifkey.hpp index b36b1d8cb1..75353044d7 100644 --- a/components/nif/nifkey.hpp +++ b/components/nif/nifkey.hpp @@ -3,33 +3,43 @@ #ifndef OPENMW_COMPONENTS_NIF_NIFKEY_HPP #define OPENMW_COMPONENTS_NIF_NIFKEY_HPP -#include - #include "nifstream.hpp" +#include +#include + +#include + +#include "niffile.hpp" + namespace Nif { template struct KeyT { T mValue; + + // FIXME: Implement Quadratic and TBC interpolation + /* T mForwardValue; // Only for Quadratic interpolation, and never for QuaternionKeyList T mBackwardValue; // Only for Quadratic interpolation, and never for QuaternionKeyList float mTension; // Only for TBC interpolation float mBias; // Only for TBC interpolation float mContinuity; // Only for TBC interpolation - - KeyT() : mTension (0), mBias (0), mContinuity (0) {} + */ }; typedef KeyT FloatKey; -typedef KeyT Vector3Key; -typedef KeyT Vector4Key; -typedef KeyT QuaternionKey; +typedef KeyT Vector3Key; +typedef KeyT Vector4Key; +typedef KeyT QuaternionKey; template struct KeyMapT { typedef std::map< float, KeyT > MapType; + typedef T ValueType; + typedef KeyT KeyType; + static const unsigned int sLinearInterpolation = 1; static const unsigned int sQuadraticInterpolation = 2; static const unsigned int sTBCInterpolation = 3; @@ -94,7 +104,12 @@ struct KeyMapT { { //Don't try to read XYZ keys into the wrong part if ( count != 1 ) - nif->file->fail("XYZ_ROTATION_KEY count should always be '1' . Retrieved Value: "+Ogre::StringConverter::toString(count)); + { + std::stringstream error; + error << "XYZ_ROTATION_KEY count should always be '1' . Retrieved Value: " + << count; + nif->file->fail(error.str()); + } } else if (0 == mInterpolationType) { @@ -102,7 +117,11 @@ struct KeyMapT { nif->file->fail("Interpolation type 0 doesn't work with keys"); } else - nif->file->fail("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); + { + std::stringstream error; + error << "Unhandled interpolation type: " << mInterpolationType; + nif->file->fail(error.str()); + } } private: @@ -111,31 +130,36 @@ private: key.mValue = (nif.*getValue)(); } - static void readQuadratic(NIFStream &nif, KeyT &key) - { - readValue(nif, key); - } - template static void readQuadratic(NIFStream &nif, KeyT &key) { readValue(nif, key); - key.mForwardValue = (nif.*getValue)(); - key.mBackwardValue = (nif.*getValue)(); + /*key.mForwardValue = */(nif.*getValue)(); + /*key.mBackwardValue = */(nif.*getValue)(); + } + + static void readQuadratic(NIFStream &nif, KeyT &key) + { + readValue(nif, key); } static void readTBC(NIFStream &nif, KeyT &key) { readValue(nif, key); - key.mTension = nif.getFloat(); - key.mBias = nif.getFloat(); - key.mContinuity = nif.getFloat(); + /*key.mTension = */nif.getFloat(); + /*key.mBias = */nif.getFloat(); + /*key.mContinuity = */nif.getFloat(); } }; typedef KeyMapT FloatKeyMap; -typedef KeyMapT Vector3KeyMap; -typedef KeyMapT Vector4KeyMap; -typedef KeyMapT QuaternionKeyMap; +typedef KeyMapT Vector3KeyMap; +typedef KeyMapT Vector4KeyMap; +typedef KeyMapT QuaternionKeyMap; + +typedef boost::shared_ptr FloatKeyMapPtr; +typedef boost::shared_ptr Vector3KeyMapPtr; +typedef boost::shared_ptr Vector4KeyMapPtr; +typedef boost::shared_ptr QuaternionKeyMapPtr; } // Namespace #endif //#ifndef OPENMW_COMPONENTS_NIF_NIFKEY_HPP diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index e5699db7b9..5f49c2d21e 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -9,19 +9,19 @@ namespace Nif uint8_t NIFStream::read_byte() { uint8_t byte; - if(inp->read(&byte, 1) != 1) return 0; + inp->read((char*)&byte, 1); return byte; } uint16_t NIFStream::read_le16() { uint8_t buffer[2]; - if(inp->read(buffer, 2) != 2) return 0; + inp->read((char*)buffer, 2); return buffer[0] | (buffer[1]<<8); } uint32_t NIFStream::read_le32() { uint8_t buffer[4]; - if(inp->read(buffer, 4) != 4) return 0; + inp->read((char*)buffer, 4); return buffer[0] | (buffer[1]<<8) | (buffer[2]<<16) | (buffer[3]<<24); } float NIFStream::read_le32f() @@ -34,43 +34,45 @@ float NIFStream::read_le32f() } //Public functions -Ogre::Vector2 NIFStream::getVector2() +osg::Vec2f NIFStream::getVector2() { - float a[2]; + osg::Vec2f vec; for(size_t i = 0;i < 2;i++) - a[i] = getFloat(); - return Ogre::Vector2(a); + vec._v[i] = getFloat(); + return vec; } -Ogre::Vector3 NIFStream::getVector3() +osg::Vec3f NIFStream::getVector3() { - float a[3]; + osg::Vec3f vec; for(size_t i = 0;i < 3;i++) - a[i] = getFloat(); - return Ogre::Vector3(a); + vec._v[i] = getFloat(); + return vec; } -Ogre::Vector4 NIFStream::getVector4() +osg::Vec4f NIFStream::getVector4() { - float a[4]; + osg::Vec4f vec; for(size_t i = 0;i < 4;i++) - a[i] = getFloat(); - return Ogre::Vector4(a); + vec._v[i] = getFloat(); + return vec; } -Ogre::Matrix3 NIFStream::getMatrix3() +Matrix3 NIFStream::getMatrix3() { - Ogre::Real a[3][3]; + Matrix3 mat; for(size_t i = 0;i < 3;i++) { for(size_t j = 0;j < 3;j++) - a[i][j] = Ogre::Real(getFloat()); + mat.mValues[i][j] = getFloat(); } - return Ogre::Matrix3(a); + return mat; } -Ogre::Quaternion NIFStream::getQuaternion() +osg::Quat NIFStream::getQuaternion() { - float a[4]; - for(size_t i = 0;i < 4;i++) - a[i] = getFloat(); - return Ogre::Quaternion(a); + osg::Quat quat; + quat.w() = getFloat(); + quat.x() = getFloat(); + quat.y() = getFloat(); + quat.z() = getFloat(); + return quat; } Transformation NIFStream::getTrafo() { @@ -83,16 +85,9 @@ Transformation NIFStream::getTrafo() std::string NIFStream::getString(size_t length) { - //Make sure we're not reading in too large of a string - unsigned int fileSize = inp->size(); - if(fileSize != 0 && fileSize < length) - file->fail("Attempted to read a string with " + Ogre::StringConverter::toString(length) + " characters , but file is only "+Ogre::StringConverter::toString(fileSize)+ " bytes!"); - std::vector str (length+1, 0); - if(inp->read(&str[0], length) != length) - throw std::runtime_error (": String length in NIF file "+ file->getFilename() +" does not match! Expected length: " - + Ogre::StringConverter::toString(length)); + inp->read(&str[0], length); return &str[0]; } @@ -103,14 +98,16 @@ std::string NIFStream::getString() } std::string NIFStream::getVersionString() { - return inp->getLine(); + std::string result; + std::getline(*inp, result); + return result; } -void NIFStream::getShorts(std::vector &vec, size_t size) +void NIFStream::getUShorts(osg::VectorGLushort* vec, size_t size) { - vec.resize(size); - for(size_t i = 0;i < vec.size();i++) - vec[i] = getShort(); + vec->reserve(size); + for(size_t i = 0;i < size;i++) + vec->push_back(getUShort()); } void NIFStream::getFloats(std::vector &vec, size_t size) { @@ -118,25 +115,25 @@ void NIFStream::getFloats(std::vector &vec, size_t size) for(size_t i = 0;i < vec.size();i++) vec[i] = getFloat(); } -void NIFStream::getVector2s(std::vector &vec, size_t size) +void NIFStream::getVector2s(osg::Vec2Array* vec, size_t size) { - vec.resize(size); - for(size_t i = 0;i < vec.size();i++) - vec[i] = getVector2(); + vec->reserve(size); + for(size_t i = 0;i < size;i++) + vec->push_back(getVector2()); } -void NIFStream::getVector3s(std::vector &vec, size_t size) +void NIFStream::getVector3s(osg::Vec3Array* vec, size_t size) { - vec.resize(size); - for(size_t i = 0;i < vec.size();i++) - vec[i] = getVector3(); + vec->reserve(size); + for(size_t i = 0;i < size;i++) + vec->push_back(getVector3()); } -void NIFStream::getVector4s(std::vector &vec, size_t size) +void NIFStream::getVector4s(osg::Vec4Array* vec, size_t size) { - vec.resize(size); - for(size_t i = 0;i < vec.size();i++) - vec[i] = getVector4(); + vec->reserve(size); + for(size_t i = 0;i < size;i++) + vec->push_back(getVector4()); } -void NIFStream::getQuaternions(std::vector &quat, size_t size) +void NIFStream::getQuaternions(std::vector &quat, size_t size) { quat.resize(size); for(size_t i = 0;i < quat.size();i++) diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index aee16f2804..f5d777bdfc 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -5,17 +5,19 @@ #include #include +#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include #include "niftypes.hpp" + namespace Nif { @@ -24,7 +26,7 @@ class NIFFile; class NIFStream { /// Input stream - Ogre::DataStreamPtr inp; + Files::IStreamPtr inp; uint8_t read_byte(); uint16_t read_le16(); @@ -35,9 +37,9 @@ public: NIFFile * const file; - NIFStream (NIFFile * file, Ogre::DataStreamPtr inp): inp (inp), file (file) {} + NIFStream (NIFFile * file, Files::IStreamPtr inp): inp (inp), file (file) {} - void skip(size_t size) { inp->skip(size); } + void skip(size_t size) { inp->ignore(size); } char getChar() { return read_byte(); } short getShort() { return read_le16(); } @@ -46,11 +48,11 @@ public: unsigned int getUInt() { return read_le32(); } float getFloat() { return read_le32f(); } - Ogre::Vector2 getVector2(); - Ogre::Vector3 getVector3(); - Ogre::Vector4 getVector4(); - Ogre::Matrix3 getMatrix3(); - Ogre::Quaternion getQuaternion(); + osg::Vec2f getVector2(); + osg::Vec3f getVector3(); + osg::Vec4f getVector4(); + Matrix3 getMatrix3(); + osg::Quat getQuaternion(); Transformation getTrafo(); ///Read in a string of the given length @@ -60,12 +62,12 @@ public: ///This is special since the version string doesn't start with a number, and ends with "\n" std::string getVersionString(); - void getShorts(std::vector &vec, size_t size); + void getUShorts(osg::VectorGLushort* vec, size_t size); void getFloats(std::vector &vec, size_t size); - void getVector2s(std::vector &vec, size_t size); - void getVector3s(std::vector &vec, size_t size); - void getVector4s(std::vector &vec, size_t size); - void getQuaternions(std::vector &quat, size_t size); + void getVector2s(osg::Vec2Array* vec, size_t size); + void getVector3s(osg::Vec3Array* vec, size_t size); + void getVector4s(osg::Vec4Array* vec, size_t size); + void getQuaternions(std::vector &quat, size_t size); }; } diff --git a/components/nif/niftypes.hpp b/components/nif/niftypes.hpp index 786c48b65e..5827448fdf 100644 --- a/components/nif/niftypes.hpp +++ b/components/nif/niftypes.hpp @@ -24,24 +24,63 @@ #ifndef OPENMW_COMPONENTS_NIF_NIFTYPES_HPP #define OPENMW_COMPONENTS_NIF_NIFTYPES_HPP -#include -#include +#include +#include // Common types used in NIF files namespace Nif { +struct Matrix3 +{ + float mValues[3][3]; + + Matrix3() + { + for (int i=0;i<3;++i) + for (int j=0;j<3;++j) + mValues[i][j] = (i==j) ? 1.f : 0.f; + } + + bool isIdentity() const + { + for (int i=0;i<3;++i) + for (int j=0;j<3;++j) + if ((i==j) != (mValues[i][j] == 1)) + return false; + return true; + } +}; + struct Transformation { - Ogre::Vector3 pos; - Ogre::Matrix3 rotation; + osg::Vec3f pos; + Matrix3 rotation; // this can contain scale components too, including negative and nonuniform scales float scale; + osg::Matrixf toMatrix() const + { + osg::Matrixf transform; + transform.setTrans(pos); + + for (int i=0;i<3;++i) + for (int j=0;j<3;++j) + transform(j,i) = rotation.mValues[i][j] * scale; // NB column/row major difference + + return transform; + } + + bool isIdentity() const + { + return pos == osg::Vec3f(0,0,0) + && rotation.isIdentity() && scale == 1.f; + } + static const Transformation& getIdentity() { static const Transformation identity = { - Ogre::Vector3::ZERO, Ogre::Matrix3::IDENTITY, 1.0f + osg::Vec3f(), Matrix3(), 1.0f }; return identity; } diff --git a/components/nif/node.cpp b/components/nif/node.cpp index d99d157ae2..e69de29bb2 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -1,63 +0,0 @@ -#include "node.hpp" - -namespace Nif -{ - -void Node::getProperties(const Nif::NiTexturingProperty *&texprop, - const Nif::NiMaterialProperty *&matprop, - const Nif::NiAlphaProperty *&alphaprop, - const Nif::NiVertexColorProperty *&vertprop, - const Nif::NiZBufferProperty *&zprop, - const Nif::NiSpecularProperty *&specprop, - const Nif::NiWireframeProperty *&wireprop, - const Nif::NiStencilProperty *&stencilprop) const -{ - if(parent) - parent->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop, stencilprop); - - for(size_t i = 0;i < props.length();i++) - { - // Entries may be empty - if(props[i].empty()) - continue; - - const Nif::Property *pr = props[i].getPtr(); - if(pr->recType == Nif::RC_NiTexturingProperty) - texprop = static_cast(pr); - else if(pr->recType == Nif::RC_NiMaterialProperty) - matprop = static_cast(pr); - else if(pr->recType == Nif::RC_NiAlphaProperty) - alphaprop = static_cast(pr); - else if(pr->recType == Nif::RC_NiVertexColorProperty) - vertprop = static_cast(pr); - else if(pr->recType == Nif::RC_NiZBufferProperty) - zprop = static_cast(pr); - else if(pr->recType == Nif::RC_NiSpecularProperty) - specprop = static_cast(pr); - else if(pr->recType == Nif::RC_NiWireframeProperty) - wireprop = static_cast(pr); - else if (pr->recType == Nif::RC_NiStencilProperty) - stencilprop = static_cast(pr); - // the following are unused by the MW engine - else if (pr->recType != Nif::RC_NiFogProperty - && pr->recType != Nif::RC_NiDitherProperty - && pr->recType != Nif::RC_NiShadeProperty) - std::cerr<< "Unhandled property type: "<recName <getWorldTransform() * getLocalTransform(); - return getLocalTransform(); -} - -} diff --git a/components/nif/node.hpp b/components/nif/node.hpp index e6edf64320..326e9802fd 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -1,8 +1,6 @@ #ifndef OPENMW_COMPONENTS_NIF_NODE_HPP #define OPENMW_COMPONENTS_NIF_NODE_HPP -#include - #include "controlled.hpp" #include "extra.hpp" #include "data.hpp" @@ -26,14 +24,14 @@ public: // Node flags. Interpretation depends somewhat on the type of node. int flags; Transformation trafo; - Ogre::Vector3 velocity; // Unused? Might be a run-time game state + osg::Vec3f velocity; // Unused? Might be a run-time game state PropertyList props; // Bounding box info bool hasBounds; - Ogre::Vector3 boundPos; - Ogre::Matrix3 boundRot; - Ogre::Vector3 boundXYZ; // Box size + osg::Vec3f boundPos; + Matrix3 boundRot; + osg::Vec3f boundXYZ; // Box size void read(NIFStream *nif) { @@ -70,7 +68,7 @@ public: NiNode *parent; // Bone transformation. If set, node is a part of a skeleton. - const NiSkinData::BoneTrafo *boneTrafo; + const Transformation *boneTrafo; // Bone weight info, from NiSkinData const NiSkinData::BoneInfo *boneInfo; @@ -79,7 +77,7 @@ public: // boneTrafo is set it is the root bone in the skeleton. short boneIndex; - void makeRootBone(const NiSkinData::BoneTrafo *tr) + void makeRootBone(const Transformation *tr) { boneTrafo = tr; boneIndex = -1; @@ -91,18 +89,6 @@ public: boneTrafo = &bi.trafo; boneIndex = ind; } - - void getProperties(const Nif::NiTexturingProperty *&texprop, - const Nif::NiMaterialProperty *&matprop, - const Nif::NiAlphaProperty *&alphaprop, - const Nif::NiVertexColorProperty *&vertprop, - const Nif::NiZBufferProperty *&zprop, - const Nif::NiSpecularProperty *&specprop, - const Nif::NiWireframeProperty *&wireprop, - const Nif::NiStencilProperty *&stencilprop) const; - - Ogre::Matrix4 getLocalTransform() const; - Ogre::Matrix4 getWorldTransform() const; }; struct NiNode : Node @@ -180,6 +166,8 @@ struct NiTriShape : Node Node::post(nif); data.post(nif); skin.post(nif); + if (!skin.empty()) + nif->setUseSkinning(true); } }; @@ -262,5 +250,41 @@ struct NiRotatingParticles : Node } }; +// A node used as the base to switch between child nodes, such as for LOD levels. +struct NiSwitchNode : public NiNode +{ + void read(NIFStream *nif) + { + NiNode::read(nif); + nif->getInt(); // unknown + } +}; + +struct NiLODNode : public NiSwitchNode +{ + osg::Vec3f lodCenter; + + struct LODRange + { + float minRange; + float maxRange; + }; + std::vector lodLevels; + + void read(NIFStream *nif) + { + NiSwitchNode::read(nif); + lodCenter = nif->getVector3(); + unsigned int numLodLevels = nif->getUInt(); + for (unsigned int i=0; igetFloat(); + r.maxRange = nif->getFloat(); + lodLevels.push_back(r); + } + } +}; + } // Namespace #endif diff --git a/components/nif/property.cpp b/components/nif/property.cpp new file mode 100644 index 0000000000..47c6d35b31 --- /dev/null +++ b/components/nif/property.cpp @@ -0,0 +1,111 @@ +#include "property.hpp" + +#include "data.hpp" +#include "controlled.hpp" + +namespace Nif +{ + +void Property::read(NIFStream *nif) +{ + Named::read(nif); + flags = nif->getUShort(); +} + +void NiTexturingProperty::Texture::read(NIFStream *nif) +{ + inUse = !!nif->getInt(); + if(!inUse) return; + + texture.read(nif); + clamp = nif->getInt(); + filter = nif->getInt(); + uvSet = nif->getInt(); + + // I have no idea, but I think these are actually two + // PS2-specific shorts (ps2L and ps2K), followed by an unknown + // short. + nif->skip(6); +} + +void NiTexturingProperty::Texture::post(NIFFile *nif) +{ + texture.post(nif); +} + +void NiTexturingProperty::read(NIFStream *nif) +{ + Property::read(nif); + apply = nif->getInt(); + + // Unknown, always 7. Probably the number of textures to read + // below + nif->getInt(); + + textures[0].read(nif); // Base + textures[1].read(nif); // Dark + textures[2].read(nif); // Detail + textures[3].read(nif); // Gloss (never present) + textures[4].read(nif); // Glow + textures[5].read(nif); // Bump map + if(textures[5].inUse) + { + // Ignore these at the moment + /*float lumaScale =*/ nif->getFloat(); + /*float lumaOffset =*/ nif->getFloat(); + /*const Vector4 *lumaMatrix =*/ nif->getVector4(); + } + textures[6].read(nif); // Decal +} + +void NiTexturingProperty::post(NIFFile *nif) +{ + Property::post(nif); + for(int i = 0;i < 7;i++) + textures[i].post(nif); +} + +void NiFogProperty::read(NIFStream *nif) +{ + Property::read(nif); + + mFogDepth = nif->getFloat(); + mColour = nif->getVector3(); +} + +void S_MaterialProperty::read(NIFStream *nif) +{ + ambient = nif->getVector3(); + diffuse = nif->getVector3(); + specular = nif->getVector3(); + emissive = nif->getVector3(); + glossiness = nif->getFloat(); + alpha = nif->getFloat(); +} + +void S_VertexColorProperty::read(NIFStream *nif) +{ + vertmode = nif->getInt(); + lightmode = nif->getInt(); +} + +void S_AlphaProperty::read(NIFStream *nif) +{ + threshold = nif->getChar(); +} + +void S_StencilProperty::read(NIFStream *nif) +{ + enabled = nif->getChar(); + compareFunc = nif->getInt(); + stencilRef = nif->getUInt(); + stencilMask = nif->getUInt(); + failAction = nif->getInt(); + zFailAction = nif->getInt(); + zPassAction = nif->getInt(); + drawMode = nif->getInt(); +} + + + +} diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 77f61d0684..96156c6d83 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -33,13 +33,9 @@ class Property : public Named { public: // The meaning of these depends on the actual property type. - int flags; + unsigned int flags; - void read(NIFStream *nif) - { - Named::read(nif); - flags = nif->getUShort(); - } + void read(NIFStream *nif); }; class NiTexturingProperty : public Property @@ -67,26 +63,8 @@ public: int clamp, uvSet, filter; short unknown2; - void read(NIFStream *nif) - { - inUse = !!nif->getInt(); - if(!inUse) return; - - texture.read(nif); - clamp = nif->getInt(); - filter = nif->getInt(); - uvSet = nif->getInt(); - - // I have no idea, but I think these are actually two - // PS2-specific shorts (ps2L and ps2K), followed by an unknown - // short. - nif->skip(6); - } - - void post(NIFFile *nif) - { - texture.post(nif); - } + void read(NIFStream *nif); + void post(NIFFile *nif); }; /* Apply mode: @@ -117,58 +95,23 @@ public: GlossTexture = 3, GlowTexture = 4, BumpTexture = 5, - DecalTexture = 6 + DecalTexture = 6, + NumTextures = 7 // Sentry value }; Texture textures[7]; - void read(NIFStream *nif) - { - Property::read(nif); - apply = nif->getInt(); - - // Unknown, always 7. Probably the number of textures to read - // below - nif->getInt(); - - textures[0].read(nif); // Base - textures[1].read(nif); // Dark - textures[2].read(nif); // Detail - textures[3].read(nif); // Gloss (never present) - textures[4].read(nif); // Glow - textures[5].read(nif); // Bump map - if(textures[5].inUse) - { - // Ignore these at the moment - /*float lumaScale =*/ nif->getFloat(); - /*float lumaOffset =*/ nif->getFloat(); - /*const Vector4 *lumaMatrix =*/ nif->getVector4(); - } - textures[6].read(nif); // Decal - } - - void post(NIFFile *nif) - { - Property::post(nif); - for(int i = 0;i < 7;i++) - textures[i].post(nif); - } + void read(NIFStream *nif); + void post(NIFFile *nif); }; class NiFogProperty : public Property { public: float mFogDepth; - Ogre::Vector3 mColour; + osg::Vec3f mColour; - - void read(NIFStream *nif) - { - Property::read(nif); - - mFogDepth = nif->getFloat(); - mColour = nif->getVector3(); - } + void read(NIFStream *nif); }; // These contain no other data than the 'flags' field in Property @@ -194,18 +137,10 @@ struct StructPropT : Property struct S_MaterialProperty { // The vector components are R,G,B - Ogre::Vector3 ambient, diffuse, specular, emissive; + osg::Vec3f ambient, diffuse, specular, emissive; float glossiness, alpha; - void read(NIFStream *nif) - { - ambient = nif->getVector3(); - diffuse = nif->getVector3(); - specular = nif->getVector3(); - emissive = nif->getVector3(); - glossiness = nif->getFloat(); - alpha = nif->getFloat(); - } + void read(NIFStream *nif); }; struct S_VertexColorProperty @@ -221,11 +156,7 @@ struct S_VertexColorProperty */ int vertmode, lightmode; - void read(NIFStream *nif) - { - vertmode = nif->getInt(); - lightmode = nif->getInt(); - } + void read(NIFStream *nif); }; struct S_AlphaProperty @@ -265,23 +196,12 @@ struct S_AlphaProperty Taken from: http://niftools.sourceforge.net/doc/nif/NiAlphaProperty.html - - Right now we only use standard alpha blending (see the Ogre code - that sets it up) and it appears that this is the only blending - used in the original game. Bloodmoon (along with several mods) do - however use other settings, such as discarding pixel values with - alpha < 1.0. This is faster because we don't have to mess with the - depth stuff like we did for blending. And OGRE has settings for - this too. */ // Tested against when certain flags are set (see above.) unsigned char threshold; - void read(NIFStream *nif) - { - threshold = nif->getChar(); - } + void read(NIFStream *nif); }; /* @@ -301,7 +221,7 @@ struct S_StencilProperty 4 TEST_GREATER 5 TEST_NOT_EQUAL 6 TEST_GREATER_EQUAL - 7 TEST_ALWAYS + 7 TEST_NEVER (though nifskope comment says TEST_ALWAYS, but ingame it is TEST_NEVER) */ int compareFunc; unsigned stencilRef; @@ -327,17 +247,7 @@ struct S_StencilProperty */ int drawMode; - void read(NIFStream *nif) - { - enabled = nif->getChar(); - compareFunc = nif->getInt(); - stencilRef = nif->getUInt(); - stencilMask = nif->getUInt(); - failAction = nif->getInt(); - zFailAction = nif->getInt(); - zPassAction = nif->getInt(); - drawMode = nif->getInt(); - } + void read(NIFStream *nif); }; class NiAlphaProperty : public StructPropT { }; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 07d7540f85..bcbdba1154 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -36,6 +36,8 @@ enum RecordType { RC_MISSING = 0, RC_NiNode, + RC_NiSwitchNode, + RC_NiLODNode, RC_NiBillboardNode, RC_AvoidNode, RC_NiTriShape, @@ -110,12 +112,6 @@ struct Record virtual void post(NIFFile *nif) {} virtual ~Record() {} - - /* - Use these later if you want custom allocation of all NIF objects - static void* operator new(size_t size); - static void operator delete(void *p); - */ }; } // Namespace diff --git a/components/nif/tests/.gitignore b/components/nif/tests/.gitignore deleted file mode 100644 index 397b4a7624..0000000000 --- a/components/nif/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.log diff --git a/components/nif/tests/niftest.cpp b/components/nif/tests/niftest.cpp deleted file mode 100644 index a06c002df1..0000000000 --- a/components/nif/tests/niftest.cpp +++ /dev/null @@ -1,96 +0,0 @@ -///Program to test .nif files both on the FileSystem and in BSA archives. - -#include "../niffile.hpp" -#include "../../bsa/bsa_file.hpp" -#include "../../bsa/bsa_archive.hpp" -#include -#include -#include -#include -#include - -///See if the file has the named extension -bool hasExtension(std::string filename, std::string extensionToFind) -{ - std::string extension = filename.substr(filename.find_last_of(".")+1); - - //Convert strings to lower case for comparison - std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); - std::transform(extensionToFind.begin(), extensionToFind.end(), extensionToFind.begin(), ::tolower); - - if(extension == extensionToFind) - return true; - else - return false; -} - -///See if the file has the "nif" extension. -bool isNIF(std::string filename) -{ - return hasExtension(filename,"nif"); -} -///See if the file has the "bsa" extension. -bool isBSA(std::string filename) -{ - return hasExtension(filename,"bsa"); -} - -///Check all the nif files in the given BSA archive -void readBSA(std::string filename) -{ - Bsa::BSAFile bsa; - bsa.open(filename.c_str()); - - const Bsa::BSAFile::FileList &files = bsa.getList(); - Bsa::addBSA(filename,"Bsa Files"); - - for(unsigned int i=0; i nifs.txt -find "$DATAFILESDIR" -iname *nif >> nifs.txt - -sed -e 's/.*/\"&\"/' nifs.txt > quoted_nifs.txt - -xargs --arg-file=quoted_nifs.txt ../../../niftest - -rm nifs.txt -rm quoted_nifs.txt diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 93c9797b25..19afe49d63 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -1,150 +1,68 @@ - /* -OpenMW - The completely unofficial reimplementation of Morrowind -Copyright (C) 2008-2010 Nicolay Korslund -Email: < korslund@gmail.com > -WWW: http://openmw.sourceforge.net/ - -This file (ogre_nif_loader.cpp) is part of the OpenMW package. - -OpenMW is distributed as free software: you can redistribute it -and/or modify it under the terms of the GNU General Public License -version 3, as published by the Free Software Foundation. - -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 -version 3 along with this program. If not, see -http://www.gnu.org/licenses/ . - -*/ - #include "bulletnifloader.hpp" #include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include -#include "../nif/niffile.hpp" -#include "../nif/node.hpp" -#include "../nif/data.hpp" -#include "../nif/property.hpp" -#include "../nif/controller.hpp" -#include "../nif/extra.hpp" -#include -#include -#include -// For warning messages -#include - -// float infinity -#include - -typedef unsigned char ubyte; - -// Extract a list of keyframe-controlled nodes from a .kf file -// FIXME: this is a similar copy of OgreNifLoader::loadKf -void extractControlledNodes(Nif::NIFFilePtr kfFile, std::set& controlled) +namespace { - if(kfFile->numRoots() < 1) - { - kfFile->warn("Found no root nodes in "+kfFile->getFilename()+"."); - return; - } - const Nif::Record *r = kfFile->getRoot(0); - assert(r != NULL); +osg::Matrixf getWorldTransform(const Nif::Node *node) +{ + if(node->parent != NULL) + return node->trafo.toMatrix() * getWorldTransform(node->parent); + return node->trafo.toMatrix(); +} - if(r->recType != Nif::RC_NiSequenceStreamHelper) - { - kfFile->warn("First root was not a NiSequenceStreamHelper, but a "+ - r->recName+"."); - return; - } - const Nif::NiSequenceStreamHelper *seq = static_cast(r); +btVector3 getbtVector(const osg::Vec3f &v) +{ + return btVector3(v.x(), v.y(), v.z()); +} - Nif::ExtraPtr extra = seq->extra; - if(extra.empty() || extra->recType != Nif::RC_NiTextKeyExtraData) - { - kfFile->warn("First extra data was not a NiTextKeyExtraData, but a "+ - (extra.empty() ? std::string("nil") : extra->recName)+"."); - return; - } - - extra = extra->extra; - Nif::ControllerPtr ctrl = seq->controller; - for(;!extra.empty() && !ctrl.empty();(extra=extra->extra),(ctrl=ctrl->next)) - { - if(extra->recType != Nif::RC_NiStringExtraData || ctrl->recType != Nif::RC_NiKeyframeController) - { - kfFile->warn("Unexpected extra data "+extra->recName+" with controller "+ctrl->recName); - continue; - } - - if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) - continue; - - const Nif::NiStringExtraData *strdata = static_cast(extra.getPtr()); - const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); - - if(key->data.empty()) - continue; - controlled.insert(strdata->string); - } } namespace NifBullet { -ManualBulletShapeLoader::~ManualBulletShapeLoader() +BulletNifLoader::BulletNifLoader() + : mCompoundShape(NULL) + , mStaticMesh(NULL) { } - -btVector3 ManualBulletShapeLoader::getbtVector(Ogre::Vector3 const &v) +BulletNifLoader::~BulletNifLoader() { - return btVector3(v[0], v[1], v[2]); } -void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) +osg::ref_ptr BulletNifLoader::load(const Nif::NIFFilePtr nif) { - mShape = static_cast(resource); - mResourceName = mShape->getName(); - mShape->mCollide = false; - mBoundingBox = NULL; - mShape->mBoxTranslation = Ogre::Vector3(0,0,0); - mShape->mBoxRotation = Ogre::Quaternion::IDENTITY; + mShape = new Resource::BulletShape; + mCompoundShape = NULL; mStaticMesh = NULL; - Nif::NIFFilePtr pnif (Nif::Cache::getInstance().load(mResourceName.substr(0, mResourceName.length()-7))); - Nif::NIFFile & nif = *pnif.get (); - if (nif.numRoots() < 1) + if (nif->numRoots() < 1) { warn("Found no root nodes in NIF."); - return; + return mShape; } - // Have to load controlled nodes from the .kf - // FIXME: the .kf has to be loaded both for rendering and physics, ideally it should be opened once and then reused - mControlledNodes.clear(); - std::string kfname = mResourceName.substr(0, mResourceName.length()-7); - Misc::StringUtils::toLower(kfname); - if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) - kfname.replace(kfname.size()-4, 4, ".kf"); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(kfname)) - { - Nif::NIFFilePtr kf (Nif::Cache::getInstance().load(kfname)); - extractControlledNodes(kf, mControlledNodes); - } - - Nif::Record *r = nif.getRoot(0); + Nif::Record *r = nif->getRoot(0); assert(r != NULL); Nif::Node *node = dynamic_cast(r); @@ -152,29 +70,39 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) { warn("First root in file was not a node, but a " + r->recName + ". Skipping file."); - return; + return mShape; } - mShape->mAutogenerated = hasAutoGeneratedCollision(node); - - //do a first pass - handleNode(node,0,false,false); - - if(mBoundingBox != NULL) + if (findBoundingBox(node)) { - mShape->mCollisionShape = mBoundingBox; - delete mStaticMesh; - if (mCompoundShape) - { - int n = mCompoundShape->getNumChildShapes(); - for(int i=0; i getChildShape(i)); - delete mCompoundShape; - mShape->mAnimatedShapes.clear(); - } + std::auto_ptr compound (new btCompoundShape); + + btBoxShape* boxShape = new btBoxShape(getbtVector(mShape->mCollisionBoxHalfExtents)); + btTransform transform = btTransform::getIdentity(); + transform.setOrigin(getbtVector(mShape->mCollisionBoxTranslate)); + compound->addChildShape(transform, boxShape); + + mShape->mCollisionShape = compound.release(); + return mShape; } else { + bool autogenerated = hasAutoGeneratedCollision(node); + bool isAnimated = false; + + // files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see Animation::addAnimSource). + // assume all nodes in the file will be animated + std::string filename = nif->getFilename(); + size_t slashpos = filename.find_last_of("/\\"); + if (slashpos == std::string::npos) + slashpos = 0; + if (slashpos+1 < filename.size() && (filename[slashpos+1] == 'x' || filename[slashpos+1] == 'X')) + { + isAnimated = true; + } + + handleNode(node, 0, autogenerated, isAnimated, autogenerated); + if (mCompoundShape) { mShape->mCollisionShape = mCompoundShape; @@ -182,37 +110,51 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) { btTransform trans; trans.setIdentity(); - mCompoundShape->addChildShape(trans, new TriangleMeshShape(mStaticMesh,true)); + mCompoundShape->addChildShape(trans, new Resource::TriangleMeshShape(mStaticMesh,true)); } } else if (mStaticMesh) - mShape->mCollisionShape = new TriangleMeshShape(mStaticMesh,true); + mShape->mCollisionShape = new Resource::TriangleMeshShape(mStaticMesh,true); + + return mShape; } - - //second pass which create a shape for raycasting. - mResourceName = mShape->getName(); - mShape->mCollide = false; - mBoundingBox = NULL; - mStaticMesh = NULL; - mCompoundShape = NULL; - - handleNode(node,0,true,true,false); - - if (mCompoundShape) - { - mShape->mRaycastingShape = mCompoundShape; - if (mStaticMesh) - { - btTransform trans; - trans.setIdentity(); - mCompoundShape->addChildShape(trans, new TriangleMeshShape(mStaticMesh,true)); - } - } - else if (mStaticMesh) - mShape->mRaycastingShape = new TriangleMeshShape(mStaticMesh,true); } -bool ManualBulletShapeLoader::hasAutoGeneratedCollision(Nif::Node const * rootNode) +// Find a boundingBox in the node hierarchy. +// Return: use bounding box for collision? +bool BulletNifLoader::findBoundingBox(const Nif::Node* node, int flags) +{ + flags |= node->flags; + + if (node->hasBounds) + { + mShape->mCollisionBoxHalfExtents = node->boundXYZ; + mShape->mCollisionBoxTranslate = node->boundPos; + + if (flags & Nif::NiNode::Flag_BBoxCollision) + { + return true; + } + } + + const Nif::NiNode *ninode = dynamic_cast(node); + if(ninode) + { + const Nif::NodeList &list = ninode->children; + for(size_t i = 0;i < list.length();i++) + { + if(!list[i].empty()) + { + bool found = findBoundingBox (list[i].getPtr()); + if (found) + return true; + } + } + } + return false; +} + +bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node* rootNode) { const Nif::NiNode *ninode = dynamic_cast(rootNode); if(ninode) @@ -230,9 +172,8 @@ bool ManualBulletShapeLoader::hasAutoGeneratedCollision(Nif::Node const * rootNo return true; } -void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags, - bool isCollisionNode, - bool raycasting, bool isAnimated) +void BulletNifLoader::handleNode(const Nif::Node *node, int flags, + bool isCollisionNode, bool isAnimated, bool autogenerated) { // Accumulate the flags from all the child nodes. This works for all // the flags we currently use, at least. @@ -242,13 +183,7 @@ void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags, && (node->controller->flags & Nif::NiNode::ControllerFlag_Active)) isAnimated = true; - if (mControlledNodes.find(node->name) != mControlledNodes.end()) - isAnimated = true; - - if (!raycasting) - isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode); - else - isCollisionNode = isCollisionNode && (node->recType != Nif::RC_RootCollisionNode); + isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode); // Don't collide with AvoidNode shapes if(node->recType == Nif::RC_AvoidNode) @@ -274,33 +209,23 @@ void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags, // No collision. Use an internal flag setting to mark this. flags |= 0x800; } - else if (sd->string == "MRK" && !mShowMarkers && (raycasting || mShape->mAutogenerated)) + else if (sd->string == "MRK" && autogenerated) { - // Marker objects should be invisible, but can still have collision if the model explicitely specifies it via a RootCollisionNode. - // Except in the editor, the marker objects are visible. + // Marker can still have collision if the model explicitely specifies it via a RootCollisionNode. return; } + } } - if (isCollisionNode || (mShape->mAutogenerated && !raycasting)) + if (isCollisionNode) { // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape! // It must be ignored completely. // (occurs in tr_ex_imp_wall_arch_04.nif) - if(node->hasBounds) + if(!node->hasBounds && node->recType == Nif::RC_NiTriShape) { - if (flags & Nif::NiNode::Flag_BBoxCollision && !raycasting) - { - mShape->mBoxTranslation = node->boundPos; - mShape->mBoxRotation = node->boundRot; - mBoundingBox = new btBoxShape(getbtVector(node->boundXYZ)); - } - } - else if(node->recType == Nif::RC_NiTriShape) - { - mShape->mCollide = !(flags&0x800); - handleNiTriShape(static_cast(node), flags, node->getWorldTransform(), raycasting, isAnimated); + handleNiTriShape(static_cast(node), flags, getWorldTransform(node), isAnimated); } } @@ -312,13 +237,12 @@ void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags, for(size_t i = 0;i < list.length();i++) { if(!list[i].empty()) - handleNode(list[i].getPtr(), flags, isCollisionNode, raycasting, isAnimated); + handleNode(list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated); } } } -void ManualBulletShapeLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform, - bool raycasting, bool isAnimated) +void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf &transform, bool isAnimated) { assert(shape != NULL); @@ -329,12 +253,12 @@ void ManualBulletShapeLoader::handleNiTriShape(const Nif::NiTriShape *shape, int // If the object was marked "NCO" earlier, it shouldn't collide with // anything. So don't do anything. - if ((flags & 0x800) && !raycasting) + if ((flags & 0x800)) { return; } - if (!collide && !bbcollide && hidden && !raycasting) + if (!collide && !bbcollide && hidden) // This mesh apparently isn't being used for anything, so don't // bother setting it up. return; @@ -351,21 +275,22 @@ void ManualBulletShapeLoader::handleNiTriShape(const Nif::NiTriShape *shape, int const Nif::NiTriShapeData *data = shape->data.getPtr(); - childMesh->preallocateVertices(data->vertices.size()); - childMesh->preallocateIndices(data->triangles.size()); + childMesh->preallocateVertices(data->vertices->size()); + childMesh->preallocateIndices(data->triangles->size()); - const std::vector &vertices = data->vertices; - const std::vector &triangles = data->triangles; + const osg::Vec3Array& vertices = *data->vertices; + const osg::DrawElementsUShort& triangles = *data->triangles; - for(size_t i = 0;i < data->triangles.size();i+=3) + size_t numtris = data->triangles->size(); + for(size_t i = 0;i < numtris;i+=3) { - Ogre::Vector3 b1 = vertices[triangles[i+0]]; - Ogre::Vector3 b2 = vertices[triangles[i+1]]; - Ogre::Vector3 b3 = vertices[triangles[i+2]]; - childMesh->addTriangle(btVector3(b1.x,b1.y,b1.z),btVector3(b2.x,b2.y,b2.z),btVector3(b3.x,b3.y,b3.z)); + osg::Vec3f b1 = vertices[triangles[i+0]]; + osg::Vec3f b2 = vertices[triangles[i+1]]; + osg::Vec3f b3 = vertices[triangles[i+2]]; + childMesh->addTriangle(getbtVector(b1), getbtVector(b2), getbtVector(b3)); } - TriangleMeshShape* childShape = new TriangleMeshShape(childMesh,true); + Resource::TriangleMeshShape* childShape = new Resource::TriangleMeshShape(childMesh,true); float scale = shape->trafo.scale; const Nif::Node* parent = shape; @@ -374,95 +299,38 @@ void ManualBulletShapeLoader::handleNiTriShape(const Nif::NiTriShape *shape, int parent = parent->parent; scale *= parent->trafo.scale; } - Ogre::Quaternion q = transform.extractQuaternion(); - Ogre::Vector3 v = transform.getTrans(); + osg::Quat q = transform.getRotate(); + osg::Vec3f v = transform.getTrans(); childShape->setLocalScaling(btVector3(scale, scale, scale)); - btTransform trans(btQuaternion(q.x, q.y, q.z, q.w), btVector3(v.x, v.y, v.z)); + btTransform trans(btQuaternion(q.x(), q.y(), q.z(), q.w()), btVector3(v.x(), v.y(), v.z())); - if (raycasting) - mShape->mAnimatedRaycastingShapes.insert(std::make_pair(shape->recIndex, mCompoundShape->getNumChildShapes())); - else - mShape->mAnimatedShapes.insert(std::make_pair(shape->recIndex, mCompoundShape->getNumChildShapes())); + mShape->mAnimatedShapes.insert(std::make_pair(shape->recIndex, mCompoundShape->getNumChildShapes())); mCompoundShape->addChildShape(trans, childShape); } else { if (!mStaticMesh) - mStaticMesh = new btTriangleMesh(); + mStaticMesh = new btTriangleMesh(false); // Static shape, just transform all vertices into position const Nif::NiTriShapeData *data = shape->data.getPtr(); - const std::vector &vertices = data->vertices; - const std::vector &triangles = data->triangles; + const osg::Vec3Array& vertices = *data->vertices; + const osg::DrawElementsUShort& triangles = *data->triangles; - for(size_t i = 0;i < data->triangles.size();i+=3) + mStaticMesh->preallocateVertices(data->vertices->size()); + mStaticMesh->preallocateIndices(data->triangles->size()); + + size_t numtris = data->triangles->size(); + for(size_t i = 0;i < numtris;i+=3) { - Ogre::Vector3 b1 = transform*vertices[triangles[i+0]]; - Ogre::Vector3 b2 = transform*vertices[triangles[i+1]]; - Ogre::Vector3 b3 = transform*vertices[triangles[i+2]]; - mStaticMesh->addTriangle(btVector3(b1.x,b1.y,b1.z),btVector3(b2.x,b2.y,b2.z),btVector3(b3.x,b3.y,b3.z)); + osg::Vec3f b1 = vertices[triangles[i+0]]*transform; + osg::Vec3f b2 = vertices[triangles[i+1]]*transform; + osg::Vec3f b3 = vertices[triangles[i+2]]*transform; + mStaticMesh->addTriangle(getbtVector(b1), getbtVector(b2), getbtVector(b3)); } } } -void ManualBulletShapeLoader::load(const std::string &name,const std::string &group) -{ - // Check if the resource already exists - Ogre::ResourcePtr ptr = OEngine::Physic::BulletShapeManager::getSingleton().getByName(name, group); - if (!ptr.isNull()) - return; - OEngine::Physic::BulletShapeManager::getSingleton().create(name,group,true,this); -} - -bool findBoundingBox (const Nif::Node* node, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation) -{ - if(node->hasBounds) - { - if (!(node->flags & Nif::NiNode::Flag_Hidden)) - { - translation = node->boundPos; - orientation = node->boundRot; - halfExtents = node->boundXYZ; - return true; - } - } - - const Nif::NiNode *ninode = dynamic_cast(node); - if(ninode) - { - const Nif::NodeList &list = ninode->children; - for(size_t i = 0;i < list.length();i++) - { - if(!list[i].empty()) - if (findBoundingBox(list[i].getPtr(), halfExtents, translation, orientation)) - return true; - } - } - return false; -} - -bool getBoundingBox(const std::string& nifFile, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation) -{ - Nif::NIFFilePtr pnif (Nif::Cache::getInstance().load(nifFile)); - Nif::NIFFile & nif = *pnif.get (); - - if (nif.numRoots() < 1) - { - return false; - } - - Nif::Record *r = nif.getRoot(0); - assert(r != NULL); - - Nif::Node *node = dynamic_cast(r); - if (node == NULL) - { - return false; - } - - return findBoundingBox(node, halfExtents, translation, orientation); -} - } // namespace NifBullet diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index f4126b7a73..a30bf8fdf1 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -1,39 +1,23 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (ogre_nif_loader.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - 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 - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - #ifndef OPENMW_COMPONENTS_NIFBULLET_BULLETNIFLOADER_HPP #define OPENMW_COMPONENTS_NIFBULLET_BULLETNIFLOADER_HPP #include #include -#include -#include -#include -#include -#include - -// For warning messages +#include #include +#include + +#include +#include +#include +#include + +#include +#include + +class btTriangleMesh; +class btCompoundShape; +class btCollisionShape; namespace Nif { @@ -45,38 +29,15 @@ namespace Nif namespace NifBullet { -// Subclass btBhvTriangleMeshShape to auto-delete the meshInterface -struct TriangleMeshShape : public btBvhTriangleMeshShape -{ - TriangleMeshShape(btStridingMeshInterface* meshInterface, bool useQuantizedAabbCompression) - : btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression) - { - } - - virtual ~TriangleMeshShape() - { - delete getTriangleInfoMap(); - delete m_meshInterface; - } -}; - - /** *Load bulletShape from NIF files. */ -class ManualBulletShapeLoader : public OEngine::Physic::BulletShapeLoader +class BulletNifLoader { public: - ManualBulletShapeLoader(bool showMarkers=false) - : mShape(NULL) - , mCompoundShape(NULL) - , mStaticMesh(NULL) - , mBoundingBox(NULL) - , mShowMarkers(showMarkers) - { - } + BulletNifLoader(); - virtual ~ManualBulletShapeLoader(); + virtual ~BulletNifLoader(); void warn(const std::string &msg) { @@ -89,57 +50,24 @@ public: abort(); } - /** - *This function should not be called manualy. Use load instead. (this is called by the BulletShapeManager when you use load). - */ - void loadResource(Ogre::Resource *resource); - - /** - *This function load a new bulletShape from a NIF file into the BulletShapeManager. - *When the file is loaded, you can then use BulletShapeManager::getByName() to retrive the bulletShape. - *Warning: this function will just crash if the resourceGroup doesn't exist! - */ - void load(const std::string &name,const std::string &group); + osg::ref_ptr load(const Nif::NIFFilePtr file); private: - btVector3 getbtVector(Ogre::Vector3 const &v); + bool findBoundingBox(const Nif::Node* node, int flags = 0); - /** - *Parse a node. - */ - void handleNode(Nif::Node const *node, int flags, bool isCollisionNode, - bool raycasting, bool isAnimated=false); + void handleNode(Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false); - /** - *Helper function - */ bool hasAutoGeneratedCollision(const Nif::Node *rootNode); - /** - *convert a NiTriShape to a bullet trishape. - */ - void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform, bool raycasting, bool isAnimated); - - std::string mResourceName; - - OEngine::Physic::BulletShape* mShape;//current shape + void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf& transform, bool isAnimated); btCompoundShape* mCompoundShape; btTriangleMesh* mStaticMesh; - btBoxShape *mBoundingBox; - - std::set mControlledNodes; - - bool mShowMarkers; + osg::ref_ptr mShape; }; - -bool getBoundingBox(const std::string& nifFile, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation); - -bool findBoundingBox(const Nif::Node* node, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation); - } #endif diff --git a/components/nifcache/nifcache.cpp b/components/nifcache/nifcache.cpp deleted file mode 100644 index 342251dbc8..0000000000 --- a/components/nifcache/nifcache.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "nifcache.hpp" - -namespace Nif -{ - -Cache* Cache::sThis = 0; - -Cache& Cache::getInstance() -{ - assert (sThis); - return *sThis; -} - -Cache* Cache::getInstancePtr() -{ - return sThis; -} - -Cache::Cache() -{ - assert (!sThis); - sThis = this; -} - -NIFFilePtr Cache::load(const std::string &filename) -{ - // TODO: normalize file path to make sure we're not loading the same file twice - - LoadedMap::iterator it = mLoadedMap.find(filename); - if (it != mLoadedMap.end()) - return it->second; - else - { - NIFFilePtr file(new Nif::NIFFile(filename)); - mLoadedMap[filename] = file; - return file; - } -} - -} diff --git a/components/nifcache/nifcache.hpp b/components/nifcache/nifcache.hpp deleted file mode 100644 index 173b918653..0000000000 --- a/components/nifcache/nifcache.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef OPENMW_COMPONENTS_NIFCACHE_H -#define OPENMW_COMPONENTS_NIFCACHE_H - -#include - -#include - -#include - -namespace Nif -{ - - typedef boost::shared_ptr NIFFilePtr; - - /// @brief A basic resource manager for NIF files - class Cache - { - public: - Cache(); - - /// Queue this file for background loading. A worker thread will start loading the file. - /// To get the loaded NIFFilePtr, use the load method, which will wait until the worker thread is finished - /// and then return the loaded file. - //void loadInBackground (const std::string& file); - - /// Read and parse the given file. May retrieve from cache if this file has been used previously. - /// @note If the file is currently loading in the background, this function will block until - /// the background loading finishes, then return the background loaded file. - /// @note Returns a SharedPtr to the file and the file will stay loaded as long as the user holds on to this pointer. - /// When all external SharedPtrs to a file are released, the cache may decide to unload the file. - NIFFilePtr load (const std::string& filename); - - /// Return instance of this class. - static Cache& getInstance(); - static Cache* getInstancePtr(); - - private: - static Cache* sThis; - - Cache(const Cache&); - Cache& operator =(const Cache&); - - typedef std::map LoadedMap; - - LoadedMap mLoadedMap; - }; - -} - -#endif diff --git a/components/nifogre/controller.hpp b/components/nifogre/controller.hpp deleted file mode 100644 index cc750ea65e..0000000000 --- a/components/nifogre/controller.hpp +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef COMPONENTS_NIFOGRE_CONTROLLER_H -#define COMPONENTS_NIFOGRE_CONTROLLER_H - -#include -#include -#include - -namespace NifOgre -{ - - class ValueInterpolator - { - protected: - float interpKey(const Nif::FloatKeyMap::MapType &keys, float time, float def=0.f) const - { - if (keys.size() == 0) - return def; - - if(time <= keys.begin()->first) - return keys.begin()->second.mValue; - - Nif::FloatKeyMap::MapType::const_iterator it = keys.lower_bound(time); - if (it != keys.end()) - { - float aTime = it->first; - const Nif::FloatKey* aKey = &it->second; - - assert (it != keys.begin()); // Shouldn't happen, was checked at beginning of this function - - Nif::FloatKeyMap::MapType::const_iterator last = --it; - float aLastTime = last->first; - const Nif::FloatKey* aLastKey = &last->second; - - float a = (time - aLastTime) / (aTime - aLastTime); - return aLastKey->mValue + ((aKey->mValue - aLastKey->mValue) * a); - } - else - return keys.rbegin()->second.mValue; - } - - Ogre::Vector3 interpKey(const Nif::Vector3KeyMap::MapType &keys, float time) const - { - if(time <= keys.begin()->first) - return keys.begin()->second.mValue; - - Nif::Vector3KeyMap::MapType::const_iterator it = keys.lower_bound(time); - if (it != keys.end()) - { - float aTime = it->first; - const Nif::Vector3Key* aKey = &it->second; - - assert (it != keys.begin()); // Shouldn't happen, was checked at beginning of this function - - Nif::Vector3KeyMap::MapType::const_iterator last = --it; - float aLastTime = last->first; - const Nif::Vector3Key* aLastKey = &last->second; - - float a = (time - aLastTime) / (aTime - aLastTime); - return aLastKey->mValue + ((aKey->mValue - aLastKey->mValue) * a); - } - else - return keys.rbegin()->second.mValue; - } - }; - - // FIXME: Should not be here. - class DefaultFunction : public Ogre::ControllerFunction - { - private: - float mFrequency; - float mPhase; - float mStartTime; - public: - float mStopTime; - - public: - DefaultFunction(const Nif::Controller *ctrl, bool deltaInput) - : Ogre::ControllerFunction(deltaInput) - , mFrequency(ctrl->frequency) - , mPhase(ctrl->phase) - , mStartTime(ctrl->timeStart) - , mStopTime(ctrl->timeStop) - { - if(mDeltaInput) - mDeltaCount = mPhase; - } - - virtual Ogre::Real calculate(Ogre::Real value) - { - if(mDeltaInput) - { - if (mStopTime - mStartTime == 0.f) - return 0.f; - - mDeltaCount += value*mFrequency; - if(mDeltaCount < mStartTime) - mDeltaCount = mStopTime - std::fmod(mStartTime - mDeltaCount, - mStopTime - mStartTime); - mDeltaCount = std::fmod(mDeltaCount - mStartTime, - mStopTime - mStartTime) + mStartTime; - return mDeltaCount; - } - - value = std::min(mStopTime, std::max(mStartTime, value+mPhase)); - return value; - } - }; - -} - -#endif diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp deleted file mode 100644 index 90930bca8e..0000000000 --- a/components/nifogre/material.cpp +++ /dev/null @@ -1,442 +0,0 @@ -#include "material.hpp" - -#include -#include -#include -#include - -#include - -#include -#include - -#include - - -namespace NifOgre -{ - -// Conversion of blend / test mode from NIF -static const char *getBlendFactor(int mode) -{ - switch(mode) - { - case 0: return "one"; - case 1: return "zero"; - case 2: return "src_colour"; - case 3: return "one_minus_src_colour"; - case 4: return "dest_colour"; - case 5: return "one_minus_dest_colour"; - case 6: return "src_alpha"; - case 7: return "one_minus_src_alpha"; - case 8: return "dest_alpha"; - case 9: return "one_minus_dest_alpha"; - case 10: return "src_alpha_saturate"; - } - std::cerr<< "Unexpected blend mode: "<setProperty(textureSlotName + "UVSet", sh::makeProperty(new sh::IntValue(tex.uvSet))); - const std::string clampMode = textureSlotName + "ClampMode"; - switch (tex.clamp) - { - case 0: - material->setProperty(clampMode, sh::makeProperty(new sh::StringValue("clamp clamp"))); - break; - case 1: - material->setProperty(clampMode, sh::makeProperty(new sh::StringValue("clamp wrap"))); - break; - case 2: - material->setProperty(clampMode, sh::makeProperty(new sh::StringValue("wrap clamp"))); - break; - case 3: - default: - material->setProperty(clampMode, sh::makeProperty(new sh::StringValue("wrap wrap"))); - break; - } -} - -Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, - const Ogre::String &name, const Ogre::String &group, - const Nif::NiTexturingProperty *texprop, - const Nif::NiMaterialProperty *matprop, - const Nif::NiAlphaProperty *alphaprop, - const Nif::NiVertexColorProperty *vertprop, - const Nif::NiZBufferProperty *zprop, - const Nif::NiSpecularProperty *specprop, - const Nif::NiWireframeProperty *wireprop, - const Nif::NiStencilProperty *stencilprop, - bool &needTangents, bool particleMaterial) -{ - Ogre::MaterialManager &matMgr = Ogre::MaterialManager::getSingleton(); - Ogre::MaterialPtr material = matMgr.getByName(name); - if(!material.isNull()) - return name; - - Ogre::Vector3 ambient(1.0f); - Ogre::Vector3 diffuse(1.0f); - Ogre::Vector3 specular(0.0f); - Ogre::Vector3 emissive(0.0f); - float glossiness = 0.0f; - float alpha = 1.0f; - int alphaFlags = 0; - int alphaTest = 0; - int vertMode = 2; - //int lightMode = 1; - int depthFlags = 3; - // Default should be 1, but Bloodmoon's models are broken - int specFlags = 0; - int wireFlags = 0; - int drawMode = 1; - Ogre::String texName[7]; - - bool vertexColour = (shapedata->colors.size() != 0); - - // Texture - if(texprop) - { - for(int i = 0;i < 7;i++) - { - if(!texprop->textures[i].inUse) - continue; - if(texprop->textures[i].texture.empty()) - { - warn("Texture layer "+Ogre::StringConverter::toString(i)+" is in use but empty in "+name); - continue; - } - - const Nif::NiSourceTexture *st = texprop->textures[i].texture.getPtr(); - if(st->external) - texName[i] = Misc::ResourceHelpers::correctTexturePath(st->filename); - else - warn("Found internal texture, ignoring."); - } - - Nif::ControllerPtr ctrls = texprop->controller; - while(!ctrls.empty()) - { - if (ctrls->recType != Nif::RC_NiFlipController) // Handled in ogrenifloader - warn("Unhandled texture controller "+ctrls->recName+" in "+name); - ctrls = ctrls->next; - } - } - - // Alpha modifiers - if(alphaprop) - { - alphaFlags = alphaprop->flags; - alphaTest = alphaprop->data.threshold; - - Nif::ControllerPtr ctrls = alphaprop->controller; - while(!ctrls.empty()) - { - warn("Unhandled alpha controller "+ctrls->recName+" in "+name); - ctrls = ctrls->next; - } - } - - // Vertex color handling - if(vertprop) - { - vertMode = vertprop->data.vertmode; - // FIXME: Handle lightmode? - //lightMode = vertprop->data.lightmode; - - Nif::ControllerPtr ctrls = vertprop->controller; - while(!ctrls.empty()) - { - warn("Unhandled vertex color controller "+ctrls->recName+" in "+name); - ctrls = ctrls->next; - } - } - - if(zprop) - { - depthFlags = zprop->flags; - // Depth function??? - - Nif::ControllerPtr ctrls = zprop->controller; - while(!ctrls.empty()) - { - warn("Unhandled depth controller "+ctrls->recName+" in "+name); - ctrls = ctrls->next; - } - } - - if(specprop) - { - specFlags = specprop->flags; - - Nif::ControllerPtr ctrls = specprop->controller; - while(!ctrls.empty()) - { - warn("Unhandled specular controller "+ctrls->recName+" in "+name); - ctrls = ctrls->next; - } - } - - if(wireprop) - { - wireFlags = wireprop->flags; - - Nif::ControllerPtr ctrls = wireprop->controller; - while(!ctrls.empty()) - { - warn("Unhandled wireframe controller "+ctrls->recName+" in "+name); - ctrls = ctrls->next; - } - } - - if(stencilprop) - { - drawMode = stencilprop->data.drawMode; - if (stencilprop->data.enabled) - warn("Unhandled stencil test in "+name); - - Nif::ControllerPtr ctrls = stencilprop->controller; - while(!ctrls.empty()) - { - warn("Unhandled stencil controller "+ctrls->recName+" in "+name); - ctrls = ctrls->next; - } - } - - // Material - if(matprop) - { - ambient = matprop->data.ambient; - diffuse = matprop->data.diffuse; - specular = matprop->data.specular; - emissive = matprop->data.emissive; - glossiness = matprop->data.glossiness; - alpha = matprop->data.alpha; - - Nif::ControllerPtr ctrls = matprop->controller; - while(!ctrls.empty()) - { - if (ctrls->recType != Nif::RC_NiAlphaController && ctrls->recType != Nif::RC_NiMaterialColorController) - warn("Unhandled material controller "+ctrls->recName+" in "+name); - ctrls = ctrls->next; - } - } - - if (particleMaterial) - { - alpha = 1.f; // Apparently ignored, might be overridden by particle vertex colors? - } - - { - // Generate a hash out of all properties that can affect the material. - size_t h = 0; - boost::hash_combine(h, ambient.x); - boost::hash_combine(h, ambient.y); - boost::hash_combine(h, ambient.z); - boost::hash_combine(h, diffuse.x); - boost::hash_combine(h, diffuse.y); - boost::hash_combine(h, diffuse.z); - boost::hash_combine(h, alpha); - boost::hash_combine(h, specular.x); - boost::hash_combine(h, specular.y); - boost::hash_combine(h, specular.z); - boost::hash_combine(h, glossiness); - boost::hash_combine(h, emissive.x); - boost::hash_combine(h, emissive.y); - boost::hash_combine(h, emissive.z); - for(int i = 0;i < 7;i++) - { - if(!texName[i].empty()) - { - boost::hash_combine(h, texName[i]); - boost::hash_combine(h, texprop->textures[i].clamp); - boost::hash_combine(h, texprop->textures[i].uvSet); - } - } - boost::hash_combine(h, drawMode); - boost::hash_combine(h, vertexColour); - boost::hash_combine(h, alphaFlags); - boost::hash_combine(h, alphaTest); - boost::hash_combine(h, vertMode); - boost::hash_combine(h, depthFlags); - boost::hash_combine(h, specFlags); - boost::hash_combine(h, wireFlags); - - std::map::iterator itr = sMaterialMap.find(h); - if (itr != sMaterialMap.end()) - { - // a suitable material exists already - use it - sh::MaterialInstance* instance = sh::Factory::getInstance().getMaterialInstance(itr->second); - needTangents = !sh::retrieveValue(instance->getProperty("normalMap"), instance).get().empty(); - return itr->second; - } - // not found, create a new one - sMaterialMap.insert(std::make_pair(h, name)); - } - - // No existing material like this. Create a new one. - sh::MaterialInstance *instance = sh::Factory::getInstance().createMaterialInstance(name, "openmw_objects_base"); - if(vertMode == 0 || !vertexColour) - { - instance->setProperty("ambient", sh::makeProperty(new sh::Vector4(ambient.x, ambient.y, ambient.z, 1))); - instance->setProperty("diffuse", sh::makeProperty(new sh::Vector4(diffuse.x, diffuse.y, diffuse.z, alpha))); - instance->setProperty("emissive", sh::makeProperty(new sh::Vector4(emissive.x, emissive.y, emissive.z, 1))); - instance->setProperty("vertmode", sh::makeProperty(new sh::StringValue("0"))); - } - else if(vertMode == 1) - { - instance->setProperty("ambient", sh::makeProperty(new sh::Vector4(ambient.x, ambient.y, ambient.z, 1))); - instance->setProperty("diffuse", sh::makeProperty(new sh::Vector4(diffuse.x, diffuse.y, diffuse.z, alpha))); - instance->setProperty("emissive", sh::makeProperty(new sh::StringValue("vertexcolour"))); - instance->setProperty("vertmode", sh::makeProperty(new sh::StringValue("1"))); - } - else if(vertMode == 2) - { - instance->setProperty("ambient", sh::makeProperty(new sh::StringValue("vertexcolour"))); - instance->setProperty("diffuse", sh::makeProperty(new sh::StringValue("vertexcolour"))); - instance->setProperty("emissive", sh::makeProperty(new sh::Vector4(emissive.x, emissive.y, emissive.z, 1))); - instance->setProperty("vertmode", sh::makeProperty(new sh::StringValue("2"))); - } - else - std::cerr<< "Unhandled vertex mode: "<setProperty("specular", sh::makeProperty( - new sh::Vector4(specular.x, specular.y, specular.z, glossiness))); - } - - if(wireFlags) - { - instance->setProperty("polygon_mode", sh::makeProperty(new sh::StringValue("wireframe"))); - } - - if (drawMode == 1) - instance->setProperty("cullmode", sh::makeProperty(new sh::StringValue("clockwise"))); - else if (drawMode == 2) - instance->setProperty("cullmode", sh::makeProperty(new sh::StringValue("anticlockwise"))); - else if (drawMode == 3) - instance->setProperty("cullmode", sh::makeProperty(new sh::StringValue("none"))); - - instance->setProperty("diffuseMap", sh::makeProperty(texName[Nif::NiTexturingProperty::BaseTexture])); - instance->setProperty("normalMap", sh::makeProperty(texName[Nif::NiTexturingProperty::BumpTexture])); - instance->setProperty("detailMap", sh::makeProperty(texName[Nif::NiTexturingProperty::DetailTexture])); - instance->setProperty("emissiveMap", sh::makeProperty(texName[Nif::NiTexturingProperty::GlowTexture])); - instance->setProperty("darkMap", sh::makeProperty(texName[Nif::NiTexturingProperty::DarkTexture])); - if (!texName[Nif::NiTexturingProperty::BaseTexture].empty()) - { - instance->setProperty("use_diffuse_map", sh::makeProperty(new sh::BooleanValue(true))); - setTextureProperties(instance, "diffuseMap", texprop->textures[Nif::NiTexturingProperty::BaseTexture]); - } - if (!texName[Nif::NiTexturingProperty::GlowTexture].empty()) - { - instance->setProperty("use_emissive_map", sh::makeProperty(new sh::BooleanValue(true))); - setTextureProperties(instance, "emissiveMap", texprop->textures[Nif::NiTexturingProperty::GlowTexture]); - } - if (!texName[Nif::NiTexturingProperty::DetailTexture].empty()) - { - instance->setProperty("use_detail_map", sh::makeProperty(new sh::BooleanValue(true))); - setTextureProperties(instance, "detailMap", texprop->textures[Nif::NiTexturingProperty::DetailTexture]); - } - if (!texName[Nif::NiTexturingProperty::DarkTexture].empty()) - { - instance->setProperty("use_dark_map", sh::makeProperty(new sh::BooleanValue(true))); - setTextureProperties(instance, "darkMap", texprop->textures[Nif::NiTexturingProperty::DarkTexture]); - } - - bool useParallax = !texName[Nif::NiTexturingProperty::BumpTexture].empty() - && texName[Nif::NiTexturingProperty::BumpTexture].find("_nh.") != std::string::npos; - instance->setProperty("use_parallax", sh::makeProperty(new sh::BooleanValue(useParallax))); - - for(int i = 0;i < 7;i++) - { - if(i == Nif::NiTexturingProperty::BaseTexture || - i == Nif::NiTexturingProperty::DetailTexture || - i == Nif::NiTexturingProperty::DarkTexture || - i == Nif::NiTexturingProperty::BumpTexture || - i == Nif::NiTexturingProperty::GlowTexture) - continue; - if(!texName[i].empty()) - warn("Ignored texture "+texName[i]+" on layer "+Ogre::StringConverter::toString(i) + " in " + name); - } - - if (vertexColour) - instance->setProperty("has_vertex_colour", sh::makeProperty(new sh::BooleanValue(true))); - - // Override alpha flags based on our override list (transparency-overrides.cfg) - if ((alphaFlags&1) && !texName[0].empty()) - { - NifOverrides::TransparencyResult result = NifOverrides::Overrides::getTransparencyOverride(texName[0]); - if (result.first) - { - alphaFlags = (1<<9) | (6<<10); /* alpha_rejection enabled, greater_equal */ - alphaTest = result.second; - depthFlags = (1<<0) | (1<<1); // depth_write on, depth_check on - } - } - - // Add transparency if NiAlphaProperty was present - if((alphaFlags&1)) - { - std::string blend_mode; - blend_mode += getBlendFactor((alphaFlags>>1)&0xf); - blend_mode += " "; - blend_mode += getBlendFactor((alphaFlags>>5)&0xf); - instance->setProperty("scene_blend", sh::makeProperty(new sh::StringValue(blend_mode))); - } - - if((alphaFlags>>9)&1) - { -#ifndef ANDROID - std::string reject; - reject += getTestMode((alphaFlags>>10)&0x7); - reject += " "; - reject += Ogre::StringConverter::toString(alphaTest); - instance->setProperty("alpha_rejection", sh::makeProperty(new sh::StringValue(reject))); -#else - // alpha test not supported in OpenGL ES 2, use manual implementation in shader - instance->setProperty("alphaTestMode", sh::makeProperty(new sh::IntValue((alphaFlags>>10)&0x7))); - instance->setProperty("alphaTestValue", sh::makeProperty(new sh::FloatValue(alphaTest/255.f))); -#endif - } - else - instance->getMaterial()->setShadowCasterMaterial("openmw_shadowcaster_noalpha"); - - // Ogre usually only sorts if depth write is disabled, so we want "force" instead of "on" - instance->setProperty("transparent_sorting", sh::makeProperty(new sh::StringValue( - ((alphaFlags&1) && !((alphaFlags>>13)&1)) ? "force" : "off"))); - - instance->setProperty("depth_check", sh::makeProperty(new sh::StringValue((depthFlags&1) ? "on" : "off"))); - instance->setProperty("depth_write", sh::makeProperty(new sh::StringValue(((depthFlags>>1)&1) ? "on" : "off"))); - // depth_func??? - - if (!texName[0].empty()) - NifOverrides::Overrides::getMaterialOverrides(texName[0], instance); - - // Don't use texName, as it may be overridden - needTangents = !sh::retrieveValue(instance->getProperty("normalMap"), instance).get().empty(); - - return name; -} - -std::map NIFMaterialLoader::sMaterialMap; - -} diff --git a/components/nifogre/material.hpp b/components/nifogre/material.hpp deleted file mode 100644 index 6be52d1a56..0000000000 --- a/components/nifogre/material.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef COMPONENTS_NIFOGRE_MATERIAL_HPP -#define COMPONENTS_NIFOGRE_MATERIAL_HPP - -#include -#include -#include -#include - -#include - -namespace Nif -{ - class ShapeData; - class NiTexturingProperty; - class NiMaterialProperty; - class NiAlphaProperty; - class NiVertexColorProperty; - class NiZBufferProperty; - class NiSpecularProperty; - class NiWireframeProperty; - class NiStencilProperty; -} - -namespace NifOgre -{ - -class NIFMaterialLoader { - static void warn(const std::string &msg) - { - std::cerr << "NIFMaterialLoader: Warn: " << msg << std::endl; - } - - static std::map sMaterialMap; - -public: - static Ogre::String getMaterial(const Nif::ShapeData *shapedata, - const Ogre::String &name, const Ogre::String &group, - const Nif::NiTexturingProperty *texprop, - const Nif::NiMaterialProperty *matprop, - const Nif::NiAlphaProperty *alphaprop, - const Nif::NiVertexColorProperty *vertprop, - const Nif::NiZBufferProperty *zprop, - const Nif::NiSpecularProperty *specprop, - const Nif::NiWireframeProperty *wireprop, - const Nif::NiStencilProperty *stencilprop, - bool &needTangents, bool particleMaterial=false); -}; - -} - -#endif diff --git a/components/nifogre/mesh.cpp b/components/nifogre/mesh.cpp deleted file mode 100644 index 85c3a7b65c..0000000000 --- a/components/nifogre/mesh.cpp +++ /dev/null @@ -1,412 +0,0 @@ -#include "mesh.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "material.hpp" - -namespace NifOgre -{ - -// Helper class that computes the bounding box and of a mesh -class BoundsFinder -{ - struct MaxMinFinder - { - float max, min; - - MaxMinFinder() - { - min = std::numeric_limits::infinity(); - max = -min; - } - - void add(float f) - { - if (f > max) max = f; - if (f < min) min = f; - } - - // Return Max(max**2, min**2) - float getMaxSquared() - { - float m1 = max*max; - float m2 = min*min; - if (m1 >= m2) return m1; - return m2; - } - }; - - MaxMinFinder X, Y, Z; - -public: - // Add 'verts' vertices to the calculation. The 'data' pointer is - // expected to point to 3*verts floats representing x,y,z for each - // point. - void add(float *data, int verts) - { - for (int i=0;idata.getPtr(); - const Nif::NiSkinInstance *skin = (shape->skin.empty() ? NULL : shape->skin.getPtr()); - std::vector srcVerts = data->vertices; - std::vector srcNorms = data->normals; - Ogre::HardwareBuffer::Usage vertUsage = Ogre::HardwareBuffer::HBU_STATIC; - bool vertShadowBuffer = false; - - if(skin != NULL) - { - vertUsage = Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY; - vertShadowBuffer = true; - - // Only set a skeleton when skinning. Unskinned meshes with a skeleton will be - // explicitly attached later. - mesh->setSkeletonName(mName); - - // Convert vertices and normals to bone space from bind position. It would be - // better to transform the bones into bind position, but there doesn't seem to - // be a reliable way to do that. - std::vector newVerts(srcVerts.size(), Ogre::Vector3(0.0f)); - std::vector newNorms(srcNorms.size(), Ogre::Vector3(0.0f)); - - const Nif::NiSkinData *data = skin->data.getPtr(); - const Nif::NodeList &bones = skin->bones; - for(size_t b = 0;b < bones.length();b++) - { - Ogre::Matrix4 mat; - mat.makeTransform(data->bones[b].trafo.trans, Ogre::Vector3(data->bones[b].trafo.scale), - Ogre::Quaternion(data->bones[b].trafo.rotation)); - mat = bones[b]->getWorldTransform() * mat; - - const std::vector &weights = data->bones[b].weights; - for(size_t i = 0;i < weights.size();i++) - { - size_t index = weights[i].vertex; - float weight = weights[i].weight; - - newVerts.at(index) += (mat*srcVerts[index]) * weight; - if(newNorms.size() > index) - { - Ogre::Vector4 vec4(srcNorms[index][0], srcNorms[index][1], srcNorms[index][2], 0.0f); - vec4 = mat*vec4 * weight; - newNorms[index] += Ogre::Vector3(&vec4[0]); - } - } - } - - srcVerts = newVerts; - srcNorms = newNorms; - } - else - { - Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr(); - if(skelMgr->getByName(mName).isNull()) - { - // No skinning and no skeleton, so just transform the vertices and - // normals into position. - Ogre::Matrix4 mat4 = shape->getWorldTransform(); - for(size_t i = 0;i < srcVerts.size();i++) - { - Ogre::Vector4 vec4(srcVerts[i].x, srcVerts[i].y, srcVerts[i].z, 1.0f); - vec4 = mat4*vec4; - srcVerts[i] = Ogre::Vector3(&vec4[0]); - } - for(size_t i = 0;i < srcNorms.size();i++) - { - Ogre::Vector4 vec4(srcNorms[i].x, srcNorms[i].y, srcNorms[i].z, 0.0f); - vec4 = mat4*vec4; - srcNorms[i] = Ogre::Vector3(&vec4[0]); - } - } - } - - // Set the bounding box first - BoundsFinder bounds; - bounds.add(&srcVerts[0][0], srcVerts.size()); - if(!bounds.isValid()) - { - float v[3] = { 0.0f, 0.0f, 0.0f }; - bounds.add(&v[0], 1); - } - - mesh->_setBounds(Ogre::AxisAlignedBox(bounds.minX()-0.5f, bounds.minY()-0.5f, bounds.minZ()-0.5f, - bounds.maxX()+0.5f, bounds.maxY()+0.5f, bounds.maxZ()+0.5f)); - mesh->_setBoundingSphereRadius(bounds.getRadius()); - - // This function is just one long stream of Ogre-barf, but it works - // great. - Ogre::HardwareBufferManager *hwBufMgr = Ogre::HardwareBufferManager::getSingletonPtr(); - Ogre::HardwareVertexBufferSharedPtr vbuf; - Ogre::HardwareIndexBufferSharedPtr ibuf; - Ogre::VertexBufferBinding *bind; - Ogre::VertexDeclaration *decl; - int nextBuf = 0; - - Ogre::SubMesh *sub = mesh->createSubMesh(); - - // Add vertices - sub->useSharedVertices = false; - sub->vertexData = new Ogre::VertexData(); - sub->vertexData->vertexStart = 0; - sub->vertexData->vertexCount = srcVerts.size(); - - decl = sub->vertexData->vertexDeclaration; - bind = sub->vertexData->vertexBufferBinding; - if(srcVerts.size()) - { - vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), - srcVerts.size(), vertUsage, vertShadowBuffer); - vbuf->writeData(0, vbuf->getSizeInBytes(), &srcVerts[0][0], true); - - decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION); - bind->setBinding(nextBuf++, vbuf); - } - - // Vertex normals - if(srcNorms.size()) - { - vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), - srcNorms.size(), vertUsage, vertShadowBuffer); - vbuf->writeData(0, vbuf->getSizeInBytes(), &srcNorms[0][0], true); - - decl->addElement(nextBuf, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); - bind->setBinding(nextBuf++, vbuf); - } - - // Vertex colors - const std::vector &colors = data->colors; - if(colors.size()) - { - Ogre::RenderSystem *rs = Ogre::Root::getSingleton().getRenderSystem(); - std::vector colorsRGB(colors.size()); - for(size_t i = 0;i < colorsRGB.size();i++) - { - Ogre::ColourValue clr(colors[i][0], colors[i][1], colors[i][2], colors[i][3]); - rs->convertColourValue(clr, &colorsRGB[i]); - } - vbuf = hwBufMgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), - colorsRGB.size(), Ogre::HardwareBuffer::HBU_STATIC); - vbuf->writeData(0, vbuf->getSizeInBytes(), &colorsRGB[0], true); - decl->addElement(nextBuf, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE); - bind->setBinding(nextBuf++, vbuf); - } - - // Texture UV coordinates - size_t numUVs = data->uvlist.size(); - if (numUVs) - { - size_t elemSize = Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2); - - for(size_t i = 0; i < numUVs; i++) - decl->addElement(nextBuf, elemSize*i, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, i); - - vbuf = hwBufMgr->createVertexBuffer(decl->getVertexSize(nextBuf), srcVerts.size(), - Ogre::HardwareBuffer::HBU_STATIC); - - std::vector allUVs; - allUVs.reserve(srcVerts.size()*numUVs); - for (size_t vert = 0; vertuvlist[i][vert]); - - vbuf->writeData(0, elemSize*srcVerts.size()*numUVs, &allUVs[0], true); - - bind->setBinding(nextBuf++, vbuf); - } - - // Triangle faces - const std::vector &srcIdx = data->triangles; - if(srcIdx.size()) - { - ibuf = hwBufMgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, srcIdx.size(), - Ogre::HardwareBuffer::HBU_STATIC); - ibuf->writeData(0, ibuf->getSizeInBytes(), &srcIdx[0], true); - sub->indexData->indexBuffer = ibuf; - sub->indexData->indexCount = srcIdx.size(); - sub->indexData->indexStart = 0; - } - - // Assign bone weights for this TriShape - if(skin != NULL) - { - Ogre::SkeletonPtr skel = Ogre::SkeletonManager::getSingleton().getByName(mName); - - const Nif::NiSkinData *data = skin->data.getPtr(); - const Nif::NodeList &bones = skin->bones; - for(size_t i = 0;i < bones.length();i++) - { - Ogre::VertexBoneAssignment boneInf; - boneInf.boneIndex = skel->getBone(bones[i]->name)->getHandle(); - - const std::vector &weights = data->bones[i].weights; - for(size_t j = 0;j < weights.size();j++) - { - boneInf.vertexIndex = weights[j].vertex; - boneInf.weight = weights[j].weight; - sub->addBoneAssignment(boneInf); - } - } - } - - const Nif::NiTexturingProperty *texprop = NULL; - const Nif::NiMaterialProperty *matprop = NULL; - const Nif::NiAlphaProperty *alphaprop = NULL; - const Nif::NiVertexColorProperty *vertprop = NULL; - const Nif::NiZBufferProperty *zprop = NULL; - const Nif::NiSpecularProperty *specprop = NULL; - const Nif::NiWireframeProperty *wireprop = NULL; - const Nif::NiStencilProperty *stencilprop = NULL; - bool needTangents = false; - - shape->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop, stencilprop); - std::string matname = NIFMaterialLoader::getMaterial(data, mesh->getName(), mGroup, - texprop, matprop, alphaprop, - vertprop, zprop, specprop, - wireprop, stencilprop, needTangents); - if(matname.length() > 0) - sub->setMaterialName(matname); - - // build tangents if the material needs them - if (needTangents) - { - unsigned short src,dest; - if (!mesh->suggestTangentVectorBuildParams(Ogre::VES_TANGENT, src,dest)) - mesh->buildTangentVectors(Ogre::VES_TANGENT, src,dest); - } - - - if(!shape->controller.empty()) - { - Nif::ControllerPtr ctrl = shape->controller; - do { - // Load GeomMorpherController into an Ogre::Pose and Animation - if(ctrl->recType == Nif::RC_NiGeomMorpherController && ctrl->flags & Nif::NiNode::ControllerFlag_Active) - { - const Nif::NiGeomMorpherController *geom = - static_cast(ctrl.getPtr()); - - const std::vector& morphs = geom->data.getPtr()->mMorphs; - // Note we are not interested in morph 0, which just contains the original vertices - for (unsigned int i = 1; i < morphs.size(); ++i) - { - Ogre::Pose* pose = mesh->createPose(i); - const Nif::NiMorphData::MorphData& data = morphs[i]; - for (unsigned int v = 0; v < data.mVertices.size(); ++v) - pose->addVertex(v, data.mVertices[v]); - - Ogre::String animationID = Ogre::StringConverter::toString(ctrl->recIndex) - + "_" + Ogre::StringConverter::toString(i); - Ogre::VertexAnimationTrack* track = - mesh->createAnimation(animationID, 0) - ->createVertexTrack(1, Ogre::VAT_POSE); - Ogre::VertexPoseKeyFrame* keyframe = track->createVertexPoseKeyFrame(0); - keyframe->addPoseReference(i-1, 1); - } - - break; - } - } while(!(ctrl=ctrl->next).empty()); - } -} - - -NIFMeshLoader::NIFMeshLoader(const std::string &name, const std::string &group, size_t idx) - : mName(name), mGroup(group), mShapeIndex(idx) -{ -} - -void NIFMeshLoader::loadResource(Ogre::Resource *resource) -{ - Ogre::Mesh *mesh = static_cast(resource); - OgreAssert(mesh, "Attempting to load a mesh into a non-mesh resource!"); - - Nif::NIFFilePtr nif = Nif::Cache::getInstance().load(mName); - if(mShapeIndex >= nif->numRecords()) - { - Ogre::SkeletonManager *skelMgr = Ogre::SkeletonManager::getSingletonPtr(); - if(!skelMgr->getByName(mName).isNull()) - mesh->setSkeletonName(mName); - return; - } - - const Nif::Record *record = nif->getRecord(mShapeIndex); - createSubMesh(mesh, static_cast(record)); -} - - -void NIFMeshLoader::createMesh(const std::string &name, const std::string &fullname, const std::string &group, size_t idx) -{ - NIFMeshLoader::LoaderMap::iterator loader; - loader = sLoaders.insert(std::make_pair(fullname, NIFMeshLoader(name, group, idx))).first; - - Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); - Ogre::MeshPtr mesh = meshMgr.createManual(fullname, group, &loader->second); - mesh->setAutoBuildEdgeLists(false); -} - -} diff --git a/components/nifogre/mesh.hpp b/components/nifogre/mesh.hpp deleted file mode 100644 index 69b3f2f78b..0000000000 --- a/components/nifogre/mesh.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef COMPONENTS_NIFOGRE_MESH_HPP -#define COMPONENTS_NIFOGRE_MESH_HPP - -#include -#include -#include -#include - -#include - -namespace Nif -{ - struct NiTriShape; -} - -namespace NifOgre -{ - -/** Manual resource loader for NiTriShapes. This is the main class responsible - * for translating the internal NIF meshes into something Ogre can use. - */ -class NIFMeshLoader : Ogre::ManualResourceLoader -{ - static void warn(const std::string &msg) - { - std::cerr << "NIFMeshLoader: Warn: " << msg << std::endl; - } - - static void fail(const std::string &msg) - { - std::cerr << "NIFMeshLoader: Fail: "<< msg << std::endl; - abort(); - } - - std::string mName; - std::string mGroup; - size_t mShapeIndex; - - // Convert NiTriShape to Ogre::SubMesh - void createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape); - - typedef std::map LoaderMap; - static LoaderMap sLoaders; - - NIFMeshLoader(const std::string &name, const std::string &group, size_t idx); - - virtual void loadResource(Ogre::Resource *resource); - -public: - static void createMesh(const std::string &name, const std::string &fullname, const std::string &group, size_t idx); -}; - -} - -#endif diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp deleted file mode 100644 index 0009c9f836..0000000000 --- a/components/nifogre/ogrenifloader.cpp +++ /dev/null @@ -1,1496 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (ogre_nif_loader.cpp) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - 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 - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -#include "ogrenifloader.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -#include "skeleton.hpp" -#include "material.hpp" -#include "mesh.hpp" -#include "controller.hpp" -#include "particles.hpp" - -namespace -{ - - void getAllNiNodes(const Nif::Node* node, std::vector& out) - { - const Nif::NiNode* ninode = dynamic_cast(node); - if (ninode) - { - out.push_back(ninode); - for (unsigned int i=0; ichildren.length(); ++i) - if (!ninode->children[i].empty()) - getAllNiNodes(ninode->children[i].getPtr(), out); - } - } - -} - -namespace NifOgre -{ - -Ogre::MaterialPtr MaterialControllerManager::getWritableMaterial(Ogre::MovableObject *movable) -{ - if (mClonedMaterials.find(movable) != mClonedMaterials.end()) - return mClonedMaterials[movable]; - - else - { - Ogre::MaterialPtr mat; - if (Ogre::Entity* ent = dynamic_cast(movable)) - mat = ent->getSubEntity(0)->getMaterial(); - else if (Ogre::ParticleSystem* partSys = dynamic_cast(movable)) - mat = Ogre::MaterialManager::getSingleton().getByName(partSys->getMaterialName()); - - static int count=0; - Ogre::String newName = mat->getName() + Ogre::StringConverter::toString(count++); - sh::Factory::getInstance().createMaterialInstance(newName, mat->getName()); - // Make sure techniques are created - sh::Factory::getInstance()._ensureMaterial(newName, "Default"); - mat = Ogre::MaterialManager::getSingleton().getByName(newName); - - mClonedMaterials[movable] = mat; - - if (Ogre::Entity* ent = dynamic_cast(movable)) - ent->getSubEntity(0)->setMaterial(mat); - else if (Ogre::ParticleSystem* partSys = dynamic_cast(movable)) - partSys->setMaterialName(mat->getName()); - - return mat; - } -} - -MaterialControllerManager::~MaterialControllerManager() -{ - for (std::map::iterator it = mClonedMaterials.begin(); it != mClonedMaterials.end(); ++it) - { - sh::Factory::getInstance().destroyMaterialInstance(it->second->getName()); - } -} - -ObjectScene::~ObjectScene() -{ - for(size_t i = 0;i < mLights.size();i++) - { - Ogre::Light *light = mLights[i]; - // If parent is a scene node, it was created specifically for this light. Destroy it now. - if(light->isAttached() && !light->isParentTagPoint()) - mSceneMgr->destroySceneNode(light->getParentSceneNode()); - mSceneMgr->destroyLight(light); - } - for(size_t i = 0;i < mParticles.size();i++) - mSceneMgr->destroyParticleSystem(mParticles[i]); - for(size_t i = 0;i < mEntities.size();i++) - mSceneMgr->destroyEntity(mEntities[i]); - mControllers.clear(); - mLights.clear(); - mParticles.clear(); - mEntities.clear(); - mSkelBase = NULL; -} - -void ObjectScene::setVisibilityFlags (unsigned int flags) -{ - for (std::vector::iterator iter (mEntities.begin()); iter!=mEntities.end(); - ++iter) - (*iter)->setVisibilityFlags (flags); - - for (std::vector::iterator iter (mParticles.begin()); - iter!=mParticles.end(); ++iter) - (*iter)->setVisibilityFlags (flags); - - for (std::vector::iterator iter (mLights.begin()); iter!=mLights.end(); - ++iter) - (*iter)->setVisibilityFlags (flags); -} - -void ObjectScene::rotateBillboardNodes(Ogre::Camera *camera) -{ - for (std::vector::iterator it = mBillboardNodes.begin(); it != mBillboardNodes.end(); ++it) - { - assert(mSkelBase); - Ogre::Node* node = *it; - node->_setDerivedOrientation(mSkelBase->getParentNode()->_getDerivedOrientation().Inverse() * - camera->getRealOrientation()); - } -} - -void ObjectScene::_notifyAttached() -{ - // convert initial particle positions to world space for world-space particle systems - // this can't be done on creation because the particle system is not in its correct world space position yet - for (std::vector::iterator it = mParticles.begin(); it != mParticles.end(); ++it) - { - Ogre::ParticleSystem* psys = *it; - if (psys->getKeepParticlesInLocalSpace()) - continue; - Ogre::ParticleIterator pi = psys->_getIterator(); - while (!pi.end()) - { - Ogre::Particle *p = pi.getNext(); - -#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) - Ogre::Vector3& position = p->mPosition; - Ogre::Vector3& direction = p->mDirection; -#else - Ogre::Vector3& position = p->position; - Ogre::Vector3& direction = p->direction; -#endif - - position = - (psys->getParentNode()->_getDerivedOrientation() * - (psys->getParentNode()->_getDerivedScale() * position)) - + psys->getParentNode()->_getDerivedPosition(); - direction = - (psys->getParentNode()->_getDerivedOrientation() * direction); - } - } -} - -// Animates a texture -class FlipController -{ -public: - class Value : public Ogre::ControllerValue - { - private: - Ogre::MovableObject* mMovable; - int mTexSlot; - float mDelta; - std::vector mTextures; - MaterialControllerManager* mMaterialControllerMgr; - - public: - Value(Ogre::MovableObject *movable, const Nif::NiFlipController *ctrl, MaterialControllerManager* materialControllerMgr) - : mMovable(movable) - , mMaterialControllerMgr(materialControllerMgr) - { - mTexSlot = ctrl->mTexSlot; - mDelta = ctrl->mDelta; - for (unsigned int i=0; imSources.length(); ++i) - { - const Nif::NiSourceTexture* tex = ctrl->mSources[i].getPtr(); - if (!tex->external) - std::cerr << "Warning: Found internal texture, ignoring." << std::endl; - mTextures.push_back(Misc::ResourceHelpers::correctTexturePath(tex->filename)); - } - } - - virtual Ogre::Real getValue() const - { - // Should not be called - return 0.0f; - } - - virtual void setValue(Ogre::Real time) - { - if (mDelta == 0) - return; - int curTexture = int(time / mDelta) % mTextures.size(); - - Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable); - Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); - while(techs.hasMoreElements()) - { - Ogre::Technique *tech = techs.getNext(); - Ogre::Technique::PassIterator passes = tech->getPassIterator(); - while(passes.hasMoreElements()) - { - Ogre::Pass *pass = passes.getNext(); - Ogre::Pass::TextureUnitStateIterator textures = pass->getTextureUnitStateIterator(); - while (textures.hasMoreElements()) - { - Ogre::TextureUnitState *texture = textures.getNext(); - if ((texture->getName() == "diffuseMap" && mTexSlot == Nif::NiTexturingProperty::BaseTexture) - || (texture->getName() == "normalMap" && mTexSlot == Nif::NiTexturingProperty::BumpTexture) - || (texture->getName() == "detailMap" && mTexSlot == Nif::NiTexturingProperty::DetailTexture) - || (texture->getName() == "darkMap" && mTexSlot == Nif::NiTexturingProperty::DarkTexture) - || (texture->getName() == "emissiveMap" && mTexSlot == Nif::NiTexturingProperty::GlowTexture)) - texture->setTextureName(mTextures[curTexture]); - } - } - } - } - }; - - typedef DefaultFunction Function; -}; - -class AlphaController -{ -public: - class Value : public Ogre::ControllerValue, public ValueInterpolator - { - private: - Ogre::MovableObject* mMovable; - Nif::FloatKeyMap mData; - MaterialControllerManager* mMaterialControllerMgr; - - public: - Value(Ogre::MovableObject *movable, const Nif::NiFloatData *data, MaterialControllerManager* materialControllerMgr) - : mMovable(movable) - , mData(data->mKeyList) - , mMaterialControllerMgr(materialControllerMgr) - { - } - - virtual Ogre::Real getValue() const - { - // Should not be called - return 0.0f; - } - - virtual void setValue(Ogre::Real time) - { - float value = interpKey(mData.mKeys, time); - Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable); - Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); - while(techs.hasMoreElements()) - { - Ogre::Technique *tech = techs.getNext(); - Ogre::Technique::PassIterator passes = tech->getPassIterator(); - while(passes.hasMoreElements()) - { - Ogre::Pass *pass = passes.getNext(); - Ogre::ColourValue diffuse = pass->getDiffuse(); - diffuse.a = value; - pass->setDiffuse(diffuse); - } - } - } - }; - - typedef DefaultFunction Function; -}; - -class MaterialColorController -{ -public: - class Value : public Ogre::ControllerValue, public ValueInterpolator - { - private: - Ogre::MovableObject* mMovable; - Nif::Vector3KeyMap mData; - MaterialControllerManager* mMaterialControllerMgr; - - public: - Value(Ogre::MovableObject *movable, const Nif::NiPosData *data, MaterialControllerManager* materialControllerMgr) - : mMovable(movable) - , mData(data->mKeyList) - , mMaterialControllerMgr(materialControllerMgr) - { - } - - virtual Ogre::Real getValue() const - { - // Should not be called - return 0.0f; - } - - virtual void setValue(Ogre::Real time) - { - Ogre::Vector3 value = interpKey(mData.mKeys, time); - Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable); - Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); - while(techs.hasMoreElements()) - { - Ogre::Technique *tech = techs.getNext(); - Ogre::Technique::PassIterator passes = tech->getPassIterator(); - while(passes.hasMoreElements()) - { - Ogre::Pass *pass = passes.getNext(); - Ogre::ColourValue diffuse = pass->getDiffuse(); - diffuse.r = value.x; - diffuse.g = value.y; - diffuse.b = value.z; - pass->setDiffuse(diffuse); - } - } - } - }; - - typedef DefaultFunction Function; -}; - -class VisController -{ -public: - class Value : public NodeTargetValue - { - private: - std::vector mData; - - bool calculate(Ogre::Real time) const - { - if(mData.size() == 0) - return true; - - for(size_t i = 1;i < mData.size();i++) - { - if(mData[i].time > time) - return mData[i-1].isSet; - } - return mData.back().isSet; - } - - static void setVisible(Ogre::Node *node, bool vis) - { - // Skinned meshes are attached to the scene node, not the bone. - // We use the Node's user data to connect it with the mesh. - Ogre::Any customData = node->getUserObjectBindings().getUserAny(); - - if (!customData.isEmpty()) - Ogre::any_cast(customData)->setVisible(vis); - - Ogre::TagPoint *tag = dynamic_cast(node); - if(tag != NULL) - { - Ogre::MovableObject *obj = tag->getChildObject(); - if(obj != NULL) - obj->setVisible(vis); - } - - Ogre::Node::ChildNodeIterator iter = node->getChildIterator(); - while(iter.hasMoreElements()) - { - node = iter.getNext(); - setVisible(node, vis); - } - } - - public: - Value(Ogre::Node *target, const Nif::NiVisData *data) - : NodeTargetValue(target) - , mData(data->mVis) - { } - - virtual Ogre::Quaternion getRotation(float time) const - { return Ogre::Quaternion(); } - - virtual Ogre::Vector3 getTranslation(float time) const - { return Ogre::Vector3(0.0f); } - - virtual Ogre::Vector3 getScale(float time) const - { return Ogre::Vector3(1.0f); } - - virtual Ogre::Real getValue() const - { - // Should not be called - return 0.0f; - } - - virtual void setValue(Ogre::Real time) - { - bool vis = calculate(time); - setVisible(mNode, vis); - } - }; - - typedef DefaultFunction Function; -}; - -class KeyframeController -{ -public: - class Value : public NodeTargetValue, public ValueInterpolator - { - private: - const Nif::QuaternionKeyMap* mRotations; - const Nif::FloatKeyMap* mXRotations; - const Nif::FloatKeyMap* mYRotations; - const Nif::FloatKeyMap* mZRotations; - const Nif::Vector3KeyMap* mTranslations; - const Nif::FloatKeyMap* mScales; - Nif::NIFFilePtr mNif; // Hold a SharedPtr to make sure key lists stay valid - - using ValueInterpolator::interpKey; - - static Ogre::Quaternion interpKey(const Nif::QuaternionKeyMap::MapType &keys, float time) - { - if(time <= keys.begin()->first) - return keys.begin()->second.mValue; - - Nif::QuaternionKeyMap::MapType::const_iterator it = keys.lower_bound(time); - if (it != keys.end()) - { - float aTime = it->first; - const Nif::QuaternionKey* aKey = &it->second; - - assert (it != keys.begin()); // Shouldn't happen, was checked at beginning of this function - - Nif::QuaternionKeyMap::MapType::const_iterator last = --it; - float aLastTime = last->first; - const Nif::QuaternionKey* aLastKey = &last->second; - - float a = (time - aLastTime) / (aTime - aLastTime); - return Ogre::Quaternion::nlerp(a, aLastKey->mValue, aKey->mValue); - } - else - return keys.rbegin()->second.mValue; - } - - Ogre::Quaternion getXYZRotation(float time) const - { - float xrot = interpKey(mXRotations->mKeys, time); - float yrot = interpKey(mYRotations->mKeys, time); - float zrot = interpKey(mZRotations->mKeys, time); - Ogre::Quaternion xr(Ogre::Radian(xrot), Ogre::Vector3::UNIT_X); - Ogre::Quaternion yr(Ogre::Radian(yrot), Ogre::Vector3::UNIT_Y); - Ogre::Quaternion zr(Ogre::Radian(zrot), Ogre::Vector3::UNIT_Z); - return (zr*yr*xr); - } - - public: - /// @note The NiKeyFrameData must be valid as long as this KeyframeController exists. - Value(Ogre::Node *target, const Nif::NIFFilePtr& nif, const Nif::NiKeyframeData *data) - : NodeTargetValue(target) - , mRotations(&data->mRotations) - , mXRotations(&data->mXRotations) - , mYRotations(&data->mYRotations) - , mZRotations(&data->mZRotations) - , mTranslations(&data->mTranslations) - , mScales(&data->mScales) - , mNif(nif) - { } - - virtual Ogre::Quaternion getRotation(float time) const - { - if(mRotations->mKeys.size() > 0) - return interpKey(mRotations->mKeys, time); - else if (!mXRotations->mKeys.empty() || !mYRotations->mKeys.empty() || !mZRotations->mKeys.empty()) - return getXYZRotation(time); - return mNode->getOrientation(); - } - - virtual Ogre::Vector3 getTranslation(float time) const - { - if(mTranslations->mKeys.size() > 0) - return interpKey(mTranslations->mKeys, time); - return mNode->getPosition(); - } - - virtual Ogre::Vector3 getScale(float time) const - { - if(mScales->mKeys.size() > 0) - return Ogre::Vector3(interpKey(mScales->mKeys, time)); - return mNode->getScale(); - } - - virtual Ogre::Real getValue() const - { - // Should not be called - return 0.0f; - } - - virtual void setValue(Ogre::Real time) - { - if(mRotations->mKeys.size() > 0) - mNode->setOrientation(interpKey(mRotations->mKeys, time)); - else if (!mXRotations->mKeys.empty() || !mYRotations->mKeys.empty() || !mZRotations->mKeys.empty()) - mNode->setOrientation(getXYZRotation(time)); - if(mTranslations->mKeys.size() > 0) - mNode->setPosition(interpKey(mTranslations->mKeys, time)); - if(mScales->mKeys.size() > 0) - mNode->setScale(Ogre::Vector3(interpKey(mScales->mKeys, time))); - } - }; - - typedef DefaultFunction Function; -}; - -class UVController -{ -public: - class Value : public Ogre::ControllerValue, public ValueInterpolator - { - private: - Ogre::MovableObject* mMovable; - Nif::FloatKeyMap mUTrans; - Nif::FloatKeyMap mVTrans; - Nif::FloatKeyMap mUScale; - Nif::FloatKeyMap mVScale; - MaterialControllerManager* mMaterialControllerMgr; - - public: - Value(Ogre::MovableObject* movable, const Nif::NiUVData *data, MaterialControllerManager* materialControllerMgr) - : mMovable(movable) - , mUTrans(data->mKeyList[0]) - , mVTrans(data->mKeyList[1]) - , mUScale(data->mKeyList[2]) - , mVScale(data->mKeyList[3]) - , mMaterialControllerMgr(materialControllerMgr) - { } - - virtual Ogre::Real getValue() const - { - // Should not be called - return 1.0f; - } - - virtual void setValue(Ogre::Real value) - { - float uTrans = interpKey(mUTrans.mKeys, value, 0.0f); - float vTrans = interpKey(mVTrans.mKeys, value, 0.0f); - float uScale = interpKey(mUScale.mKeys, value, 1.0f); - float vScale = interpKey(mVScale.mKeys, value, 1.0f); - - Ogre::MaterialPtr material = mMaterialControllerMgr->getWritableMaterial(mMovable); - - Ogre::Material::TechniqueIterator techs = material->getTechniqueIterator(); - while(techs.hasMoreElements()) - { - Ogre::Technique *tech = techs.getNext(); - Ogre::Technique::PassIterator passes = tech->getPassIterator(); - while(passes.hasMoreElements()) - { - Ogre::Pass *pass = passes.getNext(); - Ogre::TextureUnitState *tex = pass->getTextureUnitState(0); - tex->setTextureScroll(uTrans, vTrans); - tex->setTextureScale(uScale, vScale); - } - } - } - }; - - typedef DefaultFunction Function; -}; - -class ParticleSystemController -{ -public: - class Value : public Ogre::ControllerValue - { - private: - Ogre::ParticleSystem *mParticleSys; - float mEmitStart; - float mEmitStop; - - public: - Value(Ogre::ParticleSystem *psys, const Nif::NiParticleSystemController *pctrl) - : mParticleSys(psys) - , mEmitStart(pctrl->startTime) - , mEmitStop(pctrl->stopTime) - { - } - - Ogre::Real getValue() const - { return 0.0f; } - - void setValue(Ogre::Real value) - { - mParticleSys->setEmitting(value >= mEmitStart && value < mEmitStop); - } - }; - - typedef DefaultFunction Function; -}; - -class GeomMorpherController -{ -public: - class Value : public Ogre::ControllerValue, public ValueInterpolator - { - private: - Ogre::Entity *mEntity; - std::vector mMorphs; - size_t mControllerIndex; - - std::vector mVertices; - - public: - Value(Ogre::Entity *ent, const Nif::NiMorphData *data, size_t controllerIndex) - : mEntity(ent) - , mMorphs(data->mMorphs) - , mControllerIndex(controllerIndex) - { - } - - virtual Ogre::Real getValue() const - { - // Should not be called - return 0.0f; - } - - virtual void setValue(Ogre::Real time) - { - if (mMorphs.size() <= 1) - return; - int i = 1; - for (std::vector::iterator it = mMorphs.begin()+1; it != mMorphs.end(); ++it,++i) - { - float val = 0; - if (!it->mData.mKeys.empty()) - val = interpKey(it->mData.mKeys, time); - val = std::max(0.f, std::min(1.f, val)); - - Ogre::String animationID = Ogre::StringConverter::toString(mControllerIndex) - + "_" + Ogre::StringConverter::toString(i); - - Ogre::AnimationState* state = mEntity->getAnimationState(animationID); - state->setEnabled(val > 0); - state->setWeight(val); - } - } - }; - - typedef DefaultFunction Function; -}; - - -/** Object creator for NIFs. This is the main class responsible for creating - * "live" Ogre objects (entities, particle systems, controllers, etc) from - * their NIF equivalents. - */ -class NIFObjectLoader -{ - static bool sShowMarkers; -public: - static void setShowMarkers(bool show) - { - sShowMarkers = show; - } -private: - - static void warn(const std::string &msg) - { - std::cerr << "NIFObjectLoader: Warn: " << msg << std::endl; - } - - static void createEntity(const std::string &name, const std::string &group, - Ogre::SceneManager *sceneMgr, ObjectScenePtr scene, - const Nif::Node *node, int flags, int animflags) - { - const Nif::NiTriShape *shape = static_cast(node); - - std::string fullname = name+"@index="+Ogre::StringConverter::toString(shape->recIndex); - if(shape->name.length() > 0) - fullname += "@shape="+shape->name; - Misc::StringUtils::toLower(fullname); - - Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); - if(meshMgr.getByName(fullname).isNull()) - NIFMeshLoader::createMesh(name, fullname, group, shape->recIndex); - - Ogre::Entity *entity = sceneMgr->createEntity(fullname); - -#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) - // Enable skeleton-based bounding boxes. With the static bounding box, - // the animation may cause parts to go outside the box and cause culling problems. - if (entity->hasSkeleton()) - entity->setUpdateBoundingBoxFromSkeleton(true); -#endif - - entity->setVisible(!(flags&Nif::NiNode::Flag_Hidden)); - - scene->mEntities.push_back(entity); - if(scene->mSkelBase) - { - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, shape->recIndex); - Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); - trgtbone->getUserObjectBindings().setUserAny(Ogre::Any(static_cast(entity))); - - if(entity->hasSkeleton()) - entity->shareSkeletonInstanceWith(scene->mSkelBase); - else - scene->mSkelBase->attachObjectToBone(trgtbone->getName(), entity); - } - - Nif::ControllerPtr ctrl = node->controller; - while(!ctrl.empty()) - { - if (ctrl->flags & Nif::NiNode::ControllerFlag_Active) - { - bool isAnimationAutoPlay = (animflags & Nif::NiNode::AnimFlag_AutoPlay) != 0; - if(ctrl->recType == Nif::RC_NiUVController) - { - const Nif::NiUVController *uv = static_cast(ctrl.getPtr()); - - Ogre::ControllerValueRealPtr srcval(isAnimationAutoPlay ? - Ogre::ControllerManager::getSingleton().getFrameTimeSource() : - Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(entity, uv->data.getPtr(), &scene->mMaterialControllerMgr)); - - UVController::Function* function = OGRE_NEW UVController::Function(uv, isAnimationAutoPlay); - scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); - Ogre::ControllerFunctionRealPtr func(function); - - scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); - } - else if(ctrl->recType == Nif::RC_NiGeomMorpherController) - { - const Nif::NiGeomMorpherController *geom = static_cast(ctrl.getPtr()); - - Ogre::ControllerValueRealPtr srcval(isAnimationAutoPlay ? - Ogre::ControllerManager::getSingleton().getFrameTimeSource() : - Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value( - entity, geom->data.getPtr(), geom->recIndex)); - - GeomMorpherController::Function* function = OGRE_NEW GeomMorpherController::Function(geom, isAnimationAutoPlay); - scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); - Ogre::ControllerFunctionRealPtr func(function); - - scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); - } - } - ctrl = ctrl->next; - } - - createMaterialControllers(shape, entity, animflags, scene); - } - - static void createMaterialControllers (const Nif::Node* node, Ogre::MovableObject* movable, int animflags, ObjectScenePtr scene) - { - const Nif::NiTexturingProperty *texprop = NULL; - const Nif::NiMaterialProperty *matprop = NULL; - const Nif::NiAlphaProperty *alphaprop = NULL; - const Nif::NiVertexColorProperty *vertprop = NULL; - const Nif::NiZBufferProperty *zprop = NULL; - const Nif::NiSpecularProperty *specprop = NULL; - const Nif::NiWireframeProperty *wireprop = NULL; - const Nif::NiStencilProperty *stencilprop = NULL; - node->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop, stencilprop); - - bool isAnimationAutoPlay = (animflags & Nif::NiNode::AnimFlag_AutoPlay) != 0; - Ogre::ControllerValueRealPtr srcval(isAnimationAutoPlay ? - Ogre::ControllerManager::getSingleton().getFrameTimeSource() : - Ogre::ControllerValueRealPtr()); - - if(matprop) - { - Nif::ControllerPtr ctrls = matprop->controller; - while(!ctrls.empty()) - { - if (ctrls->recType == Nif::RC_NiAlphaController) - { - const Nif::NiAlphaController *alphaCtrl = static_cast(ctrls.getPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW AlphaController::Value(movable, alphaCtrl->data.getPtr(), &scene->mMaterialControllerMgr)); - AlphaController::Function* function = OGRE_NEW AlphaController::Function(alphaCtrl, isAnimationAutoPlay); - scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); - Ogre::ControllerFunctionRealPtr func(function); - scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); - } - else if (ctrls->recType == Nif::RC_NiMaterialColorController) - { - const Nif::NiMaterialColorController *matCtrl = static_cast(ctrls.getPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW MaterialColorController::Value(movable, matCtrl->data.getPtr(), &scene->mMaterialControllerMgr)); - MaterialColorController::Function* function = OGRE_NEW MaterialColorController::Function(matCtrl, isAnimationAutoPlay); - scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); - Ogre::ControllerFunctionRealPtr func(function); - scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); - } - - ctrls = ctrls->next; - } - } - if (texprop) - { - Nif::ControllerPtr ctrls = texprop->controller; - while(!ctrls.empty()) - { - if (ctrls->recType == Nif::RC_NiFlipController) - { - const Nif::NiFlipController *flipCtrl = static_cast(ctrls.getPtr()); - - - Ogre::ControllerValueRealPtr dstval(OGRE_NEW FlipController::Value( - movable, flipCtrl, &scene->mMaterialControllerMgr)); - FlipController::Function* function = OGRE_NEW FlipController::Function(flipCtrl, isAnimationAutoPlay); - scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); - Ogre::ControllerFunctionRealPtr func(function); - scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); - } - - ctrls = ctrls->next; - } - } - } - - static void createParticleEmitterAffectors(Ogre::ParticleSystem *partsys, - const Nif::NiParticleSystemController *partctrl, Ogre::Bone* bone, - const std::string& skelBaseName) - { - Ogre::ParticleEmitter *emitter = partsys->addEmitter("Nif"); - emitter->setParticleVelocity(partctrl->velocity - partctrl->velocityRandom*0.5f, - partctrl->velocity + partctrl->velocityRandom*0.5f); - - if (partctrl->emitFlags & Nif::NiParticleSystemController::NoAutoAdjust) - emitter->setEmissionRate(partctrl->emitRate); - else - emitter->setEmissionRate(partctrl->numParticles / (partctrl->lifetime + partctrl->lifetimeRandom/2)); - - emitter->setTimeToLive(std::max(0.f, partctrl->lifetime), - std::max(0.f, partctrl->lifetime + partctrl->lifetimeRandom)); - emitter->setParameter("width", Ogre::StringConverter::toString(partctrl->offsetRandom.x)); - emitter->setParameter("height", Ogre::StringConverter::toString(partctrl->offsetRandom.y)); - emitter->setParameter("depth", Ogre::StringConverter::toString(partctrl->offsetRandom.z)); - emitter->setParameter("vertical_direction", Ogre::StringConverter::toString(Ogre::Radian(partctrl->verticalDir).valueDegrees())); - emitter->setParameter("vertical_angle", Ogre::StringConverter::toString(Ogre::Radian(partctrl->verticalAngle).valueDegrees())); - emitter->setParameter("horizontal_direction", Ogre::StringConverter::toString(Ogre::Radian(partctrl->horizontalDir).valueDegrees())); - emitter->setParameter("horizontal_angle", Ogre::StringConverter::toString(Ogre::Radian(partctrl->horizontalAngle).valueDegrees())); - - Nif::ExtraPtr e = partctrl->extra; - while(!e.empty()) - { - if(e->recType == Nif::RC_NiParticleGrowFade) - { - const Nif::NiParticleGrowFade *gf = static_cast(e.getPtr()); - - Ogre::ParticleAffector *affector = partsys->addAffector("GrowFade"); - affector->setParameter("grow_time", Ogre::StringConverter::toString(gf->growTime)); - affector->setParameter("fade_time", Ogre::StringConverter::toString(gf->fadeTime)); - } - else if(e->recType == Nif::RC_NiGravity) - { - const Nif::NiGravity *gr = static_cast(e.getPtr()); - - Ogre::ParticleAffector *affector = partsys->addAffector("Gravity"); - affector->setParameter("force", Ogre::StringConverter::toString(gr->mForce)); - affector->setParameter("force_type", (gr->mType==0) ? "wind" : "point"); - affector->setParameter("direction", Ogre::StringConverter::toString(gr->mDirection)); - affector->setParameter("position", Ogre::StringConverter::toString(gr->mPosition)); - affector->setParameter("skelbase", skelBaseName); - affector->setParameter("bone", bone->getName()); - } - else if(e->recType == Nif::RC_NiParticleColorModifier) - { - const Nif::NiParticleColorModifier *cl = static_cast(e.getPtr()); - const Nif::NiColorData *clrdata = cl->data.getPtr(); - - Ogre::ParticleAffector *affector = partsys->addAffector("ColourInterpolator"); - size_t num_colors = std::min(6, clrdata->mKeyMap.mKeys.size()); - unsigned int i=0; - for (Nif::Vector4KeyMap::MapType::const_iterator it = clrdata->mKeyMap.mKeys.begin(); it != clrdata->mKeyMap.mKeys.end() && i < num_colors; ++it,++i) - { - Ogre::ColourValue color; - color.r = it->second.mValue[0]; - color.g = it->second.mValue[1]; - color.b = it->second.mValue[2]; - color.a = it->second.mValue[3]; - affector->setParameter("colour"+Ogre::StringConverter::toString(i), - Ogre::StringConverter::toString(color)); - affector->setParameter("time"+Ogre::StringConverter::toString(i), - Ogre::StringConverter::toString(it->first)); - } - } - else if(e->recType == Nif::RC_NiParticleRotation) - { - // TODO: Implement (Ogre::RotationAffector?) - } - else - warn("Unhandled particle modifier "+e->recName); - e = e->extra; - } - } - - static void createParticleSystem(const std::string &name, const std::string &group, - Ogre::SceneNode *sceneNode, ObjectScenePtr scene, - const Nif::Node *partnode, int flags, int partflags, int animflags) - { - const Nif::NiAutoNormalParticlesData *particledata = NULL; - if(partnode->recType == Nif::RC_NiAutoNormalParticles) - particledata = static_cast(partnode)->data.getPtr(); - else if(partnode->recType == Nif::RC_NiRotatingParticles) - particledata = static_cast(partnode)->data.getPtr(); - else - throw std::runtime_error("Unexpected particle node type"); - - std::string fullname = name+"@index="+Ogre::StringConverter::toString(partnode->recIndex); - if(partnode->name.length() > 0) - fullname += "@type="+partnode->name; - Misc::StringUtils::toLower(fullname); - - Ogre::ParticleSystem *partsys = sceneNode->getCreator()->createParticleSystem(); - - const Nif::NiTexturingProperty *texprop = NULL; - const Nif::NiMaterialProperty *matprop = NULL; - const Nif::NiAlphaProperty *alphaprop = NULL; - const Nif::NiVertexColorProperty *vertprop = NULL; - const Nif::NiZBufferProperty *zprop = NULL; - const Nif::NiSpecularProperty *specprop = NULL; - const Nif::NiWireframeProperty *wireprop = NULL; - const Nif::NiStencilProperty *stencilprop = NULL; - bool needTangents = false; - - partnode->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop, stencilprop); - partsys->setMaterialName(NIFMaterialLoader::getMaterial(particledata, fullname, group, - texprop, matprop, alphaprop, - vertprop, zprop, specprop, - wireprop, stencilprop, needTangents, - // MW doesn't light particles, but the MaterialProperty - // used still has lighting, so that must be ignored. - true)); - - partsys->setCullIndividually(false); - partsys->setParticleQuota(particledata->numParticles); - partsys->setKeepParticlesInLocalSpace((partflags & Nif::NiNode::ParticleFlag_LocalSpace) != 0); - - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partnode->recIndex); - Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); - scene->mSkelBase->attachObjectToBone(trgtbone->getName(), partsys); - - Nif::ControllerPtr ctrl = partnode->controller; - while(!ctrl.empty()) - { - if((ctrl->recType == Nif::RC_NiParticleSystemController || ctrl->recType == Nif::RC_NiBSPArrayController) - && ctrl->flags & Nif::NiNode::ControllerFlag_Active) - { - const Nif::NiParticleSystemController *partctrl = static_cast(ctrl.getPtr()); - - float size = partctrl->size*2; - // HACK: don't allow zero-sized particles which can rarely cause an AABB assertion in Ogre to fail - size = std::max(size, 0.00001f); - partsys->setDefaultDimensions(size, size); - - if(!partctrl->emitter.empty()) - { - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partctrl->emitter->recIndex); - Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); - // Set the emitter bone(s) as user data on the particle system - // so the emitters/affectors can access it easily. - std::vector bones; - if (partctrl->recType == Nif::RC_NiBSPArrayController) - { - std::vector nodes; - getAllNiNodes(partctrl->emitter.getPtr(), nodes); - if (nodes.empty()) - throw std::runtime_error("Emitter for NiBSPArrayController must be a NiNode"); - for (unsigned int i=0; imSkelBase->getSkeleton()->getBone( - NIFSkeletonLoader::lookupOgreBoneHandle(name, nodes[i]->recIndex))); - } - } - else - { - bones.push_back(trgtbone); - } - NiNodeHolder holder; - holder.mBones = bones; - partsys->getUserObjectBindings().setUserAny(Ogre::Any(holder)); - createParticleEmitterAffectors(partsys, partctrl, trgtbone, scene->mSkelBase->getName()); - } - - createParticleInitialState(partsys, particledata, partctrl); - - bool isParticleAutoPlay = (partflags&Nif::NiNode::ParticleFlag_AutoPlay) != 0; - Ogre::ControllerValueRealPtr srcval(isParticleAutoPlay ? - Ogre::ControllerManager::getSingleton().getFrameTimeSource() : - Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW ParticleSystemController::Value(partsys, partctrl)); - - ParticleSystemController::Function* function = - OGRE_NEW ParticleSystemController::Function(partctrl, isParticleAutoPlay); - scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); - Ogre::ControllerFunctionRealPtr func(function); - - scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); - - // Emitting state will be overwritten on frame update by the ParticleSystemController, - // but set up an initial value anyway so the user can fast-forward particle systems - // immediately after creation if desired. - partsys->setEmitting(isParticleAutoPlay); - } - ctrl = ctrl->next; - } - - partsys->setVisible(!(flags&Nif::NiNode::Flag_Hidden)); - scene->mParticles.push_back(partsys); - - createMaterialControllers(partnode, partsys, animflags, scene); - } - - static void createParticleInitialState(Ogre::ParticleSystem* partsys, const Nif::NiAutoNormalParticlesData* particledata, - const Nif::NiParticleSystemController* partctrl) - { - partsys->_update(0.f); // seems to be required to allocate mFreeParticles. TODO: patch Ogre to handle this better - int i=0; - for (std::vector::const_iterator it = partctrl->particles.begin(); - iactiveCount && it != partctrl->particles.end(); ++it, ++i) - { - const Nif::NiParticleSystemController::Particle& particle = *it; - - Ogre::Particle* created = partsys->createParticle(); - if (!created) - break; - -#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) - Ogre::Vector3& position = created->mPosition; - Ogre::Vector3& direction = created->mDirection; - Ogre::ColourValue& colour = created->mColour; - float& totalTimeToLive = created->mTotalTimeToLive; - float& timeToLive = created->mTimeToLive; -#else - Ogre::Vector3& position = created->position; - Ogre::Vector3& direction = created->direction; - Ogre::ColourValue& colour = created->colour; - float& totalTimeToLive = created->totalTimeToLive; - float& timeToLive = created->timeToLive; -#endif - - direction = particle.velocity; - position = particledata->vertices.at(particle.vertex); - - if (particle.vertex < int(particledata->colors.size())) - { - Ogre::Vector4 partcolour = particledata->colors.at(particle.vertex); - colour = Ogre::ColourValue(partcolour.x, partcolour.y, partcolour.z, partcolour.w); - } - else - colour = Ogre::ColourValue(1.f, 1.f, 1.f, 1.f); - float size = particledata->sizes.at(particle.vertex); - created->setDimensions(size, size); - totalTimeToLive = std::max(0.f, particle.lifespan); - timeToLive = std::max(0.f, particle.lifespan - particle.lifetime); - } - partsys->_update(0.f); // now apparently needs another update, otherwise it won't render in the first frame. TODO: patch Ogre to handle this better - } - - static void createNodeControllers(const Nif::NIFFilePtr& nif, const std::string &name, Nif::ControllerPtr ctrl, ObjectScenePtr scene, int animflags) - { - do { - if (ctrl->flags & Nif::NiNode::ControllerFlag_Active) - { - bool isAnimationAutoPlay = (animflags & Nif::NiNode::AnimFlag_AutoPlay) != 0; - if(ctrl->recType == Nif::RC_NiVisController) - { - const Nif::NiVisController *vis = static_cast(ctrl.getPtr()); - - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex); - Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); - Ogre::ControllerValueRealPtr srcval(isAnimationAutoPlay ? - Ogre::ControllerManager::getSingleton().getFrameTimeSource() : - Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW VisController::Value(trgtbone, vis->data.getPtr())); - - VisController::Function* function = OGRE_NEW VisController::Function(vis, isAnimationAutoPlay); - scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); - Ogre::ControllerFunctionRealPtr func(function); - - scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); - } - else if(ctrl->recType == Nif::RC_NiKeyframeController) - { - const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); - if(!key->data.empty()) - { - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex); - Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); - // The keyframe controller will control this bone manually - trgtbone->setManuallyControlled(true); - Ogre::ControllerValueRealPtr srcval(isAnimationAutoPlay ? - Ogre::ControllerManager::getSingleton().getFrameTimeSource() : - Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, nif, key->data.getPtr())); - KeyframeController::Function* function = OGRE_NEW KeyframeController::Function(key, isAnimationAutoPlay); - scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); - Ogre::ControllerFunctionRealPtr func(function); - - scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); - } - } - } - ctrl = ctrl->next; - } while(!ctrl.empty()); - } - - - static void extractTextKeys(const Nif::NiTextKeyExtraData *tk, TextKeyMap &textkeys) - { - for(size_t i = 0;i < tk->list.size();i++) - { - const std::string &str = tk->list[i].text; - std::string::size_type pos = 0; - while(pos < str.length()) - { - if(::isspace(str[pos])) - { - pos++; - continue; - } - - std::string::size_type nextpos = std::min(str.find('\r', pos), str.find('\n', pos)); - if(nextpos != std::string::npos) - { - do { - nextpos--; - } while(nextpos > pos && ::isspace(str[nextpos])); - nextpos++; - } - else if(::isspace(*str.rbegin())) - { - std::string::const_iterator last = str.end(); - do { - --last; - } while(last != str.begin() && ::isspace(*last)); - nextpos = std::distance(str.begin(), ++last); - } - std::string result = str.substr(pos, nextpos-pos); - textkeys.insert(std::make_pair(tk->list[i].time, Misc::StringUtils::toLower(result))); - - pos = nextpos; - } - } - } - - - static void createObjects(const Nif::NIFFilePtr& nif, const std::string &name, const std::string &group, - Ogre::SceneNode *sceneNode, const Nif::Node *node, - ObjectScenePtr scene, int flags, int animflags, int partflags, bool isRootCollisionNode=false) - { - // Do not create objects for the collision shape (includes all children) - if(node->recType == Nif::RC_RootCollisionNode) - isRootCollisionNode = true; - - if(node->recType == Nif::RC_NiBSAnimationNode) - animflags |= node->flags; - else if(node->recType == Nif::RC_NiBSParticleNode) - partflags |= node->flags; - else - flags |= node->flags; - - if (node->recType == Nif::RC_NiBillboardNode) - { - // TODO: figure out what the flags mean. - // NifSkope has names for them, but doesn't implement them. - // Change mBillboardNodes to map - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, node->recIndex); - Ogre::Bone* bone = scene->mSkelBase->getSkeleton()->getBone(trgtid); - bone->setManuallyControlled(true); - scene->mBillboardNodes.push_back(bone); - } - - Nif::ExtraPtr e = node->extra; - while(!e.empty()) - { - if(e->recType == Nif::RC_NiTextKeyExtraData) - { - const Nif::NiTextKeyExtraData *tk = static_cast(e.getPtr()); - - extractTextKeys(tk, scene->mTextKeys); - } - else if(e->recType == Nif::RC_NiStringExtraData) - { - const Nif::NiStringExtraData *sd = static_cast(e.getPtr()); - // String markers may contain important information - // affecting the entire subtree of this obj - if(sd->string == "MRK" && !sShowMarkers) - { - // Marker objects. These meshes are only visible in the - // editor. - flags |= 0x80000000; - } - } - - e = e->extra; - } - - if(!node->controller.empty()) - createNodeControllers(nif, name, node->controller, scene, animflags); - - if (!isRootCollisionNode) - { - if(node->recType == Nif::RC_NiCamera) - { - /* Ignored */ - } - - if(node->recType == Nif::RC_NiTriShape && !(flags&0x80000000)) - { - createEntity(name, group, sceneNode->getCreator(), scene, node, flags, animflags); - } - - if((node->recType == Nif::RC_NiAutoNormalParticles || - node->recType == Nif::RC_NiRotatingParticles) && !(flags&0x40000000)) - { - createParticleSystem(name, group, sceneNode, scene, node, flags, partflags, animflags); - } - } - - const Nif::NiNode *ninode = dynamic_cast(node); - if(ninode) - { - const Nif::NodeList &children = ninode->children; - for(size_t i = 0;i < children.length();i++) - { - if(!children[i].empty()) - createObjects(nif, name, group, sceneNode, children[i].getPtr(), scene, flags, animflags, partflags, isRootCollisionNode); - } - } - } - - static void createSkelBase(const std::string &name, const std::string &group, - Ogre::SceneManager *sceneMgr, const Nif::Node *node, - ObjectScenePtr scene) - { - /* This creates an empty mesh to which a skeleton gets attached. This - * is to ensure we have an entity with a skeleton instance, even if all - * other entities are attached to bones and not skinned. */ - Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); - if(meshMgr.getByName(name).isNull()) - NIFMeshLoader::createMesh(name, name, group, ~(size_t)0); - - scene->mSkelBase = sceneMgr->createEntity(name); - scene->mEntities.push_back(scene->mSkelBase); - } - -public: - static void load(Ogre::SceneNode *sceneNode, ObjectScenePtr scene, const std::string &name, const std::string &group, int flags=0) - { - Nif::NIFFilePtr nif = Nif::Cache::getInstance().load(name); - if(nif->numRoots() < 1) - { - nif->warn("Found no root nodes in "+name+"."); - return; - } - - const Nif::Record *r = nif->getRoot(0); - assert(r != NULL); - - const Nif::Node *node = dynamic_cast(r); - if(node == NULL) - { - nif->warn("First root in "+name+" was not a node, but a "+ - r->recName+"."); - return; - } - - if(Ogre::SkeletonManager::getSingleton().resourceExists(name) || - !NIFSkeletonLoader::createSkeleton(name, group, node).isNull()) - { - // Create a base skeleton entity if this NIF needs one - createSkelBase(name, group, sceneNode->getCreator(), node, scene); - } - createObjects(nif, name, group, sceneNode, node, scene, flags, 0, 0); - } - - static void loadKf(Ogre::Skeleton *skel, const std::string &name, - TextKeyMap &textKeys, std::vector > &ctrls) - { - Nif::NIFFilePtr nif = Nif::Cache::getInstance().load(name); - if(nif->numRoots() < 1) - { - nif->warn("Found no root nodes in "+name+"."); - return; - } - - const Nif::Record *r = nif->getRoot(0); - assert(r != NULL); - - if(r->recType != Nif::RC_NiSequenceStreamHelper) - { - nif->warn("First root was not a NiSequenceStreamHelper, but a "+ - r->recName+"."); - return; - } - const Nif::NiSequenceStreamHelper *seq = static_cast(r); - - Nif::ExtraPtr extra = seq->extra; - if(extra.empty() || extra->recType != Nif::RC_NiTextKeyExtraData) - { - nif->warn("First extra data was not a NiTextKeyExtraData, but a "+ - (extra.empty() ? std::string("nil") : extra->recName)+"."); - return; - } - - extractTextKeys(static_cast(extra.getPtr()), textKeys); - - extra = extra->extra; - Nif::ControllerPtr ctrl = seq->controller; - for(;!extra.empty() && !ctrl.empty();(extra=extra->extra),(ctrl=ctrl->next)) - { - if(extra->recType != Nif::RC_NiStringExtraData || ctrl->recType != Nif::RC_NiKeyframeController) - { - nif->warn("Unexpected extra data "+extra->recName+" with controller "+ctrl->recName); - continue; - } - - if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) - continue; - - const Nif::NiStringExtraData *strdata = static_cast(extra.getPtr()); - const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); - - if(key->data.empty()) - continue; - if(!skel->hasBone(strdata->string)) - continue; - - Ogre::Bone *trgtbone = skel->getBone(strdata->string); - Ogre::ControllerValueRealPtr srcval; - Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, nif, key->data.getPtr())); - Ogre::ControllerFunctionRealPtr func(OGRE_NEW KeyframeController::Function(key, false)); - - ctrls.push_back(Ogre::Controller(srcval, dstval, func)); - } - } -}; - - -ObjectScenePtr Loader::createObjects(Ogre::SceneNode *parentNode, std::string name, const std::string &group) -{ - ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); - - Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode, scene, name, group); - - for(size_t i = 0;i < scene->mEntities.size();i++) - { - Ogre::Entity *entity = scene->mEntities[i]; - if(!entity->isAttached()) - parentNode->attachObject(entity); - } - - scene->_notifyAttached(); - - return scene; -} - -ObjectScenePtr Loader::createObjects(Ogre::Entity *parent, const std::string &bonename, - const std::string& bonefilter, - Ogre::SceneNode *parentNode, - std::string name, const std::string &group) -{ - ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); - - Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode, scene, name, group); - - bool isskinned = false; - for(size_t i = 0;i < scene->mEntities.size();i++) - { - Ogre::Entity *ent = scene->mEntities[i]; - if(scene->mSkelBase != ent && ent->hasSkeleton()) - { - isskinned = true; - break; - } - } - - Ogre::Vector3 scale(1.0f); - if(bonename.find("Left") != std::string::npos) - scale.x *= -1.0f; - - if(isskinned) - { - // accepts anything named "filter*" or "tri filter*" - std::string filter = "@shape=tri "+bonefilter; - std::string filter2 = "@shape="+bonefilter; - Misc::StringUtils::toLower(filter); - Misc::StringUtils::toLower(filter2); - for(size_t i = 0;i < scene->mEntities.size();i++) - { - Ogre::Entity *entity = scene->mEntities[i]; - if(entity->hasSkeleton()) - { - if(entity == scene->mSkelBase || - entity->getMesh()->getName().find(filter) != std::string::npos - || entity->getMesh()->getName().find(filter2) != std::string::npos) - parentNode->attachObject(entity); - } - else - { - if(entity->getMesh()->getName().find(filter) == std::string::npos - || entity->getMesh()->getName().find(filter2) == std::string::npos) - entity->detachFromParent(); - } - } - } - else - { - for(size_t i = 0;i < scene->mEntities.size();i++) - { - Ogre::Entity *entity = scene->mEntities[i]; - if(!entity->isAttached()) - { - Ogre::TagPoint *tag = parent->attachObjectToBone(bonename, entity); - tag->setScale(scale); - } - } - } - - scene->_notifyAttached(); - - return scene; -} - - -ObjectScenePtr Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string name, const std::string &group) -{ - ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); - - Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode, scene, name, group, 0xC0000000); - - if(scene->mSkelBase) - parentNode->attachObject(scene->mSkelBase); - - return scene; -} - - -void Loader::createKfControllers(Ogre::Entity *skelBase, - const std::string &name, - TextKeyMap &textKeys, - std::vector > &ctrls) -{ - NIFObjectLoader::loadKf(skelBase->getSkeleton(), name, textKeys, ctrls); -} - -bool Loader::sShowMarkers = false; -bool NIFObjectLoader::sShowMarkers = false; - -void Loader::setShowMarkers(bool show) -{ - sShowMarkers = show; - NIFObjectLoader::setShowMarkers(show); -} - - -} // namespace NifOgre diff --git a/components/nifogre/ogrenifloader.hpp b/components/nifogre/ogrenifloader.hpp deleted file mode 100644 index b583429dd1..0000000000 --- a/components/nifogre/ogrenifloader.hpp +++ /dev/null @@ -1,151 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (ogre_nif_loader.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - 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 - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -#ifndef OPENMW_COMPONENTS_NIFOGRE_OGRENIFLOADER_HPP -#define OPENMW_COMPONENTS_NIFOGRE_OGRENIFLOADER_HPP - -#include -#include -#include - -#include -#include -#include - - -// FIXME: This namespace really doesn't do anything Nif-specific. Any supportable -// model format should go through this. -namespace NifOgre -{ - -/** - * @brief Clones materials as necessary to not make controllers affect other objects (that share the original material). - */ -class MaterialControllerManager -{ -public: - ~MaterialControllerManager(); - - /// @attention if \a movable is an Entity, it needs to have *one* SubEntity - Ogre::MaterialPtr getWritableMaterial (Ogre::MovableObject* movable); - -private: - std::map mClonedMaterials; -}; - -typedef std::multimap TextKeyMap; -static const char sTextKeyExtraDataID[] = "TextKeyExtraData"; -struct ObjectScene { - Ogre::Entity *mSkelBase; - std::vector mEntities; - std::vector mParticles; - std::vector mLights; - - // Nodes that should always face the camera when rendering - std::vector mBillboardNodes; - - Ogre::SceneManager* mSceneMgr; - - // The maximum length on any of the controllers. For animations with controllers, but no text keys, consider this the animation length. - float mMaxControllerLength; - - TextKeyMap mTextKeys; - - MaterialControllerManager mMaterialControllerMgr; - - std::vector > mControllers; - - ObjectScene(Ogre::SceneManager* sceneMgr) : mSkelBase(0), mSceneMgr(sceneMgr), mMaxControllerLength(0) - { } - - ~ObjectScene(); - - // Rotate nodes in mBillboardNodes so they face the given camera - void rotateBillboardNodes(Ogre::Camera* camera); - - void setVisibilityFlags (unsigned int flags); - - // This is called internally by the OgreNifLoader once all elements of the - // scene have been attached to their respective nodes. - void _notifyAttached(); -}; - -typedef Ogre::SharedPtr ObjectScenePtr; - - -class Loader -{ -public: - static ObjectScenePtr createObjects(Ogre::Entity *parent, const std::string &bonename, - const std::string& filter, - Ogre::SceneNode *parentNode, - std::string name, - const std::string &group="General"); - - static ObjectScenePtr createObjects(Ogre::SceneNode *parentNode, - std::string name, - const std::string &group="General"); - - static ObjectScenePtr createObjectBase(Ogre::SceneNode *parentNode, - std::string name, - const std::string &group="General"); - - /// Set whether or not nodes marked as "MRK" should be shown. - /// These should be hidden ingame, but visible in the editior. - /// Default: false. - static void setShowMarkers(bool show); - - static void createKfControllers(Ogre::Entity *skelBase, - const std::string &name, - TextKeyMap &textKeys, - std::vector > &ctrls); - -private: - static bool sShowMarkers; -}; - -// FIXME: Should be with other general Ogre extensions. -template -class NodeTargetValue : public Ogre::ControllerValue -{ -protected: - Ogre::Node *mNode; - -public: - NodeTargetValue(Ogre::Node *target) : mNode(target) - { } - - virtual Ogre::Quaternion getRotation(T value) const = 0; - virtual Ogre::Vector3 getTranslation(T value) const = 0; - virtual Ogre::Vector3 getScale(T value) const = 0; - - void setNode(Ogre::Node *target) - { mNode = target; } - Ogre::Node *getNode() const - { return mNode; } -}; -typedef Ogre::SharedPtr > NodeTargetValueRealPtr; - -} - -#endif diff --git a/components/nifogre/particles.cpp b/components/nifogre/particles.cpp deleted file mode 100644 index 936bfb435f..0000000000 --- a/components/nifogre/particles.cpp +++ /dev/null @@ -1,775 +0,0 @@ -#include "particles.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -/* FIXME: "Nif" isn't really an appropriate emitter name. */ -class NifEmitter : public Ogre::ParticleEmitter -{ -public: - std::vector mEmitterBones; - Ogre::Bone* mParticleBone; - - Ogre::ParticleSystem* getPartSys() { return mParent; } - - /** Command object for the emitter width (see Ogre::ParamCommand).*/ - class CmdWidth : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - return Ogre::StringConverter::toString(static_cast(target)->getWidth()); - } - void doSet(void *target, const Ogre::String &val) - { - static_cast(target)->setWidth(Ogre::StringConverter::parseReal(val)); - } - }; - - /** Command object for the emitter height (see Ogre::ParamCommand).*/ - class CmdHeight : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - return Ogre::StringConverter::toString(static_cast(target)->getHeight()); - } - void doSet(void *target, const Ogre::String &val) - { - static_cast(target)->setHeight(Ogre::StringConverter::parseReal(val)); - } - }; - - /** Command object for the emitter depth (see Ogre::ParamCommand).*/ - class CmdDepth : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - return Ogre::StringConverter::toString(static_cast(target)->getDepth()); - } - void doSet(void *target, const Ogre::String &val) - { - static_cast(target)->setDepth(Ogre::StringConverter::parseReal(val)); - } - }; - - /** Command object for the emitter vertical_direction (see Ogre::ParamCommand).*/ - class CmdVerticalDir : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - const NifEmitter *self = static_cast(target); - return Ogre::StringConverter::toString(self->getVerticalDirection().valueDegrees()); - } - void doSet(void *target, const Ogre::String &val) - { - NifEmitter *self = static_cast(target); - self->setVerticalDirection(Ogre::Degree(Ogre::StringConverter::parseReal(val))); - } - }; - - /** Command object for the emitter vertical_angle (see Ogre::ParamCommand).*/ - class CmdVerticalAngle : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - const NifEmitter *self = static_cast(target); - return Ogre::StringConverter::toString(self->getVerticalAngle().valueDegrees()); - } - void doSet(void *target, const Ogre::String &val) - { - NifEmitter *self = static_cast(target); - self->setVerticalAngle(Ogre::Degree(Ogre::StringConverter::parseReal(val))); - } - }; - - /** Command object for the emitter horizontal_direction (see Ogre::ParamCommand).*/ - class CmdHorizontalDir : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - const NifEmitter *self = static_cast(target); - return Ogre::StringConverter::toString(self->getHorizontalDirection().valueDegrees()); - } - void doSet(void *target, const Ogre::String &val) - { - NifEmitter *self = static_cast(target); - self->setHorizontalDirection(Ogre::Degree(Ogre::StringConverter::parseReal(val))); - } - }; - - /** Command object for the emitter horizontal_angle (see Ogre::ParamCommand).*/ - class CmdHorizontalAngle : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - const NifEmitter *self = static_cast(target); - return Ogre::StringConverter::toString(self->getHorizontalAngle().valueDegrees()); - } - void doSet(void *target, const Ogre::String &val) - { - NifEmitter *self = static_cast(target); - self->setHorizontalAngle(Ogre::Degree(Ogre::StringConverter::parseReal(val))); - } - }; - - - NifEmitter(Ogre::ParticleSystem *psys) - : Ogre::ParticleEmitter(psys) - , mEmitterBones(Ogre::any_cast(psys->getUserObjectBindings().getUserAny()).mBones) - { - assert (!mEmitterBones.empty()); - Ogre::TagPoint* tag = static_cast(mParent->getParentNode()); - mParticleBone = static_cast(tag->getParent()); - initDefaults("Nif"); - } - - /** See Ogre::ParticleEmitter. */ - unsigned short _getEmissionCount(Ogre::Real timeElapsed) - { - // Use basic constant emission - return genConstantEmissionCount(timeElapsed); - } - - /** See Ogre::ParticleEmitter. */ - void _initParticle(Ogre::Particle *particle) - { - Ogre::Vector3 xOff, yOff, zOff; - - // Call superclass - ParticleEmitter::_initParticle(particle); - - xOff = Ogre::Math::SymmetricRandom() * mXRange; - yOff = Ogre::Math::SymmetricRandom() * mYRange; - zOff = Ogre::Math::SymmetricRandom() * mZRange; - -#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) - Ogre::Vector3& position = particle->mPosition; - Ogre::Vector3& direction = particle->mDirection; - Ogre::ColourValue& colour = particle->mColour; - Ogre::Real& totalTimeToLive = particle->mTotalTimeToLive; - Ogre::Real& timeToLive = particle->mTimeToLive; -#else - Ogre::Vector3& position = particle->position; - Ogre::Vector3& direction = particle->direction; - Ogre::ColourValue& colour = particle->colour; - Ogre::Real& totalTimeToLive = particle->totalTimeToLive; - Ogre::Real& timeToLive = particle->timeToLive; -#endif - - Ogre::Node* emitterBone = mEmitterBones.at(OEngine::Misc::Rng::rollDice(mEmitterBones.size())); - - position = xOff + yOff + zOff + - mParticleBone->_getDerivedOrientation().Inverse() * (emitterBone->_getDerivedPosition() - - mParticleBone->_getDerivedPosition()); - - // Generate complex data by reference - genEmissionColour(colour); - - // NOTE: We do not use mDirection/mAngle for the initial direction. - Ogre::Radian hdir = mHorizontalDir + mHorizontalAngle*Ogre::Math::SymmetricRandom(); - Ogre::Radian vdir = mVerticalDir + mVerticalAngle*Ogre::Math::SymmetricRandom(); - direction = (mParticleBone->_getDerivedOrientation().Inverse() - * emitterBone->_getDerivedOrientation() * - Ogre::Quaternion(hdir, Ogre::Vector3::UNIT_Z) * - Ogre::Quaternion(vdir, Ogre::Vector3::UNIT_X)) * - Ogre::Vector3::UNIT_Z; - - genEmissionVelocity(direction); - - // Generate simpler data - timeToLive = totalTimeToLive = genEmissionTTL(); - } - - /** Overloaded to update the trans. matrix */ - void setDirection(const Ogre::Vector3 &dir) - { - ParticleEmitter::setDirection(dir); - genAreaAxes(); - } - - /** Sets the size of the area from which particles are emitted. - @param - size Vector describing the size of the area. The area extends - around the center point by half the x, y and z components of - this vector. The box is aligned such that it's local Z axis points - along it's direction (see setDirection) - */ - void setSize(const Ogre::Vector3 &size) - { - mSize = size; - genAreaAxes(); - } - - /** Sets the size of the area from which particles are emitted. - @param x,y,z - Individual axis lengths describing the size of the area. The area - extends around the center point by half the x, y and z components - of this vector. The box is aligned such that it's local Z axis - points along it's direction (see setDirection) - */ - void setSize(Ogre::Real x, Ogre::Real y, Ogre::Real z) - { - mSize.x = x; - mSize.y = y; - mSize.z = z; - genAreaAxes(); - } - - /** Sets the width (local x size) of the emitter. */ - void setWidth(Ogre::Real width) - { - mSize.x = width; - genAreaAxes(); - } - /** Gets the width (local x size) of the emitter. */ - Ogre::Real getWidth(void) const - { return mSize.x; } - /** Sets the height (local y size) of the emitter. */ - void setHeight(Ogre::Real height) - { - mSize.y = height; - genAreaAxes(); - } - /** Gets the height (local y size) of the emitter. */ - Ogre::Real getHeight(void) const - { return mSize.y; } - /** Sets the depth (local y size) of the emitter. */ - void setDepth(Ogre::Real depth) - { - mSize.z = depth; - genAreaAxes(); - } - /** Gets the depth (local y size) of the emitter. */ - Ogre::Real getDepth(void) const - { return mSize.z; } - - void setVerticalDirection(Ogre::Radian vdir) - { mVerticalDir = vdir; } - Ogre::Radian getVerticalDirection(void) const - { return mVerticalDir; } - - void setVerticalAngle(Ogre::Radian vangle) - { mVerticalAngle = vangle; } - Ogre::Radian getVerticalAngle(void) const - { return mVerticalAngle; } - - void setHorizontalDirection(Ogre::Radian hdir) - { mHorizontalDir = hdir; } - Ogre::Radian getHorizontalDirection(void) const - { return mHorizontalDir; } - - void setHorizontalAngle(Ogre::Radian hangle) - { mHorizontalAngle = hangle; } - Ogre::Radian getHorizontalAngle(void) const - { return mHorizontalAngle; } - - -protected: - /// Size of the area - Ogre::Vector3 mSize; - - Ogre::Radian mVerticalDir; - Ogre::Radian mVerticalAngle; - Ogre::Radian mHorizontalDir; - Ogre::Radian mHorizontalAngle; - - /// Local axes, not normalised, their magnitude reflects area size - Ogre::Vector3 mXRange, mYRange, mZRange; - - /// Internal method for generating the area axes - void genAreaAxes(void) - { - Ogre::Vector3 mLeft = mUp.crossProduct(mDirection); - mXRange = mLeft * (mSize.x * 0.5f); - mYRange = mUp * (mSize.y * 0.5f); - mZRange = mDirection * (mSize.z * 0.5f); - } - - /** Internal for initializing some defaults and parameters - @return True if custom parameters need initialising - */ - bool initDefaults(const Ogre::String &t) - { - // Defaults - mDirection = Ogre::Vector3::UNIT_Z; - mUp = Ogre::Vector3::UNIT_Y; - setSize(100.0f, 100.0f, 100.0f); - mType = t; - - // Set up parameters - if(createParamDictionary(mType + "Emitter")) - { - addBaseParameters(); - Ogre::ParamDictionary *dict = getParamDictionary(); - - // Custom params - dict->addParameter(Ogre::ParameterDef("width", - "Width of the shape in world coordinates.", - Ogre::PT_REAL), - &msWidthCmd); - dict->addParameter(Ogre::ParameterDef("height", - "Height of the shape in world coordinates.", - Ogre::PT_REAL), - &msHeightCmd); - dict->addParameter(Ogre::ParameterDef("depth", - "Depth of the shape in world coordinates.", - Ogre::PT_REAL), - &msDepthCmd); - - dict->addParameter(Ogre::ParameterDef("vertical_direction", - "Vertical direction of emitted particles (in degrees).", - Ogre::PT_REAL), - &msVerticalDirCmd); - dict->addParameter(Ogre::ParameterDef("vertical_angle", - "Vertical direction variance of emitted particles (in degrees).", - Ogre::PT_REAL), - &msVerticalAngleCmd); - dict->addParameter(Ogre::ParameterDef("horizontal_direction", - "Horizontal direction of emitted particles (in degrees).", - Ogre::PT_REAL), - &msHorizontalDirCmd); - dict->addParameter(Ogre::ParameterDef("horizontal_angle", - "Horizontal direction variance of emitted particles (in degrees).", - Ogre::PT_REAL), - &msHorizontalAngleCmd); - - return true; - } - return false; - } - - /// Command objects - static CmdWidth msWidthCmd; - static CmdHeight msHeightCmd; - static CmdDepth msDepthCmd; - static CmdVerticalDir msVerticalDirCmd; - static CmdVerticalAngle msVerticalAngleCmd; - static CmdHorizontalDir msHorizontalDirCmd; - static CmdHorizontalAngle msHorizontalAngleCmd; -}; -NifEmitter::CmdWidth NifEmitter::msWidthCmd; -NifEmitter::CmdHeight NifEmitter::msHeightCmd; -NifEmitter::CmdDepth NifEmitter::msDepthCmd; -NifEmitter::CmdVerticalDir NifEmitter::msVerticalDirCmd; -NifEmitter::CmdVerticalAngle NifEmitter::msVerticalAngleCmd; -NifEmitter::CmdHorizontalDir NifEmitter::msHorizontalDirCmd; -NifEmitter::CmdHorizontalAngle NifEmitter::msHorizontalAngleCmd; - -Ogre::ParticleEmitter* NifEmitterFactory::createEmitter(Ogre::ParticleSystem *psys) -{ - Ogre::ParticleEmitter *emitter = OGRE_NEW NifEmitter(psys); - mEmitters.push_back(emitter); - return emitter; -} - - -class GrowFadeAffector : public Ogre::ParticleAffector -{ -public: - /** Command object for grow_time (see Ogre::ParamCommand).*/ - class CmdGrowTime : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - const GrowFadeAffector *self = static_cast(target); - return Ogre::StringConverter::toString(self->getGrowTime()); - } - void doSet(void *target, const Ogre::String &val) - { - GrowFadeAffector *self = static_cast(target); - self->setGrowTime(Ogre::StringConverter::parseReal(val)); - } - }; - - /** Command object for fade_time (see Ogre::ParamCommand).*/ - class CmdFadeTime : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - const GrowFadeAffector *self = static_cast(target); - return Ogre::StringConverter::toString(self->getFadeTime()); - } - void doSet(void *target, const Ogre::String &val) - { - GrowFadeAffector *self = static_cast(target); - self->setFadeTime(Ogre::StringConverter::parseReal(val)); - } - }; - - /** Default constructor. */ - GrowFadeAffector(Ogre::ParticleSystem *psys) : ParticleAffector(psys) - { - mGrowTime = 0.0f; - mFadeTime = 0.0f; - - mType = "GrowFade"; - - // Init parameters - if(createParamDictionary("GrowFadeAffector")) - { - Ogre::ParamDictionary *dict = getParamDictionary(); - - Ogre::String grow_title("grow_time"); - Ogre::String fade_title("fade_time"); - Ogre::String grow_descr("Time from begin to reach full size."); - Ogre::String fade_descr("Time from end to shrink."); - - dict->addParameter(Ogre::ParameterDef(grow_title, grow_descr, Ogre::PT_REAL), &msGrowCmd); - dict->addParameter(Ogre::ParameterDef(fade_title, fade_descr, Ogre::PT_REAL), &msFadeCmd); - } - } - - /** See Ogre::ParticleAffector. */ - void _initParticle(Ogre::Particle *particle) - { -#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) - const Ogre::Real life_time = particle->mTotalTimeToLive; - Ogre::Real particle_time = particle->mTimeToLive; -#else - const Ogre::Real life_time = particle->totalTimeToLive; - Ogre::Real particle_time = particle->timeToLive; -#endif - Ogre::Real width = mParent->getDefaultWidth(); - Ogre::Real height = mParent->getDefaultHeight(); - if(life_time-particle_time < mGrowTime) - { - Ogre::Real scale = (life_time-particle_time) / mGrowTime; - assert (scale >= 0); - // HACK: don't allow zero-sized particles which can rarely cause an AABB assertion in Ogre to fail - scale = std::max(scale, 0.00001f); - width *= scale; - height *= scale; - } - if(particle_time < mFadeTime) - { - Ogre::Real scale = particle_time / mFadeTime; - assert (scale >= 0); - // HACK: don't allow zero-sized particles which can rarely cause an AABB assertion in Ogre to fail - scale = std::max(scale, 0.00001f); - width *= scale; - height *= scale; - } - particle->setDimensions(width, height); - } - - /** See Ogre::ParticleAffector. */ - void _affectParticles(Ogre::ParticleSystem *psys, Ogre::Real timeElapsed) - { - Ogre::ParticleIterator pi = psys->_getIterator(); - while (!pi.end()) - { - Ogre::Particle *p = pi.getNext(); -#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) - const Ogre::Real life_time = p->mTotalTimeToLive; - Ogre::Real particle_time = p->mTimeToLive; -#else - const Ogre::Real life_time = p->totalTimeToLive; - Ogre::Real particle_time = p->timeToLive; -#endif - Ogre::Real width = mParent->getDefaultWidth(); - Ogre::Real height = mParent->getDefaultHeight(); - if(life_time-particle_time < mGrowTime) - { - Ogre::Real scale = (life_time-particle_time) / mGrowTime; - assert (scale >= 0); - // HACK: don't allow zero-sized particles which can rarely cause an AABB assertion in Ogre to fail - scale = std::max(scale, 0.00001f); - width *= scale; - height *= scale; - } - if(particle_time < mFadeTime) - { - Ogre::Real scale = particle_time / mFadeTime; - assert (scale >= 0); - // HACK: don't allow zero-sized particles which can rarely cause an AABB assertion in Ogre to fail - scale = std::max(scale, 0.00001f); - width *= scale; - height *= scale; - } - p->setDimensions(width, height); - } - } - - void setGrowTime(Ogre::Real time) - { - mGrowTime = time; - } - Ogre::Real getGrowTime() const - { return mGrowTime; } - - void setFadeTime(Ogre::Real time) - { - mFadeTime = time; - } - Ogre::Real getFadeTime() const - { return mFadeTime; } - - static CmdGrowTime msGrowCmd; - static CmdFadeTime msFadeCmd; - -protected: - Ogre::Real mGrowTime; - Ogre::Real mFadeTime; -}; -GrowFadeAffector::CmdGrowTime GrowFadeAffector::msGrowCmd; -GrowFadeAffector::CmdFadeTime GrowFadeAffector::msFadeCmd; - -Ogre::ParticleAffector *GrowFadeAffectorFactory::createAffector(Ogre::ParticleSystem *psys) -{ - Ogre::ParticleAffector *p = new GrowFadeAffector(psys); - mAffectors.push_back(p); - return p; -} - - -class GravityAffector : public Ogre::ParticleAffector -{ - enum ForceType { - Type_Wind, - Type_Point - }; - -public: - Ogre::Bone* mEmitterBone; - Ogre::Bone* mParticleBone; - - Ogre::ParticleSystem* getPartSys() { return mParent; } - - /** Command object for force (see Ogre::ParamCommand).*/ - class CmdForce : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - const GravityAffector *self = static_cast(target); - return Ogre::StringConverter::toString(self->getForce()); - } - void doSet(void *target, const Ogre::String &val) - { - GravityAffector *self = static_cast(target); - self->setForce(Ogre::StringConverter::parseReal(val)); - } - }; - - /** Command object for force_type (see Ogre::ParamCommand).*/ - class CmdForceType : public Ogre::ParamCommand - { - static ForceType getTypeFromString(const Ogre::String &type) - { - if(type == "wind") - return Type_Wind; - if(type == "point") - return Type_Point; - OGRE_EXCEPT(Ogre::Exception::ERR_INVALIDPARAMS, "Invalid force type string: "+type, - "CmdForceType::getTypeFromString"); - } - - static Ogre::String getStringFromType(ForceType type) - { - switch(type) - { - case Type_Wind: return "wind"; - case Type_Point: return "point"; - } - OGRE_EXCEPT(Ogre::Exception::ERR_INVALIDPARAMS, "Invalid force type enum: "+Ogre::StringConverter::toString(type), - "CmdForceType::getStringFromType"); - } - - public: - Ogre::String doGet(const void *target) const - { - const GravityAffector *self = static_cast(target); - return getStringFromType(self->getForceType()); - } - void doSet(void *target, const Ogre::String &val) - { - GravityAffector *self = static_cast(target); - self->setForceType(getTypeFromString(val)); - } - }; - - /** Command object for direction (see Ogre::ParamCommand).*/ - class CmdDirection : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - const GravityAffector *self = static_cast(target); - return Ogre::StringConverter::toString(self->getDirection()); - } - void doSet(void *target, const Ogre::String &val) - { - GravityAffector *self = static_cast(target); - self->setDirection(Ogre::StringConverter::parseVector3(val)); - } - }; - - /** Command object for position (see Ogre::ParamCommand).*/ - class CmdPosition : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - const GravityAffector *self = static_cast(target); - return Ogre::StringConverter::toString(self->getPosition()); - } - void doSet(void *target, const Ogre::String &val) - { - GravityAffector *self = static_cast(target); - self->setPosition(Ogre::StringConverter::parseVector3(val)); - } - }; - - - /** Default constructor. */ - GravityAffector(Ogre::ParticleSystem *psys) - : ParticleAffector(psys) - , mForce(0.0f) - , mForceType(Type_Wind) - , mPosition(0.0f) - , mDirection(0.0f) - { - std::vector bones = Ogre::any_cast(psys->getUserObjectBindings().getUserAny()).mBones; - assert (!bones.empty()); - mEmitterBone = bones[0]; - Ogre::TagPoint* tag = static_cast(mParent->getParentNode()); - mParticleBone = static_cast(tag->getParent()); - - mType = "Gravity"; - - // Init parameters - if(createParamDictionary("GravityAffector")) - { - Ogre::ParamDictionary *dict = getParamDictionary(); - - Ogre::String force_title("force"); - Ogre::String force_descr("Amount of force applied to particles."); - Ogre::String force_type_title("force_type"); - Ogre::String force_type_descr("Type of force applied to particles (point or wind)."); - Ogre::String direction_title("direction"); - Ogre::String direction_descr("Direction of wind forces."); - Ogre::String position_title("position"); - Ogre::String position_descr("Position of point forces."); - - dict->addParameter(Ogre::ParameterDef(force_title, force_descr, Ogre::PT_REAL), &msForceCmd); - dict->addParameter(Ogre::ParameterDef(force_type_title, force_type_descr, Ogre::PT_STRING), &msForceTypeCmd); - dict->addParameter(Ogre::ParameterDef(direction_title, direction_descr, Ogre::PT_VECTOR3), &msDirectionCmd); - dict->addParameter(Ogre::ParameterDef(position_title, position_descr, Ogre::PT_VECTOR3), &msPositionCmd); - } - } - - /** See Ogre::ParticleAffector. */ - void _affectParticles(Ogre::ParticleSystem *psys, Ogre::Real timeElapsed) - { - switch(mForceType) - { - case Type_Wind: - applyWindForce(psys, timeElapsed); - break; - case Type_Point: - applyPointForce(psys, timeElapsed); - break; - } - } - - void setForce(Ogre::Real force) - { mForce = force; } - Ogre::Real getForce() const - { return mForce; } - - void setForceType(ForceType type) - { mForceType = type; } - ForceType getForceType() const - { return mForceType; } - - void setDirection(const Ogre::Vector3 &dir) - { mDirection = dir; } - const Ogre::Vector3 &getDirection() const - { return mDirection; } - - void setPosition(const Ogre::Vector3 &pos) - { mPosition = pos; } - const Ogre::Vector3 &getPosition() const - { return mPosition; } - - static CmdForce msForceCmd; - static CmdForceType msForceTypeCmd; - static CmdDirection msDirectionCmd; - static CmdPosition msPositionCmd; - -protected: - void applyWindForce(Ogre::ParticleSystem *psys, Ogre::Real timeElapsed) - { - const Ogre::Vector3 vec = mDirection * mForce * timeElapsed; - Ogre::ParticleIterator pi = psys->_getIterator(); - while (!pi.end()) - { - Ogre::Particle *p = pi.getNext(); -#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) - p->mDirection += vec; -#else - p->direction += vec; -#endif - } - } - - void applyPointForce(Ogre::ParticleSystem *psys, Ogre::Real timeElapsed) - { - const Ogre::Real force = mForce * timeElapsed; - Ogre::ParticleIterator pi = psys->_getIterator(); - while (!pi.end()) - { - Ogre::Particle *p = pi.getNext(); -#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) - Ogre::Vector3 position = p->mPosition; -#else - Ogre::Vector3 position = p->position; -#endif - - Ogre::Vector3 vec = (mPosition - position).normalisedCopy() * force; -#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) - p->mDirection += vec; -#else - p->direction += vec; -#endif - } - } - - - float mForce; - - ForceType mForceType; - - Ogre::Vector3 mPosition; - Ogre::Vector3 mDirection; -}; -GravityAffector::CmdForce GravityAffector::msForceCmd; -GravityAffector::CmdForceType GravityAffector::msForceTypeCmd; -GravityAffector::CmdDirection GravityAffector::msDirectionCmd; -GravityAffector::CmdPosition GravityAffector::msPositionCmd; - -Ogre::ParticleAffector *GravityAffectorFactory::createAffector(Ogre::ParticleSystem *psys) -{ - Ogre::ParticleAffector *p = new GravityAffector(psys); - mAffectors.push_back(p); - return p; -} diff --git a/components/nifogre/particles.hpp b/components/nifogre/particles.hpp deleted file mode 100644 index 6efc669fe7..0000000000 --- a/components/nifogre/particles.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef OENGINE_OGRE_PARTICLES_H -#define OENGINE_OGRE_PARTICLES_H - -#include -#include - -/** Factory class for NifEmitter. */ -class NifEmitterFactory : public Ogre::ParticleEmitterFactory -{ -public: - /** See ParticleEmitterFactory */ - Ogre::String getName() const - { return "Nif"; } - - /** See ParticleEmitterFactory */ - Ogre::ParticleEmitter* createEmitter(Ogre::ParticleSystem *psys); -}; - -/** Factory class for GrowFadeAffector. */ -class GrowFadeAffectorFactory : public Ogre::ParticleAffectorFactory -{ - /** See Ogre::ParticleAffectorFactory */ - Ogre::String getName() const - { return "GrowFade"; } - - /** See Ogre::ParticleAffectorFactory */ - Ogre::ParticleAffector *createAffector(Ogre::ParticleSystem *psys); -}; - -/** Factory class for GravityAffector. */ -class GravityAffectorFactory : public Ogre::ParticleAffectorFactory -{ - /** See Ogre::ParticleAffectorFactory */ - Ogre::String getName() const - { return "Gravity"; } - - /** See Ogre::ParticleAffectorFactory */ - Ogre::ParticleAffector *createAffector(Ogre::ParticleSystem *psys); -}; - -struct NiNodeHolder -{ - std::vector mBones; - - // Ogre::Any needs this for some reason - friend std::ostream& operator<<(std::ostream& o, const NiNodeHolder& r) - { return o; } -}; - -#endif /* OENGINE_OGRE_PARTICLES_H */ diff --git a/components/nifogre/skeleton.cpp b/components/nifogre/skeleton.cpp deleted file mode 100644 index db6a753c52..0000000000 --- a/components/nifogre/skeleton.cpp +++ /dev/null @@ -1,179 +0,0 @@ -#include "skeleton.hpp" - -#include -#include -#include -#include - -#include -#include -#include - -namespace NifOgre -{ - -void NIFSkeletonLoader::buildBones(Ogre::Skeleton *skel, const Nif::Node *node, Ogre::Bone *parent) -{ - Ogre::Bone *bone; - if (node->name.empty()) - { - // HACK: use " " instead of empty name, otherwise Ogre will replace it with an auto-generated - // name in SkeletonInstance::cloneBoneAndChildren. - static const char* emptyname = " "; - if (!skel->hasBone(emptyname)) - bone = skel->createBone(emptyname); - else - bone = skel->createBone(); - } - else - { - if(!skel->hasBone(node->name)) - bone = skel->createBone(node->name); - else - bone = skel->createBone(); - } - - if(parent) parent->addChild(bone); - mNifToOgreHandleMap[node->recIndex] = bone->getHandle(); - - bone->setOrientation(node->trafo.rotation); - bone->setPosition(node->trafo.pos); - bone->setScale(Ogre::Vector3(node->trafo.scale)); - bone->setBindingPose(); - - if(!(node->recType == Nif::RC_NiNode || /* Nothing special; children traversed below */ - node->recType == Nif::RC_RootCollisionNode || /* handled in nifbullet (hopefully) */ - node->recType == Nif::RC_NiTriShape || /* Handled in the mesh loader */ - node->recType == Nif::RC_NiBSAnimationNode || /* Handled in the object loader */ - node->recType == Nif::RC_NiBillboardNode || /* Handled in the object loader */ - node->recType == Nif::RC_NiBSParticleNode || - node->recType == Nif::RC_NiCamera || - node->recType == Nif::RC_NiAutoNormalParticles || - node->recType == Nif::RC_NiRotatingParticles - )) - warn("Unhandled "+node->recName+" "+node->name+" in "+skel->getName()); - - Nif::ControllerPtr ctrl = node->controller; - while(!ctrl.empty()) - { - if(!(ctrl->recType == Nif::RC_NiParticleSystemController || - ctrl->recType == Nif::RC_NiBSPArrayController || - ctrl->recType == Nif::RC_NiVisController || - ctrl->recType == Nif::RC_NiUVController || - ctrl->recType == Nif::RC_NiKeyframeController || - ctrl->recType == Nif::RC_NiGeomMorpherController - )) - warn("Unhandled "+ctrl->recName+" from node "+node->name+" in "+skel->getName()); - ctrl = ctrl->next; - } - - const Nif::NiNode *ninode = dynamic_cast(node); - if(ninode) - { - const Nif::NodeList &children = ninode->children; - for(size_t i = 0;i < children.length();i++) - { - if(!children[i].empty()) - buildBones(skel, children[i].getPtr(), bone); - } - } -} - -void NIFSkeletonLoader::loadResource(Ogre::Resource *resource) -{ - Ogre::Skeleton *skel = dynamic_cast(resource); - OgreAssert(skel, "Attempting to load a skeleton into a non-skeleton resource!"); - - Nif::NIFFilePtr nif(Nif::Cache::getInstance().load(skel->getName())); - const Nif::Node *node = static_cast(nif->getRoot(0)); - - try { - buildBones(skel, node); - } - catch(std::exception &e) { - std::cerr<< "Exception while loading "<getName() <boneTrafo) - return true; - - if(!node->controller.empty()) - { - Nif::ControllerPtr ctrl = node->controller; - do { - if(ctrl->recType == Nif::RC_NiKeyframeController && ctrl->flags & Nif::NiNode::ControllerFlag_Active) - return true; - } while(!(ctrl=ctrl->next).empty()); - } - - if (node->name == "AttachLight" || node->name == "ArrowBone") - return true; - - if(node->recType == Nif::RC_NiNode || node->recType == Nif::RC_RootCollisionNode) - { - const Nif::NiNode *ninode = static_cast(node); - const Nif::NodeList &children = ninode->children; - for(size_t i = 0;i < children.length();i++) - { - if(!children[i].empty()) - { - if(needSkeleton(children[i].getPtr())) - return true; - } - } - return false; - } - if(node->recType == Nif::RC_NiTriShape) - return false; - - return true; -} - -Ogre::SkeletonPtr NIFSkeletonLoader::createSkeleton(const std::string &name, const std::string &group, const Nif::Node *node) -{ - bool forceskel = false; - std::string::size_type extpos = name.rfind('.'); - if(extpos != std::string::npos && name.compare(extpos, name.size()-extpos, ".nif") == 0) - { - Ogre::ResourceGroupManager &resMgr = Ogre::ResourceGroupManager::getSingleton(); - forceskel = resMgr.resourceExistsInAnyGroup(name.substr(0, extpos)+".kf"); - } - - if(forceskel || needSkeleton(node)) - { - Ogre::SkeletonManager &skelMgr = Ogre::SkeletonManager::getSingleton(); - return skelMgr.create(name, group, true, &sLoaders[name]); - } - - return Ogre::SkeletonPtr(); -} - -// Looks up an Ogre Bone handle ID from a NIF's record index. Should only be -// used when the bone name is insufficient as this is a relatively slow lookup -int NIFSkeletonLoader::lookupOgreBoneHandle(const std::string &nifname, int idx) -{ - LoaderMap::const_iterator loader = sLoaders.find(nifname); - if(loader != sLoaders.end()) - { - std::map::const_iterator entry = loader->second.mNifToOgreHandleMap.find(idx); - if(entry != loader->second.mNifToOgreHandleMap.end()) - return entry->second; - } - throw std::runtime_error("Invalid NIF record lookup ("+nifname+", index "+Ogre::StringConverter::toString(idx)+")"); -} - -NIFSkeletonLoader::LoaderMap NIFSkeletonLoader::sLoaders; - -} diff --git a/components/nifogre/skeleton.hpp b/components/nifogre/skeleton.hpp deleted file mode 100644 index 9ec3a0c825..0000000000 --- a/components/nifogre/skeleton.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef COMPONENTS_NIFOGRE_SKELETON_HPP -#define COMPONENTS_NIFOGRE_SKELETON_HPP - -#include -#include -#include - -#include - -#include "ogrenifloader.hpp" - -namespace Nif -{ - class NiTextKeyExtraData; - class Node; - class NiKeyframeController; -} - -namespace NifOgre -{ - -/** Manual resource loader for NIF skeletons. This is the main class - responsible for translating the internal NIF skeleton structure into - something Ogre can use (includes animations and node TextKeyData). - */ -class NIFSkeletonLoader : public Ogre::ManualResourceLoader -{ - static void warn(const std::string &msg) - { - std::cerr << "NIFSkeletonLoader: Warn: " << msg << std::endl; - } - - static void fail(const std::string &msg) - { - std::cerr << "NIFSkeletonLoader: Fail: "<< msg << std::endl; - abort(); - } - - void buildBones(Ogre::Skeleton *skel, const Nif::Node *node, Ogre::Bone *parent=NULL); - - static bool needSkeleton(const Nif::Node *node); - - // Lookup to retrieve an Ogre bone handle for a given Nif record index - std::map mNifToOgreHandleMap; - - typedef std::map LoaderMap; - static LoaderMap sLoaders; - -public: - void loadResource(Ogre::Resource *resource); - - static Ogre::SkeletonPtr createSkeleton(const std::string &name, const std::string &group, const Nif::Node *node); - - // Looks up an Ogre Bone handle ID from a NIF's record index. Should only - // be used when the bone name is insufficient as this is a relatively slow - // lookup - static int lookupOgreBoneHandle(const std::string &nifname, int idx); -}; - -} - -#endif diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp new file mode 100644 index 0000000000..28f61e4b6c --- /dev/null +++ b/components/nifosg/controller.cpp @@ -0,0 +1,445 @@ +#include "controller.hpp" + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include "userdata.hpp" + +namespace NifOsg +{ + +ControllerFunction::ControllerFunction(const Nif::Controller *ctrl) + : mFrequency(ctrl->frequency) + , mPhase(ctrl->phase) + , mStartTime(ctrl->timeStart) + , mStopTime(ctrl->timeStop) + , mExtrapolationMode(static_cast((ctrl->flags&0x6) >> 1)) +{ +} + +float ControllerFunction::calculate(float value) const +{ + float time = mFrequency * value + mPhase; + if (time >= mStartTime && time <= mStopTime) + return time; + switch (mExtrapolationMode) + { + case Cycle: + { + float delta = mStopTime - mStartTime; + if ( delta <= 0 ) + return mStartTime; + float cycles = ( time - mStartTime ) / delta; + float remainder = ( cycles - std::floor( cycles ) ) * delta; + return mStartTime + remainder; + } + case Reverse: + { + float delta = mStopTime - mStartTime; + if ( delta <= 0 ) + return mStartTime; + + float cycles = ( time - mStartTime ) / delta; + float remainder = ( cycles - std::floor( cycles ) ) * delta; + + // Even number of cycles? + if ( ( static_cast(std::fabs( std::floor( cycles ) )) % 2 ) == 0 ) + return mStartTime + remainder; + + return mStopTime - remainder; + } + case Constant: + default: + return std::min(mStopTime, std::max(mStartTime, time)); + } +} + +float ControllerFunction::getMaximum() const +{ + return mStopTime; +} + +KeyframeController::KeyframeController() +{ +} + +KeyframeController::KeyframeController(const KeyframeController ©, const osg::CopyOp ©op) + : osg::NodeCallback(copy, copyop) + , Controller(copy) + , mRotations(copy.mRotations) + , mXRotations(copy.mXRotations) + , mYRotations(copy.mYRotations) + , mZRotations(copy.mZRotations) + , mTranslations(copy.mTranslations) + , mScales(copy.mScales) +{ +} + +KeyframeController::KeyframeController(const Nif::NiKeyframeData *data) + : mRotations(data->mRotations) + , mXRotations(data->mXRotations, 0.f) + , mYRotations(data->mYRotations, 0.f) + , mZRotations(data->mZRotations, 0.f) + , mTranslations(data->mTranslations, osg::Vec3f()) + , mScales(data->mScales, 1.f) +{ +} + +osg::Quat KeyframeController::getXYZRotation(float time) const +{ + float xrot = 0, yrot = 0, zrot = 0; + if (!mXRotations.empty()) + xrot = mXRotations.interpKey(time); + if (!mYRotations.empty()) + yrot = mYRotations.interpKey(time); + if (!mZRotations.empty()) + zrot = mZRotations.interpKey(time); + osg::Quat xr(xrot, osg::Vec3f(1,0,0)); + osg::Quat yr(yrot, osg::Vec3f(0,1,0)); + osg::Quat zr(zrot, osg::Vec3f(0,0,1)); + return (xr*yr*zr); +} + +osg::Vec3f KeyframeController::getTranslation(float time) const +{ + if(!mTranslations.empty()) + return mTranslations.interpKey(time); + return osg::Vec3f(); +} + +void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) +{ + if (hasInput()) + { + osg::MatrixTransform* trans = static_cast(node); + osg::Matrix mat = trans->getMatrix(); + + float time = getInputValue(nv); + + NodeUserData* userdata = static_cast(trans->getUserDataContainer()->getUserObject(0)); + Nif::Matrix3& rot = userdata->mRotationScale; + + bool setRot = false; + if(!mRotations.empty()) + { + mat.setRotate(mRotations.interpKey(time)); + setRot = true; + } + else if (!mXRotations.empty() || !mYRotations.empty() || !mZRotations.empty()) + { + mat.setRotate(getXYZRotation(time)); + setRot = true; + } + else + { + // no rotation specified, use the previous value from the UserData + for (int i=0;i<3;++i) + for (int j=0;j<3;++j) + mat(j,i) = rot.mValues[i][j]; // NB column/row major difference + } + + if (setRot) // copy the new values back to the UserData + for (int i=0;i<3;++i) + for (int j=0;j<3;++j) + rot.mValues[i][j] = mat(j,i); // NB column/row major difference + + float& scale = userdata->mScale; + if(!mScales.empty()) + scale = mScales.interpKey(time); + + for (int i=0;i<3;++i) + for (int j=0;j<3;++j) + mat(i,j) *= scale; + + if(!mTranslations.empty()) + mat.setTrans(mTranslations.interpKey(time)); + + trans->setMatrix(mat); + } + + traverse(node, nv); +} + +GeomMorpherController::GeomMorpherController() +{ +} + +GeomMorpherController::GeomMorpherController(const GeomMorpherController ©, const osg::CopyOp ©op) + : osg::Drawable::UpdateCallback(copy, copyop) + , Controller(copy) + , mKeyFrames(copy.mKeyFrames) +{ +} + +GeomMorpherController::GeomMorpherController(const Nif::NiMorphData *data) +{ + for (unsigned int i=0; imMorphs.size(); ++i) + mKeyFrames.push_back(FloatInterpolator(data->mMorphs[i].mKeyFrames)); +} + +void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable) +{ + osgAnimation::MorphGeometry* morphGeom = static_cast(drawable); + if (hasInput()) + { + if (mKeyFrames.size() <= 1) + return; + float input = getInputValue(nv); + int i = 0; + for (std::vector::iterator it = mKeyFrames.begin()+1; it != mKeyFrames.end(); ++it,++i) + { + float val = 0; + if (!(*it).empty()) + val = it->interpKey(input); + val = std::max(0.f, std::min(1.f, val)); + + osgAnimation::MorphGeometry::MorphTarget& target = morphGeom->getMorphTarget(i); + if (target.getWeight() != val) + { + target.setWeight(val); + morphGeom->dirty(); + } + } + } + + // morphGeometry::transformSoftwareMethod() done in cull callback i.e. only for visible morph geometries +} + +UVController::UVController() +{ +} + +UVController::UVController(const Nif::NiUVData *data, std::set textureUnits) + : mUTrans(data->mKeyList[0], 0.f) + , mVTrans(data->mKeyList[1], 0.f) + , mUScale(data->mKeyList[2], 1.f) + , mVScale(data->mKeyList[3], 1.f) + , mTextureUnits(textureUnits) +{ +} + +UVController::UVController(const UVController& copy, const osg::CopyOp& copyop) + : osg::Object(copy, copyop), StateSetUpdater(copy, copyop), Controller(copy) + , mUTrans(copy.mUTrans) + , mVTrans(copy.mVTrans) + , mUScale(copy.mUScale) + , mVScale(copy.mVScale) + , mTextureUnits(copy.mTextureUnits) +{ +} + +void UVController::setDefaults(osg::StateSet *stateset) +{ + osg::TexMat* texMat = new osg::TexMat; + for (std::set::const_iterator it = mTextureUnits.begin(); it != mTextureUnits.end(); ++it) + stateset->setTextureAttributeAndModes(*it, texMat, osg::StateAttribute::ON); +} + +void UVController::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) +{ + if (hasInput()) + { + float value = getInputValue(nv); + float uTrans = mUTrans.interpKey(value); + float vTrans = mVTrans.interpKey(value); + float uScale = mUScale.interpKey(value); + float vScale = mVScale.interpKey(value); + + osg::Matrixf mat = osg::Matrixf::scale(uScale, vScale, 1); + mat.setTrans(uTrans, vTrans, 0); + + // setting once is enough because all other texture units share the same TexMat (see setDefaults). + if (mTextureUnits.size()) + { + osg::TexMat* texMat = static_cast(stateset->getTextureAttribute(*mTextureUnits.begin(), osg::StateAttribute::TEXMAT)); + texMat->setMatrix(mat); + } + } +} + +VisController::VisController(const Nif::NiVisData *data) + : mData(data->mVis) +{ +} + +VisController::VisController() +{ +} + +VisController::VisController(const VisController ©, const osg::CopyOp ©op) + : osg::NodeCallback(copy, copyop) + , Controller(copy) + , mData(copy.mData) +{ +} + +bool VisController::calculate(float time) const +{ + if(mData.size() == 0) + return true; + + for(size_t i = 1;i < mData.size();i++) + { + if(mData[i].time > time) + return mData[i-1].isSet; + } + return mData.back().isSet; +} + +void VisController::operator() (osg::Node* node, osg::NodeVisitor* nv) +{ + if (hasInput()) + { + bool vis = calculate(getInputValue(nv)); + // Leave 0x1 enabled for UpdateVisitor, so we can make ourselves visible again in the future from this update callback + node->setNodeMask(vis ? ~0 : 0x1); + } + traverse(node, nv); +} + +AlphaController::AlphaController(const Nif::NiFloatData *data) + : mData(data->mKeyList, 1.f) +{ + +} + +AlphaController::AlphaController() +{ +} + +AlphaController::AlphaController(const AlphaController ©, const osg::CopyOp ©op) + : StateSetUpdater(copy, copyop), Controller(copy) + , mData(copy.mData) +{ +} + +void AlphaController::setDefaults(osg::StateSet *stateset) +{ + // need to create a deep copy of StateAttributes we will modify + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); +} + +void AlphaController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) +{ + if (hasInput()) + { + float value = mData.interpKey(getInputValue(nv)); + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + osg::Vec4f diffuse = mat->getDiffuse(osg::Material::FRONT_AND_BACK); + diffuse.a() = value; + mat->setDiffuse(osg::Material::FRONT_AND_BACK, diffuse); + } +} + +MaterialColorController::MaterialColorController(const Nif::NiPosData *data) + : mData(data->mKeyList, osg::Vec3f(1,1,1)) +{ +} + +MaterialColorController::MaterialColorController() +{ +} + +MaterialColorController::MaterialColorController(const MaterialColorController ©, const osg::CopyOp ©op) + : StateSetUpdater(copy, copyop), Controller(copy) + , mData(copy.mData) +{ +} + +void MaterialColorController::setDefaults(osg::StateSet *stateset) +{ + // need to create a deep copy of StateAttributes we will modify + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); +} + +void MaterialColorController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) +{ + if (hasInput()) + { + osg::Vec3f value = mData.interpKey(getInputValue(nv)); + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + osg::Vec4f diffuse = mat->getDiffuse(osg::Material::FRONT_AND_BACK); + diffuse.set(value.x(), value.y(), value.z(), diffuse.a()); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, diffuse); + } +} + +FlipController::FlipController(const Nif::NiFlipController *ctrl, std::vector > textures) + : mTexSlot(ctrl->mTexSlot) + , mDelta(ctrl->mDelta) + , mTextures(textures) +{ +} + +FlipController::FlipController(int texSlot, float delta, std::vector > textures) + : mTexSlot(texSlot) + , mDelta(delta) + , mTextures(textures) +{ +} + +FlipController::FlipController() + : mTexSlot(0) + , mDelta(0.f) +{ +} + +FlipController::FlipController(const FlipController ©, const osg::CopyOp ©op) + : StateSetUpdater(copy, copyop) + , Controller(copy) + , mTexSlot(copy.mTexSlot) + , mDelta(copy.mDelta) + , mTextures(copy.mTextures) +{ +} + +void FlipController::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) +{ + if (hasInput() && mDelta != 0) + { + int curTexture = int(getInputValue(nv) / mDelta) % mTextures.size(); + stateset->setTextureAttribute(mTexSlot, mTextures[curTexture]); + } +} + +ParticleSystemController::ParticleSystemController(const Nif::NiParticleSystemController *ctrl) + : mEmitStart(ctrl->startTime), mEmitStop(ctrl->stopTime) +{ +} + +ParticleSystemController::ParticleSystemController() + : mEmitStart(0.f), mEmitStop(0.f) +{ +} + +ParticleSystemController::ParticleSystemController(const ParticleSystemController ©, const osg::CopyOp ©op) + : osg::NodeCallback(copy, copyop) + , Controller(copy) + , mEmitStart(copy.mEmitStart) + , mEmitStop(copy.mEmitStop) +{ +} + +void ParticleSystemController::operator() (osg::Node* node, osg::NodeVisitor* nv) +{ + if (hasInput()) + { + osgParticle::ParticleProcessor* emitter = static_cast(node); + float time = getInputValue(nv); + emitter->setEnabled(time >= mEmitStart && time < mEmitStop); + } + traverse(node, nv); +} + +} diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp new file mode 100644 index 0000000000..e1e969d066 --- /dev/null +++ b/components/nifosg/controller.hpp @@ -0,0 +1,329 @@ +#ifndef COMPONENTS_NIFOSG_CONTROLLER_H +#define COMPONENTS_NIFOSG_CONTROLLER_H + +#include +#include +#include +#include + +#include +#include + +#include + +#include //UVController + +// FlipController +#include +#include + +#include +#include +#include +#include + + +namespace osg +{ + class Node; + class StateSet; +} + +namespace osgParticle +{ + class Emitter; +} + +namespace osgAnimation +{ + class MorphGeometry; +} + +namespace NifOsg +{ + + // interpolation of keyframes + template + class ValueInterpolator + { + public: + typedef typename MapT::ValueType ValueT; + + ValueInterpolator() + : mDefaultVal(ValueT()) + { + } + + ValueInterpolator(boost::shared_ptr keys, ValueT defaultVal = ValueT()) + : mKeys(keys) + , mDefaultVal(defaultVal) + { + if (keys) + { + mLastLowKey = mKeys->mKeys.end(); + mLastHighKey = mKeys->mKeys.end(); + } + } + + ValueT interpKey(float time) const + { + if (empty()) + return mDefaultVal; + + const typename MapT::MapType & keys = mKeys->mKeys; + + if(time <= keys.begin()->first) + return keys.begin()->second.mValue; + + // retrieve the current position in the map, optimized for the most common case + // where time moves linearly along the keyframe track + typename MapT::MapType::const_iterator it = mLastHighKey; + if (mLastHighKey != keys.end()) + { + if (time > mLastHighKey->first) + { + // try if we're there by incrementing one + ++mLastLowKey; + ++mLastHighKey; + it = mLastHighKey; + } + if (mLastHighKey == keys.end() || (time < mLastLowKey->first || time > mLastHighKey->first)) + it = keys.lower_bound(time); // still not there, reorient by performing lower_bound check on the whole map + } + else + it = keys.lower_bound(time); + + // now do the actual interpolation + if (it != keys.end()) + { + float aTime = it->first; + const typename MapT::KeyType* aKey = &it->second; + + // cache for next time + mLastHighKey = it; + + assert (it != keys.begin()); // Shouldn't happen, was checked at beginning of this function + + typename MapT::MapType::const_iterator last = --it; + mLastLowKey = last; + float aLastTime = last->first; + const typename MapT::KeyType* aLastKey = &last->second; + + float a = (time - aLastTime) / (aTime - aLastTime); + + return InterpolationFunc()(aLastKey->mValue, aKey->mValue, a); + } + else + return keys.rbegin()->second.mValue; + } + + bool empty() const + { + return !mKeys || mKeys->mKeys.empty(); + } + + private: + mutable typename MapT::MapType::const_iterator mLastLowKey; + mutable typename MapT::MapType::const_iterator mLastHighKey; + + boost::shared_ptr mKeys; + + ValueT mDefaultVal; + }; + + struct LerpFunc + { + template + inline ValueType operator()(const ValueType& a, const ValueType& b, float fraction) + { + return a + ((b - a) * fraction); + } + }; + + struct QuaternionSlerpFunc + { + inline osg::Quat operator()(const osg::Quat& a, const osg::Quat& b, float fraction) + { + osg::Quat result; + result.slerp(fraction, a, b); + return result; + } + }; + + typedef ValueInterpolator QuaternionInterpolator; + typedef ValueInterpolator FloatInterpolator; + typedef ValueInterpolator Vec3Interpolator; + + class ControllerFunction : public SceneUtil::ControllerFunction + { + private: + float mFrequency; + float mPhase; + float mStartTime; + float mStopTime; + enum ExtrapolationMode + { + Cycle = 0, + Reverse = 1, + Constant = 2 + }; + ExtrapolationMode mExtrapolationMode; + + public: + ControllerFunction(const Nif::Controller *ctrl); + + float calculate(float value) const; + + virtual float getMaximum() const; + }; + + /// Must be set on an osgAnimation::MorphGeometry. + class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller + { + public: + GeomMorpherController(const Nif::NiMorphData* data); + GeomMorpherController(); + GeomMorpherController(const GeomMorpherController& copy, const osg::CopyOp& copyop); + + META_Object(NifOsg, GeomMorpherController) + + virtual void update(osg::NodeVisitor* nv, osg::Drawable* drawable); + + private: + std::vector mKeyFrames; + }; + + class KeyframeController : public osg::NodeCallback, public SceneUtil::Controller + { + public: + KeyframeController(const Nif::NiKeyframeData *data); + KeyframeController(); + KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop); + + META_Object(NifOsg, KeyframeController) + + virtual osg::Vec3f getTranslation(float time) const; + + virtual void operator() (osg::Node*, osg::NodeVisitor*); + + private: + QuaternionInterpolator mRotations; + + FloatInterpolator mXRotations; + FloatInterpolator mYRotations; + FloatInterpolator mZRotations; + + Vec3Interpolator mTranslations; + FloatInterpolator mScales; + + osg::Quat getXYZRotation(float time) const; + }; + + class UVController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller + { + public: + UVController(); + UVController(const UVController&,const osg::CopyOp&); + UVController(const Nif::NiUVData *data, std::set textureUnits); + + META_Object(NifOsg,UVController) + + virtual void setDefaults(osg::StateSet* stateset); + virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv); + + private: + FloatInterpolator mUTrans; + FloatInterpolator mVTrans; + FloatInterpolator mUScale; + FloatInterpolator mVScale; + std::set mTextureUnits; + }; + + class VisController : public osg::NodeCallback, public SceneUtil::Controller + { + private: + std::vector mData; + + bool calculate(float time) const; + + public: + VisController(const Nif::NiVisData *data); + VisController(); + VisController(const VisController& copy, const osg::CopyOp& copyop); + + META_Object(NifOsg, VisController) + + virtual void operator() (osg::Node* node, osg::NodeVisitor* nv); + }; + + class AlphaController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller + { + private: + FloatInterpolator mData; + + public: + AlphaController(const Nif::NiFloatData *data); + AlphaController(); + AlphaController(const AlphaController& copy, const osg::CopyOp& copyop); + + virtual void setDefaults(osg::StateSet* stateset); + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* nv); + + META_Object(NifOsg, AlphaController) + }; + + class MaterialColorController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller + { + private: + Vec3Interpolator mData; + + public: + MaterialColorController(const Nif::NiPosData *data); + MaterialColorController(); + MaterialColorController(const MaterialColorController& copy, const osg::CopyOp& copyop); + + META_Object(NifOsg, MaterialColorController) + + virtual void setDefaults(osg::StateSet* stateset); + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* nv); + }; + + class FlipController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller + { + private: + int mTexSlot; + float mDelta; + std::vector > mTextures; + + public: + FlipController(const Nif::NiFlipController* ctrl, std::vector > textures); + FlipController(int texSlot, float delta, std::vector > textures); + FlipController(); + FlipController(const FlipController& copy, const osg::CopyOp& copyop); + + META_Object(NifOsg, FlipController) + + virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv); + }; + + class ParticleSystemController : public osg::NodeCallback, public SceneUtil::Controller + { + public: + ParticleSystemController(const Nif::NiParticleSystemController* ctrl); + ParticleSystemController(); + ParticleSystemController(const ParticleSystemController& copy, const osg::CopyOp& copyop); + + META_Object(NifOsg, ParticleSystemController) + + virtual void operator() (osg::Node* node, osg::NodeVisitor* nv); + + private: + float mEmitStart; + float mEmitStop; + }; + +} + +#endif diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp new file mode 100644 index 0000000000..5c28bbc7e6 --- /dev/null +++ b/components/nifosg/nifloader.cpp @@ -0,0 +1,1545 @@ +#include "nifloader.hpp" + +#include +#include +#include +#include +#include +#include +#include + +// resource +#include +#include +#include + +// skel +#include + +// particle +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "particle.hpp" +#include "userdata.hpp" + +namespace +{ + + void getAllNiNodes(const Nif::Node* node, std::vector& outIndices) + { + const Nif::NiNode* ninode = dynamic_cast(node); + if (ninode) + { + outIndices.push_back(ninode->recIndex); + for (unsigned int i=0; ichildren.length(); ++i) + if (!ninode->children[i].empty()) + getAllNiNodes(ninode->children[i].getPtr(), outIndices); + } + } + + // Collect all properties affecting the given drawable that should be handled on drawable basis rather than on the node hierarchy above it. + void collectDrawableProperties(const Nif::Node* nifNode, std::vector& out) + { + const Nif::PropertyList& props = nifNode->props; + for (size_t i = 0; i recType) + { + case Nif::RC_NiMaterialProperty: + case Nif::RC_NiVertexColorProperty: + case Nif::RC_NiSpecularProperty: + case Nif::RC_NiAlphaProperty: + out.push_back(props[i].getPtr()); + break; + default: + break; + } + } + } + if (nifNode->parent) + collectDrawableProperties(nifNode->parent, out); + } + + class FrameSwitch : public osg::Group + { + public: + FrameSwitch() + { + } + + FrameSwitch(const FrameSwitch& copy, const osg::CopyOp& copyop) + : osg::Group(copy, copyop) + { + } + + META_Object(NifOsg, FrameSwitch) + + virtual void traverse(osg::NodeVisitor& nv) + { + if (nv.getTraversalMode() != osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN && nv.getVisitorType() != osg::NodeVisitor::UPDATE_VISITOR) + osg::Group::traverse(nv); + else + { + for (unsigned int i=0; iaccept(nv); + } + } + } + }; + + // NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale + // set just like a regular MatrixTransform, but the rotation set will be overridden in order to face the camera. + // Must be set as a cull callback. + class BillboardCallback : public osg::NodeCallback + { + public: + BillboardCallback() + { + } + BillboardCallback(const BillboardCallback& copy, const osg::CopyOp& copyop) + : osg::NodeCallback(copy, copyop) + { + } + + META_Object(NifOsg, BillboardCallback) + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = dynamic_cast(nv); + if (node && cv) + { + osg::Matrix modelView = *cv->getModelViewMatrix(); + + // attempt to preserve scale + float mag[3]; + for (int i=0;i<3;++i) + { + mag[i] = std::sqrt(modelView(0,i) * modelView(0,i) + modelView(1,i) * modelView(1,i) + modelView(2,i) * modelView(2,i)); + } + + modelView.setRotate(osg::Quat()); + modelView(0,0) = mag[0]; + modelView(1,1) = mag[1]; + modelView(2,2) = mag[2]; + + cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); + + traverse(node, nv); + + cv->popModelViewMatrix(); + } + else + traverse(node, nv); + } + }; + + struct UpdateMorphGeometry : public osg::Drawable::CullCallback + { + UpdateMorphGeometry() + : mLastFrameNumber(0) + { + } + + UpdateMorphGeometry(const UpdateMorphGeometry& copy, const osg::CopyOp& copyop) + : osg::Drawable::CullCallback(copy, copyop) + , mLastFrameNumber(0) + { + } + + META_Object(NifOsg, UpdateMorphGeometry) + + virtual bool cull(osg::NodeVisitor* nv, osg::Drawable * drw, osg::State *) const + { + osgAnimation::MorphGeometry* geom = static_cast(drw); + if (!geom) + return false; + + if (mLastFrameNumber == nv->getTraversalNumber()) + return false; + mLastFrameNumber = nv->getTraversalNumber(); + + geom->transformSoftwareMethod(); + return false; + } + + private: + mutable unsigned int mLastFrameNumber; + }; + + // Callback to return a static bounding box for a MorphGeometry. The idea is to not recalculate the bounding box + // every time the morph weights change. To do so we return a maximum containing box that is big enough for all possible combinations of morph targets. + class StaticBoundingBoxCallback : public osg::Drawable::ComputeBoundingBoxCallback + { + public: + StaticBoundingBoxCallback() + { + } + + StaticBoundingBoxCallback(const osg::BoundingBox& bounds) + : mBoundingBox(bounds) + { + } + + StaticBoundingBoxCallback(const StaticBoundingBoxCallback& copy, const osg::CopyOp& copyop) + : osg::Drawable::ComputeBoundingBoxCallback(copy, copyop) + , mBoundingBox(copy.mBoundingBox) + { + } + + META_Object(NifOsg, StaticBoundingBoxCallback) + + virtual osg::BoundingBox computeBound(const osg::Drawable&) const + { + return mBoundingBox; + } + + private: + osg::BoundingBox mBoundingBox; + }; + + void extractTextKeys(const Nif::NiTextKeyExtraData *tk, NifOsg::TextKeyMap &textkeys) + { + for(size_t i = 0;i < tk->list.size();i++) + { + const std::string &str = tk->list[i].text; + std::string::size_type pos = 0; + while(pos < str.length()) + { + if(::isspace(str[pos])) + { + pos++; + continue; + } + + std::string::size_type nextpos = std::min(str.find('\r', pos), str.find('\n', pos)); + if(nextpos != std::string::npos) + { + do { + nextpos--; + } while(nextpos > pos && ::isspace(str[nextpos])); + nextpos++; + } + else if(::isspace(*str.rbegin())) + { + std::string::const_iterator last = str.end(); + do { + --last; + } while(last != str.begin() && ::isspace(*last)); + nextpos = std::distance(str.begin(), ++last); + } + std::string result = str.substr(pos, nextpos-pos); + textkeys.insert(std::make_pair(tk->list[i].time, Misc::StringUtils::lowerCase(result))); + + pos = nextpos; + } + } + } +} + +namespace NifOsg +{ + + bool Loader::sShowMarkers = false; + + void Loader::setShowMarkers(bool show) + { + sShowMarkers = show; + } + + bool Loader::getShowMarkers() + { + return sShowMarkers; + } + + class LoaderImpl + { + public: + /// @param filename used for warning messages. + LoaderImpl(const std::string& filename) + : mFilename(filename) + { + + } + std::string mFilename; + + static void loadKf(Nif::NIFFilePtr nif, KeyframeHolder& target) + { + if(nif->numRoots() < 1) + { + nif->warn("Found no root nodes"); + return; + } + + const Nif::Record *r = nif->getRoot(0); + assert(r != NULL); + + if(r->recType != Nif::RC_NiSequenceStreamHelper) + { + nif->warn("First root was not a NiSequenceStreamHelper, but a "+ + r->recName+"."); + return; + } + const Nif::NiSequenceStreamHelper *seq = static_cast(r); + + Nif::ExtraPtr extra = seq->extra; + if(extra.empty() || extra->recType != Nif::RC_NiTextKeyExtraData) + { + nif->warn("First extra data was not a NiTextKeyExtraData, but a "+ + (extra.empty() ? std::string("nil") : extra->recName)+"."); + return; + } + + extractTextKeys(static_cast(extra.getPtr()), target.mTextKeys); + + extra = extra->extra; + Nif::ControllerPtr ctrl = seq->controller; + for(;!extra.empty() && !ctrl.empty();(extra=extra->extra),(ctrl=ctrl->next)) + { + if(extra->recType != Nif::RC_NiStringExtraData || ctrl->recType != Nif::RC_NiKeyframeController) + { + nif->warn("Unexpected extra data "+extra->recName+" with controller "+ctrl->recName); + continue; + } + + if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) + continue; + + const Nif::NiStringExtraData *strdata = static_cast(extra.getPtr()); + const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); + + if(key->data.empty()) + continue; + + osg::ref_ptr callback(new NifOsg::KeyframeController(key->data.getPtr())); + callback->setFunction(boost::shared_ptr(new NifOsg::ControllerFunction(key))); + + target.mKeyframeControllers[strdata->string] = callback; + } + } + + osg::ref_ptr load(Nif::NIFFilePtr nif, Resource::TextureManager* textureManager) + { + if (nif->numRoots() < 1) + nif->fail("Found no root nodes"); + + const Nif::Record* r = nif->getRoot(0); + + const Nif::Node* nifNode = dynamic_cast(r); + if (nifNode == NULL) + nif->fail("First root was not a node, but a " + r->recName); + + osg::ref_ptr textkeys (new TextKeyMapHolder); + + osg::ref_ptr created = handleNode(nifNode, NULL, textureManager, std::vector(), 0, 0, false, &textkeys->mTextKeys); + + if (nif->getUseSkinning()) + { + osg::ref_ptr skel = new SceneUtil::Skeleton; + skel->addChild(created); + created = skel; + } + created->getOrCreateUserDataContainer()->addUserObject(textkeys); + + return created; + } + + void applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, SceneUtil::CompositeStateSetUpdater* composite, Resource::TextureManager* textureManager, std::vector& boundTextures, int animflags) + { + const Nif::PropertyList& props = nifNode->props; + for (size_t i = 0; i setSource(boost::shared_ptr(new SceneUtil::FrameTimeSource)); + + toSetup->setFunction(boost::shared_ptr(new ControllerFunction(ctrl))); + } + + void optimize (const Nif::Node* nifNode, osg::Group* node, bool skipMeshes) + { + // For nodes with an identity transform, remove the redundant Transform node + if (node->getDataVariance() == osg::Object::STATIC + // For TriShapes, we can only collapse the node, but not completely remove it, + // if the link to animated collision shapes is supposed to stay intact. + && (nifNode->recType != Nif::RC_NiTriShape || !skipMeshes)) + { + if (node->getNumParents() && nifNode->trafo.isIdentity()) + { + osg::Group* parent = node->getParent(0); + + // can be multiple children in case of ParticleSystems, with the extra ParticleSystemUpdater node + for (unsigned int i=0; igetNumChildren(); ++i) + { + osg::Node* child = node->getChild(i); + if (i == node->getNumChildren()-1) // FIXME: some nicer way to determine where our actual Drawable resides... + { + child->addUpdateCallback(node->getUpdateCallback()); + child->setStateSet(node->getStateSet()); + child->setName(node->getName()); + // make sure to copy the UserDataContainer with the record index, so that connections to an animated collision shape don't break + child->setUserDataContainer(node->getUserDataContainer()); + } + parent->addChild(child); + } + + node->removeChildren(0, node->getNumChildren()); + parent->removeChild(node); + } + } + // For NiTriShapes *with* a valid transform, perhaps we could apply the transform to the vertices. + // Need to make sure that won't break transparency sorting. Check what the original engine is doing? + } + + osg::ref_ptr handleLodNode(const Nif::NiLODNode* niLodNode) + { + osg::ref_ptr lod (new osg::LOD); + lod->setCenterMode(osg::LOD::USER_DEFINED_CENTER); + lod->setCenter(niLodNode->lodCenter); + for (unsigned int i=0; ilodLevels.size(); ++i) + { + const Nif::NiLODNode::LODRange& range = niLodNode->lodLevels[i]; + lod->setRange(i, range.minRange, range.maxRange); + } + lod->setRangeMode(osg::LOD::DISTANCE_FROM_EYE_POINT); + return lod; + } + + osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::TextureManager* textureManager, + std::vector boundTextures, int animflags, int particleflags, bool skipMeshes, TextKeyMap* textKeys, osg::Node* rootNode=NULL) + { + osg::ref_ptr node = new osg::MatrixTransform(nifNode->trafo.toMatrix()); + + // Set a default DataVariance (used as hint by optimization routines). + switch (nifNode->recType) + { + case Nif::RC_NiTriShape: + case Nif::RC_NiAutoNormalParticles: + case Nif::RC_NiRotatingParticles: + // Leaf nodes in the NIF hierarchy, so won't be able to dynamically attach children. + // No support for keyframe controllers (just crashes in the original engine). + node->setDataVariance(osg::Object::STATIC); + break; + default: + // could have new children attached at any time, or added external keyframe controllers from .kf files + node->setDataVariance(osg::Object::DYNAMIC); + break; + } + + if (nifNode->recType == Nif::RC_NiBillboardNode) + { + node->addCullCallback(new BillboardCallback); + } + else if (!rootNode && nifNode->controller.empty() && nifNode->trafo.isIdentity()) + { + // The Root node can be created as a Group if no transformation is required. + // This takes advantage of the fact root nodes can't have additional controllers + // loaded from an external .kf file (original engine just throws "can't find node" errors if you try). + node = new osg::Group; + node->setDataVariance(osg::Object::STATIC); + } + + node->setName(nifNode->name); + + if (parentNode) + parentNode->addChild(node); + + if (!rootNode) + rootNode = node; + + // UserData used for a variety of features: + // - finding the correct emitter node for a particle system + // - establishing connections to the animated collision shapes, which are handled in a separate loader + // - finding a random child NiNode in NiBspArrayController + // - storing the previous 3x3 rotation and scale values for when a KeyframeController wants to + // change only certain elements of the 4x4 transform + node->getOrCreateUserDataContainer()->addUserObject( + new NodeUserData(nifNode->recIndex, nifNode->trafo.scale, nifNode->trafo.rotation)); + + for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->extra) + { + if(e->recType == Nif::RC_NiTextKeyExtraData && textKeys) + { + const Nif::NiTextKeyExtraData *tk = static_cast(e.getPtr()); + extractTextKeys(tk, *textKeys); + } + else if(e->recType == Nif::RC_NiStringExtraData) + { + const Nif::NiStringExtraData *sd = static_cast(e.getPtr()); + // String markers may contain important information + // affecting the entire subtree of this obj + if(sd->string == "MRK" && !Loader::getShowMarkers()) + { + // Marker objects. These meshes are only visible in the editor. + skipMeshes = true; + } + } + } + + if (nifNode->recType == Nif::RC_NiBSAnimationNode) + animflags |= nifNode->flags; + if (nifNode->recType == Nif::RC_NiBSParticleNode) + particleflags |= nifNode->flags; + + // Hide collision shapes, but don't skip the subgraph + // We still need to animate the hidden bones so the physics system can access them + if (nifNode->recType == Nif::RC_RootCollisionNode) + { + skipMeshes = true; + // Leave mask for UpdateVisitor enabled + node->setNodeMask(0x1); + } + + // We can skip creating meshes for hidden nodes if they don't have a VisController that + // might make them visible later + if (nifNode->flags & Nif::NiNode::Flag_Hidden) + { + bool hasVisController = false; + for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) + hasVisController = (ctrl->recType == Nif::RC_NiVisController); + + if (!hasVisController) + skipMeshes = true; // skip child meshes, but still create the child node hierarchy for animating collision shapes + + // now hide this node, but leave the mask for UpdateVisitor enabled so that KeyframeController works + node->setNodeMask(0x1); + } + + osg::ref_ptr composite = new SceneUtil::CompositeStateSetUpdater; + + applyNodeProperties(nifNode, node, composite, textureManager, boundTextures, animflags); + + if (nifNode->recType == Nif::RC_NiTriShape && !skipMeshes) + { + const Nif::NiTriShape* triShape = static_cast(nifNode); + if (triShape->skin.empty()) + handleTriShape(triShape, node, composite, boundTextures, animflags); + else + handleSkinnedTriShape(triShape, node, composite, boundTextures, animflags); + + if (!nifNode->controller.empty()) + handleMeshControllers(nifNode, node, composite, boundTextures, animflags); + } + + if(nifNode->recType == Nif::RC_NiAutoNormalParticles || nifNode->recType == Nif::RC_NiRotatingParticles) + handleParticleSystem(nifNode, node, composite, animflags, particleflags, rootNode); + + if (composite->getNumControllers() > 0) + node->addUpdateCallback(composite); + + + // Note: NiTriShapes are not allowed to have KeyframeControllers (the vanilla engine just crashes when there is one). + // We can take advantage of this constraint for optimizations later. + if (!nifNode->controller.empty() && node->getDataVariance() == osg::Object::DYNAMIC) + handleNodeControllers(nifNode, static_cast(node.get()), animflags); + + // Optimization pass + optimize(nifNode, node, skipMeshes); + + + if (nifNode->recType == Nif::RC_NiLODNode) + { + const Nif::NiLODNode* niLodNode = static_cast(nifNode); + osg::ref_ptr lod = handleLodNode(niLodNode); + node->addChild(lod); // unsure if LOD should be above or below this node's transform + node = lod; + } + + const Nif::NiNode *ninode = dynamic_cast(nifNode); + if(ninode) + { + const Nif::NodeList &children = ninode->children; + for(size_t i = 0;i < children.length();++i) + { + if(!children[i].empty()) + { + handleNode(children[i].getPtr(), node, textureManager, boundTextures, animflags, particleflags, skipMeshes, textKeys, rootNode); + } + } + } + + return node; + } + + void handleMeshControllers(const Nif::Node *nifNode, osg::Node* node, SceneUtil::CompositeStateSetUpdater* composite, const std::vector &boundTextures, int animflags) + { + for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) + { + if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) + continue; + if (ctrl->recType == Nif::RC_NiUVController) + { + const Nif::NiUVController *uvctrl = static_cast(ctrl.getPtr()); + std::set texUnits; + for (unsigned int i=0; i ctrl = new UVController(uvctrl->data.getPtr(), texUnits); + setupController(uvctrl, ctrl, animflags); + composite->addController(ctrl); + } + else if (ctrl->recType == Nif::RC_NiVisController) + { + handleVisController(static_cast(ctrl.getPtr()), node, animflags); + } + else if(ctrl->recType == Nif::RC_NiGeomMorpherController) + {} // handled in handleTriShape + else + std::cerr << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename << std::endl; + } + } + + void handleNodeControllers(const Nif::Node* nifNode, osg::MatrixTransform* transformNode, int animflags) + { + for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) + { + if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) + continue; + if (ctrl->recType == Nif::RC_NiKeyframeController) + { + const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); + if(!key->data.empty()) + { + osg::ref_ptr callback(new KeyframeController(key->data.getPtr())); + + setupController(key, callback, animflags); + transformNode->addUpdateCallback(callback); + } + } + else if (ctrl->recType == Nif::RC_NiVisController) + { + handleVisController(static_cast(ctrl.getPtr()), transformNode, animflags); + } + else + std::cerr << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename << std::endl; + } + } + + void handleVisController(const Nif::NiVisController* visctrl, osg::Node* node, int animflags) + { + osg::ref_ptr callback(new VisController(visctrl->data.getPtr())); + setupController(visctrl, callback, animflags); + node->addUpdateCallback(callback); + } + + void handleMaterialControllers(const Nif::Property *materialProperty, SceneUtil::CompositeStateSetUpdater* composite, int animflags) + { + for (Nif::ControllerPtr ctrl = materialProperty->controller; !ctrl.empty(); ctrl = ctrl->next) + { + if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) + continue; + if (ctrl->recType == Nif::RC_NiAlphaController) + { + const Nif::NiAlphaController* alphactrl = static_cast(ctrl.getPtr()); + osg::ref_ptr ctrl(new AlphaController(alphactrl->data.getPtr())); + setupController(alphactrl, ctrl, animflags); + composite->addController(ctrl); + } + else if (ctrl->recType == Nif::RC_NiMaterialColorController) + { + const Nif::NiMaterialColorController* matctrl = static_cast(ctrl.getPtr()); + osg::ref_ptr ctrl(new MaterialColorController(matctrl->data.getPtr())); + setupController(matctrl, ctrl, animflags); + composite->addController(ctrl); + } + else + std::cerr << "Unexpected material controller " << ctrl->recType << " in " << mFilename << std::endl; + } + } + + void handleTextureControllers(const Nif::Property *texProperty, SceneUtil::CompositeStateSetUpdater* composite, Resource::TextureManager* textureManager, osg::StateSet *stateset, int animflags) + { + for (Nif::ControllerPtr ctrl = texProperty->controller; !ctrl.empty(); ctrl = ctrl->next) + { + if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) + continue; + if (ctrl->recType == Nif::RC_NiFlipController) + { + const Nif::NiFlipController* flipctrl = static_cast(ctrl.getPtr()); + std::vector > textures; + for (unsigned int i=0; imSources.length(); ++i) + { + Nif::NiSourceTexturePtr st = flipctrl->mSources[i]; + if (st.empty()) + continue; + + // inherit wrap settings from the target slot + osg::Texture2D* inherit = dynamic_cast(stateset->getTextureAttribute(flipctrl->mTexSlot, osg::StateAttribute::TEXTURE)); + osg::Texture2D::WrapMode wrapS = osg::Texture2D::CLAMP; + osg::Texture2D::WrapMode wrapT = osg::Texture2D::CLAMP; + if (inherit) + { + wrapS = inherit->getWrap(osg::Texture2D::WRAP_S); + wrapT = inherit->getWrap(osg::Texture2D::WRAP_T); + } + + std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, textureManager->getVFS()); + osg::ref_ptr texture = textureManager->getTexture2D(filename, wrapS, wrapT); + textures.push_back(texture); + } + osg::ref_ptr callback(new FlipController(flipctrl, textures)); + setupController(ctrl.getPtr(), callback, animflags); + composite->addController(callback); + } + else + std::cerr << "Unexpected texture controller " << ctrl->recName << " in " << mFilename << std::endl; + } + } + + void handleParticlePrograms(Nif::ExtraPtr affectors, Nif::ExtraPtr colliders, osg::Group *attachTo, osgParticle::ParticleSystem* partsys, osgParticle::ParticleProcessor::ReferenceFrame rf) + { + osgParticle::ModularProgram* program = new osgParticle::ModularProgram; + attachTo->addChild(program); + program->setParticleSystem(partsys); + program->setReferenceFrame(rf); + for (; !affectors.empty(); affectors = affectors->extra) + { + if (affectors->recType == Nif::RC_NiParticleGrowFade) + { + const Nif::NiParticleGrowFade *gf = static_cast(affectors.getPtr()); + program->addOperator(new GrowFadeAffector(gf->growTime, gf->fadeTime)); + } + else if (affectors->recType == Nif::RC_NiGravity) + { + const Nif::NiGravity* gr = static_cast(affectors.getPtr()); + program->addOperator(new GravityAffector(gr)); + } + else if (affectors->recType == Nif::RC_NiParticleColorModifier) + { + const Nif::NiParticleColorModifier *cl = static_cast(affectors.getPtr()); + const Nif::NiColorData *clrdata = cl->data.getPtr(); + program->addOperator(new ParticleColorAffector(clrdata)); + } + else if (affectors->recType == Nif::RC_NiParticleRotation) + { + // unused + } + else + std::cerr << "Unhandled particle modifier " << affectors->recName << " in " << mFilename << std::endl; + } + for (; !colliders.empty(); colliders = colliders->extra) + { + if (colliders->recType == Nif::RC_NiPlanarCollider) + { + const Nif::NiPlanarCollider* planarcollider = static_cast(colliders.getPtr()); + program->addOperator(new PlanarCollider(planarcollider)); + } + } + } + + // Load the initial state of the particle system, i.e. the initial particles and their positions, velocity and colors. + void handleParticleInitialState(const Nif::Node* nifNode, osgParticle::ParticleSystem* partsys, const Nif::NiParticleSystemController* partctrl) + { + const Nif::NiAutoNormalParticlesData *particledata = NULL; + if(nifNode->recType == Nif::RC_NiAutoNormalParticles) + particledata = static_cast(nifNode)->data.getPtr(); + else if(nifNode->recType == Nif::RC_NiRotatingParticles) + particledata = static_cast(nifNode)->data.getPtr(); + else + return; + + int i=0; + for (std::vector::const_iterator it = partctrl->particles.begin(); + iactiveCount && it != partctrl->particles.end(); ++it, ++i) + { + const Nif::NiParticleSystemController::Particle& particle = *it; + + ParticleAgeSetter particletemplate(std::max(0.f, particle.lifetime)); + + osgParticle::Particle* created = partsys->createParticle(&particletemplate); + created->setLifeTime(std::max(0.f, particle.lifespan)); + + // Note this position and velocity is not correct for a particle system with absolute reference frame, + // which can not be done in this loader since we are not attached to the scene yet. Will be fixed up post-load in the SceneManager. + created->setVelocity(particle.velocity); + created->setPosition(particledata->vertices->at(particle.vertex)); + + osg::Vec4f partcolor (1.f,1.f,1.f,1.f); + if (particle.vertex < int(particledata->colors->size())) + partcolor = particledata->colors->at(particle.vertex); + + float size = particledata->sizes.at(particle.vertex) * partctrl->size; + + created->setSizeRange(osgParticle::rangef(size, size)); + } + + osg::BoundingBox box; + box.expandBy(osg::BoundingSphere(osg::Vec3(0,0,0), particledata->radius)); + partsys->setInitialBound(box); + } + + osg::ref_ptr handleParticleEmitter(const Nif::NiParticleSystemController* partctrl) + { + std::vector targets; + if (partctrl->recType == Nif::RC_NiBSPArrayController) + { + getAllNiNodes(partctrl->emitter.getPtr(), targets); + } + + osg::ref_ptr emitter = new Emitter(targets); + + osgParticle::ConstantRateCounter* counter = new osgParticle::ConstantRateCounter; + if (partctrl->emitFlags & Nif::NiParticleSystemController::NoAutoAdjust) + counter->setNumberOfParticlesPerSecondToCreate(partctrl->emitRate); + else + counter->setNumberOfParticlesPerSecondToCreate(partctrl->numParticles / (partctrl->lifetime + partctrl->lifetimeRandom/2)); + + emitter->setCounter(counter); + + ParticleShooter* shooter = new ParticleShooter(partctrl->velocity - partctrl->velocityRandom*0.5f, + partctrl->velocity + partctrl->velocityRandom*0.5f, + partctrl->horizontalDir, partctrl->horizontalAngle, + partctrl->verticalDir, partctrl->verticalAngle, + partctrl->lifetime, partctrl->lifetimeRandom); + emitter->setShooter(shooter); + + osgParticle::BoxPlacer* placer = new osgParticle::BoxPlacer; + placer->setXRange(-partctrl->offsetRandom.x(), partctrl->offsetRandom.x()); + placer->setYRange(-partctrl->offsetRandom.y(), partctrl->offsetRandom.y()); + placer->setZRange(-partctrl->offsetRandom.z(), partctrl->offsetRandom.z()); + + emitter->setPlacer(placer); + return emitter; + } + + void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags, int particleflags, osg::Node* rootNode) + { + osg::ref_ptr partsys (new ParticleSystem); + partsys->setSortMode(osgParticle::ParticleSystem::SORT_BACK_TO_FRONT); + + const Nif::NiParticleSystemController* partctrl = NULL; + for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) + { + if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) + continue; + if(ctrl->recType == Nif::RC_NiParticleSystemController || ctrl->recType == Nif::RC_NiBSPArrayController) + partctrl = static_cast(ctrl.getPtr()); + else + std::cerr << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename << std::endl; + } + if (!partctrl) + { + std::cerr << "No particle controller found in " << mFilename << std::endl; + return; + } + + osgParticle::ParticleProcessor::ReferenceFrame rf = (particleflags & Nif::NiNode::ParticleFlag_LocalSpace) + ? osgParticle::ParticleProcessor::RELATIVE_RF + : osgParticle::ParticleProcessor::ABSOLUTE_RF; + + // HACK: ParticleSystem has no setReferenceFrame method + if (rf == osgParticle::ParticleProcessor::ABSOLUTE_RF) + { + partsys->getOrCreateUserDataContainer()->addDescription("worldspace"); + } + + partsys->setParticleScaleReferenceFrame(osgParticle::ParticleSystem::LOCAL_COORDINATES); + + handleParticleInitialState(nifNode, partsys, partctrl); + + partsys->setQuota(partctrl->numParticles); + + partsys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(partctrl->size, partctrl->size)); + partsys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(osg::Vec4f(1.f,1.f,1.f,1.f), osg::Vec4f(1.f,1.f,1.f,1.f))); + partsys->getDefaultParticleTemplate().setAlphaRange(osgParticle::rangef(1.f, 1.f)); + + partsys->setFreezeOnCull(true); + + if (!partctrl->emitter.empty()) + { + osg::ref_ptr emitter = handleParticleEmitter(partctrl); + emitter->setParticleSystem(partsys); + emitter->setReferenceFrame(osgParticle::ParticleProcessor::RELATIVE_RF); + + // Note: we assume that the Emitter node is placed *before* the Particle node in the scene graph. + // This seems to be true for all NIF files in the game that I've checked, suggesting that NIFs work similar to OSG with regards to update order. + // If something ever violates this assumption, the worst that could happen is the culling being one frame late, which wouldn't be a disaster. + + FindGroupByRecIndex find (partctrl->emitter->recIndex); + rootNode->accept(find); + if (!find.mFound) + { + std::cerr << "can't find emitter node, wrong node order? in " << mFilename << std::endl; + return; + } + osg::Group* emitterNode = find.mFound; + + // Emitter attached to the emitter node. Note one side effect of the emitter using the CullVisitor is that hiding its node + // actually causes the emitter to stop firing. Convenient, because MW behaves this way too! + emitterNode->addChild(emitter); + + osg::ref_ptr callback(new ParticleSystemController(partctrl)); + setupController(partctrl, callback, animflags); + emitter->setUpdateCallback(callback); + } + + // affectors must be attached *after* the emitter in the scene graph for correct update order + // attach to same node as the ParticleSystem, we need osgParticle Operators to get the correct + // localToWorldMatrix for transforming to particle space + handleParticlePrograms(partctrl->affectors, partctrl->colliders, parentNode, partsys.get(), rf); + + std::vector drawableProps; + collectDrawableProperties(nifNode, drawableProps); + applyDrawableProperties(parentNode, drawableProps, composite, true, animflags); + + // Particles don't have normals, so can't be diffuse lit. + osg::Material* mat = static_cast(parentNode->getStateSet()->getAttribute(osg::StateAttribute::MATERIAL)); + if (mat) + { + // NB ignoring diffuse.a() + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,1)); + mat->setColorMode(osg::Material::AMBIENT); + } + + // particle system updater (after the emitters and affectors in the scene graph) + // I think for correct culling needs to be *before* the ParticleSystem, though osg examples do it the other way + osg::ref_ptr updater = new osgParticle::ParticleSystemUpdater; + updater->addParticleSystem(partsys); + parentNode->addChild(updater); + +#if OSG_VERSION_LESS_THAN(3,3,3) + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(partsys); + osg::Node* toAttach = geode.get(); +#else + osg::Node* toAttach = partsys.get(); +#endif + + if (rf == osgParticle::ParticleProcessor::RELATIVE_RF) + parentNode->addChild(toAttach); + else + { + osg::MatrixTransform* trans = new osg::MatrixTransform; + trans->setUpdateCallback(new InverseWorldMatrix); + trans->addChild(toAttach); + parentNode->addChild(trans); + } + } + + void triShapeToGeometry(const Nif::NiTriShape *triShape, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + { + const Nif::NiTriShapeData* data = triShape->data.getPtr(); + + geometry->setVertexArray(data->vertices); + + if (!data->normals->empty()) + geometry->setNormalArray(data->normals); + + int textureStage = 0; + for (std::vector::const_iterator it = boundTextures.begin(); it != boundTextures.end(); ++it,++textureStage) + { + int uvSet = *it; + if (uvSet >= (int)data->uvlist.size()) + { + std::cerr << "Warning: out of bounds UV set " << uvSet << " on TriShape \"" << triShape->name << "\" in " << mFilename << std::endl; + if (data->uvlist.size()) + geometry->setTexCoordArray(textureStage, data->uvlist[0]); + continue; + } + + geometry->setTexCoordArray(textureStage, data->uvlist[uvSet]); + } + + if (!data->colors->empty()) + geometry->setColorArray(data->colors); + + if (!data->triangles->empty()) + geometry->addPrimitiveSet(data->triangles); + + // osg::Material properties are handled here for two reasons: + // - if there are no vertex colors, we need to disable colorMode. + // - there are 3 "overlapping" nif properties that all affect the osg::Material, handling them + // above the actual renderable would be tedious. + std::vector drawableProps; + collectDrawableProperties(triShape, drawableProps); + applyDrawableProperties(parentNode, drawableProps, composite, !data->colors->empty(), animflags); + } + + void handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + { + osg::ref_ptr geometry; + for (Nif::ControllerPtr ctrl = triShape->controller; !ctrl.empty(); ctrl = ctrl->next) + { + if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) + continue; + if(ctrl->recType == Nif::RC_NiGeomMorpherController) + { + geometry = handleMorphGeometry(static_cast(ctrl.getPtr())); + + osg::ref_ptr morphctrl = new GeomMorpherController( + static_cast(ctrl.getPtr())->data.getPtr()); + setupController(ctrl.getPtr(), morphctrl, animflags); + geometry->setUpdateCallback(morphctrl); + break; + } + } + + if (!geometry.get()) + geometry = new osg::Geometry; + + triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags); + +#if OSG_VERSION_LESS_THAN(3,3,3) + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(geometry); +#endif + + if (geometry->getDataVariance() == osg::Object::DYNAMIC) + { + // Add a copy, we will alternate between the two copies every other frame using the FrameSwitch + // This is so we can set the DataVariance as STATIC, giving a huge performance boost + geometry->setDataVariance(osg::Object::STATIC); + osg::ref_ptr frameswitch = new FrameSwitch; + +#if OSG_VERSION_LESS_THAN(3,3,3) + osg::ref_ptr geode2 = static_cast(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES)); + frameswitch->addChild(geode); + frameswitch->addChild(geode2); +#else + osg::ref_ptr geom2 = static_cast(osg::clone(geometry.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES)); + frameswitch->addChild(geometry); + frameswitch->addChild(geom2); +#endif + + parentNode->addChild(frameswitch); + } + else +#if OSG_VERSION_LESS_THAN(3,3,3) + parentNode->addChild(geode); +#else + parentNode->addChild(geometry); +#endif + } + + osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher) + { + osg::ref_ptr morphGeom = new osgAnimation::MorphGeometry; + morphGeom->setMethod(osgAnimation::MorphGeometry::RELATIVE); + // No normals available in the MorphData + morphGeom->setMorphNormals(false); + + morphGeom->setUpdateCallback(NULL); + morphGeom->setCullCallback(new UpdateMorphGeometry); + + const std::vector& morphs = morpher->data.getPtr()->mMorphs; + if (!morphs.size()) + return morphGeom; + // Note we are not interested in morph 0, which just contains the original vertices + for (unsigned int i = 1; i < morphs.size(); ++i) + { + osg::ref_ptr morphTarget = new osg::Geometry; + morphTarget->setVertexArray(morphs[i].mVertices); + morphGeom->addMorphTarget(morphTarget, 0.f); + } + + // build the bounding box containing all possible morph combinations + + std::vector vertBounds(morphs[0].mVertices->size()); + + // Since we don't know what combinations of morphs are being applied we need to keep track of a bounding box for each vertex. + // The minimum/maximum of the box is the minimum/maximum offset the vertex can have from its starting position. + + // Start with zero offsets which will happen when no morphs are applied. + for (unsigned int i=0; isize() && vertBounds.size(); ++j) + { + osg::BoundingBox& bounds = vertBounds[j]; + bounds.expandBy(bounds._max + (*morphs[i].mVertices)[j]); + bounds.expandBy(bounds._min + (*morphs[i].mVertices)[j]); + } + } + + osg::BoundingBox box; + for (unsigned int i=0; isetComputeBoundingBoxCallback(new StaticBoundingBoxCallback(box)); + + return morphGeom; + } + + void handleSkinnedTriShape(const Nif::NiTriShape *triShape, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, + const std::vector& boundTextures, int animflags) + { + osg::ref_ptr geometry (new osg::Geometry); + triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags); + + osg::ref_ptr rig(new SceneUtil::RigGeometry); + rig->setSourceGeometry(geometry); + + const Nif::NiSkinInstance *skin = triShape->skin.getPtr(); + + // Assign bone weights + osg::ref_ptr map (new SceneUtil::RigGeometry::InfluenceMap); + + const Nif::NiSkinData *data = skin->data.getPtr(); + const Nif::NodeList &bones = skin->bones; + for(size_t i = 0;i < bones.length();i++) + { + std::string boneName = bones[i].getPtr()->name; + + SceneUtil::RigGeometry::BoneInfluence influence; + const std::vector &weights = data->bones[i].weights; + //influence.mWeights.reserve(weights.size()); + for(size_t j = 0;j < weights.size();j++) + { + std::pair indexWeight = std::make_pair(weights[j].vertex, weights[j].weight); + influence.mWeights.insert(indexWeight); + } + influence.mInvBindMatrix = data->bones[i].trafo.toMatrix(); + influence.mBoundSphere = osg::BoundingSpheref(data->bones[i].boundSphereCenter, data->bones[i].boundSphereRadius); + + map->mMap.insert(std::make_pair(boneName, influence)); + } + rig->setInfluenceMap(map); + + // Add a copy, we will alternate between the two copies every other frame using the FrameSwitch + // This is so we can set the DataVariance as STATIC, giving a huge performance boost + rig->setDataVariance(osg::Object::STATIC); + + osg::ref_ptr frameswitch = new FrameSwitch; + +#if OSG_VERSION_LESS_THAN(3,3,3) + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(rig); + + osg::Geode* geode2 = static_cast(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES| + osg::CopyOp::DEEP_COPY_DRAWABLES)); + + frameswitch->addChild(geode); + frameswitch->addChild(geode2); +#else + SceneUtil::RigGeometry* rig2 = static_cast(osg::clone(rig.get(), osg::CopyOp::DEEP_COPY_NODES| + osg::CopyOp::DEEP_COPY_DRAWABLES)); + frameswitch->addChild(rig); + frameswitch->addChild(rig2); +#endif + + parentNode->addChild(frameswitch); + } + + osg::BlendFunc::BlendFuncMode getBlendMode(int mode) + { + switch(mode) + { + case 0: return osg::BlendFunc::ONE; + case 1: return osg::BlendFunc::ZERO; + case 2: return osg::BlendFunc::SRC_COLOR; + case 3: return osg::BlendFunc::ONE_MINUS_SRC_COLOR; + case 4: return osg::BlendFunc::DST_COLOR; + case 5: return osg::BlendFunc::ONE_MINUS_DST_COLOR; + case 6: return osg::BlendFunc::SRC_ALPHA; + case 7: return osg::BlendFunc::ONE_MINUS_SRC_ALPHA; + case 8: return osg::BlendFunc::DST_ALPHA; + case 9: return osg::BlendFunc::ONE_MINUS_DST_ALPHA; + case 10: return osg::BlendFunc::SRC_ALPHA_SATURATE; + default: + std::cerr<< "Unexpected blend mode: "<< mode << " in " << mFilename << std::endl; + return osg::BlendFunc::SRC_ALPHA; + } + } + + osg::AlphaFunc::ComparisonFunction getTestMode(int mode) + { + switch (mode) + { + case 0: return osg::AlphaFunc::ALWAYS; + case 1: return osg::AlphaFunc::LESS; + case 2: return osg::AlphaFunc::EQUAL; + case 3: return osg::AlphaFunc::LEQUAL; + case 4: return osg::AlphaFunc::GREATER; + case 5: return osg::AlphaFunc::NOTEQUAL; + case 6: return osg::AlphaFunc::GEQUAL; + case 7: return osg::AlphaFunc::NEVER; + default: + std::cerr << "Unexpected blend mode: " << mode << " in " << mFilename << std::endl; + return osg::AlphaFunc::LEQUAL; + } + } + + osg::Stencil::Function getStencilFunction(int func) + { + switch (func) + { + case 0: return osg::Stencil::NEVER; + case 1: return osg::Stencil::LESS; + case 2: return osg::Stencil::EQUAL; + case 3: return osg::Stencil::LEQUAL; + case 4: return osg::Stencil::GREATER; + case 5: return osg::Stencil::NOTEQUAL; + case 6: return osg::Stencil::GEQUAL; + case 7: return osg::Stencil::NEVER; // NifSkope says this is GL_ALWAYS, but in MW it's GL_NEVER + default: + std::cerr << "Unexpected stencil function: " << func << " in " << mFilename << std::endl; + return osg::Stencil::NEVER; + } + } + + osg::Stencil::Operation getStencilOperation(int op) + { + switch (op) + { + case 0: return osg::Stencil::KEEP; + case 1: return osg::Stencil::ZERO; + case 2: return osg::Stencil::REPLACE; + case 3: return osg::Stencil::INCR; + case 4: return osg::Stencil::DECR; + case 5: return osg::Stencil::INVERT; + default: + std::cerr << "Unexpected stencil operation: " << op << " in " << mFilename << std::endl; + return osg::Stencil::KEEP; + } + } + + void handleProperty(const Nif::Property *property, + osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::TextureManager* textureManager, std::vector& boundTextures, int animflags) + { + switch (property->recType) + { + case Nif::RC_NiStencilProperty: + { + const Nif::NiStencilProperty* stencilprop = static_cast(property); + osg::FrontFace* frontFace = new osg::FrontFace; + switch (stencilprop->data.drawMode) + { + case 2: + frontFace->setMode(osg::FrontFace::CLOCKWISE); + break; + case 0: + case 1: + default: + frontFace->setMode(osg::FrontFace::COUNTER_CLOCKWISE); + break; + } + + osg::StateSet* stateset = node->getOrCreateStateSet(); + stateset->setAttribute(frontFace, osg::StateAttribute::ON); + stateset->setMode(GL_CULL_FACE, stencilprop->data.drawMode == 3 ? osg::StateAttribute::OFF + : osg::StateAttribute::ON); + + if (stencilprop->data.enabled != 0) + { + osg::Stencil* stencil = new osg::Stencil; + stencil->setFunction(getStencilFunction(stencilprop->data.compareFunc), stencilprop->data.stencilRef, stencilprop->data.stencilMask); + stencil->setStencilFailOperation(getStencilOperation(stencilprop->data.failAction)); + stencil->setStencilPassAndDepthFailOperation(getStencilOperation(stencilprop->data.zFailAction)); + stencil->setStencilPassAndDepthPassOperation(getStencilOperation(stencilprop->data.zPassAction)); + + stateset->setAttributeAndModes(stencil, osg::StateAttribute::ON); + } + break; + } + case Nif::RC_NiWireframeProperty: + { + const Nif::NiWireframeProperty* wireprop = static_cast(property); + osg::PolygonMode* mode = new osg::PolygonMode; + mode->setMode(osg::PolygonMode::FRONT_AND_BACK, wireprop->flags == 0 ? osg::PolygonMode::FILL + : osg::PolygonMode::LINE); + node->getOrCreateStateSet()->setAttributeAndModes(mode, osg::StateAttribute::ON); + break; + } + case Nif::RC_NiZBufferProperty: + { + const Nif::NiZBufferProperty* zprop = static_cast(property); + // VER_MW doesn't support a DepthFunction according to NifSkope + osg::Depth* depth = new osg::Depth; + depth->setWriteMask((zprop->flags>>1)&1); + node->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + break; + } + // OSG groups the material properties that NIFs have separate, so we have to parse them all again when one changed + case Nif::RC_NiMaterialProperty: + case Nif::RC_NiVertexColorProperty: + case Nif::RC_NiSpecularProperty: + { + // Handled on drawable level so we know whether vertex colors are available + break; + } + case Nif::RC_NiAlphaProperty: + { + // Handled on drawable level to prevent RenderBin nesting issues + break; + } + case Nif::RC_NiTexturingProperty: + { + const Nif::NiTexturingProperty* texprop = static_cast(property); + osg::StateSet* stateset = node->getOrCreateStateSet(); + + if (boundTextures.size()) + { + // overriding a parent NiTexturingProperty, so remove what was previously bound + for (unsigned int i=0; isetTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); + boundTextures.clear(); + } + + for (int i=0; itextures[i].inUse) + { + switch(i) + { + //These are handled later on + case Nif::NiTexturingProperty::BaseTexture: + case Nif::NiTexturingProperty::GlowTexture: + case Nif::NiTexturingProperty::DarkTexture: + case Nif::NiTexturingProperty::DetailTexture: + break; + case Nif::NiTexturingProperty::GlossTexture: + { + std::cerr << "NiTexturingProperty::GlossTexture in " << mFilename << " not currently used." << std::endl; + continue; + } + case Nif::NiTexturingProperty::BumpTexture: + { + std::cerr << "NiTexturingProperty::BumpTexture in " << mFilename << " not currently used." << std::endl; + continue; + } + case Nif::NiTexturingProperty::DecalTexture: + { + std::cerr << "NiTexturingProperty::DecalTexture in " << mFilename << " not currently used." << std::endl; + continue; + } + default: + { + std::cerr << "Warning: unhandled texture stage " << i << " in " << mFilename << std::endl; + continue; + } + } + + const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i]; + if(tex.texture.empty()) + { + std::cerr << "Warning: texture layer " << i << " is in use but empty in " << mFilename << std::endl; + continue; + } + const Nif::NiSourceTexture *st = tex.texture.getPtr(); + if (!st->external) + { + std::cerr << "Warning: unhandled internal texture in " << mFilename << std::endl; + continue; + } + + std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, textureManager->getVFS()); + + unsigned int clamp = static_cast(tex.clamp); + int wrapT = (clamp) & 0x1; + int wrapS = (clamp >> 1) & 0x1; + + osg::ref_ptr texture2d = textureManager->getTexture2D(filename, + wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP, + wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); + + int texUnit = boundTextures.size(); + + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + + if (i == Nif::NiTexturingProperty::GlowTexture) + { + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; + texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); + texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); + texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); + + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + } + else if (i == Nif::NiTexturingProperty::DarkTexture) + { + osg::TexEnv* texEnv = new osg::TexEnv; + texEnv->setMode(osg::TexEnv::MODULATE); + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + } + else if (i == Nif::NiTexturingProperty::DetailTexture) + { + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; + texEnv->setScale_RGB(2.f); + texEnv->setCombine_Alpha(GL_MODULATE); + texEnv->setOperand0_Alpha(GL_SRC_ALPHA); + texEnv->setOperand1_Alpha(GL_SRC_ALPHA); + texEnv->setSource0_Alpha(GL_PREVIOUS_ARB); + texEnv->setSource1_Alpha(GL_TEXTURE); + texEnv->setCombine_RGB(GL_MODULATE); + texEnv->setOperand0_RGB(GL_SRC_COLOR); + texEnv->setOperand1_RGB(GL_SRC_COLOR); + texEnv->setSource0_RGB(GL_PREVIOUS_ARB); + texEnv->setSource1_RGB(GL_TEXTURE); + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + } + + boundTextures.push_back(tex.uvSet); + } + handleTextureControllers(texprop, composite, textureManager, stateset, animflags); + } + break; + } + // unused by mw + case Nif::RC_NiShadeProperty: + case Nif::RC_NiDitherProperty: + case Nif::RC_NiFogProperty: + { + break; + } + default: + std::cerr << "Unhandled " << property->recName << " in " << mFilename << std::endl; + break; + } + } + + void applyDrawableProperties(osg::Node* node, const std::vector& properties, SceneUtil::CompositeStateSetUpdater* composite, + bool hasVertexColors, int animflags) + { + osg::StateSet* stateset = node->getOrCreateStateSet(); + + int specFlags = 0; // Specular is disabled by default, even if there's a specular color in the NiMaterialProperty + osg::ref_ptr mat (new osg::Material); + mat->setColorMode(hasVertexColors ? osg::Material::AMBIENT_AND_DIFFUSE : osg::Material::OFF); + + // NIF material defaults don't match OpenGL defaults + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + + for (std::vector::const_reverse_iterator it = properties.rbegin(); it != properties.rend(); ++it) + { + const Nif::Property* property = *it; + switch (property->recType) + { + case Nif::RC_NiSpecularProperty: + { + specFlags = property->flags; + break; + } + case Nif::RC_NiMaterialProperty: + { + const Nif::NiMaterialProperty* matprop = static_cast(property); + + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.diffuse, matprop->data.alpha)); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.ambient, 1.f)); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.emissive, 1.f)); + + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.specular, 1.f)); + mat->setShininess(osg::Material::FRONT_AND_BACK, matprop->data.glossiness); + + if (!matprop->controller.empty()) + handleMaterialControllers(matprop, composite, animflags); + + break; + } + case Nif::RC_NiVertexColorProperty: + { + const Nif::NiVertexColorProperty* vertprop = static_cast(property); + if (!hasVertexColors) + break; + switch (vertprop->flags) + { + case 0: + mat->setColorMode(osg::Material::OFF); + break; + case 1: + mat->setColorMode(osg::Material::EMISSION); + break; + case 2: + mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + break; + } + break; + } + case Nif::RC_NiAlphaProperty: + { + const Nif::NiAlphaProperty* alphaprop = static_cast(property); + if (alphaprop->flags&1) + { + stateset->setAttributeAndModes(new osg::BlendFunc(getBlendMode((alphaprop->flags>>1)&0xf), + getBlendMode((alphaprop->flags>>5)&0xf)), + osg::StateAttribute::ON); + + bool noSort = (alphaprop->flags>>13)&1; + if (!noSort) + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + else + stateset->setRenderBinToInherit(); + } + else + { + stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); + stateset->removeMode(GL_BLEND); + stateset->setRenderBinToInherit(); + } + + if((alphaprop->flags>>9)&1) + { + stateset->setAttributeAndModes(new osg::AlphaFunc(getTestMode((alphaprop->flags>>10)&0x7), + alphaprop->data.threshold/255.f), osg::StateAttribute::ON); + } + else + { + stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); + stateset->removeMode(GL_ALPHA_TEST); + } + break; + } + } + } + + if (specFlags == 0) + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f,0.f,0.f,0.f)); + + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + } + + }; + + osg::ref_ptr Loader::load(Nif::NIFFilePtr file, Resource::TextureManager* textureManager) + { + LoaderImpl impl(file->getFilename()); + return impl.load(file, textureManager); + } + + void Loader::loadKf(Nif::NIFFilePtr kf, KeyframeHolder& target) + { + LoaderImpl impl(kf->getFilename()); + impl.loadKf(kf, target); + } + +} diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp new file mode 100644 index 0000000000..e15df53028 --- /dev/null +++ b/components/nifosg/nifloader.hpp @@ -0,0 +1,84 @@ +#ifndef OPENMW_COMPONENTS_NIFOSG_LOADER +#define OPENMW_COMPONENTS_NIFOSG_LOADER + +#include + +#include +#include + +#include "controller.hpp" + +namespace osg +{ + class Node; +} + +namespace Resource +{ + class TextureManager; +} + +namespace NifOsg +{ + typedef std::multimap TextKeyMap; + + struct TextKeyMapHolder : public osg::Object + { + public: + TextKeyMapHolder() {} + TextKeyMapHolder(const TextKeyMapHolder& copy, const osg::CopyOp& copyop) + : osg::Object(copy, copyop) + , mTextKeys(copy.mTextKeys) + {} + + TextKeyMap mTextKeys; + + META_Object(NifOsg, TextKeyMapHolder) + + }; + + class KeyframeHolder : public osg::Object + { + public: + KeyframeHolder() {} + KeyframeHolder(const KeyframeHolder& copy, const osg::CopyOp& copyop) + : mTextKeys(copy.mTextKeys) + , mKeyframeControllers(copy.mKeyframeControllers) + { + } + + TextKeyMap mTextKeys; + + META_Object(OpenMW, KeyframeHolder) + + typedef std::map > KeyframeControllerMap; + KeyframeControllerMap mKeyframeControllers; + }; + + /// The main class responsible for loading NIF files into an OSG-Scenegraph. + /// @par This scene graph is self-contained and can be cloned using osg::clone if desired. Particle emitters + /// and programs hold a pointer to their ParticleSystem, which would need to be manually updated when cloning. + class Loader + { + public: + /// Create a scene graph for the given NIF. Auto-detects when skinning is used and wraps the graph in a Skeleton if so. + static osg::ref_ptr load(Nif::NIFFilePtr file, Resource::TextureManager* textureManager); + + /// Load keyframe controllers from the given kf file. + static void loadKf(Nif::NIFFilePtr kf, KeyframeHolder& target); + + /// Set whether or not nodes marked as "MRK" should be shown. + /// These should be hidden ingame, but visible in the editor. + /// Default: false. + static void setShowMarkers(bool show); + + static bool getShowMarkers(); + + private: + + static bool sShowMarkers; + }; + +} + +#endif diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp new file mode 100644 index 0000000000..5003397222 --- /dev/null +++ b/components/nifosg/particle.cpp @@ -0,0 +1,377 @@ +#include "particle.hpp" + +#include + +#include + +#include + +#include "userdata.hpp" + +namespace NifOsg +{ + +ParticleSystem::ParticleSystem() + : osgParticle::ParticleSystem() + , mQuota(std::numeric_limits::max()) +{ +} + +ParticleSystem::ParticleSystem(const ParticleSystem ©, const osg::CopyOp ©op) + : osgParticle::ParticleSystem(copy, copyop) + , mQuota(copy.mQuota) +{ + // For some reason the osgParticle constructor doesn't copy the particles + for (int i=0;igetVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) + { + osg::NodePath path = nv->getNodePath(); + path.pop_back(); + + osg::MatrixTransform* trans = static_cast(node); + + osg::Matrix mat = osg::computeLocalToWorld( path ); + mat.orthoNormalize(mat); // don't undo the scale + mat.invert(mat); + trans->setMatrix(mat); + } + traverse(node,nv); +} + +ParticleShooter::ParticleShooter(float minSpeed, float maxSpeed, float horizontalDir, float horizontalAngle, float verticalDir, float verticalAngle, float lifetime, float lifetimeRandom) + : mMinSpeed(minSpeed), mMaxSpeed(maxSpeed), mHorizontalDir(horizontalDir) + , mHorizontalAngle(horizontalAngle), mVerticalDir(verticalDir), mVerticalAngle(verticalAngle) + , mLifetime(lifetime), mLifetimeRandom(lifetimeRandom) +{ +} + +ParticleShooter::ParticleShooter() + : mMinSpeed(0.f), mMaxSpeed(0.f), mHorizontalDir(0.f) + , mHorizontalAngle(0.f), mVerticalDir(0.f), mVerticalAngle(0.f) + , mLifetime(0.f), mLifetimeRandom(0.f) +{ +} + +ParticleShooter::ParticleShooter(const ParticleShooter ©, const osg::CopyOp ©op) + : osgParticle::Shooter(copy, copyop) +{ + *this = copy; +} + +void ParticleShooter::shoot(osgParticle::Particle *particle) const +{ + float hdir = mHorizontalDir + mHorizontalAngle * (2.f * (std::rand() / static_cast(RAND_MAX)) - 1.f); + float vdir = mVerticalDir + mVerticalAngle * (2.f * (std::rand() / static_cast(RAND_MAX)) - 1.f); + + osg::Vec3f dir = (osg::Quat(vdir, osg::Vec3f(0,1,0)) * osg::Quat(hdir, osg::Vec3f(0,0,1))) + * osg::Vec3f(0,0,1); + + float vel = mMinSpeed + (mMaxSpeed - mMinSpeed) * std::rand() / static_cast(RAND_MAX); + particle->setVelocity(dir * vel); + + // Not supposed to set this here, but there doesn't seem to be a better way of doing it + particle->setLifeTime(mLifetime + mLifetimeRandom * std::rand() / static_cast(RAND_MAX)); +} + +GrowFadeAffector::GrowFadeAffector(float growTime, float fadeTime) + : mGrowTime(growTime) + , mFadeTime(fadeTime) + , mCachedDefaultSize(0.f) +{ +} + +GrowFadeAffector::GrowFadeAffector() + : mGrowTime(0.f) + , mFadeTime(0.f) + , mCachedDefaultSize(0.f) +{ + +} + +GrowFadeAffector::GrowFadeAffector(const GrowFadeAffector& copy, const osg::CopyOp& copyop) + : osgParticle::Operator(copy, copyop) +{ + *this = copy; +} + +void GrowFadeAffector::beginOperate(osgParticle::Program *program) +{ + mCachedDefaultSize = program->getParticleSystem()->getDefaultParticleTemplate().getSizeRange().minimum; +} + +void GrowFadeAffector::operate(osgParticle::Particle* particle, double /* dt */) +{ + float size = mCachedDefaultSize; + if (particle->getAge() < mGrowTime && mGrowTime != 0.f) + size *= particle->getAge() / mGrowTime; + if (particle->getLifeTime() - particle->getAge() < mFadeTime && mFadeTime != 0.f) + size *= (particle->getLifeTime() - particle->getAge()) / mFadeTime; + particle->setSizeRange(osgParticle::rangef(size, size)); +} + +ParticleColorAffector::ParticleColorAffector(const Nif::NiColorData *clrdata) + : mData(clrdata->mKeyMap, osg::Vec4f(1,1,1,1)) +{ +} + +ParticleColorAffector::ParticleColorAffector() +{ + +} + +ParticleColorAffector::ParticleColorAffector(const ParticleColorAffector ©, const osg::CopyOp ©op) + : osgParticle::Operator(copy, copyop) +{ + *this = copy; +} + +void ParticleColorAffector::operate(osgParticle::Particle* particle, double /* dt */) +{ + float time = static_cast(particle->getAge()/particle->getLifeTime()); + osg::Vec4f color = mData.interpKey(time); + + particle->setColorRange(osgParticle::rangev4(color, color)); +} + +GravityAffector::GravityAffector(const Nif::NiGravity *gravity) + : mForce(gravity->mForce) + , mType(static_cast(gravity->mType)) + , mPosition(gravity->mPosition) + , mDirection(gravity->mDirection) + , mDecay(gravity->mDecay) +{ +} + +GravityAffector::GravityAffector() + : mForce(0), mType(Type_Wind), mDecay(0.f) +{ + +} + +GravityAffector::GravityAffector(const GravityAffector ©, const osg::CopyOp ©op) + : osgParticle::Operator(copy, copyop) +{ + *this = copy; +} + +void GravityAffector::beginOperate(osgParticle::Program* program) +{ + bool absolute = (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF); + + if (mType == Type_Point || mDecay != 0.f) // we don't need the position for Wind gravity, except if decay is being applied + mCachedWorldPosition = absolute ? program->transformLocalToWorld(mPosition) : mPosition; + + mCachedWorldDirection = absolute ? program->rotateLocalToWorld(mDirection) : mDirection; + mCachedWorldDirection.normalize(); +} + +void GravityAffector::operate(osgParticle::Particle *particle, double dt) +{ + const float magic = 1.6f; + switch (mType) + { + case Type_Wind: + { + float decayFactor = 1.f; + if (mDecay != 0.f) + { + osg::Plane gravityPlane(mCachedWorldDirection, mCachedWorldPosition); + float distance = std::abs(gravityPlane.distance(particle->getPosition())); + decayFactor = std::exp(-1.f * mDecay * distance); + } + + particle->addVelocity(mCachedWorldDirection * mForce * dt * decayFactor * magic); + + break; + } + case Type_Point: + { + osg::Vec3f diff = mCachedWorldPosition - particle->getPosition(); + + float decayFactor = 1.f; + if (mDecay != 0.f) + decayFactor = std::exp(-1.f * mDecay * diff.length()); + + diff.normalize(); + + particle->addVelocity(diff * mForce * dt * decayFactor * magic); + break; + } + } +} + +Emitter::Emitter() + : osgParticle::Emitter() +{ +} + +Emitter::Emitter(const Emitter ©, const osg::CopyOp ©op) + : osgParticle::Emitter(copy, copyop) + , mTargets(copy.mTargets) + , mPlacer(copy.mPlacer) + , mShooter(copy.mShooter) + // need a deep copy because the remainder is stored in the object + , mCounter(osg::clone(copy.mCounter.get(), osg::CopyOp::DEEP_COPY_ALL)) +{ +} + +Emitter::Emitter(const std::vector &targets) + : mTargets(targets) +{ +} + +void Emitter::setShooter(osgParticle::Shooter *shooter) +{ + mShooter = shooter; +} + +void Emitter::setPlacer(osgParticle::Placer *placer) +{ + mPlacer = placer; +} + +void Emitter::setCounter(osgParticle::Counter *counter) +{ + mCounter = counter; +} + +void Emitter::emitParticles(double dt) +{ + int n = mCounter->numParticlesToCreate(dt); + if (n == 0) + return; + + osg::Matrix worldToPs; + + // maybe this could be optimized by halting at the lowest common ancestor of the particle and emitter nodes + osg::MatrixList worldMats = getParticleSystem()->getWorldMatrices(); + if (!worldMats.empty()) + { + const osg::Matrix psToWorld = worldMats[0]; + worldToPs = osg::Matrix::inverse(psToWorld); + } + + const osg::Matrix& ltw = getLocalToWorldMatrix(); + osg::Matrix emitterToPs = ltw * worldToPs; + + if (!mTargets.empty()) + { + int randomRecIndex = mTargets[(std::rand() / (static_cast(RAND_MAX)+1.0)) * mTargets.size()]; + + // we could use a map here for faster lookup + FindGroupByRecIndex visitor(randomRecIndex); + getParent(0)->accept(visitor); + + if (!visitor.mFound) + { + std::cerr << "Emitter: Can't find emitter node" << randomRecIndex << std::endl; + return; + } + + osg::NodePath path = visitor.mFoundPath; + path.erase(path.begin()); + emitterToPs = osg::computeLocalToWorld(path) * emitterToPs; + } + + emitterToPs.orthoNormalize(emitterToPs); + + for (int i=0; icreateParticle(0); + if (P) + { + mPlacer->place(P); + + mShooter->shoot(P); + + P->transformPositionVelocity(emitterToPs); + } + } +} + +FindGroupByRecIndex::FindGroupByRecIndex(int recIndex) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mFound(NULL) + , mRecIndex(recIndex) +{ +} + +void FindGroupByRecIndex::apply(osg::Node &searchNode) +{ + if (searchNode.getUserDataContainer() && searchNode.getUserDataContainer()->getNumUserObjects()) + { + NodeUserData* holder = dynamic_cast(searchNode.getUserDataContainer()->getUserObject(0)); + if (holder && holder->mIndex == mRecIndex) + { + osg::Group* group = searchNode.asGroup(); + if (!group) + group = searchNode.getParent(0); + + mFound = group; + mFoundPath = getNodePath(); + return; + } + } + traverse(searchNode); +} + +PlanarCollider::PlanarCollider(const Nif::NiPlanarCollider *collider) + : mBounceFactor(collider->mBounceFactor) + , mPlane(-collider->mPlaneNormal, collider->mPlaneDistance) +{ +} + +PlanarCollider::PlanarCollider() + : mBounceFactor(0.f) +{ +} + +PlanarCollider::PlanarCollider(const PlanarCollider ©, const osg::CopyOp ©op) + : osgParticle::Operator(copy, copyop) + , mBounceFactor(copy.mBounceFactor) + , mPlane(copy.mPlane) + , mPlaneInParticleSpace(copy.mPlaneInParticleSpace) +{ +} + +void PlanarCollider::beginOperate(osgParticle::Program *program) +{ + mPlaneInParticleSpace = mPlane; + if (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF) + mPlaneInParticleSpace.transform(program->getLocalToWorldMatrix()); +} + +void PlanarCollider::operate(osgParticle::Particle *particle, double dt) +{ + float dotproduct = particle->getVelocity() * mPlaneInParticleSpace.getNormal(); + + if (dotproduct > 0) + { + osg::BoundingSphere bs(particle->getPosition(), 0.f); + if (mPlaneInParticleSpace.intersect(bs) == 1) + { + osg::Vec3 reflectedVelocity = particle->getVelocity() - mPlaneInParticleSpace.getNormal() * (2 * dotproduct); + reflectedVelocity *= mBounceFactor; + particle->setVelocity(reflectedVelocity); + } + } +} + +} diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp new file mode 100644 index 0000000000..a1ed3f3d0f --- /dev/null +++ b/components/nifosg/particle.hpp @@ -0,0 +1,217 @@ +#ifndef OPENMW_COMPONENTS_NIFOSG_PARTICLE_H +#define OPENMW_COMPONENTS_NIFOSG_PARTICLE_H + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "controller.hpp" // ValueInterpolator + +namespace Nif +{ + class NiGravity; + class NiPlanarCollider; +} + +namespace NifOsg +{ + + // Subclass ParticleSystem to support a limit on the number of active particles. + class ParticleSystem : public osgParticle::ParticleSystem + { + public: + ParticleSystem(); + ParticleSystem(const ParticleSystem& copy, const osg::CopyOp& copyop); + + META_Object(NifOsg, ParticleSystem) + + virtual osgParticle::Particle* createParticle(const osgParticle::Particle *ptemplate); + + void setQuota(int quota); + + private: + int mQuota; + }; + + // HACK: Particle doesn't allow setting the initial age, but we need this for loading the particle system state + class ParticleAgeSetter : public osgParticle::Particle + { + public: + ParticleAgeSetter(float age) + : Particle() + { + _t0 = age; + } + }; + + // Node callback used to set the inverse of the parent's world matrix on the MatrixTransform + // that the callback is attached to. Used for certain particle systems, + // so that the particles do not move with the node they are attached to. + class InverseWorldMatrix : public osg::NodeCallback + { + public: + InverseWorldMatrix() + { + } + InverseWorldMatrix(const InverseWorldMatrix& copy, const osg::CopyOp& op) + : osg::Object(), osg::NodeCallback() + { + } + + META_Object(NifOsg, InverseWorldMatrix) + + void operator()(osg::Node* node, osg::NodeVisitor* nv); + }; + + class ParticleShooter : public osgParticle::Shooter + { + public: + ParticleShooter(float minSpeed, float maxSpeed, float horizontalDir, float horizontalAngle, float verticalDir, float verticalAngle, + float lifetime, float lifetimeRandom); + ParticleShooter(); + ParticleShooter(const ParticleShooter& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); + + META_Object(NifOsg, ParticleShooter) + + virtual void shoot(osgParticle::Particle* particle) const; + + private: + float mMinSpeed; + float mMaxSpeed; + float mHorizontalDir; + float mHorizontalAngle; + float mVerticalDir; + float mVerticalAngle; + float mLifetime; + float mLifetimeRandom; + }; + + class PlanarCollider : public osgParticle::Operator + { + public: + PlanarCollider(const Nif::NiPlanarCollider* collider); + PlanarCollider(); + PlanarCollider(const PlanarCollider& copy, const osg::CopyOp& copyop); + + META_Object(NifOsg, PlanarCollider) + + virtual void beginOperate(osgParticle::Program* program); + virtual void operate(osgParticle::Particle* particle, double dt); + + private: + float mBounceFactor; + osg::Plane mPlane; + osg::Plane mPlaneInParticleSpace; + }; + + class GrowFadeAffector : public osgParticle::Operator + { + public: + GrowFadeAffector(float growTime, float fadeTime); + GrowFadeAffector(); + GrowFadeAffector(const GrowFadeAffector& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); + + META_Object(NifOsg, GrowFadeAffector) + + virtual void beginOperate(osgParticle::Program* program); + virtual void operate(osgParticle::Particle* particle, double dt); + + private: + float mGrowTime; + float mFadeTime; + + float mCachedDefaultSize; + }; + + typedef ValueInterpolator Vec4Interpolator; + class ParticleColorAffector : public osgParticle::Operator + { + public: + ParticleColorAffector(const Nif::NiColorData* clrdata); + ParticleColorAffector(); + ParticleColorAffector(const ParticleColorAffector& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); + + META_Object(NifOsg, ParticleColorAffector) + + virtual void operate(osgParticle::Particle* particle, double dt); + + private: + Vec4Interpolator mData; + }; + + class GravityAffector : public osgParticle::Operator + { + public: + GravityAffector(const Nif::NiGravity* gravity); + GravityAffector(); + GravityAffector(const GravityAffector& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); + + META_Object(NifOsg, GravityAffector) + + virtual void operate(osgParticle::Particle* particle, double dt); + virtual void beginOperate(osgParticle::Program *); + + private: + float mForce; + enum ForceType { + Type_Wind, + Type_Point + }; + ForceType mType; + osg::Vec3f mPosition; + osg::Vec3f mDirection; + float mDecay; + osg::Vec3f mCachedWorldPosition; + osg::Vec3f mCachedWorldDirection; + }; + + // NodeVisitor to find a Group node with the given record index, stored in the node's user data container. + // Alternatively, returns the node's parent Group if that node is not a Group (i.e. a leaf node). + class FindGroupByRecIndex : public osg::NodeVisitor + { + public: + FindGroupByRecIndex(int recIndex); + + virtual void apply(osg::Node &searchNode); + + osg::Group* mFound; + osg::NodePath mFoundPath; + private: + int mRecIndex; + }; + + // Subclass emitter to support randomly choosing one of the child node's transforms for the emit position of new particles. + class Emitter : public osgParticle::Emitter + { + public: + Emitter(const std::vector& targets); + Emitter(); + Emitter(const Emitter& copy, const osg::CopyOp& copyop); + + META_Object(NifOsg, Emitter) + + virtual void emitParticles(double dt); + + void setShooter(osgParticle::Shooter* shooter); + void setPlacer(osgParticle::Placer* placer); + void setCounter(osgParticle::Counter* counter); + + private: + // NIF Record indices + std::vector mTargets; + + osg::ref_ptr mPlacer; + osg::ref_ptr mShooter; + osg::ref_ptr mCounter; + }; + +} + +#endif diff --git a/components/nifosg/userdata.hpp b/components/nifosg/userdata.hpp new file mode 100644 index 0000000000..9770890b0d --- /dev/null +++ b/components/nifosg/userdata.hpp @@ -0,0 +1,49 @@ +#ifndef OPENMW_COMPONENTS_NIFOSG_USERDATA_H +#define OPENMW_COMPONENTS_NIFOSG_USERDATA_H + +#include + +#include + +namespace NifOsg +{ + + // Note if you are copying a scene graph with this user data you should use the DEEP_COPY_USERDATA copyop. + class NodeUserData : public osg::Object + { + public: + NodeUserData(int index, float scale, const Nif::Matrix3& rotationScale) + : mIndex(index), mScale(scale), mRotationScale(rotationScale) + { + } + NodeUserData() + : mIndex(0), mScale(0) + { + } + NodeUserData(const NodeUserData& copy, const osg::CopyOp& copyop) + : Object(copy, copyop) + , mIndex(copy.mIndex) + , mScale(copy.mScale) + , mRotationScale(copy.mRotationScale) + { + } + + META_Object(NifOsg, NodeUserData) + + // NIF record index + int mIndex; + + // Hack: account for Transform differences between OSG and NIFs. + // OSG uses a 4x4 matrix, NIF's use a 3x3 rotationScale, float scale, and vec3 position. + // Decomposing the original components from the 4x4 matrix isn't possible, which causes + // problems when a KeyframeController wants to change only one of these components. So + // we store the scale and rotation components separately here. + // Note for a cleaner solution it would be possible to write a custom Transform node, + // but then we have to fork osgAnimation :/ + float mScale; + Nif::Matrix3 mRotationScale; + }; + +} + +#endif diff --git a/components/nifoverrides/nifoverrides.cpp b/components/nifoverrides/nifoverrides.cpp deleted file mode 100644 index 972cf1b843..0000000000 --- a/components/nifoverrides/nifoverrides.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "nifoverrides.hpp" - -#include - -#include <../components/misc/stringops.hpp> - -#include "../extern/shiny/Main/MaterialInstance.hpp" - -#include - - -using namespace NifOverrides; - -Overrides::TransparencyOverrideMap Overrides::mTransparencyOverrides = Overrides::TransparencyOverrideMap(); -Overrides::MaterialOverrideMap Overrides::mMaterialOverrides = Overrides::MaterialOverrideMap(); - -void Overrides::loadTransparencyOverrides (const std::string& file) -{ - Ogre::ConfigFile cf; - cf.load(file); - - Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator(); - while (seci.hasMoreElements()) - { - Ogre::String sectionName = seci.peekNextKey(); - mTransparencyOverrides[sectionName] = - Ogre::StringConverter::parseInt(cf.getSetting("alphaRejectValue", sectionName)); - seci.getNext(); - } -} - -void Overrides::loadMaterialOverrides(const std::string &file) -{ - Ogre::ConfigFile cf; - cf.load(file); - - Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator(); - while (seci.hasMoreElements()) - { - Ogre::String sectionName = seci.peekNextKey(); - - Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext(); - Ogre::ConfigFile::SettingsMultiMap::iterator i; - std::map overrides; - for (i = settings->begin(); i != settings->end(); ++i) - { - overrides[i->first] = i->second; - } - mMaterialOverrides[sectionName] = overrides; - } - -} - -TransparencyResult Overrides::getTransparencyOverride(const std::string& texture) -{ - TransparencyResult result; - result.first = false; - - TransparencyOverrideMap::iterator it = mTransparencyOverrides.find(Misc::StringUtils::lowerCase(texture)); - if (it != mTransparencyOverrides.end()) - { - result.first = true; - result.second = it->second; - } - - return result; -} - -void Overrides::getMaterialOverrides(const std::string &texture, sh::MaterialInstance* material) -{ - MaterialOverrideMap::iterator it = mMaterialOverrides.find(Misc::StringUtils::lowerCase(texture)); - if (it != mMaterialOverrides.end()) - { - const std::map& overrides = it->second; - for (std::map::const_iterator it = overrides.begin(); it != overrides.end(); ++it) - { - material->setProperty(it->first, sh::makeProperty(it->second)); - } - } -} diff --git a/components/nifoverrides/nifoverrides.hpp b/components/nifoverrides/nifoverrides.hpp deleted file mode 100644 index edff876d44..0000000000 --- a/components/nifoverrides/nifoverrides.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef OPENMW_COMPONENTS_NIFOVERRIDES_NIFOVERRIDES_HPP -#define OPENMW_COMPONENTS_NIFOVERRIDES_NIFOVERRIDES_HPP - -#include - -namespace sh -{ - class MaterialInstance; -} - -namespace NifOverrides -{ - - typedef std::pair TransparencyResult; - - /// Allows to provide overrides for some material properties in NIF files. - /// NIFs are a bit limited in that they don't allow specifying a material externally, which is - /// painful for texture modding. - /// We also use this to patch up transparency settings in certain NIFs that bethesda has chosen poorly. - class Overrides - { - public: - typedef std::map TransparencyOverrideMap; - static TransparencyOverrideMap mTransparencyOverrides; - - typedef std::map > MaterialOverrideMap; - static MaterialOverrideMap mMaterialOverrides; - - void loadTransparencyOverrides (const std::string& file); - void loadMaterialOverrides (const std::string& file); - - static TransparencyResult getTransparencyOverride(const std::string& texture); - static void getMaterialOverrides (const std::string& texture, sh::MaterialInstance* instance); - }; - -} - -#endif diff --git a/components/ogreinit/ogreinit.cpp b/components/ogreinit/ogreinit.cpp deleted file mode 100644 index 574b67f19d..0000000000 --- a/components/ogreinit/ogreinit.cpp +++ /dev/null @@ -1,242 +0,0 @@ -#include "ogreinit.hpp" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE -#include -#endif - -#include - -#include -#include - -#include "ogreplugin.hpp" - - -namespace bfs = boost::filesystem; - -namespace -{ - /** \brief Custom Ogre::LogListener interface implementation being - able to portably handle UTF-8 encoded path. - - Effectively this is used in conjunction with default listener, - but since on every message messageLogged() set 'skip' flag to - true, there should be no troubles sharing same file. - */ - class LogListener : public Ogre::LogListener - { - bfs::ofstream file; - char buffer[16]; - - - public: - - LogListener(const std::string &path) - : file((bfs::path(path))) - { - memset(buffer, 0, sizeof(buffer)); - } - - void timestamp() - { - int local = time(0) % 86400; - int sec = local % 60; - int min = (local / 60) % 60; - int hrs = local / 3600; - sprintf(buffer, "%02d:%02d:%02d: ", hrs, min, sec); - } - - virtual void messageLogged(const std::string &msg, Ogre::LogMessageLevel lvl, bool mask, const std::string &logName, bool &skip) - { - timestamp(); - file << buffer << msg << std::endl; - skip = true; - } - }; -} - -namespace OgreInit -{ - - OgreInit::OgreInit() - : mRoot(NULL) - #ifdef ENABLE_PLUGIN_CgProgramManager - , mCgPlugin(NULL) - #endif - #ifdef ENABLE_PLUGIN_OctreeSceneManager - , mOctreePlugin(NULL) - #endif - #ifdef ENABLE_PLUGIN_ParticleFX - , mParticleFXPlugin(NULL) - #endif - #ifdef ENABLE_PLUGIN_GL - , mGLPlugin(NULL) - #endif - #ifdef ENABLE_PLUGIN_GLES2 - , mGLES2Plugin(NULL) - #endif - - #ifdef ENABLE_PLUGIN_Direct3D9 - , mD3D9Plugin(NULL) - #endif - {} - - Ogre::Root* OgreInit::init(const std::string &logPath) - { - if (mRoot) - throw std::runtime_error("OgreInit was already initialised"); - - #ifndef ANDROID - // Set up logging first - new Ogre::LogManager; - Ogre::Log *log = Ogre::LogManager::getSingleton().createLog(logPath); - - #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 - // Use custom listener only on Windows - log->addListener(new LogListener(logPath)); - #endif - - // Disable logging to cout/cerr - log->setDebugOutputEnabled(false); - #endif - mRoot = new Ogre::Root("", "", ""); - - #if defined(ENABLE_PLUGIN_GL) || (ENABLE_PLUGIN_GLES2) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX) - loadStaticPlugins(); - #else - loadPlugins(); - #endif - - loadParticleFactories(); - - return mRoot; - } - - OgreInit::~OgreInit() - { - delete mRoot; - delete Ogre::LogManager::getSingletonPtr(); - - std::vector::iterator ei; - for(ei = mEmitterFactories.begin();ei != mEmitterFactories.end();++ei) - OGRE_DELETE (*ei); - mEmitterFactories.clear(); - - std::vector::iterator ai; - for(ai = mAffectorFactories.begin();ai != mAffectorFactories.end();++ai) - OGRE_DELETE (*ai); - mAffectorFactories.clear(); - - #ifdef ENABLE_PLUGIN_GL - delete mGLPlugin; - mGLPlugin = NULL; - #endif - #ifdef ENABLE_PLUGIN_GLES2 - delete mGLES2Plugin; - mGLES2Plugin = NULL; - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 - delete mD3D9Plugin; - mD3D9Plugin = NULL; - #endif - #ifdef ENABLE_PLUGIN_CgProgramManager - delete mCgPlugin; - mCgPlugin = NULL; - #endif - #ifdef ENABLE_PLUGIN_OctreeSceneManager - delete mOctreePlugin; - mOctreePlugin = NULL; - #endif - #ifdef ENABLE_PLUGIN_ParticleFX - delete mParticleFXPlugin; - mParticleFXPlugin = NULL; - #endif - } - - void OgreInit::loadStaticPlugins() - { - #ifdef ENABLE_PLUGIN_GL - mGLPlugin = new Ogre::GLPlugin(); - mRoot->installPlugin(mGLPlugin); - #endif - #ifdef ENABLE_PLUGIN_GLES2 - mGLES2Plugin = new Ogre::GLES2Plugin(); - mRoot->installPlugin(mGLES2Plugin); - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 - mD3D9Plugin = new Ogre::D3D9Plugin(); - mRoot->installPlugin(mD3D9Plugin); - #endif - #ifdef ENABLE_PLUGIN_CgProgramManager - mCgPlugin = new Ogre::CgPlugin(); - mRoot->installPlugin(mCgPlugin); - #endif - #ifdef ENABLE_PLUGIN_OctreeSceneManager - mOctreePlugin = new Ogre::OctreePlugin(); - mRoot->installPlugin(mOctreePlugin); - #endif - #ifdef ENABLE_PLUGIN_ParticleFX - mParticleFXPlugin = new Ogre::ParticleFXPlugin(); - mRoot->installPlugin(mParticleFXPlugin); - #endif - } - - void OgreInit::loadPlugins() - { - std::string pluginDir; - const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR"); - if (pluginEnv) - pluginDir = pluginEnv; - else - { - #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 - pluginDir = ".\\"; - #endif - #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - pluginDir = OGRE_PLUGIN_DIR; - // if path is not specified try to find plugins inside the app bundle - if (pluginDir.empty()) - pluginDir = Ogre::macFrameworksPath(); - #endif - #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX - pluginDir = OGRE_PLUGIN_DIR; - #endif - } - Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mRoot); - Files::loadOgrePlugin(pluginDir, "RenderSystem_GLES2", *mRoot); - Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mRoot); - Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mRoot); - Files::loadOgrePlugin(pluginDir, "Plugin_CgProgramManager", *mRoot); - if (!Files::loadOgrePlugin(pluginDir, "Plugin_ParticleFX", *mRoot)) - throw std::runtime_error("Required Plugin_ParticleFX for Ogre not found!"); - } - - void OgreInit::loadParticleFactories() - { - Ogre::ParticleEmitterFactory *emitter; - emitter = OGRE_NEW NifEmitterFactory(); - Ogre::ParticleSystemManager::getSingleton().addEmitterFactory(emitter); - mEmitterFactories.push_back(emitter); - - Ogre::ParticleAffectorFactory *affector; - affector = OGRE_NEW GrowFadeAffectorFactory(); - Ogre::ParticleSystemManager::getSingleton().addAffectorFactory(affector); - mAffectorFactories.push_back(affector); - - affector = OGRE_NEW GravityAffectorFactory(); - Ogre::ParticleSystemManager::getSingleton().addAffectorFactory(affector); - mAffectorFactories.push_back(affector); - } - -} diff --git a/components/ogreinit/ogreinit.hpp b/components/ogreinit/ogreinit.hpp deleted file mode 100644 index 9613421f7b..0000000000 --- a/components/ogreinit/ogreinit.hpp +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef OPENMW_COMPONENTS_OGREINIT_H -#define OPENMW_COMPONENTS_OGREINIT_H - -#include -#include - -// Static plugin headers -#ifdef ENABLE_PLUGIN_CgProgramManager -# include "OgreCgPlugin.h" -#endif -#ifdef ENABLE_PLUGIN_OctreeSceneManager -# include "OgreOctreePlugin.h" -#endif -#ifdef ENABLE_PLUGIN_ParticleFX -# include "OgreParticleFXPlugin.h" -#endif -#ifdef ENABLE_PLUGIN_GL -# include "OgreGLPlugin.h" -#endif -#ifdef ENABLE_PLUGIN_GLES2 -# include "OgreGLES2Plugin.h" -#endif - -#ifdef ENABLE_PLUGIN_Direct3D9 -# include "OgreD3D9Plugin.h" -#endif - -namespace Ogre -{ - class ParticleEmitterFactory; - class ParticleAffectorFactory; - class Root; -} - -namespace OgreInit -{ - /** - * @brief Starts Ogre::Root and loads required plugins and NIF particle factories - */ - class OgreInit - { - public: - OgreInit(); - - Ogre::Root* init(const std::string &logPath // Path to directory where to store log files - ); - - ~OgreInit(); - - private: - std::vector mEmitterFactories; - std::vector mAffectorFactories; - Ogre::Root* mRoot; - - void loadStaticPlugins(); - void loadPlugins(); - void loadParticleFactories(); - - #ifdef ENABLE_PLUGIN_CgProgramManager - Ogre::CgPlugin* mCgPlugin; - #endif - #ifdef ENABLE_PLUGIN_OctreeSceneManager - Ogre::OctreePlugin* mOctreePlugin; - #endif - #ifdef ENABLE_PLUGIN_ParticleFX - Ogre::ParticleFXPlugin* mParticleFXPlugin; - #endif - #ifdef ENABLE_PLUGIN_GL - Ogre::GLPlugin* mGLPlugin; - #endif - #ifdef ENABLE_PLUGIN_GLES2 - Ogre::GLES2Plugin* mGLES2Plugin; - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 - Ogre::D3D9Plugin* mD3D9Plugin; - #endif - - }; -} - -#endif diff --git a/components/ogreinit/ogreplugin.cpp b/components/ogreinit/ogreplugin.cpp deleted file mode 100644 index 069b25e7b5..0000000000 --- a/components/ogreinit/ogreplugin.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "ogreplugin.hpp" - -#include -#include - -namespace Files { - -bool loadOgrePlugin(const std::string &pluginDir, std::string pluginName, Ogre::Root &ogreRoot) { - std::string pluginExt; -#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 - pluginExt = ".dll"; -#endif -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - pluginExt = ".framework"; -#endif -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX - pluginExt = ".so"; -#endif - - // Append plugin suffix if debugging. - std::string pluginPath; -#if defined(DEBUG) - pluginPath = pluginDir + "/" + pluginName + OGRE_PLUGIN_DEBUG_SUFFIX + pluginExt; - if (boost::filesystem::exists(pluginPath)) { - ogreRoot.loadPlugin(pluginPath); - return true; - } - else { -#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 - return false; -#endif //OGRE_PLATFORM == OGRE_PLATFORM_WIN32 - } -#endif //defined(DEBUG) - - pluginPath = pluginDir + "/" + pluginName + pluginExt; - if (boost::filesystem::exists(pluginPath)) { - ogreRoot.loadPlugin(pluginPath); - return true; - } - else { - return false; - } -} - -} diff --git a/components/ogreinit/ogreplugin.hpp b/components/ogreinit/ogreplugin.hpp deleted file mode 100644 index 6fcf613768..0000000000 --- a/components/ogreinit/ogreplugin.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef COMPONENTS_FILES_OGREPLUGIN_H -#define COMPONENTS_FILES_OGREPLUGIN_H - -#include - -#include -#include - -namespace Ogre { - class Root; -} - -#if (BOOST_VERSION <= 104500) -namespace boost { -namespace filesystem { -inline path absolute(const path& p, const path& base=current_path()) { - // call obsolete version of this function on older boost - return complete(p, base); -} -} -} -#endif /* (BOOST_VERSION <= 104300) */ - -/** - * \namespace Files - */ -namespace Files { - -/** - * \brief Loads Ogre plugin with given name. - * - * \param pluginDir absolute path to plugins - * \param pluginName plugin name, for example "RenderSystem_GL" - * \param ogreRoot Ogre::Root instance - * - * \return whether plugin was located or not - */ -bool loadOgrePlugin(const std::string &pluginDir, std::string pluginName, Ogre::Root &ogreRoot); - -} - -#endif /* COMPONENTS_FILES_OGREPLUGIN_H */ diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp new file mode 100644 index 0000000000..968dbe6c67 --- /dev/null +++ b/components/resource/bulletshape.cpp @@ -0,0 +1,111 @@ +#include "bulletshape.hpp" + +#include + +#include +#include +#include +#include + +namespace Resource +{ + +BulletShape::BulletShape() + : mCollisionShape(NULL) +{ + +} + +BulletShape::~BulletShape() +{ + deleteShape(mCollisionShape); +} + +void BulletShape::deleteShape(btCollisionShape* shape) +{ + if(shape!=NULL) + { + if(shape->isCompound()) + { + btCompoundShape* ms = static_cast(shape); + int a = ms->getNumChildShapes(); + for(int i=0; i getChildShape(i)); + } + delete shape; + } +} + +btCollisionShape* BulletShape::duplicateCollisionShape(btCollisionShape *shape) const +{ + if(shape->isCompound()) + { + btCompoundShape *comp = static_cast(shape); + btCompoundShape *newShape = new btCompoundShape; + + int numShapes = comp->getNumChildShapes(); + for(int i = 0;i < numShapes;++i) + { + btCollisionShape *child = duplicateCollisionShape(comp->getChildShape(i)); + btTransform trans = comp->getChildTransform(i); + newShape->addChildShape(trans, child); + } + + return newShape; + } + + if(btBvhTriangleMeshShape* trishape = dynamic_cast(shape)) + { +#if BT_BULLET_VERSION >= 283 + btScaledBvhTriangleMeshShape* newShape = new btScaledBvhTriangleMeshShape(trishape, btVector3(1.f, 1.f, 1.f)); +#else + // work around btScaledBvhTriangleMeshShape bug ( https://code.google.com/p/bullet/issues/detail?id=371 ) in older bullet versions + btTriangleMesh* oldMesh = static_cast(trishape->getMeshInterface()); + btTriangleMesh* newMesh = new btTriangleMesh(*oldMesh); + + // Do not build a new bvh (not needed, since it's the same as the original shape's bvh) + bool buildBvh = true; + if (trishape->getOptimizedBvh()) + buildBvh = false; + TriangleMeshShape* newShape = new TriangleMeshShape(newMesh, true, buildBvh); + // Set original shape's bvh via pointer + // The pointer is safe because the BulletShapeInstance keeps a ref_ptr to the original BulletShape + if (!buildBvh) + newShape->setOptimizedBvh(trishape->getOptimizedBvh()); +#endif + return newShape; + } + + if (btBoxShape* boxshape = dynamic_cast(shape)) + { + return new btBoxShape(*boxshape); + } + + throw std::logic_error(std::string("Unhandled Bullet shape duplication: ")+shape->getName()); +} + +btCollisionShape *BulletShape::getCollisionShape() +{ + return mCollisionShape; +} + +osg::ref_ptr BulletShape::makeInstance() +{ + osg::ref_ptr instance (new BulletShapeInstance(this)); + return instance; +} + +BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) + : BulletShape() + , mSource(source) +{ + mCollisionBoxHalfExtents = source->mCollisionBoxHalfExtents; + mCollisionBoxTranslate = source->mCollisionBoxTranslate; + + mAnimatedShapes = source->mAnimatedShapes; + + if (source->mCollisionShape) + mCollisionShape = duplicateCollisionShape(source->mCollisionShape); +} + +} diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp new file mode 100644 index 0000000000..cfae27eac4 --- /dev/null +++ b/components/resource/bulletshape.hpp @@ -0,0 +1,78 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H +#define OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H + +#include + +#include +#include +#include + +#include + +class btCollisionShape; + +namespace Resource +{ + + class BulletShapeInstance; + class BulletShape : public osg::Referenced + { + public: + BulletShape(); + virtual ~BulletShape(); + + btCollisionShape* mCollisionShape; + + // Used for actors. Note, ideally actors would use a separate loader - as it is + // we have to keep a redundant copy of the actor model around in mCollisionShape, which isn't used. + // For now, use one file <-> one resource for simplicity. + osg::Vec3f mCollisionBoxHalfExtents; + osg::Vec3f mCollisionBoxTranslate; + + // Stores animated collision shapes. If any collision nodes in the NIF are animated, then mCollisionShape + // will be a btCompoundShape (which consists of one or more child shapes). + // In this map, for each animated collision shape, + // we store the node's record index mapped to the child index of the shape in the btCompoundShape. + std::map mAnimatedShapes; + + osg::ref_ptr makeInstance(); + + btCollisionShape* duplicateCollisionShape(btCollisionShape* shape) const; + + btCollisionShape* getCollisionShape(); + + private: + void deleteShape(btCollisionShape* shape); + }; + + + // An instance of a BulletShape that may have its own unique scaling set on the mCollisionShape. + // Vertex data is shallow-copied where possible. A ref_ptr to the original shape is held to keep vertex pointers intact. + class BulletShapeInstance : public BulletShape + { + public: + BulletShapeInstance(osg::ref_ptr source); + + private: + osg::ref_ptr mSource; + }; + + // Subclass btBhvTriangleMeshShape to auto-delete the meshInterface + struct TriangleMeshShape : public btBvhTriangleMeshShape + { + TriangleMeshShape(btStridingMeshInterface* meshInterface, bool useQuantizedAabbCompression, bool buildBvh = true) + : btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression, buildBvh) + { + } + + virtual ~TriangleMeshShape() + { + delete getTriangleInfoMap(); + delete m_meshInterface; + } + }; + + +} + +#endif diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp new file mode 100644 index 0000000000..4cbe62f3c3 --- /dev/null +++ b/components/resource/bulletshapemanager.cpp @@ -0,0 +1,152 @@ +#include "bulletshapemanager.hpp" + +#include +#include +#include + +#include + +#include + +#include + +#include "bulletshape.hpp" +#include "scenemanager.hpp" +#include "niffilemanager.hpp" + + +namespace Resource +{ + +struct GetTriangleFunctor +{ + GetTriangleFunctor() + : mTriMesh(NULL) + { + } + + void setTriMesh(btTriangleMesh* triMesh) + { + mTriMesh = triMesh; + } + + void setMatrix(const osg::Matrixf& matrix) + { + mMatrix = matrix; + } + + inline btVector3 toBullet(const osg::Vec3f& vec) + { + return btVector3(vec.x(), vec.y(), vec.z()); + } + + void inline operator()( const osg::Vec3 v1, const osg::Vec3 v2, const osg::Vec3 v3, bool _temp ) + { + if (mTriMesh) + mTriMesh->addTriangle( toBullet(mMatrix.preMult(v1)), toBullet(mMatrix.preMult(v2)), toBullet(mMatrix.preMult(v3))); + } + + btTriangleMesh* mTriMesh; + osg::Matrixf mMatrix; +}; + +/// Creates a BulletShape out of a Node hierarchy. +class NodeToShapeVisitor : public osg::NodeVisitor +{ +public: + NodeToShapeVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mTriangleMesh(NULL) + { + + } + + virtual void apply(osg::Geode& geode) + { + for (unsigned int i=0; i functor; + functor.setTriMesh(mTriangleMesh); + functor.setMatrix(worldMat); + drawable.accept(functor); + } + + osg::ref_ptr getShape() + { + if (!mTriangleMesh) + return osg::ref_ptr(); + + osg::ref_ptr shape (new BulletShape); + TriangleMeshShape* meshShape = new TriangleMeshShape(mTriangleMesh, true); + shape->mCollisionShape = meshShape; + mTriangleMesh = NULL; + return shape; + } + +private: + btTriangleMesh* mTriangleMesh; +}; + +BulletShapeManager::BulletShapeManager(const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager) + : mVFS(vfs) + , mSceneManager(sceneMgr) + , mNifFileManager(nifFileManager) +{ + +} + +BulletShapeManager::~BulletShapeManager() +{ + +} + +osg::ref_ptr BulletShapeManager::createInstance(const std::string &name) +{ + std::string normalized = name; + mVFS->normalizeFilename(normalized); + + osg::ref_ptr shape; + Index::iterator it = mIndex.find(normalized); + if (it == mIndex.end()) + { + size_t extPos = normalized.find_last_of('.'); + std::string ext; + if (extPos != std::string::npos && extPos+1 < normalized.size()) + ext = normalized.substr(extPos+1); + + if (ext == "nif") + { + NifBullet::BulletNifLoader loader; + shape = loader.load(mNifFileManager->get(normalized)); + } + else + { + // TODO: support .bullet shape files + + osg::ref_ptr constNode (mSceneManager->getTemplate(normalized)); + osg::ref_ptr node (const_cast(constNode.get())); // const-trickery required because there is no const version of NodeVisitor + NodeToShapeVisitor visitor; + node->accept(visitor); + shape = visitor.getShape(); + if (!shape) + return osg::ref_ptr(); + } + + mIndex[normalized] = shape; + } + else + shape = it->second; + + osg::ref_ptr instance = shape->makeInstance(); + return instance; +} + +} diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp new file mode 100644 index 0000000000..ac1523495c --- /dev/null +++ b/components/resource/bulletshapemanager.hpp @@ -0,0 +1,43 @@ +#ifndef OPENMW_COMPONENTS_BULLETSHAPEMANAGER_H +#define OPENMW_COMPONENTS_BULLETSHAPEMANAGER_H + +#include +#include + +#include + +#include "bulletshape.hpp" + +namespace VFS +{ + class Manager; +} + +namespace Resource +{ + class SceneManager; + class NifFileManager; + + class BulletShape; + class BulletShapeInstance; + + class BulletShapeManager + { + public: + BulletShapeManager(const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager); + ~BulletShapeManager(); + + osg::ref_ptr createInstance(const std::string& name); + + private: + const VFS::Manager* mVFS; + SceneManager* mSceneManager; + NifFileManager* mNifFileManager; + + typedef std::map > Index; + Index mIndex; + }; + +} + +#endif diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp new file mode 100644 index 0000000000..7e948dcb03 --- /dev/null +++ b/components/resource/keyframemanager.cpp @@ -0,0 +1,41 @@ +#include "keyframemanager.hpp" + +#include +#include + +#include "objectcache.hpp" + +namespace Resource +{ + + KeyframeManager::KeyframeManager(const VFS::Manager* vfs) + : mCache(new osgDB::ObjectCache) + , mVFS(vfs) + { + } + + KeyframeManager::~KeyframeManager() + { + } + + osg::ref_ptr KeyframeManager::get(const std::string &name) + { + std::string normalized = name; + mVFS->normalizeFilename(normalized); + + osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); + if (obj) + return osg::ref_ptr(static_cast(obj.get())); + else + { + osg::ref_ptr loaded (new NifOsg::KeyframeHolder); + NifOsg::Loader::loadKf(Nif::NIFFilePtr(new Nif::NIFFile(mVFS->getNormalized(normalized), normalized)), *loaded.get()); + + mCache->addEntryToObjectCache(normalized, loaded); + return loaded; + } + } + + + +} diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp new file mode 100644 index 0000000000..5032d0e389 --- /dev/null +++ b/components/resource/keyframemanager.hpp @@ -0,0 +1,47 @@ +#ifndef OPENMW_COMPONENTS_KEYFRAMEMANAGER_H +#define OPENMW_COMPONENTS_KEYFRAMEMANAGER_H + +#include +#include + +namespace VFS +{ + class Manager; +} + +namespace osgDB +{ + class ObjectCache; +} + +namespace NifOsg +{ + class KeyframeHolder; +} + +namespace Resource +{ + + /// @brief Managing of keyframe resources + class KeyframeManager + { + public: + KeyframeManager(const VFS::Manager* vfs); + ~KeyframeManager(); + + void clearCache(); + + /// Retrieve a read-only keyframe resource by name (case-insensitive). + /// @note This method is safe to call from any thread. + /// @note Throws an exception if the resource is not found. + osg::ref_ptr get(const std::string& name); + + private: + osg::ref_ptr mCache; + + const VFS::Manager* mVFS; + }; + +} + +#endif diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp new file mode 100644 index 0000000000..1d8019b69d --- /dev/null +++ b/components/resource/niffilemanager.cpp @@ -0,0 +1,63 @@ +#include "niffilemanager.hpp" + +#include + +#include "objectcache.hpp" + +namespace Resource +{ + + class NifFileHolder : public osg::Object + { + public: + NifFileHolder(const Nif::NIFFilePtr& file) + : mNifFile(file) + { + } + NifFileHolder(const NifFileHolder& copy, const osg::CopyOp& copyop) + : mNifFile(copy.mNifFile) + { + } + + NifFileHolder() + { + } + + META_Object(Resource, NifFileHolder) + + Nif::NIFFilePtr mNifFile; + }; + + NifFileManager::NifFileManager(const VFS::Manager *vfs) + : mVFS(vfs) + { + mCache = new osgDB::ObjectCache; + } + + NifFileManager::~NifFileManager() + { + + } + + void NifFileManager::clearCache() + { + // NIF files aren't needed any more when the converted objects are cached in SceneManager / BulletShapeManager, + // so we'll simply drop all nif files here, unlikely to need them again + mCache->clear(); + } + + Nif::NIFFilePtr NifFileManager::get(const std::string &name) + { + osg::ref_ptr obj = mCache->getRefFromObjectCache(name); + if (obj) + return static_cast(obj.get())->mNifFile; + else + { + Nif::NIFFilePtr file (new Nif::NIFFile(mVFS->getNormalized(name), name)); + obj = new NifFileHolder(file); + mCache->addEntryToObjectCache(name, obj); + return file; + } + } + +} diff --git a/components/resource/niffilemanager.hpp b/components/resource/niffilemanager.hpp new file mode 100644 index 0000000000..90ad9fc294 --- /dev/null +++ b/components/resource/niffilemanager.hpp @@ -0,0 +1,45 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_NIFFILEMANAGER_H +#define OPENMW_COMPONENTS_RESOURCE_NIFFILEMANAGER_H + +#include + +#include + +namespace VFS +{ + class Manager; +} + +namespace osgDB +{ + class ObjectCache; +} + +namespace Resource +{ + + /// @brief Handles caching of NIFFiles. + /// @note The NifFileManager is completely thread safe. + class NifFileManager + { + public: + NifFileManager(const VFS::Manager* vfs); + ~NifFileManager(); + + void clearCache(); + + /// Retrieve a NIF file from the cache, or load it from the VFS if not cached yet. + /// @note For performance reasons the NifFileManager does not handle case folding, needs + /// to be done in advance by other managers accessing the NifFileManager. + Nif::NIFFilePtr get(const std::string& name); + + private: + // Use the osgDB::ObjectCache so objects are retrieved in thread safe way + osg::ref_ptr mCache; + + const VFS::Manager* mVFS; + }; + +} + +#endif diff --git a/components/resource/objectcache.cpp b/components/resource/objectcache.cpp new file mode 100644 index 0000000000..7264d05aa7 --- /dev/null +++ b/components/resource/objectcache.cpp @@ -0,0 +1,141 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + +#include + +#if OSG_VERSION_LESS_THAN(3,3,3) + +#include "objectcache.hpp" + +using namespace osgDB; + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// ObjectCache +// +ObjectCache::ObjectCache(): + osg::Referenced(true) +{ +// OSG_NOTICE<<"Constructed ObjectCache"< lock1(_objectCacheMutex); + OpenThreads::ScopedLock lock2(objectCache->_objectCacheMutex); + + // OSG_NOTICE<<"Inserting objects to main ObjectCache "<_objectCache.size()<_objectCache.begin(), objectCache->_objectCache.end()); +} + + +void ObjectCache::addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + _objectCache[filename]=ObjectTimeStampPair(object,timestamp); +} + +osg::Object* ObjectCache::getFromObjectCache(const std::string& fileName) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + ObjectCacheMap::iterator itr = _objectCache.find(fileName); + if (itr!=_objectCache.end()) return itr->second.first.get(); + else return 0; +} + +osg::ref_ptr ObjectCache::getRefFromObjectCache(const std::string& fileName) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + ObjectCacheMap::iterator itr = _objectCache.find(fileName); + if (itr!=_objectCache.end()) + { + // OSG_NOTICE<<"Found "<second.first; + } + else return 0; +} + +void ObjectCache::updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + + // look for objects with external references and update their time stamp. + for(ObjectCacheMap::iterator itr=_objectCache.begin(); + itr!=_objectCache.end(); + ++itr) + { + // if ref count is greater the 1 the object has an external reference. + if (itr->second.first->referenceCount()>1) + { + // so update it time stamp. + itr->second.second = referenceTime; + } + } +} + +void ObjectCache::removeExpiredObjectsInCache(double expiryTime) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + + // Remove expired entries from object cache + ObjectCacheMap::iterator oitr = _objectCache.begin(); + while(oitr != _objectCache.end()) + { + if (oitr->second.second<=expiryTime) + { + _objectCache.erase(oitr++); + } + else + { + ++oitr; + } + } +} + +void ObjectCache::removeFromObjectCache(const std::string& fileName) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + ObjectCacheMap::iterator itr = _objectCache.find(fileName); + if (itr!=_objectCache.end()) _objectCache.erase(itr); +} + +void ObjectCache::clear() +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + _objectCache.clear(); +} + +void ObjectCache::releaseGLObjects(osg::State* state) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + + for(ObjectCacheMap::iterator itr = _objectCache.begin(); + itr != _objectCache.end(); + ++itr) + { + osg::Object* object = itr->second.first.get(); + object->releaseGLObjects(state); + } +} + +#endif diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp new file mode 100644 index 0000000000..cae48ca6b7 --- /dev/null +++ b/components/resource/objectcache.hpp @@ -0,0 +1,92 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + +// Wrapper for osgDB/ObjectCache. Works around ObjectCache not being available in old OSG 3.2. +// Use "#include objectcache.hpp" in place of "#include + +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) +#include +#else + +#include + +#include +#include + +#include + +namespace osgDB { + +class /*OSGDB_EXPORT*/ ObjectCache : public osg::Referenced +{ + public: + + ObjectCache(); + + /** For each object in the cache which has an reference count greater than 1 + * (and therefore referenced by elsewhere in the application) set the time stamp + * for that object in the cache to specified time. + * This would typically be called once per frame by applications which are doing database paging, + * and need to prune objects that are no longer required. + * The time used should be taken from the FrameStamp::getReferenceTime().*/ + void updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime); + + /** Removed object in the cache which have a time stamp at or before the specified expiry time. + * This would typically be called once per frame by applications which are doing database paging, + * and need to prune objects that are no longer required, and called after the a called + * after the call to updateTimeStampOfObjectsInCacheWithExternalReferences(expirtyTime).*/ + void removeExpiredObjectsInCache(double expiryTime); + + /** Remove all objects in the cache regardless of having external references or expiry times.*/ + void clear(); + + /** Add contents of specified ObjectCache to this object cache.*/ + void addObjectCache(ObjectCache* object); + + /** Add a filename,object,timestamp triple to the Registry::ObjectCache.*/ + void addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp = 0.0); + + /** Remove Object from cache.*/ + void removeFromObjectCache(const std::string& fileName); + + /** Get an Object from the object cache*/ + osg::Object* getFromObjectCache(const std::string& fileName); + + /** Get an ref_ptr from the object cache*/ + osg::ref_ptr getRefFromObjectCache(const std::string& fileName); + + /** call rleaseGLObjects on all objects attached to the object cache.*/ + void releaseGLObjects(osg::State* state); + + protected: + + virtual ~ObjectCache(); + + typedef std::pair, double > ObjectTimeStampPair; + typedef std::map ObjectCacheMap; + + ObjectCacheMap _objectCache; + OpenThreads::Mutex _objectCacheMutex; + +}; + +} + +#endif + +#endif diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp new file mode 100644 index 0000000000..2ce8d22e6a --- /dev/null +++ b/components/resource/resourcesystem.cpp @@ -0,0 +1,55 @@ +#include "resourcesystem.hpp" + +#include "scenemanager.hpp" +#include "texturemanager.hpp" +#include "niffilemanager.hpp" +#include "keyframemanager.hpp" + +namespace Resource +{ + + ResourceSystem::ResourceSystem(const VFS::Manager *vfs) + : mVFS(vfs) + { + mNifFileManager.reset(new NifFileManager(vfs)); + mKeyframeManager.reset(new KeyframeManager(vfs)); + mTextureManager.reset(new TextureManager(vfs)); + mSceneManager.reset(new SceneManager(vfs, mTextureManager.get(), mNifFileManager.get())); + } + + ResourceSystem::~ResourceSystem() + { + // this has to be defined in the .cpp file as we can't delete incomplete types + } + + SceneManager* ResourceSystem::getSceneManager() + { + return mSceneManager.get(); + } + + TextureManager* ResourceSystem::getTextureManager() + { + return mTextureManager.get(); + } + + NifFileManager* ResourceSystem::getNifFileManager() + { + return mNifFileManager.get(); + } + + KeyframeManager* ResourceSystem::getKeyframeManager() + { + return mKeyframeManager.get(); + } + + void ResourceSystem::clearCache() + { + mNifFileManager->clearCache(); + } + + const VFS::Manager* ResourceSystem::getVFS() const + { + return mVFS; + } + +} diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp new file mode 100644 index 0000000000..3e1a793cac --- /dev/null +++ b/components/resource/resourcesystem.hpp @@ -0,0 +1,52 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_RESOURCESYSTEM_H +#define OPENMW_COMPONENTS_RESOURCE_RESOURCESYSTEM_H + +#include + +namespace VFS +{ + class Manager; +} + +namespace Resource +{ + + class SceneManager; + class TextureManager; + class NifFileManager; + class KeyframeManager; + + /// @brief Wrapper class that constructs and provides access to the most commonly used resource subsystems. + /// @par Resource subsystems can be used with multiple OpenGL contexts, just like the OSG equivalents, but + /// are built around the use of a single virtual file system. + class ResourceSystem + { + public: + ResourceSystem(const VFS::Manager* vfs); + ~ResourceSystem(); + + SceneManager* getSceneManager(); + TextureManager* getTextureManager(); + NifFileManager* getNifFileManager(); + KeyframeManager* getKeyframeManager(); + + /// Indicates to each resource manager to clear the cache, i.e. to drop cached objects that are no longer referenced. + void clearCache(); + + const VFS::Manager* getVFS() const; + + private: + std::auto_ptr mSceneManager; + std::auto_ptr mTextureManager; + std::auto_ptr mNifFileManager; + std::auto_ptr mKeyframeManager; + + const VFS::Manager* mVFS; + + ResourceSystem(const ResourceSystem&); + void operator = (const ResourceSystem&); + }; + +} + +#endif diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp new file mode 100644 index 0000000000..1d1c5a3656 --- /dev/null +++ b/components/resource/scenemanager.cpp @@ -0,0 +1,276 @@ +#include "scenemanager.hpp" + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include +#include + +#include + +#include +#include + +#include "texturemanager.hpp" +#include "niffilemanager.hpp" + +namespace +{ + + class InitWorldSpaceParticlesVisitor : public osg::NodeVisitor + { + public: + /// @param mask The node mask to set on ParticleSystem nodes. + InitWorldSpaceParticlesVisitor(unsigned int mask) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mMask(mask) + { + } + + bool isWorldSpaceParticleSystem(osgParticle::ParticleSystem* partsys) + { + // HACK: ParticleSystem has no getReferenceFrame() + return (partsys->getUserDataContainer() + && partsys->getUserDataContainer()->getNumDescriptions() > 0 + && partsys->getUserDataContainer()->getDescriptions()[0] == "worldspace"); + } + + void apply(osg::Geode& geode) + { + for (unsigned int i=0;i(geode.getDrawable(i))) + { + if (isWorldSpaceParticleSystem(partsys)) + { + // HACK: Ignore the InverseWorldMatrix transform the geode is attached to + if (geode.getNumParents() && geode.getParent(0)->getNumParents()) + transformInitialParticles(partsys, geode.getParent(0)->getParent(0)); + } + geode.setNodeMask(mMask); + } + } + } + +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) + // in OSG 3.3 and up Drawables can be directly in the scene graph without a Geode decorating them. + void apply(osg::Drawable& drw) + { + if (osgParticle::ParticleSystem* partsys = dynamic_cast(&drw)) + { + if (isWorldSpaceParticleSystem(partsys)) + { + // HACK: Ignore the InverseWorldMatrix transform the particle system is attached to + if (partsys->getNumParents() && partsys->getParent(0)->getNumParents()) + transformInitialParticles(partsys, partsys->getParent(0)->getParent(0)); + } + partsys->setNodeMask(mMask); + } + } +#endif + + void transformInitialParticles(osgParticle::ParticleSystem* partsys, osg::Node* node) + { + osg::MatrixList mats = node->getWorldMatrices(); + if (mats.empty()) + return; + osg::Matrixf worldMat = mats[0]; + worldMat.orthoNormalize(worldMat); // scale is already applied on the particle node + for (int i=0; inumParticles(); ++i) + { + partsys->getParticle(i)->transformPositionVelocity(worldMat); + } + + // transform initial bounds to worldspace + osg::BoundingSphere sphere(partsys->getInitialBound()); + SceneUtil::transformBoundingSphere(worldMat, sphere); + osg::BoundingBox box; + box.expandBy(sphere); + partsys->setInitialBound(box); + } + private: + unsigned int mMask; + }; +} + +namespace Resource +{ + + SceneManager::SceneManager(const VFS::Manager *vfs, Resource::TextureManager* textureManager, Resource::NifFileManager* nifFileManager) + : mVFS(vfs) + , mTextureManager(textureManager) + , mNifFileManager(nifFileManager) + , mParticleSystemMask(~0u) + { + } + + SceneManager::~SceneManager() + { + // this has to be defined in the .cpp file as we can't delete incomplete types + + } + + /// @brief Callback to read image files from the VFS. + class ImageReadCallback : public osgDB::ReadFileCallback + { + public: + ImageReadCallback(Resource::TextureManager* textureMgr) + : mTextureManager(textureMgr) + { + } + + virtual osgDB::ReaderWriter::ReadResult readImage(const std::string& filename, const osgDB::Options* options) + { + try + { + return osgDB::ReaderWriter::ReadResult(mTextureManager->getImage(filename), osgDB::ReaderWriter::ReadResult::FILE_LOADED); + } + catch (std::exception& e) + { + return osgDB::ReaderWriter::ReadResult(e.what()); + } + } + + private: + Resource::TextureManager* mTextureManager; + }; + + std::string getFileExtension(const std::string& file) + { + size_t extPos = file.find_last_of('.'); + if (extPos != std::string::npos && extPos+1 < file.size()) + return file.substr(extPos+1); + return std::string(); + } + + osg::ref_ptr load (Files::IStreamPtr file, const std::string& normalizedFilename, Resource::TextureManager* textureMgr, Resource::NifFileManager* nifFileManager) + { + std::string ext = getFileExtension(normalizedFilename); + if (ext == "nif") + return NifOsg::Loader::load(nifFileManager->get(normalizedFilename), textureMgr); + else + { + osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); + if (!reader) + { + std::stringstream errormsg; + errormsg << "Error loading " << normalizedFilename << ": no readerwriter for '" << ext << "' found" << std::endl; + throw std::runtime_error(errormsg.str()); + } + + osg::ref_ptr options (new osgDB::Options); + // Set a ReadFileCallback so that image files referenced in the model are read from our virtual file system instead of the osgDB. + // Note, for some formats (.obj/.mtl) that reference other (non-image) files a findFileCallback would be necessary. + // but findFileCallback does not support virtual files, so we can't implement it. + options->setReadFileCallback(new ImageReadCallback(textureMgr)); + + osgDB::ReaderWriter::ReadResult result = reader->readNode(*file, options); + if (!result.success()) + { + std::stringstream errormsg; + errormsg << "Error loading " << normalizedFilename << ": " << result.message() << " code " << result.status() << std::endl; + throw std::runtime_error(errormsg.str()); + } + return result.getNode(); + } + } + + osg::ref_ptr SceneManager::getTemplate(const std::string &name) + { + std::string normalized = name; + mVFS->normalizeFilename(normalized); + + Index::iterator it = mIndex.find(normalized); + if (it == mIndex.end()) + { + osg::ref_ptr loaded; + try + { + Files::IStreamPtr file = mVFS->get(normalized); + + loaded = load(file, normalized, mTextureManager, mNifFileManager); + } + catch (std::exception& e) + { + std::cerr << "Failed to load '" << name << "': " << e.what() << ", using marker_error.nif instead" << std::endl; + Files::IStreamPtr file = mVFS->get("meshes/marker_error.nif"); + normalized = "meshes/marker_error.nif"; + loaded = load(file, normalized, mTextureManager, mNifFileManager); + } + + osgDB::Registry::instance()->getOrCreateSharedStateManager()->share(loaded.get()); + + if (mIncrementalCompileOperation) + mIncrementalCompileOperation->add(loaded); + + mIndex[normalized] = loaded; + return loaded; + } + else + return it->second; + } + + osg::ref_ptr SceneManager::createInstance(const std::string &name) + { + osg::ref_ptr scene = getTemplate(name); + osg::ref_ptr cloned = osg::clone(scene.get(), SceneUtil::CopyOp()); + return cloned; + } + + osg::ref_ptr SceneManager::createInstance(const std::string &name, osg::Group* parentNode) + { + osg::ref_ptr cloned = createInstance(name); + attachTo(cloned, parentNode); + return cloned; + } + + void SceneManager::attachTo(osg::Node *instance, osg::Group *parentNode) const + { + parentNode->addChild(instance); + notifyAttached(instance); + } + + void SceneManager::releaseGLObjects(osg::State *state) + { + for (Index::iterator it = mIndex.begin(); it != mIndex.end(); ++it) + { + it->second->releaseGLObjects(state); + } + } + + void SceneManager::setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation *ico) + { + mIncrementalCompileOperation = ico; + } + + void SceneManager::notifyAttached(osg::Node *node) const + { + InitWorldSpaceParticlesVisitor visitor (mParticleSystemMask); + node->accept(visitor); + } + + const VFS::Manager* SceneManager::getVFS() const + { + return mVFS; + } + + Resource::TextureManager* SceneManager::getTextureManager() + { + return mTextureManager; + } + + void SceneManager::setParticleSystemMask(unsigned int mask) + { + mParticleSystemMask = mask; + } + +} diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp new file mode 100644 index 0000000000..67fa2ab433 --- /dev/null +++ b/components/resource/scenemanager.hpp @@ -0,0 +1,91 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_SCENEMANAGER_H +#define OPENMW_COMPONENTS_RESOURCE_SCENEMANAGER_H + +#include +#include + +#include +#include + +namespace Resource +{ + class TextureManager; + class NifFileManager; +} + +namespace VFS +{ + class Manager; +} + +namespace osgUtil +{ + class IncrementalCompileOperation; +} + +namespace Resource +{ + + /// @brief Handles loading and caching of scenes, e.g. NIF files + class SceneManager + { + public: + SceneManager(const VFS::Manager* vfs, Resource::TextureManager* textureManager, Resource::NifFileManager* nifFileManager); + ~SceneManager(); + + /// Get a read-only copy of this scene "template" + /// @note If the given filename does not exist or fails to load, an error marker mesh will be used instead. + /// If even the error marker mesh can not be found, an exception is thrown. + osg::ref_ptr getTemplate(const std::string& name); + + /// Create an instance of the given scene template + /// @see getTemplate + osg::ref_ptr createInstance(const std::string& name); + + /// Create an instance of the given scene template and immediately attach it to a parent node + /// @see getTemplate + osg::ref_ptr createInstance(const std::string& name, osg::Group* parentNode); + + /// Attach the given scene instance to the given parent node + /// @note You should have the parentNode in its intended position before calling this method, + /// so that world space particles of the \a instance get transformed correctly. + /// @note Assumes the given instance was not attached to any parents before. + void attachTo(osg::Node* instance, osg::Group* parentNode) const; + + /// Manually release created OpenGL objects for the given graphics context. This may be required + /// in cases where multiple contexts are used over the lifetime of the application. + void releaseGLObjects(osg::State* state); + + /// Set up an IncrementalCompileOperation for background compiling of loaded scenes. + void setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation* ico); + + /// @note SceneManager::attachTo calls this method automatically, only needs to be called by users if manually attaching + void notifyAttached(osg::Node* node) const; + + const VFS::Manager* getVFS() const; + + Resource::TextureManager* getTextureManager(); + + /// @param mask The node mask to apply to loaded particle system nodes. + void setParticleSystemMask(unsigned int mask); + + private: + const VFS::Manager* mVFS; + Resource::TextureManager* mTextureManager; + Resource::NifFileManager* mNifFileManager; + + osg::ref_ptr mIncrementalCompileOperation; + + unsigned int mParticleSystemMask; + + // observer_ptr? + typedef std::map > Index; + Index mIndex; + + SceneManager(const SceneManager&); + void operator = (const SceneManager&); + }; + +} + +#endif diff --git a/components/resource/texturemanager.cpp b/components/resource/texturemanager.cpp new file mode 100644 index 0000000000..d7f3fc61a1 --- /dev/null +++ b/components/resource/texturemanager.cpp @@ -0,0 +1,314 @@ +#include "texturemanager.hpp" + +#include +#include +#include +#include + +#include + +#include + +#ifdef OSG_LIBRARY_STATIC +// This list of plugins should match with the list in the top-level CMakelists.txt. +USE_OSGPLUGIN(png) +USE_OSGPLUGIN(tga) +USE_OSGPLUGIN(dds) +USE_OSGPLUGIN(jpeg) +#endif + +namespace +{ + + osg::ref_ptr createWarningTexture() + { + osg::ref_ptr warningImage = new osg::Image; + + int width = 8, height = 8; + warningImage->allocateImage(width, height, 1, GL_RGB, GL_UNSIGNED_BYTE); + assert (warningImage->isDataContiguous()); + unsigned char* data = warningImage->data(); + for (int i=0;i warningTexture = new osg::Texture2D; + warningTexture->setImage(warningImage); + return warningTexture; + } + +} + +namespace Resource +{ + + TextureManager::TextureManager(const VFS::Manager *vfs) + : mVFS(vfs) + , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR) + , mMagFilter(osg::Texture::LINEAR) + , mMaxAnisotropy(1) + , mWarningTexture(createWarningTexture()) + , mUnRefImageDataAfterApply(false) + { + + } + + TextureManager::~TextureManager() + { + + } + + void TextureManager::setUnRefImageDataAfterApply(bool unref) + { + mUnRefImageDataAfterApply = unref; + } + + void TextureManager::setFilterSettings(const std::string &magfilter, const std::string &minfilter, + const std::string &mipmap, int maxAnisotropy, + osgViewer::Viewer *viewer) + { + osg::Texture::FilterMode min = osg::Texture::LINEAR; + osg::Texture::FilterMode mag = osg::Texture::LINEAR; + + if(magfilter == "nearest") + mag = osg::Texture::NEAREST; + else if(magfilter != "linear") + std::cerr<< "Invalid texture mag filter: "<stopThreading(); + setFilterSettings(min, mag, maxAnisotropy); + if(viewer) viewer->startThreading(); + } + + void TextureManager::setFilterSettings(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter, int maxAnisotropy) + { + mMinFilter = minFilter; + mMagFilter = magFilter; + mMaxAnisotropy = std::max(1, maxAnisotropy); + + for (std::map >::iterator it = mTextures.begin(); it != mTextures.end(); ++it) + { + osg::ref_ptr tex = it->second; + + // Keep mip-mapping disabled if the texture creator explicitely requested no mipmapping. + osg::Texture::FilterMode oldMin = tex->getFilter(osg::Texture::MIN_FILTER); + if (oldMin == osg::Texture::LINEAR || oldMin == osg::Texture::NEAREST) + { + osg::Texture::FilterMode newMin = osg::Texture::LINEAR; + switch (mMinFilter) + { + case osg::Texture::LINEAR: + case osg::Texture::LINEAR_MIPMAP_LINEAR: + case osg::Texture::LINEAR_MIPMAP_NEAREST: + newMin = osg::Texture::LINEAR; + break; + case osg::Texture::NEAREST: + case osg::Texture::NEAREST_MIPMAP_LINEAR: + case osg::Texture::NEAREST_MIPMAP_NEAREST: + newMin = osg::Texture::NEAREST; + break; + } + tex->setFilter(osg::Texture::MIN_FILTER, newMin); + } + else + tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); + + tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); + tex->setMaxAnisotropy(static_cast(mMaxAnisotropy)); + } + } + + /* + osg::ref_ptr TextureManager::getImage(const std::string &filename) + { + + } + */ + + bool checkSupported(osg::Image* image, const std::string& filename) + { + switch(image->getPixelFormat()) + { + case(GL_COMPRESSED_RGB_S3TC_DXT1_EXT): + case(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT): + case(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT): + case(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT): + { +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) + osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); + if (exts && !exts->isTextureCompressionS3TCSupported + // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG. + && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) +#else + osg::Texture::Extensions* exts = osg::Texture::getExtensions(0, false); + if (exts && !exts->isTextureCompressionS3TCSupported() + // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG. + && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) +#endif + { + std::cerr << "Error loading " << filename << ": no S3TC texture compression support installed" << std::endl; + return false; + } + break; + } + // not bothering with checks for other compression formats right now, we are unlikely to ever use those anyway + default: + return true; + } + return true; + } + + osg::ref_ptr TextureManager::getImage(const std::string &filename) + { + std::string normalized = filename; + mVFS->normalizeFilename(normalized); + std::map >::iterator found = mImages.find(normalized); + if (found != mImages.end()) + return found->second; + else + { + Files::IStreamPtr stream; + try + { + stream = mVFS->get(normalized.c_str()); + } + catch (std::exception& e) + { + std::cerr << "Failed to open image: " << e.what() << std::endl; + return NULL; + } + + osg::ref_ptr opts (new osgDB::Options); + opts->setOptionString("dds_dxt1_detect_rgba"); // tx_creature_werewolf.dds isn't loading in the correct format without this option + size_t extPos = normalized.find_last_of('.'); + std::string ext; + if (extPos != std::string::npos && extPos+1 < normalized.size()) + ext = normalized.substr(extPos+1); + osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); + if (!reader) + { + std::cerr << "Error loading " << filename << ": no readerwriter for '" << ext << "' found" << std::endl; + return NULL; + } + + osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, opts); + if (!result.success()) + { + std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl; + return NULL; + } + + osg::Image* image = result.getImage(); + if (!checkSupported(image, filename)) + { + return NULL; + } + + mImages.insert(std::make_pair(normalized, image)); + return image; + } + } + + osg::ref_ptr TextureManager::getTexture2D(const std::string &filename, osg::Texture::WrapMode wrapS, osg::Texture::WrapMode wrapT) + { + std::string normalized = filename; + mVFS->normalizeFilename(normalized); + MapKey key = std::make_pair(std::make_pair(wrapS, wrapT), normalized); + std::map >::iterator found = mTextures.find(key); + if (found != mTextures.end()) + { + return found->second; + } + else + { + Files::IStreamPtr stream; + try + { + stream = mVFS->get(normalized.c_str()); + } + catch (std::exception& e) + { + std::cerr << "Failed to open texture: " << e.what() << std::endl; + return mWarningTexture; + } + + osg::ref_ptr opts (new osgDB::Options); + opts->setOptionString("dds_dxt1_detect_rgba"); // tx_creature_werewolf.dds isn't loading in the correct format without this option + size_t extPos = normalized.find_last_of('.'); + std::string ext; + if (extPos != std::string::npos && extPos+1 < normalized.size()) + ext = normalized.substr(extPos+1); + osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); + if (!reader) + { + std::cerr << "Error loading " << filename << ": no readerwriter for '" << ext << "' found" << std::endl; + return mWarningTexture; + } + + osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, opts); + if (!result.success()) + { + std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl; + return mWarningTexture; + } + + osg::Image* image = result.getImage(); + if (!checkSupported(image, filename)) + { + return mWarningTexture; + } + + // We need to flip images, because the Morrowind texture coordinates use the DirectX convention (top-left image origin), + // but OpenGL uses bottom left as the image origin. + // For some reason this doesn't concern DDS textures, which are already flipped when loaded. + if (ext != "dds") + { + image->flipVertical(); + } + + osg::ref_ptr texture(new osg::Texture2D); + texture->setImage(image); + texture->setWrap(osg::Texture::WRAP_S, wrapS); + texture->setWrap(osg::Texture::WRAP_T, wrapT); + texture->setFilter(osg::Texture::MIN_FILTER, mMinFilter); + texture->setFilter(osg::Texture::MAG_FILTER, mMagFilter); + texture->setMaxAnisotropy(mMaxAnisotropy); + + texture->setUnRefImageDataAfterApply(mUnRefImageDataAfterApply); + + mTextures.insert(std::make_pair(key, texture)); + return texture; + } + } + + osg::Texture2D* TextureManager::getWarningTexture() + { + return mWarningTexture.get(); + } + +} diff --git a/components/resource/texturemanager.hpp b/components/resource/texturemanager.hpp new file mode 100644 index 0000000000..e12dfa0900 --- /dev/null +++ b/components/resource/texturemanager.hpp @@ -0,0 +1,76 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_TEXTUREMANAGER_H +#define OPENMW_COMPONENTS_RESOURCE_TEXTUREMANAGER_H + +#include +#include + +#include +#include +#include + +namespace osgViewer +{ + class Viewer; +} + +namespace VFS +{ + class Manager; +} + +namespace Resource +{ + + /// @brief Handles loading/caching of Images and Texture StateAttributes. + class TextureManager + { + public: + TextureManager(const VFS::Manager* vfs); + ~TextureManager(); + + void setFilterSettings(const std::string &magfilter, const std::string &minfilter, + const std::string &mipmap, int maxAnisotropy, + osgViewer::Viewer *view); + + /// Keep a copy of the texture data around in system memory? This is needed when using multiple graphics contexts, + /// otherwise should be disabled to reduce memory usage. + void setUnRefImageDataAfterApply(bool unref); + + /// Create or retrieve a Texture2D using the specified image filename, and wrap parameters. + /// Returns the dummy texture if the given texture is not found. + osg::ref_ptr getTexture2D(const std::string& filename, osg::Texture::WrapMode wrapS, osg::Texture::WrapMode wrapT); + + /// Create or retrieve an Image + osg::ref_ptr getImage(const std::string& filename); + + const VFS::Manager* getVFS() { return mVFS; } + + osg::Texture2D* getWarningTexture(); + + private: + const VFS::Manager* mVFS; + + osg::Texture::FilterMode mMinFilter; + osg::Texture::FilterMode mMagFilter; + int mMaxAnisotropy; + + typedef std::pair, std::string> MapKey; + + std::map > mImages; + + std::map > mTextures; + + osg::ref_ptr mWarningTexture; + + bool mUnRefImageDataAfterApply; + + /// @warning It is unsafe to call this function when a draw thread is using the textures. Call stopThreading() first! + void setFilterSettings(osg::Texture::FilterMode minFilter, osg::Texture::FilterMode maxFilter, int maxAnisotropy); + + TextureManager(const TextureManager&); + void operator = (const TextureManager&); + }; + +} + +#endif diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp new file mode 100644 index 0000000000..d8cfa428a2 --- /dev/null +++ b/components/sceneutil/attach.cpp @@ -0,0 +1,115 @@ +#include "attach.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "visitor.hpp" + +namespace SceneUtil +{ + + class CopyRigVisitor : public osg::NodeVisitor + { + public: + CopyRigVisitor(osg::ref_ptr parent, const std::string& filter) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mParent(parent) + , mFilter(Misc::StringUtils::lowerCase(filter)) + { + mFilter2 = "tri " + mFilter; + } + + virtual void apply(osg::Node& node) + { + std::string lowerName = Misc::StringUtils::lowerCase(node.getName()); + if ((lowerName.size() >= mFilter.size() && lowerName.compare(0, mFilter.size(), mFilter) == 0) + || (lowerName.size() >= mFilter2.size() && lowerName.compare(0, mFilter2.size(), mFilter2) == 0)) + { + mParent->addChild(&node); + } + else + traverse(node); + } + + private: + osg::ref_ptr mParent; + std::string mFilter; + std::string mFilter2; + }; + + osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node *master, const std::string &filter, const std::string &attachNode) + { + if (dynamic_cast(toAttach.get())) + { + osg::ref_ptr handle = new osg::Group; + + CopyRigVisitor copyVisitor(handle, filter); + toAttach->accept(copyVisitor); + + master->asGroup()->addChild(handle); + + return handle; + } + else + { + FindByNameVisitor find(attachNode); + master->accept(find); + if (!find.mFoundNode) + throw std::runtime_error(std::string("Can't find attachment node ") + attachNode); + + FindByNameVisitor findBoneOffset("BoneOffset"); + toAttach->accept(findBoneOffset); + + osg::ref_ptr trans; + + if (findBoneOffset.mFoundNode) + { + osg::MatrixTransform* boneOffset = dynamic_cast(findBoneOffset.mFoundNode); + if (!boneOffset) + throw std::runtime_error("BoneOffset must be a MatrixTransform"); + + trans = new osg::PositionAttitudeTransform; + trans->setPosition(boneOffset->getMatrix().getTrans()); + // The BoneOffset rotation seems to be incorrect + trans->setAttitude(osg::Quat(osg::DegreesToRadians(-90.f), osg::Vec3f(1,0,0))); + } + + if (attachNode.find("Left") != std::string::npos) + { + if (!trans) + trans = new osg::PositionAttitudeTransform; + trans->setScale(osg::Vec3f(-1.f, 1.f, 1.f)); + + // Need to invert culling because of the negative scale + // Note: for absolute correctness we would need to check the current front face for every mesh then invert it + // However MW isn't doing this either, so don't. Assuming all meshes are using backface culling is more efficient. + osg::FrontFace* frontFace = new osg::FrontFace; + frontFace->setMode(osg::FrontFace::CLOCKWISE); + trans->getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON); + } + + if (trans) + { + find.mFoundNode->addChild(trans); + trans->addChild(toAttach); + return trans; + } + else + { + find.mFoundNode->addChild(toAttach); + return toAttach; + } + } + } + +} diff --git a/components/sceneutil/attach.hpp b/components/sceneutil/attach.hpp new file mode 100644 index 0000000000..72f7809e9d --- /dev/null +++ b/components/sceneutil/attach.hpp @@ -0,0 +1,25 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_ATTACH_H +#define OPENMW_COMPONENTS_SCENEUTIL_ATTACH_H + +#include + +#include + +namespace osg +{ + class Node; +} + +namespace SceneUtil +{ + + /// Attach parts of the \a toAttach scenegraph to the \a master scenegraph, using the specified filter and attachment node. + /// If the \a toAttach scene graph contains skinned objects, we will attach only those (filtered by the \a filter). + /// Otherwise, just attach all of the toAttach scenegraph to the attachment node on the master scenegraph, with no filtering. + /// @note The master scene graph is expected to include a skeleton. + /// @return A newly created node that is directly attached to the master scene graph + osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node* master, const std::string& filter, const std::string& attachNode); + +} + +#endif diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp new file mode 100644 index 0000000000..a372b1ebd8 --- /dev/null +++ b/components/sceneutil/clone.cpp @@ -0,0 +1,127 @@ +#include "clone.hpp" + +#include +#include + +#include +#include +#include +#include + +#include + +#include + +namespace SceneUtil +{ + + CopyOp::CopyOp() + { + setCopyFlags(osg::CopyOp::DEEP_COPY_NODES + // Controller might need different inputs per scene instance + | osg::CopyOp::DEEP_COPY_CALLBACKS + | osg::CopyOp::DEEP_COPY_USERDATA); + } + + osg::StateSet* CopyOp::operator ()(const osg::StateSet* stateset) const + { + if (!stateset) + return NULL; + if (stateset->getDataVariance() == osg::StateSet::DYNAMIC) + return osg::clone(stateset, *this); + return const_cast(stateset); + } + + osg::Node* CopyOp::operator ()(const osg::Node* node) const + { + if (const osgParticle::ParticleProcessor* processor = dynamic_cast(node)) + return operator()(processor); + if (const osgParticle::ParticleSystemUpdater* updater = dynamic_cast(node)) + { + osgParticle::ParticleSystemUpdater* cloned = osg::clone(updater, *this); + mMap2[cloned] = updater->getParticleSystem(0); + return cloned; + } + return osg::CopyOp::operator()(node); + } + + osg::Drawable* CopyOp::operator ()(const osg::Drawable* drawable) const + { + if (const osgParticle::ParticleSystem* partsys = dynamic_cast(drawable)) + return operator()(partsys); + if (dynamic_cast(drawable)) + { + osg::CopyOp copyop = *this; + copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_ARRAYS); + +#if OSG_VERSION_LESS_THAN(3,5,0) + /* + + Deep copy of primitives required to work around the following (bad?) code in osg::Geometry copy constructor: + + if ((copyop.getCopyFlags() & osg::CopyOp::DEEP_COPY_ARRAYS)) + { + if (_useVertexBufferObjects) + { + // copying of arrays doesn't set up buffer objects so we'll need to force + // Geometry to assign these, we'll do this by switching off VBO's then renabling them. + setUseVertexBufferObjects(false); + setUseVertexBufferObjects(true); + } + } + + In case of DEEP_COPY_PRIMITIVES=Off, DEEP_COPY_ARRAYS=On, the above code makes a modification to the original const Geometry& we copied from, + causing problems if we relied on the original Geometry to remain static such as when it was added to an osgUtil::IncrementalCompileOperation. + + Fixed in OSG 3.5 ( http://forum.openscenegraph.org/viewtopic.php?t=15217 ). + + */ + + copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_PRIMITIVES); +#endif + + osg::Drawable* cloned = osg::clone(drawable, copyop); + if (cloned->getUpdateCallback()) + cloned->setUpdateCallback(osg::clone(cloned->getUpdateCallback(), *this)); + return cloned; + } + if (dynamic_cast(drawable)) + { + return osg::clone(drawable, *this); + } + + + return osg::CopyOp::operator()(drawable); + } + + osgParticle::ParticleProcessor* CopyOp::operator() (const osgParticle::ParticleProcessor* processor) const + { + osgParticle::ParticleProcessor* cloned = osg::clone(processor, *this); + mMap[cloned] = processor->getParticleSystem(); + return cloned; + } + + osgParticle::ParticleSystem* CopyOp::operator ()(const osgParticle::ParticleSystem* partsys) const + { + osgParticle::ParticleSystem* cloned = osg::clone(partsys, *this); + + for (std::map::const_iterator it = mMap.begin(); it != mMap.end(); ++it) + { + if (it->second == partsys) + { + it->first->setParticleSystem(cloned); + } + } + for (std::map::const_iterator it = mMap2.begin(); it != mMap2.end(); ++it) + { + if (it->second == partsys) + { + osgParticle::ParticleSystemUpdater* updater = it->first; + updater->removeParticleSystem(updater->getParticleSystem(0)); + updater->addParticleSystem(cloned); + } + } + return cloned; + } + +} diff --git a/components/sceneutil/clone.hpp b/components/sceneutil/clone.hpp new file mode 100644 index 0000000000..662dad543a --- /dev/null +++ b/components/sceneutil/clone.hpp @@ -0,0 +1,45 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_CLONE_H +#define OPENMW_COMPONENTS_SCENEUTIL_CLONE_H + +#include + +#include + +namespace osgParticle +{ + class ParticleProcessor; + class ParticleSystem; + class ParticleSystemUpdater; +} + +namespace SceneUtil +{ + + /// @par Defines the cloning behaviour we need: + /// * Assigns updated ParticleSystem pointers on cloned emitters and programs. + /// * Creates deep copy of StateSets if they have a DYNAMIC data variance. + /// * Deep copies RigGeometry and MorphGeometry so they can animate without affecting clones. + /// @warning Do not use an object of this class for more than one copy operation. + class CopyOp : public osg::CopyOp + { + public: + CopyOp(); + + virtual osgParticle::ParticleSystem* operator() (const osgParticle::ParticleSystem* partsys) const; + virtual osgParticle::ParticleProcessor* operator() (const osgParticle::ParticleProcessor* processor) const; + + virtual osg::Node* operator() (const osg::Node* node) const; + virtual osg::Drawable* operator() (const osg::Drawable* drawable) const; + + virtual osg::StateSet* operator() (const osg::StateSet* stateset) const; + + private: + // maps new ParticleProcessor to their old ParticleSystem pointer + // a little messy, but I think this should be the most efficient way + mutable std::map mMap; + mutable std::map mMap2; + }; + +} + +#endif diff --git a/components/sceneutil/controller.cpp b/components/sceneutil/controller.cpp new file mode 100644 index 0000000000..7762b48d07 --- /dev/null +++ b/components/sceneutil/controller.cpp @@ -0,0 +1,146 @@ +#include "controller.hpp" + +#include "statesetupdater.hpp" + +#include +#include +#include +#include + +namespace SceneUtil +{ + + + Controller::Controller() + { + } + + bool Controller::hasInput() const + { + return mSource.get() != NULL; + } + + float Controller::getInputValue(osg::NodeVisitor* nv) + { + if (mFunction) + return mFunction->calculate(mSource->getValue(nv)); + else + return mSource->getValue(nv); + } + + void Controller::setSource(boost::shared_ptr source) + { + mSource = source; + } + + void Controller::setFunction(boost::shared_ptr function) + { + mFunction = function; + } + + boost::shared_ptr Controller::getSource() const + { + return mSource; + } + + boost::shared_ptr Controller::getFunction() const + { + return mFunction; + } + + FrameTimeSource::FrameTimeSource() + { + } + + float FrameTimeSource::getValue(osg::NodeVisitor *nv) + { + return nv->getFrameStamp()->getSimulationTime(); + } + + ControllerVisitor::ControllerVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + + } + + void ControllerVisitor::apply(osg::Node &node) + { +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) + osg::Callback* callback = node.getUpdateCallback(); +#else + osg::NodeCallback* callback = node.getUpdateCallback(); +#endif + while (callback) + { + if (Controller* ctrl = dynamic_cast(callback)) + visit(node, *ctrl); + if (CompositeStateSetUpdater* composite = dynamic_cast(callback)) + { + for (unsigned int i=0; igetNumControllers(); ++i) + { + StateSetUpdater* statesetcontroller = composite->getController(i); + if (Controller* ctrl = dynamic_cast(statesetcontroller)) + visit(node, *ctrl); + } + } + + callback = callback->getNestedCallback(); + } + + traverse(node); + } + + void ControllerVisitor::apply(osg::Geode &geode) + { + for (unsigned int i=0; igetUpdateCallback(); +#else + osg::Drawable::UpdateCallback* callback = drw->getUpdateCallback(); +#endif + + if (Controller* ctrl = dynamic_cast(callback)) + visit(geode, *ctrl); + } + + apply(static_cast(geode)); + } + + AssignControllerSourcesVisitor::AssignControllerSourcesVisitor() + : ControllerVisitor() + { + } + + AssignControllerSourcesVisitor::AssignControllerSourcesVisitor(boost::shared_ptr toAssign) + : ControllerVisitor() + , mToAssign(toAssign) + { + } + + void AssignControllerSourcesVisitor::visit(osg::Node&, Controller &ctrl) + { + if (!ctrl.getSource()) + ctrl.setSource(mToAssign); + } + + FindMaxControllerLengthVisitor::FindMaxControllerLengthVisitor() + : SceneUtil::ControllerVisitor() + , mMaxLength(0) + { + } + + void FindMaxControllerLengthVisitor::visit(osg::Node &, Controller &ctrl) + { + if (ctrl.getFunction()) + mMaxLength = std::max(mMaxLength, ctrl.getFunction()->getMaximum()); + } + + float FindMaxControllerLengthVisitor::getMaxLength() const + { + return mMaxLength; + } + +} diff --git a/components/sceneutil/controller.hpp b/components/sceneutil/controller.hpp new file mode 100644 index 0000000000..7399ecad58 --- /dev/null +++ b/components/sceneutil/controller.hpp @@ -0,0 +1,101 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_CONTROLLER_H +#define OPENMW_COMPONENTS_SCENEUTIL_CONTROLLER_H + +#include + +#include + +namespace SceneUtil +{ + + class ControllerSource + { + public: + virtual ~ControllerSource() { } + virtual float getValue(osg::NodeVisitor* nv) = 0; + }; + + class FrameTimeSource : public ControllerSource + { + public: + FrameTimeSource(); + virtual float getValue(osg::NodeVisitor* nv); + }; + + /// @note ControllerFunctions may be shared - you should not hold any state in it. That is why all its methods are declared const. + class ControllerFunction + { + public: + virtual float calculate(float input) const = 0; + + /// Get the "stop time" of the controller function, typically the maximum of the calculate() function. + /// May not be meaningful for all types of controller functions. + virtual float getMaximum() const = 0; + }; + + class Controller + { + public: + Controller(); + virtual ~Controller() {} + + bool hasInput() const; + + float getInputValue(osg::NodeVisitor* nv); + + void setSource(boost::shared_ptr source); + void setFunction(boost::shared_ptr function); + + boost::shared_ptr getSource() const; + boost::shared_ptr getFunction() const; + + private: + boost::shared_ptr mSource; + + // The source value gets passed through this function before it's passed on to the DestValue. + boost::shared_ptr mFunction; + }; + + /// Pure virtual base class - visit() all controllers that are attached as UpdateCallbacks in a scene graph. + class ControllerVisitor : public osg::NodeVisitor + { + public: + ControllerVisitor(); + + virtual void apply(osg::Node& node); + virtual void apply(osg::Geode& geode); + + virtual void visit(osg::Node& node, Controller& ctrl) = 0; + }; + + class AssignControllerSourcesVisitor : public ControllerVisitor + { + public: + AssignControllerSourcesVisitor(); + AssignControllerSourcesVisitor(boost::shared_ptr toAssign); + + /// Assign the wanted ControllerSource. May be overridden in derived classes. + /// By default assigns the ControllerSource passed to the constructor of this class if no ControllerSource is assigned to that controller yet. + virtual void visit(osg::Node& node, Controller& ctrl); + + private: + boost::shared_ptr mToAssign; + }; + + /// Finds the maximum of all controller functions in the given scene graph + class FindMaxControllerLengthVisitor : public ControllerVisitor + { + public: + FindMaxControllerLengthVisitor(); + + virtual void visit(osg::Node& , Controller& ctrl); + + float getMaxLength() const; + + private: + float mMaxLength; + }; + +} + +#endif diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp new file mode 100644 index 0000000000..511937a282 --- /dev/null +++ b/components/sceneutil/lightcontroller.cpp @@ -0,0 +1,133 @@ +#include "lightcontroller.hpp" + +#include + +#include + +#include + +#include + +namespace +{ + + float pulseAmplitude(float time) + { + return std::sin(time); + } + + float flickerAmplitude(float time) + { + static const float fb = 1.17024f; + static const float f[3] = { 1.5708f, 4.18774f, 5.19934f }; + static const float o[3] = { 0.804248f, 2.11115f, 3.46832f }; + static const float m[3] = { 1.0f, 0.785f, 0.876f }; + static const float s = 0.394f; + + float v = 0.0f; + for(int i = 0;i < 3;++i) + v += std::sin(fb*time*f[i] + o[1])*m[i]; + return v * s; + } + + float flickerFrequency(float phase) + { + static const float fa = 0.785398f; + static const float tdo = 0.94f; + static const float tdm = 2.48f; + + return tdo + tdm*std::sin(fa * phase); + } + +} + +namespace SceneUtil +{ + + LightController::LightController() + : mType(LT_Normal) + , mPhase((Misc::Rng::rollClosedProbability() * 2.f - 1.f) * 500.f) + , mDeltaCount(0.f) + , mDirection(1.f) + , mLastTime(0.0) + { + } + + void LightController::setType(LightController::LightType type) + { + mType = type; + } + + void LightController::operator ()(osg::Node* node, osg::NodeVisitor* nv) + { + double time = nv->getFrameStamp()->getSimulationTime(); + + // disabled early out, light state needs to be set every frame regardless of change, due to the double buffering + //if (time == mLastTime) + // return; + + float dt = static_cast(time - mLastTime); + mLastTime = time; + + float brightness = 1.0f; + float cycle_time; + float time_distortion; + + const float pi = 3.14159265359; + + if(mType == LT_Pulse || mType == LT_PulseSlow) + { + cycle_time = 2.0f * pi; + time_distortion = mType == LT_Pulse ? 20.0f : 4.f; + } + else + { + static const float fa = 0.785398f; + static const float phase_wavelength = 120.0f * pi / fa; + + cycle_time = 500.0f; + mPhase = std::fmod(mPhase + dt, phase_wavelength); + time_distortion = flickerFrequency(mPhase); + } + + mDeltaCount += mDirection*dt*time_distortion; + if(mDirection > 0 && mDeltaCount > +cycle_time) + { + mDirection = -1.0f; + mDeltaCount = 2.0f*cycle_time - mDeltaCount; + } + if(mDirection < 0 && mDeltaCount < -cycle_time) + { + mDirection = +1.0f; + mDeltaCount = -2.0f*cycle_time - mDeltaCount; + } + + static const float fast = 4.0f/1.0f; + static const float slow = 1.0f/1.0f; + + // These formulas are just guesswork, but they work pretty well + if(mType == LT_Normal) + { + // Less than 1/255 light modifier for a constant light: + brightness = 1.0f + flickerAmplitude(mDeltaCount*slow)/255.0f; + } + else if(mType == LT_Flicker) + brightness = 0.75f + flickerAmplitude(mDeltaCount*fast)*0.25f; + else if(mType == LT_FlickerSlow) + brightness = 0.75f + flickerAmplitude(mDeltaCount*slow)*0.25f; + else if(mType == LT_Pulse) + brightness = 0.7f + pulseAmplitude(mDeltaCount*fast)*0.3f; + else if(mType == LT_PulseSlow) + brightness = 0.7f + pulseAmplitude(mDeltaCount*slow)*0.3f; + + static_cast(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * brightness); + + traverse(node, nv); + } + + void LightController::setDiffuse(osg::Vec4f color) + { + mDiffuseColor = color; + } + +} diff --git a/components/sceneutil/lightcontroller.hpp b/components/sceneutil/lightcontroller.hpp new file mode 100644 index 0000000000..f6e2fa9faf --- /dev/null +++ b/components/sceneutil/lightcontroller.hpp @@ -0,0 +1,42 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_LIGHTCONTROLLER_H +#define OPENMW_COMPONENTS_SCENEUTIL_LIGHTCONTROLLER_H + +#include +#include + +namespace SceneUtil +{ + + /// @brief Controller class to handle a pulsing and/or flickering light + /// @note Must be set on a SceneUtil::LightSource. + class LightController : public osg::NodeCallback + { + public: + enum LightType { + LT_Normal, + LT_Flicker, + LT_FlickerSlow, + LT_Pulse, + LT_PulseSlow + }; + + LightController(); + + void setType(LightType type); + + void setDiffuse(osg::Vec4f color); + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv); + + private: + LightType mType; + osg::Vec4f mDiffuseColor; + float mPhase; + float mDeltaCount; + int mDirection; + double mLastTime; + }; + +} + +#endif diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp new file mode 100644 index 0000000000..1706bb2b1e --- /dev/null +++ b/components/sceneutil/lightmanager.cpp @@ -0,0 +1,398 @@ +#include "lightmanager.hpp" + +#include + +#include +#include + +#include + +#include + +#include + +namespace SceneUtil +{ + + // Resets the modelview matrix to just the view matrix before applying lights. + class LightStateAttribute : public osg::StateAttribute + { + public: + LightStateAttribute() : mIndex(0) {} + LightStateAttribute(unsigned int index, const std::vector >& lights) : mIndex(index), mLights(lights) {} + + LightStateAttribute(const LightStateAttribute& copy,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) + : osg::StateAttribute(copy,copyop), mIndex(copy.mIndex), mLights(copy.mLights) {} + + unsigned int getMember() const + { + return mIndex; + } + + virtual bool getModeUsage(ModeUsage & usage) const + { + for (unsigned int i=0; isetLightNum(i+mIndex); + mLights[i]->apply(state); + } + + state.applyModelViewMatrix(modelViewMatrix); + } + + private: + unsigned int mIndex; + + std::vector > mLights; + }; + + LightManager* findLightManager(const osg::NodePath& path) + { + for (unsigned int i=0;i(path[i])) + return lightManager; + } + return NULL; + } + + // Set on a LightSource. Adds the light source to its light manager for the current frame. + // This allows us to keep track of the current lights in the scene graph without tying creation & destruction to the manager. + class CollectLightCallback : public osg::NodeCallback + { + public: + CollectLightCallback() + : mLightManager(0) { } + + CollectLightCallback(const CollectLightCallback& copy, const osg::CopyOp& copyop) + : osg::NodeCallback(copy, copyop) + , mLightManager(0) { } + + META_Object(SceneUtil, SceneUtil::CollectLightCallback) + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if (!mLightManager) + { + mLightManager = findLightManager(nv->getNodePath()); + + if (!mLightManager) + throw std::runtime_error("can't find parent LightManager"); + } + + mLightManager->addLight(static_cast(node), osg::computeLocalToWorld(nv->getNodePath()), nv->getTraversalNumber()); + + traverse(node, nv); + } + + private: + LightManager* mLightManager; + }; + + // Set on a LightManager. Clears the data from the previous frame. + class LightManagerUpdateCallback : public osg::NodeCallback + { + public: + LightManagerUpdateCallback() + { } + + LightManagerUpdateCallback(const LightManagerUpdateCallback& copy, const osg::CopyOp& copyop) + : osg::NodeCallback(copy, copyop) + { } + + META_Object(SceneUtil, SceneUtil::LightManagerUpdateCallback) + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + LightManager* lightManager = static_cast(node); + lightManager->update(); + + traverse(node, nv); + } + }; + + LightManager::LightManager() + : mStartLight(0) + , mLightingMask(~0u) + { + setUpdateCallback(new LightManagerUpdateCallback); + } + + LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op) + : osg::Group(copy, copyop) + , mStartLight(copy.mStartLight) + , mLightingMask(copy.mLightingMask) + { + + } + + void LightManager::setLightingMask(unsigned int mask) + { + mLightingMask = mask; + } + + unsigned int LightManager::getLightingMask() const + { + return mLightingMask; + } + + void LightManager::update() + { + mLights.clear(); + mLightsInViewSpace.clear(); + + // do an occasional cleanup for orphaned lights + for (int i=0; i<2; ++i) + { + if (mStateSetCache[i].size() > 5000) + mStateSetCache[i].clear(); + } + } + + void LightManager::addLight(LightSource* lightSource, const osg::Matrixf& worldMat, unsigned int frameNum) + { + LightSourceTransform l; + l.mLightSource = lightSource; + l.mWorldMatrix = worldMat; + lightSource->getLight(frameNum)->setPosition(osg::Vec4f(worldMat.getTrans().x(), + worldMat.getTrans().y(), + worldMat.getTrans().z(), 1.f)); + mLights.push_back(l); + } + + osg::ref_ptr LightManager::getLightListStateSet(const LightList &lightList, unsigned int frameNum) + { + // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists) + size_t hash = 0; + for (unsigned int i=0; imLightSource->getId()); + + LightStateSetMap& stateSetCache = mStateSetCache[frameNum%2]; + + LightStateSetMap::iterator found = stateSetCache.find(hash); + if (found != stateSetCache.end()) + return found->second; + else + { + + std::vector > lights; + for (unsigned int i=0; imLightSource->getLight(frameNum)); + + osg::ref_ptr attr = new LightStateAttribute(mStartLight, lights); + + osg::ref_ptr stateset = new osg::StateSet; + + // don't use setAttributeAndModes, that does not support light indices! + stateset->setAttribute(attr, osg::StateAttribute::ON); + stateset->setAssociatedModes(attr, osg::StateAttribute::ON); + + stateSetCache.insert(std::make_pair(hash, stateset)); + return stateset; + } + } + + const std::vector& LightManager::getLights() const + { + return mLights; + } + + const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix) + { + osg::observer_ptr camPtr (camera); + std::map, LightSourceViewBoundCollection>::iterator it = mLightsInViewSpace.find(camPtr); + + if (it == mLightsInViewSpace.end()) + { + it = mLightsInViewSpace.insert(std::make_pair(camPtr, LightSourceViewBoundCollection())).first; + + for (std::vector::iterator lightIt = mLights.begin(); lightIt != mLights.end(); ++lightIt) + { + osg::Matrixf worldViewMat = lightIt->mWorldMatrix * (*viewMatrix); + osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), lightIt->mLightSource->getRadius()); + transformBoundingSphere(worldViewMat, viewBound); + + LightSourceViewBound l; + l.mLightSource = lightIt->mLightSource; + l.mViewBound = viewBound; + it->second.push_back(l); + } + } + return it->second; + } + + void LightManager::setStartLight(int start) + { + mStartLight = start; + } + + int LightManager::getStartLight() const + { + return mStartLight; + } + + static int sLightId = 0; + + LightSource::LightSource() + : mRadius(0.f) + { + setUpdateCallback(new CollectLightCallback); + mId = sLightId++; + } + + LightSource::LightSource(const LightSource ©, const osg::CopyOp ©op) + : osg::Node(copy, copyop) + , mRadius(copy.mRadius) + { + mId = sLightId++; + + for (int i=0; i<2; ++i) + mLight[i] = osg::clone(copy.mLight[i].get(), copyop); + } + + + bool sortLights (const LightManager::LightSourceViewBound* left, const LightManager::LightSourceViewBound* right) + { + return left->mViewBound.center().length2() - left->mViewBound.radius2()/4.f < right->mViewBound.center().length2() - right->mViewBound.radius2()/4.f; + } + + void LightListCallback::operator()(osg::Node *node, osg::NodeVisitor *nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); + + if (!mLightManager) + { + mLightManager = findLightManager(nv->getNodePath()); + if (!mLightManager) + { + traverse(node, nv); + return; + } + } + + if (!(cv->getCurrentCamera()->getCullMask() & mLightManager->getLightingMask())) + { + traverse(node, nv); + return; + } + + // Possible optimizations: + // - cull list of lights by the camera frustum + // - organize lights in a quad tree + + + // update light list if necessary + // makes sure we don't update it more than once per frame when rendering with multiple cameras + if (mLastFrameNumber != nv->getTraversalNumber()) + { + mLastFrameNumber = nv->getTraversalNumber(); + + // Don't use Camera::getViewMatrix, that one might be relative to another camera! + const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); + const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix); + + // we do the intersections in view space + osg::BoundingSphere nodeBound = node->getBound(); + osg::Matrixf mat = *cv->getModelViewMatrix(); + transformBoundingSphere(mat, nodeBound); + + mLightList.clear(); + for (unsigned int i=0; i (8 - mLightManager->getStartLight()); + + osg::StateSet* stateset = NULL; + + if (mLightList.size() > maxLights) + { + // remove lights culled by this camera + LightManager::LightList lightList = mLightList; + for (LightManager::LightList::iterator it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights; ) + { + osg::CullStack::CullingStack& stack = cv->getModelViewCullingStack(); + + osg::BoundingSphere bs = (*it)->mViewBound; + bs._radius = bs._radius*2; + osg::CullingSet& cullingSet = stack.front(); + if (cullingSet.isCulled(bs)) + { + it = lightList.erase(it); + continue; + } + else + ++it; + } + + if (lightList.size() > maxLights) + { + // sort by proximity to camera, then get rid of furthest away lights + std::sort(lightList.begin(), lightList.end(), sortLights); + while (lightList.size() > maxLights) + lightList.pop_back(); + } + stateset = mLightManager->getLightListStateSet(lightList, nv->getTraversalNumber()); + } + else + stateset = mLightManager->getLightListStateSet(mLightList, nv->getTraversalNumber()); + + + cv->pushStateSet(stateset); + + traverse(node, nv); + + cv->popStateSet(); + } + else + traverse(node, nv); + } + + void configureLight(osg::Light *light, float radius, bool isExterior, bool outQuadInLin, bool useQuadratic, + float quadraticValue, float quadraticRadiusMult, bool useLinear, float linearRadiusMult, float linearValue) + { + bool quadratic = useQuadratic && (!outQuadInLin || isExterior); + + float quadraticAttenuation = 0; + float linearAttenuation = 0; + if (quadratic) + { + float r = radius * quadraticRadiusMult; + quadraticAttenuation = quadraticValue / std::pow(r, 2); + } + if (useLinear) + { + float r = radius * linearRadiusMult; + linearAttenuation = linearValue / r; + } + + light->setLinearAttenuation(linearAttenuation); + light->setQuadraticAttenuation(quadraticAttenuation); + light->setConstantAttenuation(0.f); + + } + +} diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp new file mode 100644 index 0000000000..3e6d3251bb --- /dev/null +++ b/components/sceneutil/lightmanager.hpp @@ -0,0 +1,177 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H +#define OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H + +#include + +#include +#include + +namespace SceneUtil +{ + + /// LightSource managed by a LightManager. + /// @par Typically used for point lights. Spot lights are not supported yet. Directional lights affect the whole scene + /// so do not need to be managed by a LightManager - so for directional lights use a plain osg::LightSource instead. + /// @note LightSources must be decorated by a LightManager node in order to have an effect. Typical use would + /// be one LightManager as the root of the scene graph. + /// @note One needs to attach LightListCallback's to the scene to have objects receive lighting from LightSources. + /// See the documentation of LightListCallback for more information. + /// @note The position of the contained osg::Light is automatically updated based on the LightSource's world position. + class LightSource : public osg::Node + { + // double buffered osg::Light's, since one of them may be in use by the draw thread at any given time + osg::ref_ptr mLight[2]; + + // LightSource will affect objects within this radius + float mRadius; + + int mId; + + public: + + META_Node(SceneUtil, SceneUtil::LightSource) + + LightSource(); + + LightSource(const LightSource& copy, const osg::CopyOp& copyop); + + float getRadius() const + { + return mRadius; + } + + /// The LightSource will affect objects within this radius. + void setRadius(float radius) + { + mRadius = radius; + } + + /// Get the osg::Light safe for modification in the given frame. + /// @par May be used externally to animate the light's color/attenuation properties, + /// and is used internally to synchronize the light's position with the position of the LightSource. + osg::Light* getLight(unsigned int frame) + { + return mLight[frame % 2]; + } + + /// @warning It is recommended not to replace an existing osg::Light, because there might still be + /// references to it in the light StateSet cache that are associated with this LightSource's ID. + /// These references will stay valid due to ref_ptr but will point to the old object. + /// @warning Do not modify the \a light after you've called this function. + void setLight(osg::Light* light) + { + mLight[0] = light; + mLight[1] = osg::clone(light); + } + + /// Get the unique ID for this light source. + int getId() const + { + return mId; + } + }; + + /// @brief Decorator node implementing the rendering of any number of LightSources that can be anywhere in the subgraph. + class LightManager : public osg::Group + { + public: + + META_Node(SceneUtil, SceneUtil::LightManager) + + LightManager(); + + LightManager(const LightManager& copy, const osg::CopyOp& copyop); + + /// @param mask This mask is compared with the current Camera's cull mask to determine if lighting is desired. + /// By default, it's ~0u i.e. always on. + /// If you have some views that do not require lighting, then set the Camera's cull mask to not include + /// the lightingMask for a much faster cull and rendering. + void setLightingMask (unsigned int mask); + + unsigned int getLightingMask() const; + + /// Set the first light index that should be used by this manager, typically the number of directional lights in the scene. + void setStartLight(int start); + + int getStartLight() const; + + /// Internal use only, called automatically by the LightManager's UpdateCallback + void update(); + + /// Internal use only, called automatically by the LightSource's UpdateCallback + void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, unsigned int frameNum); + + struct LightSourceTransform + { + LightSource* mLightSource; + osg::Matrixf mWorldMatrix; + }; + + const std::vector& getLights() const; + + struct LightSourceViewBound + { + LightSource* mLightSource; + osg::BoundingSphere mViewBound; + }; + + const std::vector& getLightsInViewSpace(osg::Camera* camera, const osg::RefMatrix* viewMatrix); + + typedef std::vector LightList; + + osg::ref_ptr getLightListStateSet(const LightList& lightList, unsigned int frameNum); + + private: + // Lights collected from the scene graph. Only valid during the cull traversal. + std::vector mLights; + + typedef std::vector LightSourceViewBoundCollection; + std::map, LightSourceViewBoundCollection> mLightsInViewSpace; + + // < Light list hash , StateSet > + typedef std::map > LightStateSetMap; + LightStateSetMap mStateSetCache[2]; + + int mStartLight; + + unsigned int mLightingMask; + }; + + /// To receive lighting, objects must be decorated by a LightListCallback. Light list callbacks must be added via + /// node->addCullCallback(new LightListCallback). Once a light list callback is added to a node, that node and all + /// its child nodes can receive lighting. + /// @par The placement of these LightListCallbacks affects the granularity of light lists. Having too fine grained + /// light lists can result in degraded performance. Too coarse grained light lists can result in lights no longer + /// rendering when the size of a light list exceeds the OpenGL limit on the number of concurrent lights (8). A good + /// starting point is to attach a LightListCallback to each game object's base node. + /// @note Not thread safe for CullThreadPerCamera threading mode. + class LightListCallback : public osg::NodeCallback + { + public: + LightListCallback() + : mLightManager(NULL) + , mLastFrameNumber(0) + {} + LightListCallback(const LightListCallback& copy, const osg::CopyOp& copyop) + : osg::Object(copy, copyop), osg::NodeCallback(copy, copyop) + , mLightManager(copy.mLightManager) + , mLastFrameNumber(0) + {} + + META_Object(SceneUtil, LightListCallback) + + void operator()(osg::Node* node, osg::NodeVisitor* nv); + + private: + LightManager* mLightManager; + unsigned int mLastFrameNumber; + LightManager::LightList mLightList; + }; + + /// @brief Configures a light's attenuation according to vanilla Morrowind attenuation settings. + void configureLight(osg::Light* light, float radius, bool isExterior, bool outQuadInLin, bool useQuadratic, float quadraticValue, + float quadraticRadiusMult, bool useLinear, float linearRadiusMult, float linearValue); + +} + +#endif diff --git a/components/sceneutil/positionattitudetransform.cpp b/components/sceneutil/positionattitudetransform.cpp new file mode 100644 index 0000000000..5f6b57e979 --- /dev/null +++ b/components/sceneutil/positionattitudetransform.cpp @@ -0,0 +1,51 @@ +#include "positionattitudetransform.hpp" + +#include + +namespace SceneUtil +{ + +PositionAttitudeTransform::PositionAttitudeTransform(): + _scale(1.0,1.0,1.0) +{ +} + +bool PositionAttitudeTransform::computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor*) const +{ + if (_referenceFrame==RELATIVE_RF) + { + matrix.preMultTranslate(_position); + matrix.preMultRotate(_attitude); + matrix.preMultScale(_scale); + } + else // absolute + { + matrix.makeRotate(_attitude); + matrix.postMultTranslate(_position); + matrix.preMultScale(_scale); + } + return true; +} + + +bool PositionAttitudeTransform::computeWorldToLocalMatrix(osg::Matrix& matrix, osg::NodeVisitor*) const +{ + if (_scale.x() == 0.0 || _scale.y() == 0.0 || _scale.z() == 0.0) + return false; + + if (_referenceFrame==RELATIVE_RF) + { + matrix.postMultTranslate(-_position); + matrix.postMultRotate(_attitude.inverse()); + matrix.postMultScale(osg::Vec3f(1.0/_scale.x(), 1.0/_scale.y(), 1.0/_scale.z())); + } + else // absolute + { + matrix.makeRotate(_attitude.inverse()); + matrix.preMultTranslate(-_position); + matrix.postMultScale(osg::Vec3f(1.0/_scale.x(), 1.0/_scale.y(), 1.0/_scale.z())); + } + return true; +} + +} diff --git a/components/sceneutil/positionattitudetransform.hpp b/components/sceneutil/positionattitudetransform.hpp new file mode 100644 index 0000000000..b6f92ee84e --- /dev/null +++ b/components/sceneutil/positionattitudetransform.hpp @@ -0,0 +1,53 @@ +#ifndef OPENMW_COMPONENTS_POSITIONATTITUDE_TRANSFORM_H +#define OPENMW_COMPONENTS_POSITIONATTITUDE_TRANSFORM_H + +#include + +namespace SceneUtil +{ + +/// @brief A customized version of osg::PositionAttitudeTransform optimized for speed. +/// Uses single precision values. Also removed _pivotPoint which we don't need. +class PositionAttitudeTransform : public osg::Transform +{ + public : + PositionAttitudeTransform(); + + PositionAttitudeTransform(const PositionAttitudeTransform& pat,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY): + Transform(pat,copyop), + _position(pat._position), + _attitude(pat._attitude), + _scale(pat._scale){} + + + META_Node(SceneUtil, PositionAttitudeTransform) + + inline void setPosition(const osg::Vec3f& pos) { _position = pos; dirtyBound(); } + inline const osg::Vec3f& getPosition() const { return _position; } + + + inline void setAttitude(const osg::Quat& quat) { _attitude = quat; dirtyBound(); } + inline const osg::Quat& getAttitude() const { return _attitude; } + + + inline void setScale(const osg::Vec3f& scale) { _scale = scale; dirtyBound(); } + inline const osg::Vec3f& getScale() const { return _scale; } + + + + virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix,osg::NodeVisitor* nv) const; + virtual bool computeWorldToLocalMatrix(osg::Matrix& matrix,osg::NodeVisitor* nv) const; + + + protected : + + virtual ~PositionAttitudeTransform() {} + + osg::Vec3f _position; + osg::Quat _attitude; + osg::Vec3f _scale; +}; + +} + +#endif diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp new file mode 100644 index 0000000000..88b907fafa --- /dev/null +++ b/components/sceneutil/riggeometry.cpp @@ -0,0 +1,324 @@ +#include "riggeometry.hpp" + +#include +#include +#include + +#include +#include + +#include "skeleton.hpp" +#include "util.hpp" + +namespace SceneUtil +{ + +class UpdateRigBounds : public osg::Drawable::UpdateCallback +{ +public: + UpdateRigBounds() + { + } + + UpdateRigBounds(const UpdateRigBounds& copy, const osg::CopyOp& copyop) + : osg::Drawable::UpdateCallback(copy, copyop) + { + } + + META_Object(SceneUtil, UpdateRigBounds) + + void update(osg::NodeVisitor* nv, osg::Drawable* drw) + { + RigGeometry* rig = static_cast(drw); + + rig->updateBounds(nv); + } +}; + +// TODO: make threadsafe for multiple cull threads +class UpdateRigGeometry : public osg::Drawable::CullCallback +{ +public: + UpdateRigGeometry() + { + } + + UpdateRigGeometry(const UpdateRigGeometry& copy, const osg::CopyOp& copyop) + : osg::Drawable::CullCallback(copy, copyop) + { + } + + META_Object(SceneUtil, UpdateRigGeometry) + + virtual bool cull(osg::NodeVisitor* nv, osg::Drawable* drw, osg::State*) const + { + RigGeometry* geom = static_cast(drw); + geom->update(nv); + return false; + } +}; + +// We can't compute the bounds without a NodeVisitor, since we need the current geomToSkelMatrix. +// So we return nothing. Bounds are updated every frame in the UpdateCallback. +class DummyComputeBoundCallback : public osg::Drawable::ComputeBoundingBoxCallback +{ +public: + virtual osg::BoundingBox computeBound(const osg::Drawable&) const { return osg::BoundingBox(); } +}; + +RigGeometry::RigGeometry() + : mSkeleton(NULL) + , mLastFrameNumber(0) + , mBoundsFirstFrame(true) +{ + setCullCallback(new UpdateRigGeometry); + setUpdateCallback(new UpdateRigBounds); + setSupportsDisplayList(false); + setComputeBoundingBoxCallback(new DummyComputeBoundCallback); +} + +RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op) + : osg::Geometry(copy, copyop) + , mSkeleton(NULL) + , mInfluenceMap(copy.mInfluenceMap) + , mLastFrameNumber(0) + , mBoundsFirstFrame(true) +{ + setSourceGeometry(copy.mSourceGeometry); +} + +void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) +{ + mSourceGeometry = sourceGeometry; + + osg::Geometry& from = *sourceGeometry; + + if (from.getStateSet()) + setStateSet(from.getStateSet()); + + // copy over primitive sets. + getPrimitiveSetList() = from.getPrimitiveSetList(); + + if (from.getColorArray()) + setColorArray(from.getColorArray()); + + if (from.getSecondaryColorArray()) + setSecondaryColorArray(from.getSecondaryColorArray()); + + if (from.getFogCoordArray()) + setFogCoordArray(from.getFogCoordArray()); + + for(unsigned int ti=0;ti(from.getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL))); + setNormalArray(dynamic_cast(from.getNormalArray()->clone(osg::CopyOp::DEEP_COPY_ALL)), osg::Array::BIND_PER_VERTEX); +} + +bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) +{ + const osg::NodePath& path = nv->getNodePath(); + for (osg::NodePath::const_reverse_iterator it = path.rbegin(); it != path.rend(); ++it) + { + osg::Node* node = *it; + if (Skeleton* skel = dynamic_cast(node)) + { + mSkeleton = skel; + break; + } + } + + if (!mSkeleton) + { + std::cerr << "A RigGeometry did not find its parent skeleton" << std::endl; + return false; + } + + if (!mInfluenceMap) + { + std::cerr << "No InfluenceMap set on RigGeometry" << std::endl; + return false; + } + + typedef std::map > Vertex2BoneMap; + Vertex2BoneMap vertex2BoneMap; + for (std::map::const_iterator it = mInfluenceMap->mMap.begin(); it != mInfluenceMap->mMap.end(); ++it) + { + Bone* bone = mSkeleton->getBone(it->first); + if (!bone) + { + std::cerr << "RigGeometry did not find bone " << it->first << std::endl; + continue; + } + + mBoneSphereMap[bone] = it->second.mBoundSphere; + + const BoneInfluence& bi = it->second; + + const std::map& weights = it->second.mWeights; + for (std::map::const_iterator weightIt = weights.begin(); weightIt != weights.end(); ++weightIt) + { + std::vector& vec = vertex2BoneMap[weightIt->first]; + + BoneWeight b = std::make_pair(std::make_pair(bone, bi.mInvBindMatrix), weightIt->second); + + vec.push_back(b); + } + } + + for (Vertex2BoneMap::iterator it = vertex2BoneMap.begin(); it != vertex2BoneMap.end(); it++) + { + mBone2VertexMap[it->second].push_back(it->first); + } + + return true; +} + +void accumulateMatrix(const osg::Matrixf& invBindMatrix, const osg::Matrixf& matrix, float weight, osg::Matrixf& result) +{ + osg::Matrixf m = invBindMatrix * matrix; + float* ptr = m.ptr(); + float* ptrresult = result.ptr(); + ptrresult[0] += ptr[0] * weight; + ptrresult[1] += ptr[1] * weight; + ptrresult[2] += ptr[2] * weight; + + ptrresult[4] += ptr[4] * weight; + ptrresult[5] += ptr[5] * weight; + ptrresult[6] += ptr[6] * weight; + + ptrresult[8] += ptr[8] * weight; + ptrresult[9] += ptr[9] * weight; + ptrresult[10] += ptr[10] * weight; + + ptrresult[12] += ptr[12] * weight; + ptrresult[13] += ptr[13] * weight; + ptrresult[14] += ptr[14] * weight; +} + +void RigGeometry::update(osg::NodeVisitor* nv) +{ + if (!mSkeleton) + { + std::cerr << "RigGeometry rendering with no skeleton, should have been initialized by UpdateVisitor" << std::endl; + // try to recover anyway, though rendering is likely to be incorrect. + if (!initFromParentSkeleton(nv)) + return; + } + + if (!mSkeleton->getActive() && mLastFrameNumber != 0) + return; + + if (mLastFrameNumber == nv->getTraversalNumber()) + return; + mLastFrameNumber = nv->getTraversalNumber(); + + mSkeleton->updateBoneMatrices(nv); + + // skinning + osg::Vec3Array* positionSrc = static_cast(mSourceGeometry->getVertexArray()); + osg::Vec3Array* normalSrc = static_cast(mSourceGeometry->getNormalArray()); + + osg::Vec3Array* positionDst = static_cast(getVertexArray()); + osg::Vec3Array* normalDst = static_cast(getNormalArray()); + + for (Bone2VertexMap::const_iterator it = mBone2VertexMap.begin(); it != mBone2VertexMap.end(); ++it) + { + osg::Matrixf resultMat (0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 1); + + for (std::vector::const_iterator weightIt = it->first.begin(); weightIt != it->first.end(); ++weightIt) + { + Bone* bone = weightIt->first.first; + const osg::Matrix& invBindMatrix = weightIt->first.second; + float weight = weightIt->second; + const osg::Matrixf& boneMatrix = bone->mMatrixInSkeletonSpace; + accumulateMatrix(invBindMatrix, boneMatrix, weight, resultMat); + } + resultMat = resultMat * mGeomToSkelMatrix; + + for (std::vector::const_iterator vertexIt = it->second.begin(); vertexIt != it->second.end(); ++vertexIt) + { + unsigned short vertex = *vertexIt; + (*positionDst)[vertex] = resultMat.preMult((*positionSrc)[vertex]); + (*normalDst)[vertex] = osg::Matrix::transform3x3((*normalSrc)[vertex], resultMat); + } + } + + positionDst->dirty(); + normalDst->dirty(); +} + +void RigGeometry::updateBounds(osg::NodeVisitor *nv) +{ + if (!mSkeleton) + { + if (!initFromParentSkeleton(nv)) + return; + } + + if (!mSkeleton->getActive() && !mBoundsFirstFrame) + return; + mBoundsFirstFrame = false; + + mSkeleton->updateBoneMatrices(nv); + + updateGeomToSkelMatrix(nv); + + osg::BoundingBox box; + for (BoneSphereMap::const_iterator it = mBoneSphereMap.begin(); it != mBoneSphereMap.end(); ++it) + { + Bone* bone = it->first; + osg::BoundingSpheref bs = it->second; + transformBoundingSphere(bone->mMatrixInSkeletonSpace * mGeomToSkelMatrix, bs); + box.expandBy(bs); + } + + _boundingBox = box; + _boundingBoxComputed = true; +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) + // in OSG 3.3.3 and up Drawable inherits from Node, so has a bounding sphere as well. + _boundingSphere = osg::BoundingSphere(_boundingBox); + _boundingSphereComputed = true; +#endif + for (unsigned int i=0; idirtyBound(); +} + +void RigGeometry::updateGeomToSkelMatrix(osg::NodeVisitor *nv) +{ + mSkelToGeomPath.clear(); + bool foundSkel = false; + for (osg::NodePath::const_iterator it = nv->getNodePath().begin(); it != nv->getNodePath().end(); ++it) + { + if (!foundSkel) + { + if (*it == mSkeleton) + foundSkel = true; + } + else + mSkelToGeomPath.push_back(*it); + } + mGeomToSkelMatrix = osg::computeWorldToLocal(mSkelToGeomPath); +} + +void RigGeometry::setInfluenceMap(osg::ref_ptr influenceMap) +{ + mInfluenceMap = influenceMap; +} + + +} diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp new file mode 100644 index 0000000000..03c287b812 --- /dev/null +++ b/components/sceneutil/riggeometry.hpp @@ -0,0 +1,80 @@ +#ifndef OPENMW_COMPONENTS_NIFOSG_RIGGEOMETRY_H +#define OPENMW_COMPONENTS_NIFOSG_RIGGEOMETRY_H + +#include +#include + +namespace SceneUtil +{ + + class Skeleton; + class Bone; + + /// @brief Mesh skinning implementation. + /// @note A RigGeometry may be attached directly to a Skeleton, or somewhere below a Skeleton. + /// Note though that the RigGeometry ignores any transforms below the Skeleton, so the attachment point is not that important. + class RigGeometry : public osg::Geometry + { + public: + RigGeometry(); + RigGeometry(const RigGeometry& copy, const osg::CopyOp& copyop); + + META_Object(SceneUtil, RigGeometry) + + struct BoneInfluence + { + osg::Matrixf mInvBindMatrix; + osg::BoundingSpheref mBoundSphere; + // + std::map mWeights; + }; + + struct InfluenceMap : public osg::Referenced + { + std::map mMap; + }; + + void setInfluenceMap(osg::ref_ptr influenceMap); + + void setSourceGeometry(osg::ref_ptr sourceGeom); + + // Called automatically by our CullCallback + void update(osg::NodeVisitor* nv); + + // Called automatically by our UpdateCallback + void updateBounds(osg::NodeVisitor* nv); + + private: + osg::ref_ptr mSourceGeometry; + Skeleton* mSkeleton; + + osg::NodePath mSkelToGeomPath; + osg::Matrixf mGeomToSkelMatrix; + + osg::ref_ptr mInfluenceMap; + + typedef std::pair BoneBindMatrixPair; + + typedef std::pair BoneWeight; + + typedef std::vector VertexList; + + typedef std::map, VertexList> Bone2VertexMap; + + Bone2VertexMap mBone2VertexMap; + + typedef std::map BoneSphereMap; + + BoneSphereMap mBoneSphereMap; + + unsigned int mLastFrameNumber; + bool mBoundsFirstFrame; + + bool initFromParentSkeleton(osg::NodeVisitor* nv); + + void updateGeomToSkelMatrix(osg::NodeVisitor* nv); + }; + +} + +#endif diff --git a/components/sceneutil/skeleton.cpp b/components/sceneutil/skeleton.cpp new file mode 100644 index 0000000000..d1299c0587 --- /dev/null +++ b/components/sceneutil/skeleton.cpp @@ -0,0 +1,178 @@ +#include "skeleton.hpp" + +#include +#include + +#include + +#include + +namespace SceneUtil +{ + +class InitBoneCacheVisitor : public osg::NodeVisitor +{ +public: + InitBoneCacheVisitor(std::map >& cache) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mCache(cache) + { + } + + void apply(osg::Transform &node) + { + osg::MatrixTransform* bone = node.asMatrixTransform(); + if (!bone) + return; + + mCache[Misc::StringUtils::lowerCase(bone->getName())] = std::make_pair(getNodePath(), bone); + + traverse(node); + } +private: + std::map >& mCache; +}; + +Skeleton::Skeleton() + : mBoneCacheInit(false) + , mNeedToUpdateBoneMatrices(true) + , mActive(true) + , mLastFrameNumber(0) +{ + +} + +Skeleton::Skeleton(const Skeleton ©, const osg::CopyOp ©op) + : osg::Group(copy, copyop) + , mBoneCacheInit(false) + , mNeedToUpdateBoneMatrices(true) + , mActive(copy.mActive) + , mLastFrameNumber(0) +{ + +} + +Bone* Skeleton::getBone(const std::string &name) +{ + if (!mBoneCacheInit) + { + InitBoneCacheVisitor visitor(mBoneCache); + accept(visitor); + mBoneCacheInit = true; + } + + BoneCache::iterator found = mBoneCache.find(Misc::StringUtils::lowerCase(name)); + if (found == mBoneCache.end()) + return NULL; + + // find or insert in the bone hierarchy + + if (!mRootBone.get()) + { + mRootBone.reset(new Bone); + } + + const osg::NodePath& path = found->second.first; + Bone* bone = mRootBone.get(); + for (osg::NodePath::const_iterator it = path.begin(); it != path.end(); ++it) + { + osg::MatrixTransform* matrixTransform = dynamic_cast(*it); + if (!matrixTransform) + continue; + + Bone* child = NULL; + for (unsigned int i=0; imChildren.size(); ++i) + { + if (bone->mChildren[i]->mNode == *it) + { + child = bone->mChildren[i]; + break; + } + } + + if (!child) + { + child = new Bone; + bone->mChildren.push_back(child); + mNeedToUpdateBoneMatrices = true; + } + bone = child; + + bone->mNode = matrixTransform; + } + + return bone; +} + +void Skeleton::updateBoneMatrices(osg::NodeVisitor* nv) +{ + if (nv->getTraversalNumber() != mLastFrameNumber) + mNeedToUpdateBoneMatrices = true; + + mLastFrameNumber = nv->getTraversalNumber(); + + if (mNeedToUpdateBoneMatrices) + { + if (mRootBone.get()) + { + for (unsigned int i=0; imChildren.size(); ++i) + mRootBone->mChildren[i]->update(NULL); + } + else + std::cerr << "no root bone" << std::endl; + + mNeedToUpdateBoneMatrices = false; + } +} + +void Skeleton::setActive(bool active) +{ + mActive = active; +} + +bool Skeleton::getActive() const +{ + return mActive; +} + +void Skeleton::traverse(osg::NodeVisitor& nv) +{ + if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR + // need to process at least 2 frames before shutting off update, since we need to have both frame-alternating RigGeometries initialized + // this would be more naturally handled if the double-buffering was implemented in RigGeometry itself rather than in a FrameSwitch decorator node + && mLastFrameNumber != 0 && mLastFrameNumber+2 <= nv.getTraversalNumber()) + return; + osg::Group::traverse(nv); +} + +Bone::Bone() + : mNode(NULL) +{ +} + +Bone::~Bone() +{ + for (unsigned int i=0; igetMatrix() * (*parentMatrixInSkeletonSpace); + else + mMatrixInSkeletonSpace = mNode->getMatrix(); + + for (unsigned int i=0; iupdate(&mMatrixInSkeletonSpace); + } +} + +} diff --git a/components/sceneutil/skeleton.hpp b/components/sceneutil/skeleton.hpp new file mode 100644 index 0000000000..d98d367518 --- /dev/null +++ b/components/sceneutil/skeleton.hpp @@ -0,0 +1,76 @@ +#ifndef OPENMW_COMPONENTS_NIFOSG_SKELETON_H +#define OPENMW_COMPONENTS_NIFOSG_SKELETON_H + +#include + +#include + +namespace SceneUtil +{ + + /// @brief Defines a Bone hierarchy, used for updating of skeleton-space bone matrices. + /// @note To prevent unnecessary updates, only bones that are used for skinning will be added to this hierarchy. + class Bone + { + public: + Bone(); + ~Bone(); + + osg::Matrixf mMatrixInSkeletonSpace; + + osg::MatrixTransform* mNode; + + std::vector mChildren; + + /// Update the skeleton-space matrix of this bone and all its children. + void update(const osg::Matrixf* parentMatrixInSkeletonSpace); + + private: + Bone(const Bone&); + void operator=(const Bone&); + }; + + /// @brief Handles the bone matrices for any number of child RigGeometries. + /// @par Bones should be created as osg::MatrixTransform children of the skeleton. + /// To be a referenced by a RigGeometry, a bone needs to have a unique name. + class Skeleton : public osg::Group + { + public: + Skeleton(); + Skeleton(const Skeleton& copy, const osg::CopyOp& copyop); + + META_Node(SceneUtil, Skeleton) + + /// Retrieve a bone by name. + Bone* getBone(const std::string& name); + + /// Request an update of bone matrices. May be a no-op if already updated in this frame. + void updateBoneMatrices(osg::NodeVisitor* nv); + + /// Set the skinning active flag. Inactive skeletons will not have their child rigs updated. + /// You should set this flag to false if you know that bones are not currently moving. + void setActive(bool active); + + bool getActive() const; + + void traverse(osg::NodeVisitor& nv); + + private: + // The root bone is not a "real" bone, it has no corresponding node in the scene graph. + // As far as the scene graph goes we support multiple root bones. + std::auto_ptr mRootBone; + + typedef std::map > BoneCache; + BoneCache mBoneCache; + bool mBoneCacheInit; + + bool mNeedToUpdateBoneMatrices; + + bool mActive; + + unsigned int mLastFrameNumber; + }; + +} + +#endif diff --git a/components/sceneutil/statesetupdater.cpp b/components/sceneutil/statesetupdater.cpp new file mode 100644 index 0000000000..0e325082e1 --- /dev/null +++ b/components/sceneutil/statesetupdater.cpp @@ -0,0 +1,86 @@ +#include "statesetupdater.hpp" + +#include +#include + +namespace SceneUtil +{ + + void StateSetUpdater::operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if (!mStateSets[0]) + { + // first time setup + osg::StateSet* src = node->getOrCreateStateSet(); + for (int i=0; i<2; ++i) // Using SHALLOW_COPY for StateAttributes, if users want to modify it is their responsibility to set a non-shared one first + // This can be done conveniently in user implementations of the setDefaults() method + { + mStateSets[i] = static_cast(osg::clone(src, osg::CopyOp::SHALLOW_COPY)); + setDefaults(mStateSets[i]); + } + } + + osg::StateSet* stateset = mStateSets[nv->getTraversalNumber()%2]; + node->setStateSet(stateset); + + apply(stateset, nv); + + traverse(node, nv); + } + + void StateSetUpdater::reset() + { + mStateSets[0] = NULL; + mStateSets[1] = NULL; + } + + StateSetUpdater::StateSetUpdater() + { + } + + StateSetUpdater::StateSetUpdater(const StateSetUpdater ©, const osg::CopyOp ©op) + : osg::NodeCallback(copy, copyop) + { + } + + // ---------------------------------------------------------------------------------- + + void CompositeStateSetUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) + { + for (unsigned int i=0; iapply(stateset, nv); + } + + void CompositeStateSetUpdater::setDefaults(osg::StateSet *stateset) + { + for (unsigned int i=0; isetDefaults(stateset); + } + + CompositeStateSetUpdater::CompositeStateSetUpdater() + { + } + + CompositeStateSetUpdater::CompositeStateSetUpdater(const CompositeStateSetUpdater ©, const osg::CopyOp ©op) + : StateSetUpdater(copy, copyop) + { + for (unsigned int i=0; i(osg::clone(copy.mCtrls[i].get(), copyop))); + } + + unsigned int CompositeStateSetUpdater::getNumControllers() + { + return mCtrls.size(); + } + + StateSetUpdater* CompositeStateSetUpdater::getController(int i) + { + return mCtrls[i]; + } + + void CompositeStateSetUpdater::addController(StateSetUpdater *ctrl) + { + mCtrls.push_back(ctrl); + } + +} diff --git a/components/sceneutil/statesetupdater.hpp b/components/sceneutil/statesetupdater.hpp new file mode 100644 index 0000000000..51398844cf --- /dev/null +++ b/components/sceneutil/statesetupdater.hpp @@ -0,0 +1,71 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_STATESETCONTROLLER_H +#define OPENMW_COMPONENTS_SCENEUTIL_STATESETCONTROLLER_H + +#include + +namespace SceneUtil +{ + + /// @brief Implements efficient per-frame updating of StateSets. + /// @par With a naive update there would be race conditions when the OSG draw thread of the last frame + /// queues up a StateSet that we want to modify for the next frame. To solve this we could set the StateSet to + /// DYNAMIC data variance but that would undo all the benefits of the threading model - having the cull and draw + /// traversals run in parallel can yield up to 200% framerates. + /// @par Race conditions are prevented using a "double buffering" scheme - we have two StateSets that take turns, + /// one StateSet we can write to, the second one is currently in use by the draw traversal of the last frame. + /// @par Must be set as UpdateCallback on a Node. + /// @note Do not add the same StateSetUpdater to multiple nodes. + /// @note Do not add multiple StateSetControllers on the same Node as they will conflict - instead use the CompositeStateSetUpdater. + class StateSetUpdater : public osg::NodeCallback + { + public: + StateSetUpdater(); + StateSetUpdater(const StateSetUpdater& copy, const osg::CopyOp& copyop); + + META_Object(SceneUtil, StateSetUpdater) + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv); + + /// Apply state - to override in derived classes + /// @note Due to the double buffering approach you *have* to apply all state + /// even if it has not changed since the last frame. + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) {} + + /// Set default state - optionally override in derived classes + /// @par May be used e.g. to allocate StateAttributes. + virtual void setDefaults(osg::StateSet* stateset) {} + + protected: + /// Reset mStateSets, forcing a setDefaults() on the next frame. Can be used to change the defaults if needed. + void reset(); + + private: + osg::ref_ptr mStateSets[2]; + }; + + /// @brief A variant of the StateSetController that can be made up of multiple controllers all controlling the same target. + class CompositeStateSetUpdater : public StateSetUpdater + { + public: + CompositeStateSetUpdater(); + CompositeStateSetUpdater(const CompositeStateSetUpdater& copy, const osg::CopyOp& copyop); + + META_Object(SceneUtil, CompositeStateSetUpdater) + + unsigned int getNumControllers(); + StateSetUpdater* getController(int i); + + void addController(StateSetUpdater* ctrl); + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* nv); + + protected: + + virtual void setDefaults(osg::StateSet *stateset); + + std::vector > mCtrls; + }; + +} + +#endif diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp new file mode 100644 index 0000000000..3add3bb236 --- /dev/null +++ b/components/sceneutil/util.cpp @@ -0,0 +1,54 @@ +#include "util.hpp" + +namespace SceneUtil +{ + +void transformBoundingSphere (const osg::Matrixf& matrix, osg::BoundingSphere& bsphere) +{ + osg::BoundingSphere::vec_type xdash = bsphere._center; + xdash.x() += bsphere._radius; + xdash = xdash*matrix; + + osg::BoundingSphere::vec_type ydash = bsphere._center; + ydash.y() += bsphere._radius; + ydash = ydash*matrix; + + osg::BoundingSphere::vec_type zdash = bsphere._center; + zdash.z() += bsphere._radius; + zdash = zdash*matrix; + + bsphere._center = bsphere._center*matrix; + + xdash -= bsphere._center; + osg::BoundingSphere::value_type sqrlen_xdash = xdash.length2(); + + ydash -= bsphere._center; + osg::BoundingSphere::value_type sqrlen_ydash = ydash.length2(); + + zdash -= bsphere._center; + osg::BoundingSphere::value_type sqrlen_zdash = zdash.length2(); + + bsphere._radius = sqrlen_xdash; + if (bsphere._radius> 0) & 0xFF) / 255.0f, + ((clr >> 8) & 0xFF) / 255.0f, + ((clr >> 16) & 0xFF) / 255.0f, 1.f); + return colour; +} + +osg::Vec4f colourFromRGBA(unsigned int clr) +{ + osg::Vec4f colour(((clr >> 0) & 0xFF) / 255.0f, + ((clr >> 8) & 0xFF) / 255.0f, + ((clr >> 16) & 0xFF) / 255.0f, + ((clr >> 24) & 0xFF) / 255.0f); + return colour; +} + +} diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp new file mode 100644 index 0000000000..d8fefdb291 --- /dev/null +++ b/components/sceneutil/util.hpp @@ -0,0 +1,22 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_UTIL_H +#define OPENMW_COMPONENTS_SCENEUTIL_UTIL_H + +#include +#include +#include + +namespace SceneUtil +{ + + // Transform a bounding sphere by a matrix + // based off private code in osg::Transform + // TODO: patch osg to make public + void transformBoundingSphere (const osg::Matrixf& matrix, osg::BoundingSphere& bsphere); + + osg::Vec4f colourFromRGB (unsigned int clr); + + osg::Vec4f colourFromRGBA (unsigned int clr); + +} + +#endif diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp new file mode 100644 index 0000000000..0a5ad2d006 --- /dev/null +++ b/components/sceneutil/visitor.cpp @@ -0,0 +1,34 @@ +#include "visitor.hpp" + +#include + +#include + +#include + +namespace SceneUtil +{ + + void FindByNameVisitor::apply(osg::Group &group) + { + if (Misc::StringUtils::ciEqual(group.getName(), mNameToFind)) + { + mFoundNode = &group; + return; + } + traverse(group); + } + + void DisableFreezeOnCullVisitor::apply(osg::Geode &geode) + { + for (unsigned int i=0; i(&drw)) + partsys->setFreezeOnCull(false); + } + +} diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp new file mode 100644 index 0000000000..dcfefe9cd6 --- /dev/null +++ b/components/sceneutil/visitor.hpp @@ -0,0 +1,43 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_VISITOR_H +#define OPENMW_COMPONENTS_SCENEUTIL_VISITOR_H + +#include + +// Commonly used scene graph visitors +namespace SceneUtil +{ + + // Find a Group by name, case-insensitive + // If not found, mFoundNode will be NULL + class FindByNameVisitor : public osg::NodeVisitor + { + public: + FindByNameVisitor(const std::string& nameToFind) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mNameToFind(nameToFind) + , mFoundNode(NULL) + { + } + + virtual void apply(osg::Group& group); + + std::string mNameToFind; + osg::Group* mFoundNode; + }; + + // Disable freezeOnCull for all visited particlesystems + class DisableFreezeOnCullVisitor : public osg::NodeVisitor + { + public: + DisableFreezeOnCullVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + virtual void apply(osg::Geode &geode); + virtual void apply(osg::Drawable& drw); + }; + +} + +#endif diff --git a/components/sceneutil/workqueue.cpp b/components/sceneutil/workqueue.cpp new file mode 100644 index 0000000000..b642687f0f --- /dev/null +++ b/components/sceneutil/workqueue.cpp @@ -0,0 +1,122 @@ +#include "workqueue.hpp" + +namespace SceneUtil +{ + +void WorkTicket::waitTillDone() +{ + if (mDone > 0) + return; + + OpenThreads::ScopedLock lock(mMutex); + while (mDone == 0) + { + mCondition.wait(&mMutex); + } +} + +void WorkTicket::signalDone() +{ + { + OpenThreads::ScopedLock lock(mMutex); + mDone.exchange(1); + } + mCondition.broadcast(); +} + +WorkItem::WorkItem() + : mTicket(new WorkTicket) +{ + mTicket->setThreadSafeRefUnref(true); +} + +WorkItem::~WorkItem() +{ +} + +void WorkItem::doWork() +{ + mTicket->signalDone(); +} + +osg::ref_ptr WorkItem::getTicket() +{ + return mTicket; +} + +WorkQueue::WorkQueue(int workerThreads) + : mIsReleased(false) +{ + for (int i=0; istartThread(); + } +} + +WorkQueue::~WorkQueue() +{ + { + OpenThreads::ScopedLock lock(mMutex); + while (mQueue.size()) + { + WorkItem* item = mQueue.front(); + delete item; + mQueue.pop(); + } + mIsReleased = true; + mCondition.broadcast(); + } + + for (unsigned int i=0; ijoin(); + delete mThreads[i]; + } +} + +osg::ref_ptr WorkQueue::addWorkItem(WorkItem *item) +{ + osg::ref_ptr ticket = item->getTicket(); + OpenThreads::ScopedLock lock(mMutex); + mQueue.push(item); + mCondition.signal(); + return ticket; +} + +WorkItem *WorkQueue::removeWorkItem() +{ + OpenThreads::ScopedLock lock(mMutex); + while (!mQueue.size() && !mIsReleased) + { + mCondition.wait(&mMutex); + } + if (mQueue.size()) + { + WorkItem* item = mQueue.front(); + mQueue.pop(); + return item; + } + else + return NULL; +} + +WorkThread::WorkThread(WorkQueue *workQueue) + : mWorkQueue(workQueue) +{ +} + +void WorkThread::run() +{ + while (true) + { + WorkItem* item = mWorkQueue->removeWorkItem(); + if (!item) + return; + item->doWork(); + delete item; + } +} + +} diff --git a/components/sceneutil/workqueue.hpp b/components/sceneutil/workqueue.hpp new file mode 100644 index 0000000000..492bbd090e --- /dev/null +++ b/components/sceneutil/workqueue.hpp @@ -0,0 +1,91 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_WORKQUEUE_H +#define OPENMW_COMPONENTS_SCENEUTIL_WORKQUEUE_H + +#include +#include +#include +#include + +#include +#include + +#include + +namespace SceneUtil +{ + + class WorkTicket : public osg::Referenced + { + public: + void waitTillDone(); + + void signalDone(); + + private: + OpenThreads::Atomic mDone; + OpenThreads::Mutex mMutex; + OpenThreads::Condition mCondition; + }; + + class WorkItem + { + public: + WorkItem(); + virtual ~WorkItem(); + + /// Override in a derived WorkItem to perform actual work. + /// By default, just signals the ticket that the work is done. + virtual void doWork(); + + osg::ref_ptr getTicket(); + + protected: + osg::ref_ptr mTicket; + }; + + class WorkQueue; + + class WorkThread : public OpenThreads::Thread + { + public: + WorkThread(WorkQueue* workQueue); + + virtual void run(); + + private: + WorkQueue* mWorkQueue; + }; + + /// @brief A work queue that users can push work items onto, to be completed by one or more background threads. + class WorkQueue + { + public: + WorkQueue(int numWorkerThreads=1); + ~WorkQueue(); + + /// Add a new work item to the back of the queue. + /// @par The returned WorkTicket may be used by the caller to wait until the work is complete. + osg::ref_ptr addWorkItem(WorkItem* item); + + /// Get the next work item from the front of the queue. If the queue is empty, waits until a new item is added. + /// If the workqueue is in the process of being destroyed, may return NULL. + /// @note The caller must free the returned WorkItem + WorkItem* removeWorkItem(); + + void runThread(); + + private: + bool mIsReleased; + std::queue mQueue; + + OpenThreads::Mutex mMutex; + OpenThreads::Condition mCondition; + + std::vector mThreads; + }; + + + +} + +#endif diff --git a/extern/sdl4ogre/OISCompat.h b/components/sdlutil/OISCompat.hpp similarity index 100% rename from extern/sdl4ogre/OISCompat.h rename to components/sdlutil/OISCompat.hpp diff --git a/extern/sdl4ogre/events.h b/components/sdlutil/events.hpp similarity index 99% rename from extern/sdl4ogre/events.h rename to components/sdlutil/events.hpp index 986dd7d8b5..7c79470fff 100644 --- a/extern/sdl4ogre/events.h +++ b/components/sdlutil/events.hpp @@ -8,7 +8,8 @@ // Events // //////////// -namespace SFO { +namespace SDLUtil +{ /** Extended mouse event struct where we treat the wheel like an axis, like everyone expects */ struct MouseMotionEvent : SDL_MouseMotionEvent { diff --git a/components/sdlutil/imagetosurface.cpp b/components/sdlutil/imagetosurface.cpp new file mode 100644 index 0000000000..6313c0a8fd --- /dev/null +++ b/components/sdlutil/imagetosurface.cpp @@ -0,0 +1,28 @@ +#include "imagetosurface.hpp" + +#include +#include + +namespace SDLUtil +{ + +SDL_Surface* imageToSurface(osg::Image *image, bool flip) +{ + int width = image->s(); + int height = image->t(); + SDL_Surface* surface = SDL_CreateRGBSurface(0, width, height, 32, 0xFF000000,0x00FF0000,0x0000FF00,0x000000FF); + + for(int x = 0; x < width; ++x) + for(int y = 0; y < height; ++y) + { + osg::Vec4f clr = image->getColor(x, flip ? ((height-1)-y) : y); + int bpp = surface->format->BytesPerPixel; + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; + *(Uint32*)(p) = SDL_MapRGBA(surface->format, static_cast(clr.r() * 255), + static_cast(clr.g() * 255), static_cast(clr.b() * 255), static_cast(clr.a() * 255)); + } + + return surface; +} + +} diff --git a/components/sdlutil/imagetosurface.hpp b/components/sdlutil/imagetosurface.hpp new file mode 100644 index 0000000000..ad0457433c --- /dev/null +++ b/components/sdlutil/imagetosurface.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_COMPONENTS_SDLUTIL_IMAGETOSURFACE_H +#define OPENMW_COMPONENTS_SDLUTIL_IMAGETOSURFACE_H + +struct SDL_Surface; + +namespace osg +{ + class Image; +} + +namespace SDLUtil +{ + + /// Convert an osg::Image to an SDL_Surface. + /// @note The returned surface must be freed using SDL_FreeSurface. + SDL_Surface* imageToSurface(osg::Image* image, bool flip=false); + +} + +#endif diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp new file mode 100644 index 0000000000..9ecef04839 --- /dev/null +++ b/components/sdlutil/sdlcursormanager.cpp @@ -0,0 +1,246 @@ +#include "sdlcursormanager.hpp" + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "imagetosurface.hpp" + +#ifdef OSG_LIBRARY_STATIC +// Sets the default windowing system interface according to the OS. +// Necessary for OpenSceneGraph to do some things, like decompression. +USE_GRAPHICSWINDOW() +#endif + +namespace +{ + + class MyGraphicsContext { + public: + MyGraphicsContext(int w, int h) + { + osg::ref_ptr traits = new osg::GraphicsContext::Traits; + traits->x = 0; + traits->y = 0; + traits->width = w; + traits->height = h; + traits->red = 8; + traits->green = 8; + traits->blue = 8; + traits->alpha = 8; + traits->windowDecoration = false; + traits->doubleBuffer = false; + traits->sharedContext = 0; + traits->pbuffer = true; + + osg::GraphicsContext::ScreenIdentifier si; + si.readDISPLAY(); + if (si.displayNum<0) si.displayNum = 0; + traits->displayNum = si.displayNum; + traits->screenNum = si.screenNum; + traits->hostName = si.hostName; + + _gc = osg::GraphicsContext::createGraphicsContext(traits.get()); + + if (!_gc) + { + std::cerr << "Failed to create pbuffer, failing back to normal graphics window." << std::endl; + + traits->pbuffer = false; + _gc = osg::GraphicsContext::createGraphicsContext(traits.get()); + if (!_gc) + throw std::runtime_error("Failed to create graphics context for image decompression"); + } + + if (_gc.valid()) + { + _gc->realize(); + _gc->makeCurrent(); + } + } + + osg::ref_ptr getContext() + { + return _gc; + } + + bool valid() const { return _gc.valid() && _gc->isRealized(); } + + private: + osg::ref_ptr _gc; + }; + + osg::ref_ptr decompress (osg::ref_ptr source, float rotDegrees) + { + int width = source->s(); + int height = source->t(); + + MyGraphicsContext context(width, height); + + osg::ref_ptr state = context.getContext()->getState(); + + osg::ref_ptr texture = new osg::Texture2D; + texture->setImage(source); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_BORDER); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_BORDER); + texture->setBorderColor(osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + + osg::ref_ptr texmat = new osg::TexMat; + osg::Matrix texRot (osg::Matrix::identity()); + float theta ( osg::DegreesToRadians(-rotDegrees) ); + float cosTheta = std::cos(theta); + float sinTheta = std::sin(theta); + + texRot(0,0) = cosTheta; + texRot(1,0) = -sinTheta; + texRot(0,1) = sinTheta; + texRot(1,1) = cosTheta; + // Offset center of rotation to center of texture + texRot(3,0) = 0.5f + ( (-0.5f * cosTheta) - (-0.5f * sinTheta) ); + texRot(3,1) = 0.5f + ( (-0.5f * sinTheta) + (-0.5f * cosTheta) ); + + texmat->setMatrix(texRot); + + state->applyTextureAttribute(0, texmat); + + osg::ref_ptr identity (new osg::RefMatrix(osg::Matrix::identity())); + state->applyModelViewMatrix(identity); + state->applyProjectionMatrix(identity); + + state->applyMode(GL_TEXTURE_2D, true); + state->applyTextureAttribute(0, texture); + + osg::ref_ptr resultImage = new osg::Image; + resultImage->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE); + + osg::RenderInfo renderInfo; + renderInfo.setState(state); + + glViewport(0, 0, width, height); + + osg::ref_ptr geom; + +#if defined(__APPLE__) + // Extra flip needed on Intel graphics OS X systems due to a driver bug + std::string vendorString = (const char*)glGetString(GL_VENDOR); + if (vendorString.find("Intel") != std::string::npos) + geom = osg::createTexturedQuadGeometry(osg::Vec3(-1,1,0), osg::Vec3(2,0,0), osg::Vec3(0,-2,0)); + else +#endif + geom = osg::createTexturedQuadGeometry(osg::Vec3(-1,-1,0), osg::Vec3(2,0,0), osg::Vec3(0,2,0)); + + geom->drawImplementation(renderInfo); + + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, resultImage->data()); + + geom->releaseGLObjects(); + source->releaseGLObjects(); + texture->releaseGLObjects(); + + return resultImage; + } + +} + +namespace SDLUtil +{ + + SDLCursorManager::SDLCursorManager() : + mEnabled(false), + mInitialized(false) + { + } + + SDLCursorManager::~SDLCursorManager() + { + CursorMap::const_iterator curs_iter = mCursorMap.begin(); + + while(curs_iter != mCursorMap.end()) + { + SDL_FreeCursor(curs_iter->second); + ++curs_iter; + } + + mCursorMap.clear(); + } + + void SDLCursorManager::setEnabled(bool enabled) + { + if(mInitialized && enabled == mEnabled) + return; + + mInitialized = true; + mEnabled = enabled; + + //turn on hardware cursors + if(enabled) + { + _setGUICursor(mCurrentCursor); + } + //turn off hardware cursors + else + { + SDL_ShowCursor(SDL_FALSE); + } + } + + void SDLCursorManager::cursorChanged(const std::string& name) + { + mCurrentCursor = name; + + CursorMap::const_iterator curs_iter = mCursorMap.find(name); + + if(curs_iter != mCursorMap.end()) + { + //we have this cursor + _setGUICursor(name); + } + } + + void SDLCursorManager::_setGUICursor(const std::string &name) + { + SDL_SetCursor(mCursorMap.find(name)->second); + } + + void SDLCursorManager::createCursor(const std::string& name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) + { + _createCursorFromResource(name, rotDegrees, image, size_x, size_y, hotspot_x, hotspot_y); + } + + void SDLCursorManager::_createCursorFromResource(const std::string& name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) + { + osg::ref_ptr decompressed; + + if (mCursorMap.find(name) != mCursorMap.end()) + return; + + try { + decompressed = decompress(image, static_cast(rotDegrees)); + } catch (std::exception& e) { + std::cerr << e.what() << std::endl; + std::cerr <<"Using default cursor."< +#include + +#include + +struct SDL_Cursor; +struct SDL_Surface; + +namespace osg +{ + class Image; +} + +namespace SDLUtil +{ + class SDLCursorManager + { + public: + SDLCursorManager(); + virtual ~SDLCursorManager(); + + /// \brief sets whether to actively manage cursors or not + virtual void setEnabled(bool enabled); + + /// \brief Tell the manager that the cursor has changed, giving the + /// name of the cursor we changed to ("arrow", "ibeam", etc) + virtual void cursorChanged(const std::string &name); + + virtual void createCursor(const std::string &name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); + + private: + void _createCursorFromResource(const std::string &name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); + void _putPixel(SDL_Surface *surface, int x, int y, Uint32 pixel); + + void _setGUICursor(const std::string& name); + + typedef std::map CursorMap; + CursorMap mCursorMap; + + std::string mCurrentCursor; + bool mEnabled; + bool mInitialized; + }; +} + +#endif diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp new file mode 100644 index 0000000000..a0483e84df --- /dev/null +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -0,0 +1,208 @@ +#include "sdlgraphicswindow.hpp" + +#include + +#include + +namespace SDLUtil +{ + +GraphicsWindowSDL2::~GraphicsWindowSDL2() +{ + close(true); +} + +GraphicsWindowSDL2::GraphicsWindowSDL2(osg::GraphicsContext::Traits *traits) + : mWindow(0) + , mContext(0) + , mValid(false) + , mRealized(false) + , mOwnsWindow(false) +{ + _traits = traits; + + init(); + if(valid()) + { + setState(new osg::State); + getState()->setGraphicsContext(this); + + if(_traits.valid() && _traits->sharedContext.valid()) + { + getState()->setContextID(_traits->sharedContext->getState()->getContextID()); + incrementContextIDUsageCount(getState()->getContextID()); + } + else + { + getState()->setContextID(osg::GraphicsContext::createNewContextID()); + } + } +} + + +bool GraphicsWindowSDL2::setWindowDecorationImplementation(bool flag) +{ + if(!mWindow) return false; + + SDL_SetWindowBordered(mWindow, flag ? SDL_TRUE : SDL_FALSE); + return true; +} + +bool GraphicsWindowSDL2::setWindowRectangleImplementation(int x, int y, int width, int height) +{ + if(!mWindow) return false; + + SDL_SetWindowPosition(mWindow, x, y); + SDL_SetWindowSize(mWindow, width, height); + return true; +} + +void GraphicsWindowSDL2::setWindowName(const std::string &name) +{ + if(!mWindow) return; + + SDL_SetWindowTitle(mWindow, name.c_str()); + _traits->windowName = name; +} + +void GraphicsWindowSDL2::setCursor(MouseCursor mouseCursor) +{ + _traits->useCursor = false; +} + + +void GraphicsWindowSDL2::init() +{ + if(mValid) return; + + if(!_traits.valid()) + return; + + WindowData *inheritedWindowData = dynamic_cast(_traits->inheritedWindowData.get()); + mWindow = inheritedWindowData ? inheritedWindowData->mWindow : NULL; + + mOwnsWindow = (mWindow == 0); + if(mOwnsWindow) + { + OSG_FATAL<<"Error: No SDL window provided."<vsync ? 1 : 0); + + SDL_GL_MakeCurrent(oldWin, oldCtx); + + mValid = true; + +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,4) + getEventQueue()->syncWindowRectangleWithGraphicsContext(); +#else + getEventQueue()->syncWindowRectangleWithGraphcisContext(); +#endif +} + + +bool GraphicsWindowSDL2::realizeImplementation() +{ + if(mRealized) + { + OSG_NOTICE<< "GraphicsWindowSDL2::realizeImplementation() Already realized" <syncWindowRectangleWithGraphicsContext(); +#else + getEventQueue()->syncWindowRectangleWithGraphcisContext(); +#endif + + mRealized = true; + + return true; +} + +bool GraphicsWindowSDL2::makeCurrentImplementation() +{ + if(!mRealized) + { + OSG_WARN<<"Warning: GraphicsWindow not realized, cannot do makeCurrent."< + +#include + +namespace SDLUtil +{ + +class GraphicsWindowSDL2 : public osgViewer::GraphicsWindow +{ + SDL_Window* mWindow; + SDL_GLContext mContext; + + bool mValid; + bool mRealized; + bool mOwnsWindow; + + void init(); + + virtual ~GraphicsWindowSDL2(); + +public: + GraphicsWindowSDL2(osg::GraphicsContext::Traits *traits); + + virtual bool isSameKindAs(const Object* object) const { return dynamic_cast(object)!=0; } + virtual const char* libraryName() const { return "osgViewer"; } + virtual const char* className() const { return "GraphicsWindowSDL2"; } + + virtual bool valid() const { return mValid; } + + /** Realise the GraphicsContext.*/ + virtual bool realizeImplementation(); + + /** Return true if the graphics context has been realised and is ready to use.*/ + virtual bool isRealizedImplementation() const { return mRealized; } + + /** Close the graphics context.*/ + virtual void closeImplementation(); + + /** Make this graphics context current.*/ + virtual bool makeCurrentImplementation(); + + /** Release the graphics context.*/ + virtual bool releaseContextImplementation(); + + /** Swap the front and back buffers.*/ + virtual void swapBuffersImplementation(); + + /** Set sync-to-vblank. */ + virtual void setSyncToVBlank(bool on); + + /** Set Window decoration.*/ + virtual bool setWindowDecorationImplementation(bool flag); + + /** Raise specified window */ + virtual void raiseWindow(); + + /** Set the window's position and size.*/ + virtual bool setWindowRectangleImplementation(int x, int y, int width, int height); + + /** Set the name of the window */ + virtual void setWindowName(const std::string &name); + + /** Set mouse cursor to a specific shape.*/ + virtual void setCursor(MouseCursor cursor); + + /** Get focus.*/ + virtual void grabFocus() {} + + /** Get focus on if the pointer is in this window.*/ + virtual void grabFocusIfPointerInWindow() {} + + /** WindowData is used to pass in the SDL2 window handle attached to the GraphicsContext::Traits structure. */ + struct WindowData : public osg::Referenced + { + WindowData(SDL_Window *window) : mWindow(window) + { } + + SDL_Window *mWindow; + }; +}; + +} + +#endif /* OSGGRAPHICSWINDOW_H */ diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp similarity index 91% rename from extern/sdl4ogre/sdlinputwrapper.cpp rename to components/sdlutil/sdlinputwrapper.cpp index 7e3536ab40..647a65005c 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -1,15 +1,16 @@ #include "sdlinputwrapper.hpp" -#include -#include -#include +#include +#include +#include -namespace SFO +namespace SDLUtil { - /// \brief General purpose wrapper for OGRE applications around SDL's event - /// queue, mostly used for handling input-related events. - InputWrapper::InputWrapper(SDL_Window* window, Ogre::RenderWindow* ogreWindow, bool grab) : + +InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr viewer, bool grab) : + mSDLWindow(window), + mViewer(viewer), mMouseListener(NULL), mKeyboardListener(NULL), mWindowListener(NULL), @@ -29,11 +30,13 @@ namespace SFO mMouseX(0), mMouseY(0), mWindowHasFocus(true), - mMouseInWindow(true), - mSDLWindow(window), - mOgreWindow(ogreWindow) + mMouseInWindow(true) { _setupOISKeys(); + + Uint32 flags = SDL_GetWindowFlags(mSDLWindow); + mWindowHasFocus = (flags & SDL_WINDOW_INPUT_FOCUS); + mMouseInWindow = (flags & SDL_WINDOW_MOUSE_FOCUS); } InputWrapper::~InputWrapper() @@ -42,6 +45,8 @@ namespace SFO void InputWrapper::capture(bool windowEventsOnly) { + mViewer->getEventQueue()->frame(0.f); + SDL_PumpEvents(); SDL_Event evt; @@ -83,10 +88,20 @@ namespace SFO case SDL_KEYDOWN: if (!evt.key.repeat) mKeyboardListener->keyPressed(evt.key); + + // temporary for the stats viewer + if (evt.key.keysym.sym == SDLK_F3) + mViewer->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KEY_F3); + break; case SDL_KEYUP: if (!evt.key.repeat) mKeyboardListener->keyReleased(evt.key); + + // temporary for the stats viewer + if (evt.key.keysym.sym == SDLK_F3) + mViewer->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KEY_F3); + break; case SDL_TEXTINPUT: mKeyboardListener->textInput(evt.text); @@ -127,6 +142,16 @@ namespace SFO break; case SDL_CLIPBOARDUPDATE: break; // We don't need this event, clipboard is retrieved on demand + + case SDL_FINGERDOWN: + case SDL_FINGERUP: + case SDL_FINGERMOTION: + case SDL_DOLLARGESTURE: + case SDL_DOLLARRECORD: + case SDL_MULTIGESTURE: + // No use for touch & gesture events + break; + default: std::ios::fmtflags f(std::cerr.flags()); std::cerr << "Unhandled SDL event of type 0x" << std::hex << evt.type << std::endl; @@ -147,28 +172,26 @@ namespace SFO mMouseInWindow = false; updateMouseSettings(); break; + case SDL_WINDOWEVENT_MOVED: + // I'm not sure what OSG is using the window position for, but I don't think it's needed, + // so we ignore window moved events (improves window movement performance) + break; case SDL_WINDOWEVENT_SIZE_CHANGED: int w,h; SDL_GetWindowSize(mSDLWindow, &w, &h); - // TODO: Fix Ogre to handle this more consistently (fixed in 1.9) -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX - mOgreWindow->resize(w, h); -#else - mOgreWindow->windowMovedOrResized(); -#endif + int x,y; + SDL_GetWindowPosition(mSDLWindow, &x,&y); + mViewer->getCamera()->getGraphicsContext()->resized(x,y,w,h); + + mViewer->getEventQueue()->windowResize(x,y,w,h); + if (mWindowListener) mWindowListener->windowResized(w, h); + break; case SDL_WINDOWEVENT_RESIZED: - // TODO: Fix Ogre to handle this more consistently (fixed in 1.9) -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX - mOgreWindow->resize(evt.window.data1, evt.window.data2); -#else - mOgreWindow->windowMovedOrResized(); -#endif - if (mWindowListener) - mWindowListener->windowResized(evt.window.data1, evt.window.data2); + // This should also fire SIZE_CHANGED, so no need to handle break; case SDL_WINDOWEVENT_FOCUS_GAINED: @@ -187,12 +210,10 @@ namespace SFO case SDL_WINDOWEVENT_CLOSE: break; case SDL_WINDOWEVENT_SHOWN: - mOgreWindow->setVisible(true); if (mWindowListener) mWindowListener->windowVisibilityChange(true); break; case SDL_WINDOWEVENT_HIDDEN: - mOgreWindow->setVisible(false); if (mWindowListener) mWindowListener->windowVisibilityChange(false); break; diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/components/sdlutil/sdlinputwrapper.hpp similarity index 67% rename from extern/sdl4ogre/sdlinputwrapper.hpp rename to components/sdlutil/sdlinputwrapper.hpp index a7023207c6..a821b9012c 100644 --- a/extern/sdl4ogre/sdlinputwrapper.hpp +++ b/components/sdlutil/sdlinputwrapper.hpp @@ -1,24 +1,27 @@ -#ifndef SDL4OGRE_SDLINPUTWRAPPER_H -#define SDL4OGRE_SDLINPUTWRAPPER_H +#ifndef OPENMW_COMPONENTS_SDLUTIL_SDLINPUTWRAPPER_H +#define OPENMW_COMPONENTS_SDLUTIL_SDLINPUTWRAPPER_H -#define NOMINMAX +#include + +#include #include -#include -#include +#include "OISCompat.hpp" +#include "events.hpp" -#include "OISCompat.h" -#include "events.h" - - - -namespace SFO +namespace osgViewer { + class Viewer; +} + +namespace SDLUtil +{ + /// \brief A wrapper around SDL's event queue, mostly used for handling input-related events. class InputWrapper { public: - InputWrapper(SDL_Window *window, Ogre::RenderWindow* ogreWindow, bool grab); + InputWrapper(SDL_Window *window, osg::ref_ptr viewer, bool grab); ~InputWrapper(); void setMouseEventCallback(MouseListener* listen) { mMouseListener = listen; } @@ -27,8 +30,8 @@ namespace SFO void setControllerEventCallback(ControllerListener* listen) { mConListener = listen; } void capture(bool windowEventsOnly); - bool isModifierHeld(SDL_Keymod mod); - bool isKeyDown(SDL_Scancode key); + bool isModifierHeld(SDL_Keymod mod); + bool isKeyDown(SDL_Scancode key); void setMouseVisible (bool visible); void setMouseRelative(bool relative); @@ -42,7 +45,6 @@ namespace SFO void updateMouseSettings(); private: - void handleWindowEvent(const SDL_Event& evt); bool _handleWarpMotion(const SDL_MouseMotionEvent& evt); @@ -51,12 +53,15 @@ namespace SFO void _setupOISKeys(); - SFO::MouseListener* mMouseListener; - SFO::KeyListener* mKeyboardListener; - SFO::WindowListener* mWindowListener; - SFO::ControllerListener* mConListener; + SDL_Window* mSDLWindow; + osg::ref_ptr mViewer; - typedef boost::unordered_map KeyMap; + MouseListener* mMouseListener; + KeyListener* mKeyboardListener; + WindowListener* mWindowListener; + ControllerListener* mConListener; + + typedef std::map KeyMap; KeyMap mKeyMap; Uint16 mWarpX; @@ -79,9 +84,6 @@ namespace SFO bool mWindowHasFocus; bool mMouseInWindow; - - SDL_Window* mSDLWindow; - Ogre::RenderWindow* mOgreWindow; }; } diff --git a/components/sdlutil/sdlvideowrapper.cpp b/components/sdlutil/sdlvideowrapper.cpp new file mode 100644 index 0000000000..dd89d10724 --- /dev/null +++ b/components/sdlutil/sdlvideowrapper.cpp @@ -0,0 +1,94 @@ +#include "sdlvideowrapper.hpp" + +#include + +#include + +#include + +namespace SDLUtil +{ + + VideoWrapper::VideoWrapper(SDL_Window *window, osg::ref_ptr viewer) + : mWindow(window) + , mViewer(viewer) + , mGamma(1.f) + , mContrast(1.f) + , mHasSetGammaContrast(false) + { + SDL_GetWindowGammaRamp(mWindow, mOldSystemGammaRamp, &mOldSystemGammaRamp[256], &mOldSystemGammaRamp[512]); + } + + VideoWrapper::~VideoWrapper() + { + SDL_SetWindowFullscreen(mWindow, 0); + + // If user hasn't touched the defaults no need to restore + if (mHasSetGammaContrast) + SDL_SetWindowGammaRamp(mWindow, mOldSystemGammaRamp, &mOldSystemGammaRamp[256], &mOldSystemGammaRamp[512]); + } + + void VideoWrapper::setSyncToVBlank(bool sync) + { + osgViewer::Viewer::Windows windows; + mViewer->getWindows(windows); + mViewer->stopThreading(); + for (osgViewer::Viewer::Windows::iterator it = windows.begin(); it != windows.end(); ++it) + { + osgViewer::GraphicsWindow* win = *it; + win->setSyncToVBlank(sync); + } + mViewer->startThreading(); + } + + void VideoWrapper::setGammaContrast(float gamma, float contrast) + { + if (gamma == mGamma && contrast == mContrast) + return; + + mGamma = gamma; + mContrast = contrast; + + mHasSetGammaContrast = true; + + Uint16 red[256], green[256], blue[256]; + for (int i = 0; i < 256; i++) + { + float k = i/256.0f; + k = (k - 0.5f) * contrast + 0.5f; + k = pow(k, 1.f/gamma); + k *= 256; + float value = k*256; + if (value > 65535) value = 65535; + else if (value < 0) value = 0; + + red[i] = green[i] = blue[i] = static_cast(value); + } + if (SDL_SetWindowGammaRamp(mWindow, red, green, blue) < 0) + std::cout << "Couldn't set gamma: " << SDL_GetError() << std::endl; + } + + void VideoWrapper::setVideoMode(int width, int height, bool fullscreen, bool windowBorder) + { + SDL_SetWindowFullscreen(mWindow, 0); + + if (SDL_GetWindowFlags(mWindow) & SDL_WINDOW_MAXIMIZED) + SDL_RestoreWindow(mWindow); + + if (fullscreen) + { + SDL_DisplayMode mode; + SDL_GetWindowDisplayMode(mWindow, &mode); + mode.w = width; + mode.h = height; + SDL_SetWindowDisplayMode(mWindow, &mode); + SDL_SetWindowFullscreen(mWindow, fullscreen); + } + else + { + SDL_SetWindowSize(mWindow, width, height); + SDL_SetWindowBordered(mWindow, windowBorder ? SDL_TRUE : SDL_FALSE); + } + } + +} diff --git a/components/sdlutil/sdlvideowrapper.hpp b/components/sdlutil/sdlvideowrapper.hpp new file mode 100644 index 0000000000..77f0b80393 --- /dev/null +++ b/components/sdlutil/sdlvideowrapper.hpp @@ -0,0 +1,44 @@ +#ifndef OPENMW_COMPONENTS_SDLUTIL_SDLVIDEOWRAPPER_H +#define OPENMW_COMPONENTS_SDLUTIL_SDLVIDEOWRAPPER_H + +#include + +#include + +struct SDL_Window; + +namespace osgViewer +{ + class Viewer; +} + +namespace SDLUtil +{ + + class VideoWrapper + { + public: + VideoWrapper(SDL_Window* window, osg::ref_ptr viewer); + ~VideoWrapper(); + + void setSyncToVBlank(bool sync); + + void setGammaContrast(float gamma, float contrast); + + void setVideoMode(int width, int height, bool fullscreen, bool windowBorder); + + private: + SDL_Window* mWindow; + osg::ref_ptr mViewer; + + float mGamma; + float mContrast; + bool mHasSetGammaContrast; + + // Store system gamma ramp on window creation. Restore system gamma ramp on exit + Uint16 mOldSystemGammaRamp[256*3]; + }; + +} + +#endif diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index a9a78d0351..0e5324bd97 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -1,13 +1,57 @@ #include "settings.hpp" #include +#include +#include -#include +#include #include #include #include +namespace +{ + + bool parseBool(const std::string& string) + { + return (Misc::StringUtils::ciEqual(string, "true")); + } + + float parseFloat(const std::string& string) + { + std::stringstream stream; + stream << string; + float ret = 0.f; + stream >> ret; + return ret; + } + + int parseInt(const std::string& string) + { + std::stringstream stream; + stream << string; + int ret = 0; + stream >> ret; + return ret; + } + + template + std::string toString(T val) + { + std::ostringstream stream; + stream << val; + return stream.str(); + } + + template <> + std::string toString(bool val) + { + return val ? "true" : "false"; + } + +} + namespace Settings { @@ -15,6 +59,7 @@ CategorySettingValueMap Manager::mDefaultSettings = CategorySettingValueMap(); CategorySettingValueMap Manager::mUserSettings = CategorySettingValueMap(); CategorySettingVector Manager::mChangedSettings = CategorySettingVector(); +typedef std::map< CategorySetting, bool > CategorySettingStatusMap; class SettingsFileParser { @@ -26,6 +71,7 @@ public: mFile = file; boost::filesystem::ifstream stream; stream.open(boost::filesystem::path(file)); + std::cout << "Loading settings file: " << file << std::endl; std::string currentCategory; mLine = 0; while (!stream.eof() && !stream.fail()) @@ -74,6 +120,215 @@ public: } } + void saveSettingsFile (const std::string& file, CategorySettingValueMap& settings) + { + // No options have been written to the file yet. + CategorySettingStatusMap written; + for (CategorySettingValueMap::iterator it = settings.begin(); it != settings.end(); ++it) { + written[it->first] = false; + } + + // Have we substantively changed the settings file? + bool changed = false; + + // Were there any lines at all in the file? + bool existing = false; + + // The category/section we're currently in. + std::string currentCategory; + + // Open the existing settings.cfg file to copy comments. This might not be the same file + // as the output file if we're copying the setting from the default settings.cfg for the + // first time. A minor change in API to pass the source file might be in order here. + boost::filesystem::ifstream istream; + boost::filesystem::path ipath(file); + istream.open(ipath); + + // Create a new string stream to write the current settings to. It's likely that the + // input file and the output file are the same, so this stream serves as a temporary file + // of sorts. The setting files aren't very large so there's no performance issue. + std::stringstream ostream; + + // For every line in the input file... + while (!istream.eof() && !istream.fail()) { + std::string line; + std::getline(istream, line); + + // The current character position in the line. + size_t i = 0; + + // Don't add additional newlines at the end of the file. + if (istream.eof()) continue; + + // Copy entirely blank lines. + if (!skipWhiteSpace(i, line)) { + ostream << line << std::endl; + continue; + } + + // There were at least some comments in the input file. + existing = true; + + // Copy comments. + if (line[i] == '#') { + ostream << line << std::endl; + continue; + } + + // Category heading. + if (line[i] == '[') { + size_t end = line.find(']', i); + // This should never happen unless the player edited the file while playing. + if (end == std::string::npos) { + ostream << "# unterminated category: " << line << std::endl; + changed = true; + continue; + } + + // Ensure that all options in the current category have been written. + for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { + if (mit->second == false && mit->first.first == currentCategory) { + std::cout << "Added new setting: [" << currentCategory << "] " + << mit->first.second << " = " << settings[mit->first] << std::endl; + ostream << mit->first.second << " = " << settings[mit->first] << std::endl; + mit->second = true; + changed = true; + } + } + + // Update the current category. + currentCategory = line.substr(i+1, end - (i+1)); + boost::algorithm::trim(currentCategory); + + // Write the (new) current category to the file. + ostream << "[" << currentCategory << "]" << std::endl; + //std::cout << "Wrote category: " << currentCategory << std::endl; + + // A setting can apparently follow the category on an input line. That's rather + // inconvenient, since it makes it more likely to have duplicative sections, + // which our algorithm doesn't like. Do the best we can. + i = end+1; + } + + // Truncate trailing whitespace, since we're changing the file anayway. + if (!skipWhiteSpace(i, line)) + continue; + + // If we've found settings before the first category, something's wrong. This + // should never happen unless the player edited the file while playing, since + // the loadSettingsFile() logic rejects it. + if (currentCategory.empty()) { + ostream << "# empty category name: " << line << std::endl; + changed = true; + continue; + } + + // Which setting was at this location in the input file? + size_t settingEnd = line.find('=', i); + // This should never happen unless the player edited the file while playing. + if (settingEnd == std::string::npos) { + ostream << "# unterminated setting name: " << line << std::endl; + changed = true; + continue; + } + std::string setting = line.substr(i, (settingEnd-i)); + boost::algorithm::trim(setting); + + // Get the existing value so we can see if we've changed it. + size_t valueBegin = settingEnd+1; + std::string value = line.substr(valueBegin); + boost::algorithm::trim(value); + + // Construct the setting map key to determine whether the setting has already been + // written to the file. + CategorySetting key = std::make_pair(currentCategory, setting); + CategorySettingStatusMap::iterator finder = written.find(key); + + // Settings not in the written map are definitely invalid. Currently, this can only + // happen if the player edited the file while playing, because loadSettingsFile() + // will accept anything and pass it along in the map, but in the future, we might + // want to handle invalid settings more gracefully here. + if (finder == written.end()) { + ostream << "# invalid setting: " << line << std::endl; + changed = true; + continue; + } + + // Write the current value of the setting to the file. The key must exist in the + // settings map because of how written was initialized and finder != end(). + ostream << setting << " = " << settings[key] << std::endl; + // Mark that setting as written. + finder->second = true; + // Did we really change it? + if (value != settings[key]) { + std::cout << "Changed setting: [" << currentCategory << "] " + << setting << " = " << settings[key] << std::endl; + changed = true; + } + // No need to write the current line, because we just emitted a replacement. + + // Curiously, it appears that comments at the ends of lines with settings are not + // allowed, and the comment becomes part of the value. Was that intended? + } + + // We're done with the input stream file. + istream.close(); + + // Ensure that all options in the current category have been written. We must complete + // the current category at the end of the file before moving on to any new categories. + for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { + if (mit->second == false && mit->first.first == currentCategory) { + std::cout << "Added new setting: [" << mit->first.first << "] " + << mit->first.second << " = " << settings[mit->first] << std::endl; + ostream << mit->first.second << " = " << settings[mit->first] << std::endl; + mit->second = true; + changed = true; + } + } + + // If there was absolutely nothing in the file (or more likely the file didn't + // exist), start the newly created file with a helpful comment. + if (!existing) { + ostream << "# This is the OpenMW user 'settings.cfg' file. This file only contains" << std::endl; + ostream << "# explicitly changed settings. If you would like to revert a setting" << std::endl; + ostream << "# to its default, simply remove it from this file. For available" << std::endl; + ostream << "# settings, see the file 'settings-default.cfg' or the documentation at:" << std::endl; + ostream << "#" << std::endl; + ostream << "# https://wiki.openmw.org/index.php?title=Settings" << std::endl; + } + + // We still have one more thing to do before we're completely done writing the file. + // It's possible that there are entirely new categories, or that the input file had + // disappeared completely, so we need ensure that all settings are written to the file + // regardless of those issues. + for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { + // If the setting hasn't been written yet. + if (mit->second == false) { + // If the catgory has changed, write a new category header. + if (mit->first.first != currentCategory) { + currentCategory = mit->first.first; + std::cout << "Created new setting section: " << mit->first.first << std::endl; + ostream << std::endl; + ostream << "[" << currentCategory << "]" << std::endl; + } + std::cout << "Added new setting: [" << mit->first.first << "] " + << mit->first.second << " = " << settings[mit->first] << std::endl; + // Then write the setting. No need to mark it as written because we're done. + ostream << mit->first.second << " = " << settings[mit->first] << std::endl; + changed = true; + } + } + + // Now install the newly written file in the requested place. + if (changed) { + std::cout << "Updating settings file: " << ipath << std::endl; + boost::filesystem::ofstream ofstream; + ofstream.open(ipath); + ofstream << ostream.rdbuf(); + ofstream.close(); + } + } + private: /// Increment i until it longer points to a whitespace character /// in the string or has reached the end of the string. @@ -98,6 +353,13 @@ private: int mLine; }; +void Manager::clear() +{ + mDefaultSettings.clear(); + mUserSettings.clear(); + mChangedSettings.clear(); +} + void Manager::loadDefault(const std::string &file) { SettingsFileParser parser; @@ -112,18 +374,8 @@ void Manager::loadUser(const std::string &file) void Manager::saveUser(const std::string &file) { - boost::filesystem::ofstream stream; - stream.open(boost::filesystem::path(file)); - std::string currentCategory; - for (CategorySettingValueMap::iterator it = mUserSettings.begin(); it != mUserSettings.end(); ++it) - { - if (it->first.first != currentCategory) - { - currentCategory = it->first.first; - stream << "\n[" << currentCategory << "]\n"; - } - stream << it->first.second << " = " << it->second << "\n"; - } + SettingsFileParser parser; + parser.saveSettingsFile(file, mUserSettings); } std::string Manager::getString(const std::string &setting, const std::string &category) @@ -143,17 +395,17 @@ std::string Manager::getString(const std::string &setting, const std::string &ca float Manager::getFloat (const std::string& setting, const std::string& category) { - return Ogre::StringConverter::parseReal( getString(setting, category) ); + return parseFloat( getString(setting, category) ); } int Manager::getInt (const std::string& setting, const std::string& category) { - return Ogre::StringConverter::parseInt( getString(setting, category) ); + return parseInt( getString(setting, category) ); } bool Manager::getBool (const std::string& setting, const std::string& category) { - return Ogre::StringConverter::parseBool( getString(setting, category) ); + return parseBool( getString(setting, category) ); } void Manager::setString(const std::string &setting, const std::string &category, const std::string &value) @@ -174,17 +426,17 @@ void Manager::setString(const std::string &setting, const std::string &category, void Manager::setInt (const std::string& setting, const std::string& category, const int value) { - setString(setting, category, Ogre::StringConverter::toString(value)); + setString(setting, category, toString(value)); } void Manager::setFloat (const std::string &setting, const std::string &category, const float value) { - setString(setting, category, Ogre::StringConverter::toString(value)); + setString(setting, category, toString(value)); } void Manager::setBool(const std::string &setting, const std::string &category, const bool value) { - setString(setting, category, Ogre::StringConverter::toString(value)); + setString(setting, category, toString(value)); } const CategorySettingVector Manager::apply() diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index c16ff5a1ef..7adcb9b396 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -23,6 +23,9 @@ namespace Settings static CategorySettingVector mChangedSettings; ///< tracks all the settings that were changed since the last apply() call + void clear(); + ///< clears all settings and default settings + void loadDefault (const std::string& file); ///< load file as the default settings (can be overridden by user settings) diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index 1b000fabb1..a64f8ffd1d 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -1,35 +1,16 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ #include "buffercache.hpp" -#include +#include + +#include #include "defs.hpp" namespace { -template -Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigned int verts, Ogre::HardwareIndexBuffer::IndexType type) +template +osg::ref_ptr createIndexBuffer(unsigned int flags, unsigned int verts) { // LOD level n means every 2^n-th vertex is kept size_t lodLevel = (flags >> (4*4)); @@ -42,8 +23,9 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne size_t increment = 1 << lodLevel; assert(increment < verts); - std::vector indices; - indices.reserve((verts-1)*(verts-1)*2*3 / increment); + + osg::ref_ptr indices (new IndexArrayType(osg::PrimitiveSet::TRIANGLES)); + indices->reserve((verts-1)*(verts-1)*2*3 / increment); size_t rowStart = 0, colStart = 0, rowEnd = verts-1, colEnd = verts-1; // If any edge needs stitching we'll skip all edges at this point, @@ -62,23 +44,23 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne // diamond pattern if ((row + col%2) % 2 == 1) { - indices.push_back(verts*(col+increment)+row); - indices.push_back(verts*(col+increment)+row+increment); - indices.push_back(verts*col+row+increment); + indices->push_back(verts*(col+increment)+row); + indices->push_back(verts*(col+increment)+row+increment); + indices->push_back(verts*col+row+increment); - indices.push_back(verts*col+row); - indices.push_back(verts*(col+increment)+row); - indices.push_back(verts*(col)+row+increment); + indices->push_back(verts*col+row); + indices->push_back(verts*(col+increment)+row); + indices->push_back(verts*(col)+row+increment); } else { - indices.push_back(verts*col+row); - indices.push_back(verts*(col+increment)+row+increment); - indices.push_back(verts*col+row+increment); + indices->push_back(verts*col+row); + indices->push_back(verts*(col+increment)+row+increment); + indices->push_back(verts*col+row+increment); - indices.push_back(verts*col+row); - indices.push_back(verts*(col+increment)+row); - indices.push_back(verts*(col+increment)+row+increment); + indices->push_back(verts*col+row); + indices->push_back(verts*(col+increment)+row); + indices->push_back(verts*(col+increment)+row+increment); } } } @@ -94,22 +76,22 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne size_t outerStep = 1 << (lodDeltas[Terrain::South] + lodLevel); for (size_t col = 0; col < verts-1; col += outerStep) { - indices.push_back(verts*col+row); - indices.push_back(verts*(col+outerStep)+row); + indices->push_back(verts*col+row); + indices->push_back(verts*(col+outerStep)+row); // Make sure not to touch the right edge if (col+outerStep == verts-1) - indices.push_back(verts*(col+outerStep-innerStep)+row+innerStep); + indices->push_back(verts*(col+outerStep-innerStep)+row+innerStep); else - indices.push_back(verts*(col+outerStep)+row+innerStep); + indices->push_back(verts*(col+outerStep)+row+innerStep); for (size_t i = 0; i < outerStep; i += innerStep) { // Make sure not to touch the left or right edges if (col+i == 0 || col+i == verts-1-innerStep) continue; - indices.push_back(verts*(col)+row); - indices.push_back(verts*(col+i+innerStep)+row+innerStep); - indices.push_back(verts*(col+i)+row+innerStep); + indices->push_back(verts*(col)+row); + indices->push_back(verts*(col+i+innerStep)+row+innerStep); + indices->push_back(verts*(col+i)+row+innerStep); } } @@ -118,22 +100,22 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne outerStep = size_t(1) << (lodDeltas[Terrain::North] + lodLevel); for (size_t col = 0; col < verts-1; col += outerStep) { - indices.push_back(verts*(col+outerStep)+row); - indices.push_back(verts*col+row); + indices->push_back(verts*(col+outerStep)+row); + indices->push_back(verts*col+row); // Make sure not to touch the left edge if (col == 0) - indices.push_back(verts*(col+innerStep)+row-innerStep); + indices->push_back(verts*(col+innerStep)+row-innerStep); else - indices.push_back(verts*col+row-innerStep); + indices->push_back(verts*col+row-innerStep); for (size_t i = 0; i < outerStep; i += innerStep) { // Make sure not to touch the left or right edges if (col+i == 0 || col+i == verts-1-innerStep) continue; - indices.push_back(verts*(col+i)+row-innerStep); - indices.push_back(verts*(col+i+innerStep)+row-innerStep); - indices.push_back(verts*(col+outerStep)+row); + indices->push_back(verts*(col+i)+row-innerStep); + indices->push_back(verts*(col+i+innerStep)+row-innerStep); + indices->push_back(verts*(col+outerStep)+row); } } @@ -142,22 +124,22 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne outerStep = size_t(1) << (lodDeltas[Terrain::West] + lodLevel); for (size_t row = 0; row < verts-1; row += outerStep) { - indices.push_back(verts*col+row+outerStep); - indices.push_back(verts*col+row); + indices->push_back(verts*col+row+outerStep); + indices->push_back(verts*col+row); // Make sure not to touch the top edge if (row+outerStep == verts-1) - indices.push_back(verts*(col+innerStep)+row+outerStep-innerStep); + indices->push_back(verts*(col+innerStep)+row+outerStep-innerStep); else - indices.push_back(verts*(col+innerStep)+row+outerStep); + indices->push_back(verts*(col+innerStep)+row+outerStep); for (size_t i = 0; i < outerStep; i += innerStep) { // Make sure not to touch the top or bottom edges if (row+i == 0 || row+i == verts-1-innerStep) continue; - indices.push_back(verts*col+row); - indices.push_back(verts*(col+innerStep)+row+i); - indices.push_back(verts*(col+innerStep)+row+i+innerStep); + indices->push_back(verts*col+row); + indices->push_back(verts*(col+innerStep)+row+i); + indices->push_back(verts*(col+innerStep)+row+i+innerStep); } } @@ -166,31 +148,27 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne outerStep = size_t(1) << (lodDeltas[Terrain::East] + lodLevel); for (size_t row = 0; row < verts-1; row += outerStep) { - indices.push_back(verts*col+row); - indices.push_back(verts*col+row+outerStep); + indices->push_back(verts*col+row); + indices->push_back(verts*col+row+outerStep); // Make sure not to touch the bottom edge if (row == 0) - indices.push_back(verts*(col-innerStep)+row+innerStep); + indices->push_back(verts*(col-innerStep)+row+innerStep); else - indices.push_back(verts*(col-innerStep)+row); + indices->push_back(verts*(col-innerStep)+row); for (size_t i = 0; i < outerStep; i += innerStep) { // Make sure not to touch the top or bottom edges if (row+i == 0 || row+i == verts-1-innerStep) continue; - indices.push_back(verts*col+row+outerStep); - indices.push_back(verts*(col-innerStep)+row+i+innerStep); - indices.push_back(verts*(col-innerStep)+row+i); + indices->push_back(verts*col+row+outerStep); + indices->push_back(verts*(col-innerStep)+row+i+innerStep); + indices->push_back(verts*(col-innerStep)+row+i); } } } - Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); - Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(type, - indices.size(), Ogre::HardwareBuffer::HBU_STATIC); - buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true); - return buffer; + return indices; } } @@ -198,7 +176,7 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne namespace Terrain { - Ogre::HardwareVertexBufferSharedPtr BufferCache::getUVBuffer() + osg::ref_ptr BufferCache::getUVBuffer() { if (mUvBufferMap.find(mNumVerts) != mUvBufferMap.end()) { @@ -207,30 +185,26 @@ namespace Terrain int vertexCount = mNumVerts * mNumVerts; - std::vector uvs; - uvs.reserve(vertexCount*2); + osg::ref_ptr uvs (new osg::Vec2Array); + uvs->reserve(vertexCount); for (unsigned int col = 0; col < mNumVerts; ++col) { for (unsigned int row = 0; row < mNumVerts; ++row) { - uvs.push_back(col / static_cast(mNumVerts-1)); // U - uvs.push_back(row / static_cast(mNumVerts-1)); // V + uvs->push_back(osg::Vec2f(col / static_cast(mNumVerts-1), + row / static_cast(mNumVerts-1))); } } - Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); - Ogre::HardwareVertexBufferSharedPtr buffer = mgr->createVertexBuffer( - Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2), - vertexCount, Ogre::HardwareBuffer::HBU_STATIC); + // Assign a VBO here to enable state sharing between different Geometries. + uvs->setVertexBufferObject(new osg::VertexBufferObject); - buffer->writeData(0, buffer->getSizeInBytes(), &uvs[0], true); - - mUvBufferMap[mNumVerts] = buffer; - return buffer; + mUvBufferMap[mNumVerts] = uvs; + return uvs; } - Ogre::HardwareIndexBufferSharedPtr BufferCache::getIndexBuffer(unsigned int flags) + osg::ref_ptr BufferCache::getIndexBuffer(unsigned int flags) { unsigned int verts = mNumVerts; @@ -239,11 +213,15 @@ namespace Terrain return mIndexBufferMap[flags]; } - Ogre::HardwareIndexBufferSharedPtr buffer; + osg::ref_ptr buffer; + if (verts*verts > (0xffffu)) - buffer = createIndexBuffer(flags, verts, Ogre::HardwareIndexBuffer::IT_32BIT); + buffer = createIndexBuffer(flags, verts); else - buffer = createIndexBuffer(flags, verts, Ogre::HardwareIndexBuffer::IT_16BIT); + buffer = createIndexBuffer(flags, verts); + + // Assign a EBO here to enable state sharing between different Geometries. + buffer->setElementBufferObject(new osg::ElementBufferObject); mIndexBufferMap[flags] = buffer; return buffer; diff --git a/components/terrain/buffercache.hpp b/components/terrain/buffercache.hpp index 887f0822ef..ca210f2380 100644 --- a/components/terrain/buffercache.hpp +++ b/components/terrain/buffercache.hpp @@ -1,29 +1,8 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ #ifndef COMPONENTS_TERRAIN_BUFFERCACHE_H #define COMPONENTS_TERRAIN_BUFFERCACHE_H -#include -#include +#include +#include #include @@ -38,16 +17,18 @@ namespace Terrain /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each) /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices) - Ogre::HardwareIndexBufferSharedPtr getIndexBuffer (unsigned int flags); + osg::ref_ptr getIndexBuffer (unsigned int flags); - Ogre::HardwareVertexBufferSharedPtr getUVBuffer (); + osg::ref_ptr getUVBuffer(); + + // TODO: add releaseGLObjects() for our vertex/element buffer objects private: // Index buffers are shared across terrain batches where possible. There is one index buffer for each // combination of LOD deltas and index buffer LOD we may need. - std::map mIndexBufferMap; + std::map > mIndexBufferMap; - std::map mUvBufferMap; + std::map > mUvBufferMap; unsigned int mNumVerts; }; diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp deleted file mode 100644 index cce5abd363..0000000000 --- a/components/terrain/chunk.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include "chunk.hpp" - -#include -#include -#include -#include -#include - -#include - -namespace Terrain -{ - - Chunk::Chunk(Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, - const std::vector& positions, const std::vector& normals, const std::vector& colours) - : mBounds(bounds) - , mOwnMaterial(false) - { - mVertexData = OGRE_NEW Ogre::VertexData; - mVertexData->vertexStart = 0; - mVertexData->vertexCount = positions.size()/3; - - // Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc) - Ogre::VertexDeclaration* vertexDecl = mVertexData->vertexDeclaration; - - Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); - size_t nextBuffer = 0; - - // Positions - vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION); - Ogre::HardwareVertexBufferSharedPtr vertexBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), - mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); - - // Normals - vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); - Ogre::HardwareVertexBufferSharedPtr normalBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), - mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); - - - // UV texture coordinates - vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT2, - Ogre::VES_TEXTURE_COORDINATES, 0); - - // Colours - vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE); - Ogre::HardwareVertexBufferSharedPtr colourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), - mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); - - vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true); - normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true); - colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colours[0], true); - - mVertexData->vertexBufferBinding->setBinding(0, vertexBuffer); - mVertexData->vertexBufferBinding->setBinding(1, normalBuffer); - mVertexData->vertexBufferBinding->setBinding(2, uvBuffer); - mVertexData->vertexBufferBinding->setBinding(3, colourBuffer); - - // Assign a default material in case terrain material fails to be created - mMaterial = Ogre::MaterialManager::getSingleton().getByName("BaseWhite"); - - mIndexData = OGRE_NEW Ogre::IndexData(); - mIndexData->indexStart = 0; - } - - void Chunk::setIndexBuffer(Ogre::HardwareIndexBufferSharedPtr buffer) - { - mIndexData->indexBuffer = buffer; - mIndexData->indexCount = buffer->getNumIndexes(); - } - - Chunk::~Chunk() - { - if (!mMaterial.isNull() && mOwnMaterial) - { -#if TERRAIN_USE_SHADER - sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName()); -#endif - Ogre::MaterialManager::getSingleton().remove(mMaterial->getName()); - } - OGRE_DELETE mVertexData; - OGRE_DELETE mIndexData; - } - - void Chunk::setMaterial(const Ogre::MaterialPtr &material, bool own) - { - // Clean up the previous material, if we own it - if (!mMaterial.isNull() && mOwnMaterial) - { -#if TERRAIN_USE_SHADER - sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName()); -#endif - Ogre::MaterialManager::getSingleton().remove(mMaterial->getName()); - } - - mMaterial = material; - mOwnMaterial = own; - } - - const Ogre::AxisAlignedBox& Chunk::getBoundingBox(void) const - { - return mBounds; - } - - Ogre::Real Chunk::getBoundingRadius(void) const - { - return mBounds.getHalfSize().length(); - } - - void Chunk::_updateRenderQueue(Ogre::RenderQueue* queue) - { - queue->addRenderable(this, mRenderQueueID); - } - - void Chunk::visitRenderables(Ogre::Renderable::Visitor* visitor, - bool debugRenderables) - { - visitor->visit(this, 0, false); - } - - const Ogre::MaterialPtr& Chunk::getMaterial(void) const - { - return mMaterial; - } - - void Chunk::getRenderOperation(Ogre::RenderOperation& op) - { - assert (!mIndexData->indexBuffer.isNull() && "Trying to render, but no index buffer set!"); - assert(!mMaterial.isNull() && "Trying to render, but no material set!"); - op.useIndexes = true; - op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST; - op.vertexData = mVertexData; - op.indexData = mIndexData; - } - - void Chunk::getWorldTransforms(Ogre::Matrix4* xform) const - { - *xform = getParentSceneNode()->_getFullTransform(); - } - - Ogre::Real Chunk::getSquaredViewDepth(const Ogre::Camera* cam) const - { - return getParentSceneNode()->getSquaredViewDepth(cam); - } - - const Ogre::LightList& Chunk::getLights(void) const - { - return queryLights(); - } - -} diff --git a/components/terrain/chunk.hpp b/components/terrain/chunk.hpp deleted file mode 100644 index 22b4f26ef4..0000000000 --- a/components/terrain/chunk.hpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#ifndef COMPONENTS_TERRAIN_TERRAINBATCH_H -#define COMPONENTS_TERRAIN_TERRAINBATCH_H - -#include -#include - -namespace Terrain -{ - - /** - * @brief A movable object representing a chunk of terrain. - */ - class Chunk : public Ogre::Renderable, public Ogre::MovableObject - { - public: - Chunk (Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, - const std::vector& positions, - const std::vector& normals, - const std::vector& colours); - - virtual ~Chunk(); - - /// @param own Should we take ownership of the material? - void setMaterial (const Ogre::MaterialPtr& material, bool own=true); - - void setIndexBuffer(Ogre::HardwareIndexBufferSharedPtr buffer); - - // Inherited from MovableObject - virtual const Ogre::String& getMovableType(void) const { static Ogre::String t = "MW_TERRAIN"; return t; } - virtual const Ogre::AxisAlignedBox& getBoundingBox(void) const; - virtual Ogre::Real getBoundingRadius(void) const; - virtual void _updateRenderQueue(Ogre::RenderQueue* queue); - virtual void visitRenderables(Renderable::Visitor* visitor, - bool debugRenderables = false); - - // Inherited from Renderable - virtual const Ogre::MaterialPtr& getMaterial(void) const; - virtual void getRenderOperation(Ogre::RenderOperation& op); - virtual void getWorldTransforms(Ogre::Matrix4* xform) const; - virtual Ogre::Real getSquaredViewDepth(const Ogre::Camera* cam) const; - virtual const Ogre::LightList& getLights(void) const; - - private: - Ogre::AxisAlignedBox mBounds; - Ogre::MaterialPtr mMaterial; - bool mOwnMaterial; // Should we remove mMaterial on destruction? - - Ogre::VertexData* mVertexData; - Ogre::IndexData* mIndexData; - }; - -} - -#endif diff --git a/components/terrain/defaultworld.cpp b/components/terrain/defaultworld.cpp deleted file mode 100644 index 7bc73ddda8..0000000000 --- a/components/terrain/defaultworld.cpp +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include "defaultworld.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include "storage.hpp" -#include "quadtreenode.hpp" - -namespace -{ - - bool isPowerOfTwo(int x) - { - return ( (x > 0) && ((x & (x - 1)) == 0) ); - } - - int nextPowerOfTwo (int v) - { - if (isPowerOfTwo(v)) return v; - int depth=0; - while(v) - { - v >>= 1; - depth++; - } - return 1 << depth; - } - - Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node) - { - if (center == node->getCenter()) - return node; - - if (center.x > node->getCenter().x && center.y > node->getCenter().y) - return findNode(center, node->getChild(Terrain::NE)); - else if (center.x > node->getCenter().x && center.y < node->getCenter().y) - return findNode(center, node->getChild(Terrain::SE)); - else if (center.x < node->getCenter().x && center.y > node->getCenter().y) - return findNode(center, node->getChild(Terrain::NW)); - else //if (center.x < node->getCenter().x && center.y < node->getCenter().y) - return findNode(center, node->getChild(Terrain::SW)); - } - -} - -namespace Terrain -{ - - const Ogre::uint REQ_ID_CHUNK = 1; - const Ogre::uint REQ_ID_LAYERS = 2; - - DefaultWorld::DefaultWorld(Ogre::SceneManager* sceneMgr, - Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize) - : World(sceneMgr, storage, visibilityFlags, shaders, align) - , mWorkQueueChannel(0) - , mVisible(true) - , mChunksLoading(0) - , mMinX(0) - , mMaxX(0) - , mMinY(0) - , mMaxY(0) - , mMinBatchSize(minBatchSize) - , mMaxBatchSize(maxBatchSize) - , mLayerLoadPending(true) - { -#if TERRAIN_USE_SHADER == 0 - if (mShaders) - std::cerr << "Compiled Terrain without shader support, disabling..." << std::endl; - mShaders = false; -#endif - - mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); - - /// \todo make composite map size configurable - Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a"); - mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual( - "terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET); - mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget(); - mCompositeMapRenderTarget->setAutoUpdated(false); - mCompositeMapRenderTarget->addViewport(compositeMapCam); - - storage->getBounds(mMinX, mMaxX, mMinY, mMaxY); - - int origSizeX = static_cast(mMaxX - mMinX); - int origSizeY = static_cast(mMaxY - mMinY); - - // Dividing a quad tree only works well for powers of two, so round up to the nearest one - int size = nextPowerOfTwo(std::max(origSizeX, origSizeY)); - - // Adjust the center according to the new size - float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f; - float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f; - - mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - - // While building the quadtree, remember leaf nodes since we need to load their layers - LayersRequestData data; - data.mPack = getShadersEnabled(); - - mRootNode = new QuadTreeNode(this, Root, static_cast(size), Ogre::Vector2(centerX, centerY), NULL); - buildQuadTree(mRootNode, data.mNodes); - //loadingListener->indicateProgress(); - mRootNode->initAabb(); - //loadingListener->indicateProgress(); - mRootNode->initNeighbours(); - //loadingListener->indicateProgress(); - - Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); - mWorkQueueChannel = wq->getChannel("LargeTerrain"); - wq->addRequestHandler(mWorkQueueChannel, this); - wq->addResponseHandler(mWorkQueueChannel, this); - - // Start loading layers in the background (for leaf nodes) - wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data)); - } - - DefaultWorld::~DefaultWorld() - { - Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); - wq->removeRequestHandler(mWorkQueueChannel, this); - wq->removeResponseHandler(mWorkQueueChannel, this); - - delete mRootNode; - } - - void DefaultWorld::buildQuadTree(QuadTreeNode *node, std::vector& leafs) - { - float halfSize = node->getSize()/2.f; - - if (node->getSize() <= mMinBatchSize) - { - // We arrived at a leaf - float minZ,maxZ; - Ogre::Vector2 center = node->getCenter(); - float cellWorldSize = getStorage()->getCellWorldSize(); - if (mStorage->getMinMaxHeights(static_cast(node->getSize()), center, minZ, maxZ)) - { - Ogre::AxisAlignedBox bounds(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ), - Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ)); - convertBounds(bounds); - node->setBoundingBox(bounds); - leafs.push_back(node); - } - else - node->markAsDummy(); // no data available for this node, skip it - return; - } - - if (node->getCenter().x - halfSize > mMaxX - || node->getCenter().x + halfSize < mMinX - || node->getCenter().y - halfSize > mMaxY - || node->getCenter().y + halfSize < mMinY ) - // Out of bounds of the actual terrain - this will happen because - // we rounded the size up to the next power of two - { - node->markAsDummy(); - return; - } - - // Not a leaf, create its children - node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f); - node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f)); - node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f)); - node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f); - buildQuadTree(node->getChild(SW), leafs); - buildQuadTree(node->getChild(SE), leafs); - buildQuadTree(node->getChild(NW), leafs); - buildQuadTree(node->getChild(NE), leafs); - - // if all children are dummy, we are also dummy - for (int i=0; i<4; ++i) - { - if (!node->getChild((ChildDirection)i)->isDummy()) - return; - } - node->markAsDummy(); - } - - void DefaultWorld::update(const Ogre::Vector3& cameraPos) - { - if (!mVisible) - return; - mRootNode->update(cameraPos); - mRootNode->updateIndexBuffers(); - } - - Ogre::AxisAlignedBox DefaultWorld::getWorldBoundingBox (const Ogre::Vector2& center) - { - if (center.x > mMaxX - || center.x < mMinX - || center.y > mMaxY - || center.y < mMinY) - return Ogre::AxisAlignedBox::BOX_NULL; - QuadTreeNode* node = findNode(center, mRootNode); - return node->getWorldBoundingBox(); - } - - void DefaultWorld::renderCompositeMap(Ogre::TexturePtr target) - { - mCompositeMapRenderTarget->update(); - target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer()); - } - - void DefaultWorld::clearCompositeMapSceneManager() - { - mCompositeMapSceneMgr->destroyAllManualObjects(); - mCompositeMapSceneMgr->clearScene(); - } - - void DefaultWorld::applyMaterials(bool shadows, bool splitShadows) - { - mShadows = shadows; - mSplitShadows = splitShadows; - mRootNode->applyMaterials(); - } - - void DefaultWorld::setVisible(bool visible) - { - if (visible && !mVisible) - mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode); - else if (!visible && mVisible) - mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode); - - mVisible = visible; - } - - bool DefaultWorld::getVisible() - { - return mVisible; - } - - void DefaultWorld::syncLoad() - { - while (mChunksLoading || mLayerLoadPending) - { - OGRE_THREAD_SLEEP(0); - Ogre::Root::getSingleton().getWorkQueue()->processResponses(); - } - } - - Ogre::WorkQueue::Response* DefaultWorld::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ) - { - if (req->getType() == REQ_ID_CHUNK) - { - const LoadRequestData data = Ogre::any_cast(req->getData()); - - QuadTreeNode* node = data.mNode; - - LoadResponseData* responseData = new LoadResponseData(); - - getStorage()->fillVertexBuffers(node->getNativeLodLevel(), static_cast(node->getSize()), node->getCenter(), getAlign(), - responseData->mPositions, responseData->mNormals, responseData->mColours); - - return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); - } - else // REQ_ID_LAYERS - { - const LayersRequestData data = Ogre::any_cast(req->getData()); - - LayersResponseData* responseData = new LayersResponseData(); - - getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack); - - return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); - } - } - - void DefaultWorld::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ) - { - assert(res->succeeded() && "Response failure not handled"); - - if (res->getRequest()->getType() == REQ_ID_CHUNK) - { - LoadResponseData* data = Ogre::any_cast(res->getData()); - - const LoadRequestData requestData = Ogre::any_cast(res->getRequest()->getData()); - - requestData.mNode->load(*data); - - delete data; - - --mChunksLoading; - } - else // REQ_ID_LAYERS - { - LayersResponseData* data = Ogre::any_cast(res->getData()); - - for (std::vector::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it) - { - it->mTarget->loadLayers(*it); - } - - delete data; - - mRootNode->loadMaterials(); - - mLayerLoadPending = false; - } - } - - void DefaultWorld::queueLoad(QuadTreeNode *node) - { - LoadRequestData data; - data.mNode = node; - - Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data)); - ++mChunksLoading; - } -} diff --git a/components/terrain/defaultworld.hpp b/components/terrain/defaultworld.hpp deleted file mode 100644 index 4caef8462f..0000000000 --- a/components/terrain/defaultworld.hpp +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#ifndef COMPONENTS_TERRAIN_H -#define COMPONENTS_TERRAIN_H - -#include -#include -#include - -#include "world.hpp" - -namespace Ogre -{ - class Camera; -} - -namespace Terrain -{ - - class QuadTreeNode; - class Storage; - - /** - * @brief A quadtree-based terrain implementation suitable for large data sets. \n - * Near cells are rendered with alpha splatting, distant cells are merged - * together in batches and have their layers pre-rendered onto a composite map. \n - * Cracks at LOD transitions are avoided using stitching. - * @note Multiple cameras are not supported yet - */ - class DefaultWorld : public World, public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler - { - public: - /// @note takes ownership of \a storage - /// @param sceneMgr scene manager to use - /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) - /// @param visbilityFlags visibility flags for the created meshes - /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually - /// faster so this is just here for compatibility. - /// @param align The align of the terrain, see Alignment enum - /// @param minBatchSize Minimum size of a terrain batch along one side (in cell units). Used for building the quad tree. - /// @param maxBatchSize Maximum size of a terrain batch along one side (in cell units). Used when traversing the quad tree. - DefaultWorld(Ogre::SceneManager* sceneMgr, - Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize); - ~DefaultWorld(); - - /// Update chunk LODs according to this camera position - /// @note Calling this method might lead to composite textures being rendered, so it is best - /// not to call it when render commands are still queued, since that would cause a flush. - virtual void update (const Ogre::Vector3& cameraPos); - - /// Get the world bounding box of a chunk of terrain centered at \a center - virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center); - - Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; } - - /// Show or hide the whole terrain - /// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden - virtual void setVisible(bool visible); - virtual bool getVisible(); - - /// Recreate materials used by terrain chunks. This should be called whenever settings of - /// the material factory are changed. (Relying on the factory to update those materials is not - /// enough, since turning a feature on/off can change the number of texture units available for layer/blend - /// textures, and to properly respond to this we may need to change the structure of the material, such as - /// adding or removing passes. This can only be achieved by a full rebuild.) - virtual void applyMaterials(bool shadows, bool splitShadows); - - int getMaxBatchSize() { return static_cast(mMaxBatchSize); } - - /// Wait until all background loading is complete. - void syncLoad(); - - private: - // Called from a background worker thread - virtual Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ); - // Called from the main thread - virtual void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ); - Ogre::uint16 mWorkQueueChannel; - - bool mVisible; - - QuadTreeNode* mRootNode; - Ogre::SceneNode* mRootSceneNode; - - /// The number of chunks currently loading in a background thread. If 0, we have finished loading! - int mChunksLoading; - - Ogre::SceneManager* mCompositeMapSceneMgr; - - /// Bounds in cell units - float mMinX, mMaxX, mMinY, mMaxY; - - /// Minimum size of a terrain batch along one side (in cell units) - float mMinBatchSize; - /// Maximum size of a terrain batch along one side (in cell units) - float mMaxBatchSize; - - void buildQuadTree(QuadTreeNode* node, std::vector& leafs); - - // Are layers for leaf nodes loaded? This is done once at startup (but in a background thread) - bool mLayerLoadPending; - - public: - // ----INTERNAL---- - Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; } - - bool areLayersLoaded() { return !mLayerLoadPending; } - - // Delete all quads - void clearCompositeMapSceneManager(); - void renderCompositeMap (Ogre::TexturePtr target); - - // Adds a WorkQueue request to load a chunk for this node in the background. - void queueLoad (QuadTreeNode* node); - - private: - Ogre::RenderTarget* mCompositeMapRenderTarget; - Ogre::TexturePtr mCompositeMapRenderTexture; - }; - - struct LoadRequestData - { - QuadTreeNode* mNode; - - friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r) - { return o; } - }; - - struct LoadResponseData - { - std::vector mPositions; - std::vector mNormals; - std::vector mColours; - - friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r) - { return o; } - }; - - struct LayersRequestData - { - std::vector mNodes; - bool mPack; - - friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r) - { return o; } - }; - - struct LayersResponseData - { - std::vector mLayerCollections; - - friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r) - { return o; } - }; - -} - -#endif diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp index 6d173d1361..234e05a98a 100644 --- a/components/terrain/defs.hpp +++ b/components/terrain/defs.hpp @@ -1,62 +1,10 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ #ifndef COMPONENTS_TERRAIN_DEFS_HPP #define COMPONENTS_TERRAIN_DEFS_HPP +#include + namespace Terrain { - class QuadTreeNode; - - /// The alignment of the terrain - enum Alignment - { - /// Terrain is in the X/Z plane - Align_XZ = 0, - /// Terrain is in the X/Y plane - Align_XY = 1, - /// Terrain is in the Y/Z plane. - /// UNTESTED - use at own risk. - /// Besides, X as up axis? What is wrong with you? ;) - Align_YZ = 2 - }; - - inline void convertPosition(Alignment align, float &x, float &y, float &z) - { - switch (align) - { - case Align_XY: - return; - case Align_XZ: - std::swap(y, z); - // This is since -Z should be going *into* the screen - // If not doing this, we'd get wrong vertex winding - z *= -1; - return; - case Align_YZ: - std::swap(x, y); - std::swap(y, z); - return; - } - } enum Direction { @@ -74,13 +22,6 @@ namespace Terrain bool mSpecular; // Specular info in diffuse map alpha channel? }; - struct LayerCollection - { - QuadTreeNode* mTarget; - // Since we can't create a texture from a different thread, this only holds the raw texel data - std::vector mBlendmaps; - std::vector mLayers; - }; } #endif diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 4d2318aa6b..59d06f2542 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -1,373 +1,90 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ #include "material.hpp" -#include -#include -#include - -#include - -#if TERRAIN_USE_SHADER -#include -#endif - -namespace -{ - -int getBlendmapIndexForLayer (int layerIndex) -{ - return static_cast(std::floor((layerIndex - 1) / 4.f)); -} - -std::string getBlendmapComponentForLayer (int layerIndex) -{ - int n = (layerIndex-1)%4; - if (n == 0) - return "x"; - if (n == 1) - return "y"; - if (n == 2) - return "z"; - else - return "w"; -} - -} +#include +#include +#include +#include +#include +#include namespace Terrain { - MaterialGenerator::MaterialGenerator() - : mShaders(true) - , mShadows(false) - , mSplitShadows(false) - , mNormalMapping(true) - , mParallaxMapping(true) + FixedFunctionTechnique::FixedFunctionTechnique(const std::vector >& layers, + const std::vector >& blendmaps, int blendmapScale, float layerTileSize) { - - } - - Ogre::MaterialPtr MaterialGenerator::generate() - { - assert(!mLayerList.empty() && "Can't create material with no layers"); - - return create(false, false); - } - - Ogre::MaterialPtr MaterialGenerator::generateForCompositeMapRTT() - { - assert(!mLayerList.empty() && "Can't create material with no layers"); - - return create(true, false); - } - - Ogre::MaterialPtr MaterialGenerator::generateForCompositeMap() - { - return create(false, true); - } - - Ogre::MaterialPtr MaterialGenerator::create(bool renderCompositeMap, bool displayCompositeMap) - { - assert(!renderCompositeMap || !displayCompositeMap); - - static int count = 0; - std::stringstream name; - name << "terrain/mat" << count++; - - if (!mShaders) + bool firstLayer = true; + int i=0; + for (std::vector >::const_iterator it = layers.begin(); it != layers.end(); ++it) { - Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create(name.str(), - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - Ogre::Technique* technique = mat->getTechnique(0); - technique->removeAllPasses(); + osg::ref_ptr stateset (new osg::StateSet); - if (displayCompositeMap) + if (!firstLayer) { - Ogre::Pass* pass = technique->createPass(); - pass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE); - pass->createTextureUnitState(mCompositeMap)->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); - } - else - { - assert(mLayerList.size() == mBlendmapList.size()+1); - std::vector::iterator blend = mBlendmapList.begin(); - for (std::vector::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer) - { - Ogre::Pass* pass = technique->createPass(); - pass->setLightingEnabled(false); - pass->setVertexColourTracking(Ogre::TVC_NONE); - // TODO: How to handle fog? - pass->setFog(true, Ogre::FOG_NONE); - - bool first = (layer == mLayerList.begin()); - - Ogre::TextureUnitState* tus; - - if (!first) - { - pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); - pass->setDepthFunction(Ogre::CMPF_EQUAL); - - tus = pass->createTextureUnitState((*blend)->getName()); - tus->setAlphaOperation(Ogre::LBX_BLEND_TEXTURE_ALPHA, - Ogre::LBS_TEXTURE, - Ogre::LBS_TEXTURE); - tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA, - Ogre::LBS_TEXTURE, - Ogre::LBS_TEXTURE); - tus->setIsAlpha(true); - tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); - - float scale = (16/(16.f+1.f)); - tus->setTextureScale(1.f/scale,1.f/scale); - } - - // Add the actual layer texture on top of the alpha map. - tus = pass->createTextureUnitState(layer->mDiffuseMap); - if (!first) - tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA, - Ogre::LBS_TEXTURE, - Ogre::LBS_CURRENT); - - tus->setTextureScale(1/16.f,1/16.f); - - if (!first) - ++blend; - } - - if (!renderCompositeMap) - { - Ogre::Pass* lightingPass = technique->createPass(); - lightingPass->setSceneBlending(Ogre::SBT_MODULATE); - lightingPass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE); - lightingPass->setFog(true, Ogre::FOG_NONE); - } + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + osg::ref_ptr depth (new osg::Depth); + depth->setFunction(osg::Depth::EQUAL); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); } - return mat; + int texunit = 0; + if(!firstLayer) + { + osg::ref_ptr blendmap = blendmaps.at(i++); + + stateset->setTextureAttributeAndModes(texunit, blendmap.get()); + + // This is to map corner vertices directly to the center of a blendmap texel. + osg::Matrixf texMat; + float scale = (blendmapScale/(static_cast(blendmapScale)+1.f)); + texMat.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f)); + texMat.preMultScale(osg::Vec3f(scale, scale, 1.f)); + texMat.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f)); + + stateset->setTextureAttributeAndModes(texunit, new osg::TexMat(texMat)); + + osg::ref_ptr texEnvCombine (new osg::TexEnvCombine); + texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); + texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + + stateset->setTextureAttributeAndModes(texunit, texEnvCombine, osg::StateAttribute::ON); + + ++texunit; + } + + // Add the actual layer texture multiplied by the alpha map. + osg::ref_ptr tex = *it; + stateset->setTextureAttributeAndModes(texunit, tex.get()); + + osg::ref_ptr texMat (new osg::TexMat); + texMat->setMatrix(osg::Matrix::scale(osg::Vec3f(layerTileSize,layerTileSize,1.f))); + stateset->setTextureAttributeAndModes(texunit, texMat, osg::StateAttribute::ON); + + firstLayer = false; + + addPass(stateset); } -#if TERRAIN_USE_SHADER - else - { - sh::MaterialInstance* material = sh::Factory::getInstance().createMaterialInstance (name.str()); - material->setProperty ("allow_fixed_function", sh::makeProperty(new sh::BooleanValue(false))); + } - if (displayCompositeMap) - { - sh::MaterialInstancePass* p = material->createPass (); + Effect::Effect(const std::vector > &layers, const std::vector > &blendmaps, + int blendmapScale, float layerTileSize) + : mLayers(layers) + , mBlendmaps(blendmaps) + , mBlendmapScale(blendmapScale) + , mLayerTileSize(layerTileSize) + { + osg::ref_ptr material (new osg::Material); + material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON); - p->setProperty ("vertex_program", sh::makeProperty(new sh::StringValue("terrain_vertex"))); - p->setProperty ("fragment_program", sh::makeProperty(new sh::StringValue("terrain_fragment"))); - p->mShaderProperties.setProperty ("is_first_pass", sh::makeProperty(new sh::BooleanValue(true))); - p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(false))); - p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(true))); - p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue("0"))); - p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue("0"))); - p->mShaderProperties.setProperty ("normal_map_enabled", sh::makeProperty (new sh::BooleanValue(false))); - p->mShaderProperties.setProperty ("parallax_enabled", sh::makeProperty (new sh::BooleanValue(false))); - p->mShaderProperties.setProperty ("normal_maps", - sh::makeProperty (new sh::IntValue(0))); + selectTechnique(0); + } - sh::MaterialInstanceTextureUnit* tex = p->createTextureUnit ("compositeMap"); - tex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mCompositeMap))); - tex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); + bool Effect::define_techniques() + { + addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); - // shadow. TODO: repeated, put in function - if (mShadows) - { - for (int i = 0; i < (mSplitShadows ? 3 : 1); ++i) - { - sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); - shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); - } - } - p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue( - Ogre::StringConverter::toString(1)))); - - p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(0))); - } - else - { - - bool shadows = mShadows && !renderCompositeMap; - - int layerOffset = 0; - while (layerOffset < (int)mLayerList.size()) - { - int blendmapOffset = (layerOffset == 0) ? 1 : 0; // the first layer of the first pass is the base layer and does not need a blend map - - // Check how many layers we can fit in this pass - int numLayersInThisPass = 0; - int numBlendTextures = 0; - std::vector blendTextures; - int remainingTextureUnits = OGRE_MAX_TEXTURE_LAYERS; - if (shadows) - remainingTextureUnits -= (mSplitShadows ? 3 : 1); - while (remainingTextureUnits && layerOffset + numLayersInThisPass < (int)mLayerList.size()) - { - int layerIndex = numLayersInThisPass + layerOffset; - - int neededTextureUnits=0; - int neededBlendTextures=0; - - if (layerIndex != 0) - { - std::string blendTextureName = mBlendmapList[getBlendmapIndexForLayer(layerIndex)]->getName(); - if (std::find(blendTextures.begin(), blendTextures.end(), blendTextureName) == blendTextures.end()) - { - blendTextures.push_back(blendTextureName); - ++neededBlendTextures; - ++neededTextureUnits; // blend texture - } - } - ++neededTextureUnits; // layer texture - - // Check if this layer has a normal map - if (mNormalMapping && !mLayerList[layerIndex].mNormalMap.empty() && !renderCompositeMap) - ++neededTextureUnits; // normal map - if (neededTextureUnits <= remainingTextureUnits) - { - // We can fit another! - remainingTextureUnits -= neededTextureUnits; - numBlendTextures += neededBlendTextures; - ++numLayersInThisPass; - } - else - break; // We're full - } - - - sh::MaterialInstancePass* p = material->createPass (); - p->setProperty ("vertex_program", sh::makeProperty(new sh::StringValue("terrain_vertex"))); - p->setProperty ("fragment_program", sh::makeProperty(new sh::StringValue("terrain_fragment"))); - if (layerOffset != 0) - { - p->setProperty ("scene_blend", sh::makeProperty(new sh::StringValue("alpha_blend"))); - // Only write if depth is equal to the depth value written by the previous pass. - p->setProperty ("depth_func", sh::makeProperty(new sh::StringValue("equal"))); - } - - p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(renderCompositeMap))); - p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(displayCompositeMap))); - - p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass)))); - p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures)))); - p->mShaderProperties.setProperty ("normal_map_enabled", - sh::makeProperty (new sh::BooleanValue(false))); - - // blend maps - // the index of the first blend map used in this pass - int blendmapStart; - if (mLayerList.size() == 1) // special case. if there's only one layer, we don't need blend maps at all - blendmapStart = 0; - else - blendmapStart = getBlendmapIndexForLayer(layerOffset+blendmapOffset); - for (int i = 0; i < numBlendTextures; ++i) - { - sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i)); - blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mBlendmapList[blendmapStart+i]->getName()))); - blendTex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); - } - - // layer maps - bool anyNormalMaps = false; - bool anyParallax = false; - size_t normalMaps = 0; - for (int i = 0; i < numLayersInThisPass; ++i) - { - const LayerInfo& layer = mLayerList[layerOffset+i]; - // diffuse map - sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i)); - diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mDiffuseMap))); - - // normal map (optional) - bool useNormalMap = mNormalMapping && !mLayerList[layerOffset+i].mNormalMap.empty() && !renderCompositeMap; - bool useParallax = useNormalMap && mParallaxMapping && layer.mParallax; - bool useSpecular = layer.mSpecular; - if (useNormalMap) - { - anyNormalMaps = true; - anyParallax = anyParallax || useParallax; - sh::MaterialInstanceTextureUnit* normalTex = p->createTextureUnit ("normalMap" + Ogre::StringConverter::toString(i)); - normalTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mNormalMap))); - } - p->mShaderProperties.setProperty ("use_normal_map_" + Ogre::StringConverter::toString(i), - sh::makeProperty (new sh::BooleanValue(useNormalMap))); - p->mShaderProperties.setProperty ("use_parallax_" + Ogre::StringConverter::toString(i), - sh::makeProperty (new sh::BooleanValue(useParallax))); - p->mShaderProperties.setProperty ("use_specular_" + Ogre::StringConverter::toString(i), - sh::makeProperty (new sh::BooleanValue(useSpecular))); - boost::hash_combine(normalMaps, useNormalMap); - boost::hash_combine(normalMaps, useNormalMap && layer.mParallax); - boost::hash_combine(normalMaps, useSpecular); - - if (i+layerOffset > 0) - { - int blendTextureIndex = getBlendmapIndexForLayer(layerOffset+i); - std::string blendTextureComponent = getBlendmapComponentForLayer(layerOffset+i); - p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), - sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent))); - } - else - { - // just to make it shut up about blendmap_component_0 not existing in the first pass. - // it might be retrieved, but will never survive the preprocessing step. - p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), - sh::makeProperty (new sh::StringValue(""))); - } - } - p->mShaderProperties.setProperty ("normal_map_enabled", - sh::makeProperty (new sh::BooleanValue(anyNormalMaps))); - p->mShaderProperties.setProperty ("parallax_enabled", - sh::makeProperty (new sh::BooleanValue(anyParallax))); - // Since the permutation handler can't handle dynamic property names, - // combine normal map settings for all layers into one value - p->mShaderProperties.setProperty ("normal_maps", - sh::makeProperty (new sh::IntValue(normalMaps))); - - // shadow - if (shadows) - { - for (int i = 0; i < (mSplitShadows ? 3 : 1); ++i) - { - sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); - shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); - } - } - p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue( - Ogre::StringConverter::toString(numBlendTextures + numLayersInThisPass)))); - - // Make sure the pass index is fed to the permutation handler, because blendmap components may be different - p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(layerOffset))); - - assert ((int)p->mTexUnits.size() == OGRE_MAX_TEXTURE_LAYERS - remainingTextureUnits); - - layerOffset += numLayersInThisPass; - } - } - } -#endif - return Ogre::MaterialManager::getSingleton().getByName(name.str()); + return true; } } diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index b79df9f48c..dd00e41ed4 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -1,72 +1,57 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ #ifndef COMPONENTS_TERRAIN_MATERIAL_H #define COMPONENTS_TERRAIN_MATERIAL_H -#include +#include +#include -#include "storage.hpp" +#include "defs.hpp" + +namespace osg +{ + class Texture2D; +} namespace Terrain { - class MaterialGenerator + class FixedFunctionTechnique : public osgFX::Technique { public: - MaterialGenerator (); + FixedFunctionTechnique( + const std::vector >& layers, + const std::vector >& blendmaps, int blendmapScale, float layerTileSize); - void setLayerList (const std::vector& layerList) { mLayerList = layerList; } - bool hasLayers() { return mLayerList.size() > 0; } - void setBlendmapList (const std::vector& blendmapList) { mBlendmapList = blendmapList; } - const std::vector& getBlendmapList() { return mBlendmapList; } - void setCompositeMap (const std::string& name) { mCompositeMap = name; } + protected: + virtual void define_passes() {} + }; - void enableShaders(bool shaders) { mShaders = shaders; } - void enableShadows(bool shadows) { mShadows = shadows; } - void enableNormalMapping(bool normalMapping) { mNormalMapping = normalMapping; } - void enableParallaxMapping(bool parallaxMapping) { mParallaxMapping = parallaxMapping; } - void enableSplitShadows(bool splitShadows) { mSplitShadows = splitShadows; } + class Effect : public osgFX::Effect + { + public: + Effect( + const std::vector >& layers, + const std::vector >& blendmaps, int blendmapScale, float layerTileSize); - /// Creates a material suitable for displaying a chunk of terrain using alpha-blending. - Ogre::MaterialPtr generate (); + virtual bool define_techniques(); - /// Creates a material suitable for displaying a chunk of terrain using a ready-made composite map. - Ogre::MaterialPtr generateForCompositeMap (); - - /// Creates a material suitable for rendering composite maps, i.e. for "baking" several layer textures - /// into one. The main difference compared to a normal material is that no shading is applied at this point. - Ogre::MaterialPtr generateForCompositeMapRTT (); + virtual const char *effectName() const + { + return NULL; + } + virtual const char *effectDescription() const + { + return NULL; + } + virtual const char *effectAuthor() const + { + return NULL; + } private: - Ogre::MaterialPtr create (bool renderCompositeMap, bool displayCompositeMap); - - std::vector mLayerList; - std::vector mBlendmapList; - std::string mCompositeMap; - bool mShaders; - bool mShadows; - bool mSplitShadows; - bool mNormalMapping; - bool mParallaxMapping; + std::vector > mLayers; + std::vector > mBlendmaps; + int mBlendmapScale; + float mLayerTileSize; }; } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp deleted file mode 100644 index 89e5e34a36..0000000000 --- a/components/terrain/quadtreenode.cpp +++ /dev/null @@ -1,611 +0,0 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include "quadtreenode.hpp" - -#include -#include -#include -#include -#include - -#include "defaultworld.hpp" -#include "chunk.hpp" -#include "storage.hpp" -#include "buffercache.hpp" -#include "material.hpp" - -using namespace Terrain; - -namespace -{ - int Log2( int n ) - { - assert(n > 0); - int targetlevel = 0; - while (n >>= 1) ++targetlevel; - return targetlevel; - } - - // Utility functions for neighbour finding algorithm - ChildDirection reflect(ChildDirection dir, Direction dir2) - { - assert(dir != Root); - - const int lookupTable[4][4] = - { - // NW NE SW SE - { SW, SE, NW, NE }, // N - { NE, NW, SE, SW }, // E - { SW, SE, NW, NE }, // S - { NE, NW, SE, SW } // W - }; - return (ChildDirection)lookupTable[dir2][dir]; - } - - bool adjacent(ChildDirection dir, Direction dir2) - { - assert(dir != Root); - const bool lookupTable[4][4] = - { - // NW NE SW SE - { true, true, false, false }, // N - { false, true, false, true }, // E - { false, false, true, true }, // S - { true, false, true, false } // W - }; - return lookupTable[dir2][dir]; - } - - // Algorithm described by Hanan Samet - 'Neighbour Finding in Quadtrees' - // http://www.cs.umd.edu/~hjs/pubs/SametPRIP81.pdf - QuadTreeNode* searchNeighbourRecursive (QuadTreeNode* currentNode, Direction dir) - { - if (!currentNode->getParent()) - return NULL; // Arrived at root node, the root node does not have neighbours - - QuadTreeNode* nextNode; - if (adjacent(currentNode->getDirection(), dir)) - nextNode = searchNeighbourRecursive(currentNode->getParent(), dir); - else - nextNode = currentNode->getParent(); - - if (nextNode && nextNode->hasChildren()) - return nextNode->getChild(reflect(currentNode->getDirection(), dir)); - else - return NULL; - } - - // Create a 2D quad - void makeQuad(Ogre::SceneManager* sceneMgr, float left, float top, float right, float bottom, Ogre::MaterialPtr material) - { - Ogre::ManualObject* manual = sceneMgr->createManualObject(); - - // Use identity view/projection matrices to get a 2d quad - manual->setUseIdentityProjection(true); - manual->setUseIdentityView(true); - - manual->begin(material->getName()); - - float normLeft = left*2-1; - float normTop = top*2-1; - float normRight = right*2-1; - float normBottom = bottom*2-1; - - manual->position(normLeft, normTop, 0.0); - manual->textureCoord(0, 1); - manual->position(normRight, normTop, 0.0); - manual->textureCoord(1, 1); - manual->position(normRight, normBottom, 0.0); - manual->textureCoord(1, 0); - manual->position(normLeft, normBottom, 0.0); - manual->textureCoord(0, 0); - - manual->quad(0,1,2,3); - - manual->end(); - - Ogre::AxisAlignedBox aabInf; - aabInf.setInfinite(); - manual->setBoundingBox(aabInf); - - sceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(manual); - } -} - -QuadTreeNode::QuadTreeNode(DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent) - : mMaterialGenerator(NULL) - , mLoadState(LS_Unloaded) - , mIsDummy(false) - , mSize(size) - , mLodLevel(Log2(static_cast(mSize))) - , mBounds(Ogre::AxisAlignedBox::BOX_NULL) - , mWorldBounds(Ogre::AxisAlignedBox::BOX_NULL) - , mDirection(dir) - , mCenter(center) - , mSceneNode(NULL) - , mParent(parent) - , mChunk(NULL) - , mTerrain(terrain) -{ - mBounds.setNull(); - for (int i=0; i<4; ++i) - mChildren[i] = NULL; - for (int i=0; i<4; ++i) - mNeighbours[i] = NULL; - - if (mDirection == Root) - mSceneNode = mTerrain->getRootSceneNode(); - else - mSceneNode = mTerrain->getSceneManager()->createSceneNode(); - Ogre::Vector2 pos (0,0); - if (mParent) - pos = mParent->getCenter(); - pos = mCenter - pos; - float cellWorldSize = mTerrain->getStorage()->getCellWorldSize(); - - Ogre::Vector3 sceneNodePos (pos.x*cellWorldSize, pos.y*cellWorldSize, 0); - mTerrain->convertPosition(sceneNodePos); - - mSceneNode->setPosition(sceneNodePos); - - mMaterialGenerator = new MaterialGenerator(); - mMaterialGenerator->enableShaders(mTerrain->getShadersEnabled()); -} - -void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 ¢er) -{ - mChildren[id] = new QuadTreeNode(mTerrain, id, size, center, this); -} - -QuadTreeNode::~QuadTreeNode() -{ - for (int i=0; i<4; ++i) - delete mChildren[i]; - delete mChunk; - delete mMaterialGenerator; -} - -QuadTreeNode* QuadTreeNode::getNeighbour(Direction dir) -{ - return mNeighbours[static_cast(dir)]; -} - -void QuadTreeNode::initNeighbours() -{ - for (int i=0; i<4; ++i) - mNeighbours[i] = searchNeighbourRecursive(this, (Direction)i); - - if (hasChildren()) - for (int i=0; i<4; ++i) - mChildren[i]->initNeighbours(); -} - -void QuadTreeNode::initAabb() -{ - float cellWorldSize = mTerrain->getStorage()->getCellWorldSize(); - if (hasChildren()) - { - for (int i=0; i<4; ++i) - { - mChildren[i]->initAabb(); - mBounds.merge(mChildren[i]->getBoundingBox()); - } - float minH, maxH; - switch (mTerrain->getAlign()) - { - case Terrain::Align_XY: - minH = mBounds.getMinimum().z; - maxH = mBounds.getMaximum().z; - break; - case Terrain::Align_XZ: - minH = mBounds.getMinimum().y; - maxH = mBounds.getMaximum().y; - break; - case Terrain::Align_YZ: - minH = mBounds.getMinimum().x; - maxH = mBounds.getMaximum().x; - break; - } - Ogre::Vector3 min(-mSize/2*cellWorldSize, -mSize/2*cellWorldSize, minH); - Ogre::Vector3 max(Ogre::Vector3(mSize/2*cellWorldSize, mSize/2*cellWorldSize, maxH)); - mBounds = Ogre::AxisAlignedBox (min, max); - mTerrain->convertBounds(mBounds); - } - Ogre::Vector3 offset(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0); - mTerrain->convertPosition(offset); - mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + offset, - mBounds.getMaximum() + offset); -} - -void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box) -{ - mBounds = box; -} - -const Ogre::AxisAlignedBox& QuadTreeNode::getBoundingBox() -{ - return mBounds; -} - -const Ogre::AxisAlignedBox& QuadTreeNode::getWorldBoundingBox() -{ - return mWorldBounds; -} - -bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos) -{ - if (isDummy()) - return true; - - if (mBounds.isNull()) - return true; - - float dist = mWorldBounds.distance(cameraPos); - - // Make sure our scene node is attached - if (!mSceneNode->isInSceneGraph()) - { - mParent->getSceneNode()->addChild(mSceneNode); - } - - // Simple LOD selection - /// \todo use error metrics? - size_t wantedLod = 0; - float cellWorldSize = mTerrain->getStorage()->getCellWorldSize(); - - if (dist > cellWorldSize*64) - wantedLod = 6; - else if (dist > cellWorldSize*32) - wantedLod = 5; - else if (dist > cellWorldSize*12) - wantedLod = 4; - else if (dist > cellWorldSize*5) - wantedLod = 3; - else if (dist > cellWorldSize*2) - wantedLod = 2; - else if (dist > cellWorldSize * 1.42) // < sqrt2 so the 3x3 grid around player is always highest lod - wantedLod = 1; - - bool wantToDisplay = mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod; - - if (wantToDisplay) - { - // Wanted LOD is small enough to render this node in one chunk - if (mLoadState == LS_Unloaded) - { - mLoadState = LS_Loading; - mTerrain->queueLoad(this); - return false; - } - - if (mLoadState == LS_Loaded) - { - // Additional (index buffer) LOD is currently disabled. - // This is due to a problem with the LOD selection when a node splits. - // After splitting, the distance is measured from the children's bounding boxes, which are possibly - // further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD - // than the original node. - // In short, we'd sometimes get a switch to a lesser detail when actually moving closer. - // This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour - // node hasn't split yet, and has a higher LOD than our node's child: - // ----- ----- ------------ - // | LOD | LOD | | - // | 1 | 1 | | - // |-----|-----| LOD 0 | - // | LOD | LOD | | - // | 0 | 0 | | - // ----- ----- ------------ - // To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're - // doing here. - // But this "solution" does increase triangle overhead, so eventually we need to find a more clever way. - //mChunk->setAdditionalLod(wantedLod - mLodLevel); - - if (!mChunk->getVisible() && hasChildren()) - { - for (int i=0; i<4; ++i) - mChildren[i]->unload(true); - } - mChunk->setVisible(true); - - return true; - } - return false; // LS_Loading - } - - // We do not want to display this node - delegate to children if they are already loaded - if (!wantToDisplay && hasChildren()) - { - if (mChunk) - { - // Are children already loaded? - bool childrenLoaded = true; - for (int i=0; i<4; ++i) - if (!mChildren[i]->update(cameraPos)) - childrenLoaded = false; - - if (!childrenLoaded) - { - mChunk->setVisible(true); - // Make sure child scene nodes are detached until all children are loaded - mSceneNode->removeAllChildren(); - } - else - { - // Delegation went well, we can unload now - unload(); - - for (int i=0; i<4; ++i) - { - if (!mChildren[i]->getSceneNode()->isInSceneGraph()) - mSceneNode->addChild(mChildren[i]->getSceneNode()); - } - } - return true; - } - else - { - bool success = true; - for (int i=0; i<4; ++i) - success = mChildren[i]->update(cameraPos) & success; - return success; - } - } - return false; -} - -void QuadTreeNode::load(const LoadResponseData &data) -{ - assert (!mChunk); - - mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data.mPositions, data.mNormals, data.mColours); - mChunk->setVisibilityFlags(mTerrain->getVisibilityFlags()); - mChunk->setCastShadows(true); - mSceneNode->attachObject(mChunk); - - mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); - mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled()); - - if (mTerrain->areLayersLoaded()) - { - if (mSize == 1) - { - mChunk->setMaterial(mMaterialGenerator->generate()); - } - else - { - ensureCompositeMap(); - mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); - mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap()); - } - } - // else: will be loaded in loadMaterials() after background thread has finished loading layers - mChunk->setVisible(false); - - mLoadState = LS_Loaded; -} - -void QuadTreeNode::unload(bool recursive) -{ - if (mChunk) - { - mSceneNode->detachObject(mChunk); - - delete mChunk; - mChunk = NULL; - - if (!mCompositeMap.isNull()) - { - Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName()); - mCompositeMap.setNull(); - } - - // Do *not* set this when we are still loading! - mLoadState = LS_Unloaded; - } - - if (recursive && hasChildren()) - { - for (int i=0; i<4; ++i) - mChildren[i]->unload(true); - } -} - -void QuadTreeNode::updateIndexBuffers() -{ - if (hasChunk()) - { - // Fetch a suitable index buffer (which may be shared) - size_t ourLod = getActualLodLevel(); - - unsigned int flags = 0; - - for (int i=0; i<4; ++i) - { - QuadTreeNode* neighbour = getNeighbour((Direction)i); - - // If the neighbour isn't currently rendering itself, - // go up until we find one. NOTE: We don't need to go down, - // because in that case neighbour's detail would be higher than - // our detail and the neighbour would handle stitching by itself. - while (neighbour && !neighbour->hasChunk()) - neighbour = neighbour->getParent(); - size_t lod = 0; - if (neighbour) - lod = neighbour->getActualLodLevel(); - if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are - - lod = 0; // neighbours with more detail will do the stitching themselves - // Use 4 bits for each LOD delta - if (lod > 0) - { - assert (lod - ourLod < (1 << 4)); - flags |= static_cast(lod - ourLod) << (4*i); - } - } - flags |= 0 /*((int)mAdditionalLod)*/ << (4*4); - - mChunk->setIndexBuffer(mTerrain->getBufferCache().getIndexBuffer(flags)); - } - else if (hasChildren()) - { - for (int i=0; i<4; ++i) - mChildren[i]->updateIndexBuffers(); - } -} - -bool QuadTreeNode::hasChunk() -{ - return mSceneNode->isInSceneGraph() && mChunk && mChunk->getVisible(); -} - -size_t QuadTreeNode::getActualLodLevel() -{ - assert(hasChunk() && "Can't get actual LOD level if this node has no render chunk"); - return mLodLevel /* + mChunk->getAdditionalLod() */; -} - -void QuadTreeNode::loadLayers(const LayerCollection& collection) -{ - assert (!mMaterialGenerator->hasLayers()); - - std::vector blendTextures; - for (std::vector::const_iterator it = collection.mBlendmaps.begin(); it != collection.mBlendmaps.end(); ++it) - { - // TODO: clean up blend textures on destruction - static int count=0; - Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/" - + Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, it->getWidth(), it->getHeight(), 0, it->format); - - Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(it->data, it->getWidth()*it->getHeight()*Ogre::PixelUtil::getNumElemBytes(it->format), true)); - map->loadRawData(stream, it->getWidth(), it->getHeight(), it->format); - blendTextures.push_back(map); - } - - mMaterialGenerator->setLayerList(collection.mLayers); - mMaterialGenerator->setBlendmapList(blendTextures); -} - -void QuadTreeNode::loadMaterials() -{ - if (isDummy()) - return; - - // Load children first since we depend on them when creating a composite map - if (hasChildren()) - { - for (int i=0; i<4; ++i) - mChildren[i]->loadMaterials(); - } - - if (mChunk) - { - if (mSize == 1) - { - mChunk->setMaterial(mMaterialGenerator->generate()); - } - else - { - ensureCompositeMap(); - mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); - mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap()); - } - } -} - -void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) -{ - Ogre::SceneManager* sceneMgr = mTerrain->getCompositeMapSceneManager(); - - if (mIsDummy) - { - // TODO - store this default material somewhere instead of creating one for each empty cell - MaterialGenerator matGen; - matGen.enableShaders(mTerrain->getShadersEnabled()); - std::vector layer; - layer.push_back(mTerrain->getStorage()->getDefaultLayer()); - matGen.setLayerList(layer); - makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT()); - return; - } - if (mSize > 1) - { - assert(hasChildren()); - - // 0,0 -------- 1,0 - // | | | - // |-----|------| - // | | | - // 0,1 -------- 1,1 - - float halfW = area.width()/2.f; - float halfH = area.height()/2.f; - mChildren[NW]->prepareForCompositeMap(Ogre::TRect(area.left, area.top, area.right-halfW, area.bottom-halfH)); - mChildren[NE]->prepareForCompositeMap(Ogre::TRect(area.left+halfW, area.top, area.right, area.bottom-halfH)); - mChildren[SW]->prepareForCompositeMap(Ogre::TRect(area.left, area.top+halfH, area.right-halfW, area.bottom)); - mChildren[SE]->prepareForCompositeMap(Ogre::TRect(area.left+halfW, area.top+halfH, area.right, area.bottom)); - } - else - { - // TODO: when to destroy? - Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT(); - makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, material); - } -} - -void QuadTreeNode::ensureCompositeMap() -{ - if (!mCompositeMap.isNull()) - return; - - static int i=0; - std::stringstream name; - name << "terrain/comp" << i++; - - const int size = 128; - mCompositeMap = Ogre::TextureManager::getSingleton().createManual( - name.str(), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, size, size, Ogre::MIP_DEFAULT, Ogre::PF_A8B8G8R8); - - // Create quads for each cell - prepareForCompositeMap(Ogre::TRect(0,0,1,1)); - - mTerrain->renderCompositeMap(mCompositeMap); - - mTerrain->clearCompositeMapSceneManager(); - -} - -void QuadTreeNode::applyMaterials() -{ - if (mChunk) - { - mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); - mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled()); - if (mSize <= 1) - mChunk->setMaterial(mMaterialGenerator->generate()); - else - mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap()); - } - if (hasChildren()) - for (int i=0; i<4; ++i) - mChildren[i]->applyMaterials(); -} diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp deleted file mode 100644 index e44b646004..0000000000 --- a/components/terrain/quadtreenode.hpp +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#ifndef COMPONENTS_TERRAIN_QUADTREENODE_H -#define COMPONENTS_TERRAIN_QUADTREENODE_H - -#include -#include -#include - -#include "defs.hpp" - -namespace Ogre -{ - class Rectangle2D; -} - -namespace Terrain -{ - class DefaultWorld; - class Chunk; - class MaterialGenerator; - struct LoadResponseData; - - enum ChildDirection - { - NW = 0, - NE = 1, - SW = 2, - SE = 3, - Root - }; - - enum LoadState - { - LS_Unloaded, - LS_Loading, - LS_Loaded - }; - - /** - * @brief A node in the quad tree for our terrain. Depending on LOD, - * a node can either choose to render itself in one batch (merging its children), - * or delegate the render process to its children, rendering each child in at least one batch. - */ - class QuadTreeNode - { - public: - /// @param terrain - /// @param dir relative to parent, or Root if we are the root node - /// @param size size (in *cell* units!) - /// @param center center (in *cell* units!) - /// @param parent parent node - QuadTreeNode (DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent); - ~QuadTreeNode(); - - /// Rebuild all materials - void applyMaterials(); - - /// Initialize neighbours - do this after the quadtree is built - void initNeighbours(); - /// Initialize bounding boxes of non-leafs by merging children bounding boxes. - /// Do this after the quadtree is built - note that leaf bounding boxes - /// need to be set first via setBoundingBox! - void initAabb(); - - /// @note takes ownership of \a child - void createChild (ChildDirection id, float size, const Ogre::Vector2& center); - - /// Mark this node as a dummy node. This can happen if the terrain size isn't a power of two. - /// For the QuadTree to work, we need to round the size up to a power of two, which means we'll - /// end up with empty nodes that don't actually render anything. - void markAsDummy() { mIsDummy = true; } - bool isDummy() { return mIsDummy; } - - QuadTreeNode* getParent() { return mParent; } - - Ogre::SceneNode* getSceneNode() { return mSceneNode; } - - int getSize() { return static_cast(mSize); } - Ogre::Vector2 getCenter() { return mCenter; } - - bool hasChildren() { return mChildren[0] != 0; } - QuadTreeNode* getChild(ChildDirection dir) { return mChildren[dir]; } - - /// Get neighbour node in this direction - QuadTreeNode* getNeighbour (Direction dir); - - /// Returns our direction relative to the parent node, or Root if we are the root node. - ChildDirection getDirection() { return mDirection; } - - /// Set bounding box in local coordinates. Should be done at load time for leaf nodes. - /// Other nodes can merge AABB of child nodes. - void setBoundingBox (const Ogre::AxisAlignedBox& box); - - /// Get bounding box in local coordinates - const Ogre::AxisAlignedBox& getBoundingBox(); - - const Ogre::AxisAlignedBox& getWorldBoundingBox(); - - DefaultWorld* getTerrain() { return mTerrain; } - - /// Adjust LODs for the given camera position, possibly splitting up chunks or merging them. - /// @return Did we (or all of our children) choose to render? - bool update (const Ogre::Vector3& cameraPos); - - /// Adjust index buffers of chunks to stitch together chunks of different LOD, so that cracks are avoided. - /// Call after QuadTreeNode::update! - void updateIndexBuffers(); - - /// Destroy chunks rendered by this node *and* its children (if param is true) - void destroyChunks(bool children); - - /// Get the effective LOD level if this node was rendered in one chunk - /// with Storage::getCellVertices^2 vertices - size_t getNativeLodLevel() { return mLodLevel; } - - /// Get the effective current LOD level used by the chunk rendering this node - size_t getActualLodLevel(); - - /// Is this node currently configured to render itself? - bool hasChunk(); - - /// Add a textured quad to a specific 2d area in the composite map scenemanager. - /// Only nodes with size <= 1 can be rendered with alpha blending, so larger nodes will simply - /// call this method on their children. - /// @note Do not call this before World::areLayersLoaded() == true - /// @param area area in image space to put the quad - void prepareForCompositeMap(Ogre::TRect area); - - /// Create a chunk for this node from the given data. - void load (const LoadResponseData& data); - void unload(bool recursive=false); - void loadLayers (const LayerCollection& collection); - /// This is recursive! Call it once on the root node after all leafs have loaded layers. - void loadMaterials(); - - LoadState getLoadState() { return mLoadState; } - - private: - // Stored here for convenience in case we need layer list again - MaterialGenerator* mMaterialGenerator; - - LoadState mLoadState; - - bool mIsDummy; - float mSize; - size_t mLodLevel; // LOD if we were to render this node in one chunk - Ogre::AxisAlignedBox mBounds; - Ogre::AxisAlignedBox mWorldBounds; - ChildDirection mDirection; - Ogre::Vector2 mCenter; - - Ogre::SceneNode* mSceneNode; - - QuadTreeNode* mParent; - QuadTreeNode* mChildren[4]; - QuadTreeNode* mNeighbours[4]; - - Chunk* mChunk; - - DefaultWorld* mTerrain; - - Ogre::TexturePtr mCompositeMap; - - void ensureCompositeMap(); - }; - -} - -#endif diff --git a/components/terrain/storage.cpp b/components/terrain/storage.cpp index 14009127d2..bdc8194812 100644 --- a/components/terrain/storage.cpp +++ b/components/terrain/storage.cpp @@ -1,21 +1 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ +#include "storage.hpp" diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index 7846e91c6e..bd5706b25e 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -1,31 +1,20 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ #ifndef COMPONENTS_TERRAIN_STORAGE_H #define COMPONENTS_TERRAIN_STORAGE_H -#include +#include + +#include +#include +#include +#include #include "defs.hpp" +namespace osg +{ + class Image; +} + namespace Terrain { /// We keep storage of terrain data abstract here since we need different implementations for game and editor @@ -46,7 +35,7 @@ namespace Terrain /// @param min min height will be stored here /// @param max max height will be stored here /// @return true if there was data available for this terrain chunk - virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max) = 0; + virtual bool getMinMaxHeights (float size, const osg::Vec2f& center, float& min, float& max) = 0; /// Fill vertex buffers for a terrain chunk. /// @note May be called from background threads. Make sure to only call thread-safe functions from here! @@ -59,11 +48,12 @@ namespace Terrain /// @param positions buffer to write vertices /// @param normals buffer to write vertex normals /// @param colours buffer to write vertex colours - virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, - std::vector& positions, - std::vector& normals, - std::vector& colours) = 0; + virtual void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, + osg::ref_ptr positions, + osg::ref_ptr normals, + osg::ref_ptr colours) = 0; + typedef std::vector > ImageVector; /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. @@ -75,23 +65,11 @@ namespace Terrain /// can utilize packing, FFP can't. /// @param blendmaps created blendmaps will be written here /// @param layerList names of the layer textures used will be written here - virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, - std::vector& blendmaps, + virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, bool pack, + ImageVector& blendmaps, std::vector& layerList) = 0; - /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. - /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. - /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might - /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. - /// @note May be called from background threads. Make sure to only call thread-safe functions from here! - /// @param nodes A collection of nodes for which to retrieve the aforementioned data - /// @param out Output vector - /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - - /// otherwise, each texture contains blend values for one layer only. Shader-based rendering - /// can utilize packing, FFP can't. - virtual void getBlendmaps (const std::vector& nodes, std::vector& out, bool pack) = 0; - - virtual float getHeightAt (const Ogre::Vector3& worldPos) = 0; + virtual float getHeightAt (const osg::Vec3f& worldPos) = 0; virtual LayerInfo getDefaultLayer() = 0; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index bb99ca23e4..aa82e0bd65 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -1,40 +1,57 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ #include "terraingrid.hpp" -#include -#include -#include +#include -#include "chunk.hpp" +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include + +#include "material.hpp" +#include "storage.hpp" + +namespace +{ + class StaticBoundingBoxCallback : public osg::Drawable::ComputeBoundingBoxCallback + { + public: + StaticBoundingBoxCallback(const osg::BoundingBox& bounds) + : mBoundingBox(bounds) + { + } + + virtual osg::BoundingBox computeBound(const osg::Drawable&) const + { + return mBoundingBox; + } + + private: + osg::BoundingBox mBoundingBox; + }; +} namespace Terrain { -TerrainGrid::TerrainGrid(Ogre::SceneManager *sceneMgr, Terrain::Storage *storage, int visibilityFlags, bool shaders, Terrain::Alignment align) - : Terrain::World(sceneMgr, storage, visibilityFlags, shaders, align) - , mVisible(true) +TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, + Storage* storage, int nodeMask) + : Terrain::World(parent, resourceSystem, ico, storage, nodeMask) + , mNumSplits(4) + , mKdTreeBuilder(new osg::KdTreeBuilder) { - mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); + mCache = BufferCache((storage->getCellVertices()-1)/static_cast(mNumSplits) + 1); } TerrainGrid::~TerrainGrid() @@ -43,12 +60,131 @@ TerrainGrid::~TerrainGrid() { unloadCell(mGrid.begin()->first.first, mGrid.begin()->first.second); } - - mSceneMgr->destroySceneNode(mRootNode); } -void TerrainGrid::update(const Ogre::Vector3 &cameraPos) +class GridElement { +public: + osg::ref_ptr mNode; +}; + +osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter) +{ + if (chunkSize * mNumSplits > 1.f) + { + // keep splitting + osg::ref_ptr group (new osg::Group); + if (parent) + parent->addChild(group); + + float newChunkSize = chunkSize/2.f; + buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(newChunkSize/2.f, newChunkSize/2.f)); + buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(newChunkSize/2.f, -newChunkSize/2.f)); + buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize/2.f, newChunkSize/2.f)); + buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize/2.f, -newChunkSize/2.f)); + return group; + } + else + { + float minH, maxH; + if (!mStorage->getMinMaxHeights(chunkSize, chunkCenter, minH, maxH)) + return NULL; // no terrain defined + + osg::Vec2f worldCenter = chunkCenter*mStorage->getCellWorldSize(); + osg::ref_ptr transform (new SceneUtil::PositionAttitudeTransform); + transform->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f)); + + if (parent) + parent->addChild(transform); + + osg::ref_ptr positions (new osg::Vec3Array); + osg::ref_ptr normals (new osg::Vec3Array); + osg::ref_ptr colors (new osg::Vec4Array); + + osg::ref_ptr vbo (new osg::VertexBufferObject); + positions->setVertexBufferObject(vbo); + normals->setVertexBufferObject(vbo); + colors->setVertexBufferObject(vbo); + + mStorage->fillVertexBuffers(0, chunkSize, chunkCenter, positions, normals, colors); + + osg::ref_ptr geometry (new osg::Geometry); + geometry->setVertexArray(positions); + geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); + geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); + geometry->setUseDisplayList(false); + geometry->setUseVertexBufferObjects(true); + + geometry->addPrimitiveSet(mCache.getIndexBuffer(0)); + + // we already know the bounding box, so no need to let OSG compute it. + osg::Vec3f min(-0.5f*mStorage->getCellWorldSize()*chunkSize, + -0.5f*mStorage->getCellWorldSize()*chunkSize, + minH); + osg::Vec3f max (0.5f*mStorage->getCellWorldSize()*chunkSize, + 0.5f*mStorage->getCellWorldSize()*chunkSize, + maxH); + osg::BoundingBox bounds(min, max); + geometry->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(bounds)); + + std::vector layerList; + std::vector > blendmaps; + mStorage->getBlendmaps(chunkSize, chunkCenter, false, blendmaps, layerList); + + // For compiling textures, I don't think the osgFX::Effect does it correctly + osg::ref_ptr textureCompileDummy (new osg::Node); + + std::vector > layerTextures; + for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) + { + layerTextures.push_back(mResourceSystem->getTextureManager()->getTexture2D(it->mDiffuseMap, osg::Texture::REPEAT, osg::Texture::REPEAT)); + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back()); + } + + std::vector > blendmapTextures; + for (std::vector >::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) + { + osg::ref_ptr texture (new osg::Texture2D); + texture->setImage(*it); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + texture->setResizeNonPowerOfTwoHint(false); + blendmapTextures.push_back(texture); + + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back()); + } + + // use texture coordinates for both texture units, the layer texture and blend texture + for (unsigned int i=0; i<2; ++i) + geometry->setTexCoordArray(i, mCache.getUVBuffer()); + + float blendmapScale = ESM::Land::LAND_TEXTURE_SIZE*chunkSize; + osg::ref_ptr effect (new Terrain::Effect(layerTextures, blendmapTextures, blendmapScale, blendmapScale)); + + effect->addCullCallback(new SceneUtil::LightListCallback); + + transform->addChild(effect); + +#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) + osg::Node* toAttach = geometry.get(); +#else + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(geometry); + osg::Node* toAttach = geode.get(); +#endif + + effect->addChild(toAttach); + + if (mIncrementalCompileOperation) + { + mIncrementalCompileOperation->add(toAttach); + mIncrementalCompileOperation->add(textureCompileDummy); + } + + return transform; + } } void TerrainGrid::loadCell(int x, int y) @@ -56,68 +192,24 @@ void TerrainGrid::loadCell(int x, int y) if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) return; // already loaded - Ogre::Vector2 center(x+0.5f, y+0.5f); - float minH, maxH; - if (!mStorage->getMinMaxHeights(1, center, minH, maxH)) + osg::Vec2f center(x+0.5f, y+0.5f); + + osg::ref_ptr terrainNode = buildTerrain(NULL, 1.f, center); + if (!terrainNode) return; // no terrain defined - Ogre::Vector3 min (-0.5f*mStorage->getCellWorldSize(), - -0.5f*mStorage->getCellWorldSize(), - minH); - Ogre::Vector3 max (0.5f*mStorage->getCellWorldSize(), - 0.5f*mStorage->getCellWorldSize(), - maxH); + std::auto_ptr element (new GridElement); + element->mNode = terrainNode; + mTerrainRoot->addChild(element->mNode); - Ogre::AxisAlignedBox bounds(min, max); + // kdtree probably not needed with mNumSplits=4 + /* + // build a kdtree to speed up intersection tests with the terrain + // Note, the build could be optimized using a custom kdtree builder, since we know that the terrain can be represented by a quadtree + geode->accept(*mKdTreeBuilder); + */ - GridElement element; - - Ogre::Vector2 worldCenter = center*mStorage->getCellWorldSize(); - element.mSceneNode = mRootNode->createChildSceneNode(Ogre::Vector3(worldCenter.x, worldCenter.y, 0)); - - std::vector positions; - std::vector normals; - std::vector colours; - mStorage->fillVertexBuffers(0, 1, center, mAlign, positions, normals, colours); - - element.mChunk = new Terrain::Chunk(mCache.getUVBuffer(), bounds, positions, normals, colours); - element.mChunk->setIndexBuffer(mCache.getIndexBuffer(0)); - element.mChunk->setVisibilityFlags(mVisibilityFlags); - element.mChunk->setCastShadows(true); - - std::vector blendmaps; - std::vector layerList; - mStorage->getBlendmaps(1, center, mShaders, blendmaps, layerList); - - element.mMaterialGenerator.setLayerList(layerList); - - // upload blendmaps to GPU - std::vector blendTextures; - for (std::vector::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) - { - static int count=0; - Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/" - + Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, it->getWidth(), it->getHeight(), 0, it->format); - - Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(it->data, it->getWidth()*it->getHeight()*Ogre::PixelUtil::getNumElemBytes(it->format), true)); - map->loadRawData(stream, it->getWidth(), it->getHeight(), it->format); - blendTextures.push_back(map); - } - - element.mMaterialGenerator.setBlendmapList(blendTextures); - - element.mSceneNode->attachObject(element.mChunk); - updateMaterial(element); - - mGrid[std::make_pair(x,y)] = element; -} - -void TerrainGrid::updateMaterial(GridElement &element) -{ - element.mMaterialGenerator.enableShadows(getShadowsEnabled()); - element.mMaterialGenerator.enableSplitShadows(getSplitShadowsEnabled()); - element.mChunk->setMaterial(element.mMaterialGenerator.generate()); + mGrid[std::make_pair(x,y)] = element.release(); } void TerrainGrid::unloadCell(int x, int y) @@ -126,60 +218,11 @@ void TerrainGrid::unloadCell(int x, int y) if (it == mGrid.end()) return; - GridElement& element = it->second; - delete element.mChunk; - element.mChunk = NULL; - - const std::vector& blendmaps = element.mMaterialGenerator.getBlendmapList(); - for (std::vector::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) - Ogre::TextureManager::getSingleton().remove((*it)->getName()); - - mSceneMgr->destroySceneNode(element.mSceneNode); - element.mSceneNode = NULL; + GridElement* element = it->second; + mTerrainRoot->removeChild(element->mNode); + delete element; mGrid.erase(it); } -void TerrainGrid::applyMaterials(bool shadows, bool splitShadows) -{ - mShadows = shadows; - mSplitShadows = splitShadows; - for (Grid::iterator it = mGrid.begin(); it != mGrid.end(); ++it) - { - updateMaterial(it->second); - } -} - -bool TerrainGrid::getVisible() -{ - return mVisible; -} - -void TerrainGrid::setVisible(bool visible) -{ - mVisible = visible; - mRootNode->setVisible(visible); -} - -Ogre::AxisAlignedBox TerrainGrid::getWorldBoundingBox (const Ogre::Vector2& center) -{ - int cellX = static_cast(std::floor(center.x)); - int cellY = static_cast(std::floor(center.y)); - - Grid::iterator it = mGrid.find(std::make_pair(cellX, cellY)); - if (it == mGrid.end()) - return Ogre::AxisAlignedBox::BOX_NULL; - - Terrain::Chunk* chunk = it->second.mChunk; - Ogre::SceneNode* node = it->second.mSceneNode; - Ogre::AxisAlignedBox box = chunk->getBoundingBox(); - box = Ogre::AxisAlignedBox(box.getMinimum() + node->getPosition(), box.getMaximum() + node->getPosition()); - return box; -} - -void TerrainGrid::syncLoad() -{ - -} - } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 97ef6d14d3..169a9a6228 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -1,90 +1,42 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ #ifndef COMPONENTS_TERRAIN_TERRAINGRID_H #define COMPONENTS_TERRAIN_TERRAINGRID_H +#include + #include "world.hpp" #include "material.hpp" +namespace osg +{ + class KdTreeBuilder; +} + namespace Terrain { - class Chunk; - struct GridElement - { - Ogre::SceneNode* mSceneNode; - - Terrain::MaterialGenerator mMaterialGenerator; - - Terrain::Chunk* mChunk; - }; + class GridElement; /// @brief Simple terrain implementation that loads cells in a grid, with no LOD class TerrainGrid : public Terrain::World { public: - /// @note takes ownership of \a storage - /// @param sceneMgr scene manager to use - /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) - /// @param visbilityFlags visibility flags for the created meshes - /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually - /// faster so this is just here for compatibility. - /// @param align The align of the terrain, see Alignment enum - TerrainGrid(Ogre::SceneManager* sceneMgr, - Terrain::Storage* storage, int visibilityFlags, bool shaders, Terrain::Alignment align); + TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, + Storage* storage, int nodeMask); ~TerrainGrid(); - /// Update chunk LODs according to this camera position - virtual void update (const Ogre::Vector3& cameraPos); - virtual void loadCell(int x, int y); virtual void unloadCell(int x, int y); - /// Get the world bounding box of a chunk of terrain centered at \a center - virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center); - - /// Show or hide the whole terrain - /// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden - virtual void setVisible(bool visible); - virtual bool getVisible(); - - /// Recreate materials used by terrain chunks. This should be called whenever settings of - /// the material factory are changed. (Relying on the factory to update those materials is not - /// enough, since turning a feature on/off can change the number of texture units available for layer/blend - /// textures, and to properly respond to this we may need to change the structure of the material, such as - /// adding or removing passes. This can only be achieved by a full rebuild.) - virtual void applyMaterials(bool shadows, bool splitShadows); - - /// Wait until all background loading is complete. - virtual void syncLoad(); - private: - void updateMaterial (GridElement& element); + osg::ref_ptr buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); - typedef std::map, GridElement> Grid; + // split each ESM::Cell into mNumSplits*mNumSplits terrain chunks + unsigned int mNumSplits; + + typedef std::map, GridElement*> Grid; Grid mGrid; - Ogre::SceneNode* mRootNode; - bool mVisible; + osg::ref_ptr mKdTreeBuilder; }; } diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 3baaaed44e..2250b593d2 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -1,83 +1,38 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ #include "world.hpp" -#include +#include +#include #include "storage.hpp" namespace Terrain { -World::World(Ogre::SceneManager* sceneMgr, - Storage* storage, int visibilityFlags, bool shaders, Alignment align) - : mShaders(shaders) - , mShadows(false) - , mSplitShadows(false) - , mAlign(align) - , mStorage(storage) - , mVisibilityFlags(visibilityFlags) - , mSceneMgr(sceneMgr) +World::World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, + Storage* storage, int nodeMask) + : mStorage(storage) , mCache(storage->getCellVertices()) + , mParent(parent) + , mResourceSystem(resourceSystem) + , mIncrementalCompileOperation(ico) { + mTerrainRoot = new osg::Group; + mTerrainRoot->setNodeMask(nodeMask); + mTerrainRoot->getOrCreateStateSet()->setRenderingHint(osg::StateSet::OPAQUE_BIN); + + mParent->addChild(mTerrainRoot); } World::~World() { + mParent->removeChild(mTerrainRoot); + delete mStorage; } -float World::getHeightAt(const Ogre::Vector3 &worldPos) +float World::getHeightAt(const osg::Vec3f &worldPos) { return mStorage->getHeightAt(worldPos); } -void World::convertPosition(float &x, float &y, float &z) -{ - Terrain::convertPosition(mAlign, x, y, z); -} - -void World::convertPosition(Ogre::Vector3 &pos) -{ - convertPosition(pos.x, pos.y, pos.z); -} - -void World::convertBounds(Ogre::AxisAlignedBox& bounds) -{ - switch (mAlign) - { - case Align_XY: - return; - case Align_XZ: - convertPosition(bounds.getMinimum()); - convertPosition(bounds.getMaximum()); - // Because we changed sign of Z - std::swap(bounds.getMinimum().z, bounds.getMaximum().z); - return; - case Align_YZ: - convertPosition(bounds.getMinimum()); - convertPosition(bounds.getMaximum()); - return; - } -} - } diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 3e63b4c936..4212f2a0c2 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -1,35 +1,24 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ #ifndef COMPONENTS_TERRAIN_WORLD_H #define COMPONENTS_TERRAIN_WORLD_H -#include +#include #include "defs.hpp" #include "buffercache.hpp" -namespace Ogre +namespace osg { - class SceneManager; + class Group; +} + +namespace osgUtil +{ + class IncrementalCompileOperation; +} + +namespace Resource +{ + class ResourceSystem; } namespace Terrain @@ -44,79 +33,31 @@ namespace Terrain { public: /// @note takes ownership of \a storage - /// @param sceneMgr scene manager to use /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) - /// @param visbilityFlags visibility flags for the created meshes - /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually - /// faster so this is just here for compatibility. - /// @param align The align of the terrain, see Alignment enum - World(Ogre::SceneManager* sceneMgr, - Storage* storage, int visiblityFlags, bool shaders, Alignment align); + /// @param nodeMask mask for the terrain root + World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, + Storage* storage, int nodeMask); virtual ~World(); - bool getShadersEnabled() { return mShaders; } - bool getShadowsEnabled() { return mShadows; } - bool getSplitShadowsEnabled() { return mSplitShadows; } - - float getHeightAt (const Ogre::Vector3& worldPos); - - /// Update chunk LODs according to this camera position - /// @note Calling this method might lead to composite textures being rendered, so it is best - /// not to call it when render commands are still queued, since that would cause a flush. - virtual void update (const Ogre::Vector3& cameraPos) = 0; + float getHeightAt (const osg::Vec3f& worldPos); // This is only a hint and may be ignored by the implementation. virtual void loadCell(int x, int y) {} virtual void unloadCell(int x, int y) {} - /// Get the world bounding box of a chunk of terrain centered at \a center - virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center) = 0; - - Ogre::SceneManager* getSceneManager() { return mSceneMgr; } - Storage* getStorage() { return mStorage; } - /// Show or hide the whole terrain - /// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden - virtual void setVisible(bool visible) = 0; - virtual bool getVisible() = 0; - - /// Recreate materials used by terrain chunks. This should be called whenever settings of - /// the material factory are changed. (Relying on the factory to update those materials is not - /// enough, since turning a feature on/off can change the number of texture units available for layer/blend - /// textures, and to properly respond to this we may need to change the structure of the material, such as - /// adding or removing passes. This can only be achieved by a full rebuild.) - virtual void applyMaterials(bool shadows, bool splitShadows) = 0; - - int getVisibilityFlags() { return mVisibilityFlags; } - - Alignment getAlign() { return mAlign; } - - /// Wait until all background loading is complete. - virtual void syncLoad() {} - protected: - bool mShaders; - bool mShadows; - bool mSplitShadows; - Alignment mAlign; - Storage* mStorage; - int mVisibilityFlags; - - Ogre::SceneManager* mSceneMgr; - BufferCache mCache; - public: - // ----INTERNAL---- - BufferCache& getBufferCache() { return mCache; } + osg::ref_ptr mParent; + osg::ref_ptr mTerrainRoot; - // Convert the given position from Z-up align, i.e. Align_XY to the wanted align set in mAlign - void convertPosition (float& x, float& y, float& z); - void convertPosition (Ogre::Vector3& pos); - void convertBounds (Ogre::AxisAlignedBox& bounds); + Resource::ResourceSystem* mResourceSystem; + + osg::ref_ptr mIncrementalCompileOperation; }; } diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index d7f9c3a382..8af0bc5ed8 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -85,10 +85,10 @@ std::string Utf8Encoder::getUtf8(const char* input, size_t size) assert(input[size] == 0); // Note: The rest of this function is designed for single-character - // input encodings only. It also assumes that the input the input - // encoding shares its first 128 values (0-127) with ASCII. There are - // no plans to add more encodings to this module (we are using utf8 - // for new content files), so that shouldn't be an issue. + // input encodings only. It also assumes that the input encoding + // shares its first 128 values (0-127) with ASCII. There are no plans + // to add more encodings to this module (we are using utf8 for new + // content files), so that shouldn't be an issue. // Compute output length, and check for pure ascii input at the same // time. diff --git a/components/version/version.cpp b/components/version/version.cpp new file mode 100644 index 0000000000..c87943f9e2 --- /dev/null +++ b/components/version/version.cpp @@ -0,0 +1,41 @@ +#include "version.hpp" + +#include +#include + +namespace Version +{ + +Version getOpenmwVersion(const std::string &resourcePath) +{ + boost::filesystem::path path (resourcePath + "/version"); + + boost::filesystem::ifstream stream (path); + + Version v; + std::getline(stream, v.mVersion); + std::getline(stream, v.mCommitHash); + std::getline(stream, v.mTagHash); + return v; +} + +std::string Version::describe() +{ + std::string str = "OpenMW version " + mVersion; + std::string rev = mCommitHash; + std::string tag = mTagHash; + if (!rev.empty() && !tag.empty()) + { + rev = rev.substr(0, 10); + str += "\nRevision: " + rev; + } + return str; +} + +std::string getOpenmwVersionDescription(const std::string &resourcePath) +{ + Version v = getOpenmwVersion(resourcePath); + return v.describe(); +} + +} diff --git a/components/version/version.hpp b/components/version/version.hpp new file mode 100644 index 0000000000..7371e786e0 --- /dev/null +++ b/components/version/version.hpp @@ -0,0 +1,28 @@ +#ifndef VERSION_HPP +#define VERSION_HPP + +#include + +namespace Version +{ + + struct Version + { + std::string mVersion; + std::string mCommitHash; + std::string mTagHash; + + std::string describe(); + }; + + /// Read OpenMW version from the version file located in resourcePath. + Version getOpenmwVersion(const std::string& resourcePath); + + /// Helper function to getOpenmwVersion and describe() it + std::string getOpenmwVersionDescription(const std::string& resourcePath); + +} + + +#endif // VERSION_HPP + diff --git a/components/version/version.hpp.cmake b/components/version/version.hpp.cmake deleted file mode 100644 index 4cdfa32f08..0000000000 --- a/components/version/version.hpp.cmake +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef VERSION_HPP -#define VERSION_HPP - -#define OPENMW_VERSION_MAJOR @OPENMW_VERSION_MAJOR@ -#define OPENMW_VERSION_MINOR @OPENMW_VERSION_MINOR@ -#define OPENMW_VERSION_RELEASE @OPENMW_VERSION_RELEASE@ -#define OPENMW_VERSION "@OPENMW_VERSION@" - -#define OPENMW_VERSION_COMMITHASH "@OPENMW_VERSION_COMMITHASH@" -#define OPENMW_VERSION_TAGHASH "@OPENMW_VERSION_TAGHASH@" - -#endif // VERSION_HPP - diff --git a/components/vfs/archive.hpp b/components/vfs/archive.hpp new file mode 100644 index 0000000000..b36c7117b0 --- /dev/null +++ b/components/vfs/archive.hpp @@ -0,0 +1,30 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_ARCHIVE_H +#define OPENMW_COMPONENTS_RESOURCE_ARCHIVE_H + +#include + +#include + +namespace VFS +{ + + class File + { + public: + virtual ~File() {} + + virtual Files::IStreamPtr open() = 0; + }; + + class Archive + { + public: + virtual ~Archive() {} + + /// List all resources contained in this archive, and run the resource names through the given normalize function. + virtual void listResources(std::map& out, char (*normalize_function) (char)) = 0; + }; + +} + +#endif diff --git a/components/vfs/bsaarchive.cpp b/components/vfs/bsaarchive.cpp new file mode 100644 index 0000000000..a527a6ad92 --- /dev/null +++ b/components/vfs/bsaarchive.cpp @@ -0,0 +1,43 @@ +#include "bsaarchive.hpp" + +namespace VFS +{ + + +BsaArchive::BsaArchive(const std::string &filename) +{ + mFile.open(filename); + + const Bsa::BSAFile::FileList &filelist = mFile.getList(); + for(Bsa::BSAFile::FileList::const_iterator it = filelist.begin();it != filelist.end();++it) + { + mResources.push_back(BsaArchiveFile(&*it, &mFile)); + } +} + +void BsaArchive::listResources(std::map &out, char (*normalize_function)(char)) +{ + for (std::vector::iterator it = mResources.begin(); it != mResources.end(); ++it) + { + std::string ent = it->mInfo->name; + std::transform(ent.begin(), ent.end(), ent.begin(), normalize_function); + + out[ent] = &*it; + } +} + +// ------------------------------------------------------------------------------ + +BsaArchiveFile::BsaArchiveFile(const Bsa::BSAFile::FileStruct *info, Bsa::BSAFile* bsa) + : mInfo(info) + , mFile(bsa) +{ + +} + +Files::IStreamPtr BsaArchiveFile::open() +{ + return mFile->getFile(mInfo); +} + +} diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp new file mode 100644 index 0000000000..25ad60e0a6 --- /dev/null +++ b/components/vfs/bsaarchive.hpp @@ -0,0 +1,37 @@ +#ifndef VFS_BSAARCHIVE_HPP_ +#define VFS_BSAARCHIVE_HPP_ + +#include "archive.hpp" + +#include + +namespace VFS +{ + + class BsaArchiveFile : public File + { + public: + BsaArchiveFile(const Bsa::BSAFile::FileStruct* info, Bsa::BSAFile* bsa); + + virtual Files::IStreamPtr open(); + + const Bsa::BSAFile::FileStruct* mInfo; + Bsa::BSAFile* mFile; + }; + + class BsaArchive : public Archive + { + public: + BsaArchive(const std::string& filename); + + virtual void listResources(std::map& out, char (*normalize_function) (char)); + + private: + Bsa::BSAFile mFile; + + std::vector mResources; + }; + +} + +#endif \ No newline at end of file diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp new file mode 100644 index 0000000000..ad5150a441 --- /dev/null +++ b/components/vfs/filesystemarchive.cpp @@ -0,0 +1,65 @@ +#include "filesystemarchive.hpp" + +#include + +namespace VFS +{ + + FileSystemArchive::FileSystemArchive(const std::string &path) + : mBuiltIndex(false) + , mPath(path) + { + + } + + void FileSystemArchive::listResources(std::map &out, char (*normalize_function)(char)) + { + if (!mBuiltIndex) + { + typedef boost::filesystem::recursive_directory_iterator directory_iterator; + + directory_iterator end; + + size_t prefix = mPath.size (); + + if (mPath.size () > 0 && mPath [prefix - 1] != '\\' && mPath [prefix - 1] != '/') + ++prefix; + + for (directory_iterator i (mPath); i != end; ++i) + { + if(boost::filesystem::is_directory (*i)) + continue; + + std::string proper = i->path ().string (); + + FileSystemArchiveFile file(proper); + + std::string searchable; + + std::transform(proper.begin() + prefix, proper.end(), std::back_inserter(searchable), normalize_function); + + mIndex.insert (std::make_pair (searchable, file)); + } + + mBuiltIndex = true; + } + + for (index::iterator it = mIndex.begin(); it != mIndex.end(); ++it) + { + out[it->first] = &it->second; + } + } + + // ---------------------------------------------------------------------------------- + + FileSystemArchiveFile::FileSystemArchiveFile(const std::string &path) + : mPath(path) + { + } + + Files::IStreamPtr FileSystemArchiveFile::open() + { + return Files::openConstrainedFileStream(mPath.c_str()); + } + +} diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp new file mode 100644 index 0000000000..6c8e1b82bb --- /dev/null +++ b/components/vfs/filesystemarchive.hpp @@ -0,0 +1,40 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_FILESYSTEMARCHIVE_H +#define OPENMW_COMPONENTS_RESOURCE_FILESYSTEMARCHIVE_H + +#include "archive.hpp" + +namespace VFS +{ + + class FileSystemArchiveFile : public File + { + public: + FileSystemArchiveFile(const std::string& path); + + virtual Files::IStreamPtr open(); + + private: + std::string mPath; + + }; + + class FileSystemArchive : public Archive + { + public: + FileSystemArchive(const std::string& path); + + virtual void listResources(std::map& out, char (*normalize_function) (char)); + + + private: + typedef std::map index; + index mIndex; + + bool mBuiltIndex; + std::string mPath; + + }; + +} + +#endif diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp new file mode 100644 index 0000000000..457947d40d --- /dev/null +++ b/components/vfs/manager.cpp @@ -0,0 +1,94 @@ +#include "manager.hpp" + +#include +#include + +#include + +#include "archive.hpp" + +namespace +{ + + char strict_normalize_char(char ch) + { + return ch == '\\' ? '/' : ch; + } + + char nonstrict_normalize_char(char ch) + { + return ch == '\\' ? '/' : Misc::StringUtils::toLower(ch); + } + + void normalize_path(std::string& path, bool strict) + { + char (*normalize_char)(char) = strict ? &strict_normalize_char : &nonstrict_normalize_char; + std::transform(path.begin(), path.end(), path.begin(), normalize_char); + } + +} + +namespace VFS +{ + + Manager::Manager(bool strict) + : mStrict(strict) + { + + } + + Manager::~Manager() + { + for (std::vector::iterator it = mArchives.begin(); it != mArchives.end(); ++it) + delete *it; + mArchives.clear(); + } + + void Manager::addArchive(Archive *archive) + { + mArchives.push_back(archive); + } + + void Manager::buildIndex() + { + mIndex.clear(); + + for (std::vector::const_iterator it = mArchives.begin(); it != mArchives.end(); ++it) + (*it)->listResources(mIndex, mStrict ? &strict_normalize_char : &nonstrict_normalize_char); + } + + Files::IStreamPtr Manager::get(const std::string &name) const + { + std::string normalized = name; + normalize_path(normalized, mStrict); + + return getNormalized(normalized); + } + + Files::IStreamPtr Manager::getNormalized(const std::string &normalizedName) const + { + std::map::const_iterator found = mIndex.find(normalizedName); + if (found == mIndex.end()) + throw std::runtime_error("Resource '" + normalizedName + "' not found"); + return found->second->open(); + } + + bool Manager::exists(const std::string &name) const + { + std::string normalized = name; + normalize_path(normalized, mStrict); + + return mIndex.find(normalized) != mIndex.end(); + } + + const std::map& Manager::getIndex() const + { + return mIndex; + } + + void Manager::normalizeFilename(std::string &name) const + { + normalize_path(name, mStrict); + } + +} diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp new file mode 100644 index 0000000000..f74914977a --- /dev/null +++ b/components/vfs/manager.hpp @@ -0,0 +1,62 @@ +#ifndef OPENMW_COMPONENTS_RESOURCEMANAGER_H +#define OPENMW_COMPONENTS_RESOURCEMANAGER_H + +#include + +#include +#include + +namespace VFS +{ + + class Archive; + class File; + + /// @brief The main class responsible for loading files from a virtual file system. + /// @par Various archive types (e.g. directories on the filesystem, or compressed archives) + /// can be registered, and will be merged into a single file tree. If the same filename is + /// contained in multiple archives, the last added archive will have priority. + class Manager + { + public: + /// @param strict Use strict path handling? If enabled, no case folding will + /// be done, but slash/backslash conversions are always done. + Manager(bool strict); + + ~Manager(); + + /// Register the given archive. All files contained in it will be added to the index on the next buildIndex() call. + /// @note Takes ownership of the given pointer. + void addArchive(Archive* archive); + + /// Build the file index. Should be called when all archives have been registered. + void buildIndex(); + + /// Does a file with this name exist? + bool exists(const std::string& name) const; + + /// Get a complete list of files from all archives + const std::map& getIndex() const; + + /// Normalize the given filename, making slashes/backslashes consistent, and lower-casing if mStrict is false. + void normalizeFilename(std::string& name) const; + + /// Retrieve a file by name. + /// @note Throws an exception if the file can not be found. + Files::IStreamPtr get(const std::string& name) const; + + /// Retrieve a file by name (name is already normalized). + /// @note Throws an exception if the file can not be found. + Files::IStreamPtr getNormalized(const std::string& normalizedName) const; + + private: + bool mStrict; + + std::vector mArchives; + + std::map mIndex; + }; + +} + +#endif diff --git a/components/vfs/registerarchives.cpp b/components/vfs/registerarchives.cpp new file mode 100644 index 0000000000..b361e3f42f --- /dev/null +++ b/components/vfs/registerarchives.cpp @@ -0,0 +1,46 @@ +#include "registerarchives.hpp" + +#include +#include + +#include +#include +#include + +namespace VFS +{ + + void registerArchives(VFS::Manager *vfs, const Files::Collections &collections, const std::vector &archives, bool useLooseFiles) + { + const Files::PathContainer& dataDirs = collections.getPaths(); + + for (std::vector::const_iterator archive = archives.begin(); archive != archives.end(); ++archive) + { + if (collections.doesExist(*archive)) + { + // Last BSA has the highest priority + const std::string archivePath = collections.getPath(*archive).string(); + std::cout << "Adding BSA archive " << archivePath << std::endl; + + vfs->addArchive(new BsaArchive(archivePath)); + } + else + { + std::stringstream message; + message << "Archive '" << *archive << "' not found"; + throw std::runtime_error(message.str()); + } + } + + if (useLooseFiles) + for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) + { + std::cout << "Adding data directory " << iter->string() << std::endl; + // Last data dir has the highest priority + vfs->addArchive(new FileSystemArchive(iter->string())); + } + + vfs->buildIndex(); + } + +} diff --git a/components/vfs/registerarchives.hpp b/components/vfs/registerarchives.hpp new file mode 100644 index 0000000000..1ef13f0f9e --- /dev/null +++ b/components/vfs/registerarchives.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_COMPONENTS_VFS_REGISTER_ARCHIVES_H +#define OPENMW_COMPONENTS_VFS_REGISTER_ARCHIVES_H + +#include + +namespace VFS +{ + class Manager; + + /// @brief Register BSA and file system archives based on the given OpenMW configuration. + void registerArchives (VFS::Manager* vfs, const Files::Collections& collections, + const std::vector& archives, bool useLooseFiles); +} + +#endif diff --git a/components/widgets/imagebutton.cpp b/components/widgets/imagebutton.cpp index 1cd8829751..8e3f8ed690 100644 --- a/components/widgets/imagebutton.cpp +++ b/components/widgets/imagebutton.cpp @@ -42,18 +42,31 @@ namespace Gui ImageBox::onMouseButtonPressed(_left, _top, _id); } - MyGUI::IntSize ImageButton::getRequestedSize(bool logError) + MyGUI::IntSize ImageButton::getRequestedSize() { MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(mImageNormal); if (!texture) { - if (logError) - std::cerr << "ImageButton: can't find " << mImageNormal << std::endl; + std::cerr << "ImageButton: can't find " << mImageNormal << std::endl; return MyGUI::IntSize(0,0); } return MyGUI::IntSize (texture->getWidth(), texture->getHeight()); } + void ImageButton::setImage(const std::string &image) + { + size_t extpos = image.find_last_of("."); + std::string imageNoExt = image.substr(0, extpos); + + std::string ext = image.substr(extpos); + + mImageNormal = imageNoExt + "_idle" + ext; + mImageHighlighted = imageNoExt + "_over" + ext; + mImagePushed = imageNoExt + "_pressed" + ext; + + setImageTexture(mImageNormal); + } + void ImageButton::onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id) { if (_id == MyGUI::MouseButton::Left) diff --git a/components/widgets/imagebutton.hpp b/components/widgets/imagebutton.hpp index bed6a27940..a539f15c97 100644 --- a/components/widgets/imagebutton.hpp +++ b/components/widgets/imagebutton.hpp @@ -1,5 +1,5 @@ -#ifndef MWGUI_IMAGEBUTTON_H -#define MWGUI_IMAGEBUTTON_H +#ifndef OPENMW_COMPONENTS_WIDGETS_IMAGEBUTTON_H +#define OPENMW_COMPONENTS_WIDGETS_IMAGEBUTTON_H #include @@ -14,7 +14,10 @@ namespace Gui MYGUI_RTTI_DERIVED(ImageButton) public: - MyGUI::IntSize getRequestedSize(bool logError = true); + MyGUI::IntSize getRequestedSize(); + + /// Set mImageNormal, mImageHighlighted and mImagePushed based on file convention (image_idle.ext, image_over.ext and image_pressed.ext) + void setImage(const std::string& image); protected: virtual void setPropertyOverride(const std::string& _key, const std::string& _value); diff --git a/components/widgets/list.cpp b/components/widgets/list.cpp index df7e7d61d2..c5a459f22b 100644 --- a/components/widgets/list.cpp +++ b/components/widgets/list.cpp @@ -70,7 +70,7 @@ namespace Gui button->setCaption((*it)); button->getSubWidgetText()->setWordWrap(true); button->getSubWidgetText()->setTextAlign(MyGUI::Align::Left); - button->eventMouseWheel += MyGUI::newDelegate(this, &MWList::onMouseWheel); + button->eventMouseWheel += MyGUI::newDelegate(this, &MWList::onMouseWheelMoved); button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWList::onItemSelected); int height = button->getTextSize().height; @@ -135,7 +135,7 @@ namespace Gui mItems.clear(); } - void MWList::onMouseWheel(MyGUI::Widget* _sender, int _rel) + void MWList::onMouseWheelMoved(MyGUI::Widget* _sender, int _rel) { //NB view offset is negative if (mScrollView->getViewOffset().top + _rel*0.3f > 0) diff --git a/components/widgets/list.hpp b/components/widgets/list.hpp index 3efe1ff756..cc7b75c2ff 100644 --- a/components/widgets/list.hpp +++ b/components/widgets/list.hpp @@ -1,5 +1,5 @@ -#ifndef MWGUI_LIST_HPP -#define MWGUI_LIST_HPP +#ifndef OPENMW_COMPONENTS_WIDGETS_LIST_HPP +#define OPENMW_COMPONENTS_WIDGETS_LIST_HPP #include @@ -55,7 +55,7 @@ namespace Gui void redraw(bool scrollbarShown = false); - void onMouseWheel(MyGUI::Widget* _sender, int _rel); + void onMouseWheelMoved(MyGUI::Widget* _sender, int _rel); void onItemSelected(MyGUI::Widget* _sender); private: diff --git a/extern/ogre-ffmpeg-videoplayer/CMakeLists.txt b/extern/ogre-ffmpeg-videoplayer/CMakeLists.txt deleted file mode 100644 index edd5575f46..0000000000 --- a/extern/ogre-ffmpeg-videoplayer/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -set(OGRE_FFMPEG_VIDEOPLAYER_LIBRARY "ogre-ffmpeg-videoplayer") - -# Sources - -set(OGRE_FFMPEG_VIDEOPLAYER_SOURCE_FILES - videoplayer.cpp - videostate.cpp - videodefs.hpp - libavwrapper.cpp - audiodecoder.cpp - audiofactory.hpp -) - -include_directories(${FFMPEG_INCLUDE_DIRS}) -add_library(${OGRE_FFMPEG_VIDEOPLAYER_LIBRARY} STATIC ${OGRE_FFMPEG_VIDEOPLAYER_SOURCE_FILES}) -target_link_libraries(${OGRE_FFMPEG_VIDEOPLAYER_LIBRARY} ${FFMPEG_LIBRARIES} ${Boost_THREAD_LIBRARY}) - -link_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/extern/ogre-ffmpeg-videoplayer/libavwrapper.cpp b/extern/ogre-ffmpeg-videoplayer/libavwrapper.cpp deleted file mode 100644 index 6edc362077..0000000000 --- a/extern/ogre-ffmpeg-videoplayer/libavwrapper.cpp +++ /dev/null @@ -1,113 +0,0 @@ -// This file is a wrapper around the libavresample library (the API-incompatible swresample replacement in the libav fork of ffmpeg), to make it look like swresample to the user. - -#ifndef HAVE_LIBSWRESAMPLE -extern "C" -{ -#ifndef __STDC_CONSTANT_MACROS -#define __STDC_CONSTANT_MACROS -#endif -#include - -#include -#include -// From libavutil version 52.2.0 and onward the declaration of -// AV_CH_LAYOUT_* is removed from libavcodec/avcodec.h and moved to -// libavutil/channel_layout.h -#if AV_VERSION_INT(52, 2, 0) <= AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ - LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO) - #include -#endif -#include -#include - -/* FIXME: delete this file once libswresample is packaged for Debian */ - -int swr_init(AVAudioResampleContext *avr) { return 1; } - -void swr_free(AVAudioResampleContext **avr) { avresample_free(avr); } - -int swr_convert( - AVAudioResampleContext *avr, - uint8_t** output, - int out_samples, - const uint8_t** input, - int in_samples) -{ - // FIXME: potential performance hit - int out_plane_size = 0; - int in_plane_size = 0; - return avresample_convert(avr, output, out_plane_size, out_samples, - (uint8_t **)input, in_plane_size, in_samples); -} - -AVAudioResampleContext * swr_alloc_set_opts( - AVAudioResampleContext *avr, - int64_t out_ch_layout, - AVSampleFormat out_fmt, - int out_rate, - int64_t in_ch_layout, - AVSampleFormat in_fmt, - int in_rate, - int o, - void* l) -{ - avr = avresample_alloc_context(); - if(!avr) - return 0; - - int res; - res = av_opt_set_int(avr, "out_channel_layout", out_ch_layout, 0); - if(res < 0) - { - av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_ch_layout = %d\n", res); - return 0; - } - res = av_opt_set_int(avr, "out_sample_fmt", out_fmt, 0); - if(res < 0) - { - av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_fmt = %d\n", res); - return 0; - } - res = av_opt_set_int(avr, "out_sample_rate", out_rate, 0); - if(res < 0) - { - av_log(avr, AV_LOG_ERROR, "av_opt_set_int: out_rate = %d\n", res); - return 0; - } - res = av_opt_set_int(avr, "in_channel_layout", in_ch_layout, 0); - if(res < 0) - { - av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_ch_layout = %d\n", res); - return 0; - } - res = av_opt_set_int(avr, "in_sample_fmt", in_fmt, 0); - if(res < 0) - { - av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_fmt = %d\n", res); - return 0; - } - res = av_opt_set_int(avr, "in_sample_rate", in_rate, 0); - if(res < 0) - { - av_log(avr, AV_LOG_ERROR, "av_opt_set_int: in_rate = %d\n", res); - return 0; - } - res = av_opt_set_int(avr, "internal_sample_fmt", AV_SAMPLE_FMT_FLTP, 0); - if(res < 0) - { - av_log(avr, AV_LOG_ERROR, "av_opt_set_int: internal_sample_fmt\n"); - return 0; - } - - - if(avresample_open(avr) < 0) - { - av_log(avr, AV_LOG_ERROR, "Error opening context\n"); - return 0; - } - else - return avr; -} - -} -#endif diff --git a/extern/oics/ICSInputControlSystem.h b/extern/oics/ICSInputControlSystem.h index 51b701b48c..a82a11d751 100644 --- a/extern/oics/ICSInputControlSystem.h +++ b/extern/oics/ICSInputControlSystem.h @@ -32,8 +32,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "ICSControl.h" #include "ICSChannel.h" -#include "../sdl4ogre/events.h" - #include "boost/lexical_cast.hpp" #define ICS_LOG(text) if(mLog) mLog->logMessage( ("ICS: " + std::string(text)).c_str() ); @@ -51,11 +49,8 @@ namespace ICS virtual void logMessage(const char* text) = 0; }; - class DllExport InputControlSystem : - public SFO::MouseListener, - public SFO::KeyListener, - public SFO::ControllerListener - { + class DllExport InputControlSystem + { public: @@ -64,7 +59,7 @@ namespace ICS typedef NamedAxis MouseAxis; // MouseAxis is deprecated. It will be removed in future versions - typedef std::map JoystickInstanceMap; + typedef std::map JoystickInstanceMap; typedef std::list JoystickIDList; typedef struct @@ -101,13 +96,13 @@ namespace ICS inline void activate(){ this->mActive = true; }; inline void deactivate(){ this->mActive = false; }; - void controllerAdded (int deviceID, const SDL_ControllerDeviceEvent &args); + void controllerAdded (int deviceID, const SDL_ControllerDeviceEvent &args); void controllerRemoved(const SDL_ControllerDeviceEvent &args); - JoystickIDList& getJoystickIdList(){ return mJoystickIDList; }; + JoystickIDList& getJoystickIdList(){ return mJoystickIDList; }; JoystickInstanceMap& getJoystickInstanceMap(){ return mJoystickInstanceMap; }; // MouseListener - void mouseMoved(const SFO::MouseMotionEvent &evt); + void mouseMoved(const SDL_MouseMotionEvent &evt); void mousePressed(const SDL_MouseButtonEvent &evt, Uint8); void mouseReleased(const SDL_MouseButtonEvent &evt, Uint8); @@ -185,9 +180,9 @@ namespace ICS typedef std::map ControlsKeyBinderMapType; // typedef std::map ControlsAxisBinderMapType; // - typedef std::map ControlsButtonBinderMapType; // - - typedef std::map JoystickAxisBinderMapType; // > + typedef std::map ControlsButtonBinderMapType; // + + typedef std::map JoystickAxisBinderMapType; // > typedef std::map JoystickButtonBinderMapType; // > ControlsAxisBinderMapType mControlsMouseAxisBinderMap; // @@ -210,7 +205,7 @@ namespace ICS bool mXmouseAxisBinded; bool mYmouseAxisBinded; - JoystickIDList mJoystickIDList; + JoystickIDList mJoystickIDList; JoystickInstanceMap mJoystickInstanceMap; int mMouseAxisBindingInitialValues[3]; @@ -237,7 +232,7 @@ namespace ICS , int axis, Control::ControlChangingDirection direction); virtual void joystickButtonBindingDetected(InputControlSystem* ICS, int deviceID, Control* control - , unsigned int button, Control::ControlChangingDirection direction); + , unsigned int button, Control::ControlChangingDirection direction); }; diff --git a/extern/oics/ICSInputControlSystem_mouse.cpp b/extern/oics/ICSInputControlSystem_mouse.cpp index 9742d389c1..5decaf1eb8 100644 --- a/extern/oics/ICSInputControlSystem_mouse.cpp +++ b/extern/oics/ICSInputControlSystem_mouse.cpp @@ -224,11 +224,11 @@ namespace ICS } // mouse Listeners - void InputControlSystem::mouseMoved(const SFO::MouseMotionEvent& evt) + void InputControlSystem::mouseMoved(const SDL_MouseMotionEvent& evt) { if(mActive) { - if(!mDetectingBindingControl) + if(!mDetectingBindingControl) { if(mXmouseAxisBinded && evt.xrel) { @@ -289,7 +289,7 @@ namespace ICS mMouseAxisBindingInitialValues[0] += evt.xrel; mMouseAxisBindingInitialValues[1] += evt.yrel; - mMouseAxisBindingInitialValues[2] += evt.zrel; + // mMouseAxisBindingInitialValues[2] += evt.zrel; if( abs(mMouseAxisBindingInitialValues[0]) > ICS_MOUSE_BINDING_MARGIN ) { diff --git a/extern/osg-ffmpeg-videoplayer/CMakeLists.txt b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt new file mode 100644 index 0000000000..6009f69de5 --- /dev/null +++ b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt @@ -0,0 +1,17 @@ +set(OSG_FFMPEG_VIDEOPLAYER_LIBRARY "osg-ffmpeg-videoplayer") + +# Sources + +set(OSG_FFMPEG_VIDEOPLAYER_SOURCE_FILES + videoplayer.cpp + videostate.cpp + videodefs.hpp + audiodecoder.cpp + audiofactory.hpp +) + +include_directories(${FFMPEG_INCLUDE_DIRS}) +add_library(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} STATIC ${OSG_FFMPEG_VIDEOPLAYER_SOURCE_FILES}) +target_link_libraries(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} ${FFMPEG_LIBRARIES} ${Boost_THREAD_LIBRARY}) + +link_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/extern/ogre-ffmpeg-videoplayer/License.txt b/extern/osg-ffmpeg-videoplayer/License.txt similarity index 100% rename from extern/ogre-ffmpeg-videoplayer/License.txt rename to extern/osg-ffmpeg-videoplayer/License.txt diff --git a/extern/ogre-ffmpeg-videoplayer/audiodecoder.cpp b/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp similarity index 93% rename from extern/ogre-ffmpeg-videoplayer/audiodecoder.cpp rename to extern/osg-ffmpeg-videoplayer/audiodecoder.cpp index 77e6b4b6c0..f095d1617a 100644 --- a/extern/ogre-ffmpeg-videoplayer/audiodecoder.cpp +++ b/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp @@ -6,24 +6,12 @@ extern "C" #include - #ifdef HAVE_LIBSWRESAMPLE #include - #else - // FIXME: remove this section once libswresample is packaged for Debian - #include - #include - #define SwrContext AVAudioResampleContext - int swr_init(AVAudioResampleContext *avr); - void swr_free(AVAudioResampleContext **avr); - int swr_convert( AVAudioResampleContext *avr, uint8_t** output, int out_samples, const uint8_t** input, int in_samples); - AVAudioResampleContext * swr_alloc_set_opts( AVAudioResampleContext *avr, int64_t out_ch_layout, AVSampleFormat out_fmt, int out_rate, int64_t in_ch_layout, AVSampleFormat in_fmt, int in_rate, int o, void* l); - #endif #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1) #define av_frame_alloc avcodec_alloc_frame #endif - } #include "videostate.hpp" diff --git a/extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp b/extern/osg-ffmpeg-videoplayer/audiodecoder.hpp similarity index 97% rename from extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp rename to extern/osg-ffmpeg-videoplayer/audiodecoder.hpp index fe36ec39f1..a592b02d37 100644 --- a/extern/ogre-ffmpeg-videoplayer/audiodecoder.hpp +++ b/extern/osg-ffmpeg-videoplayer/audiodecoder.hpp @@ -1,9 +1,6 @@ #ifndef VIDEOPLAYER_AUDIODECODER_H #define VIDEOPLAYER_AUDIODECODER_H -#ifndef __STDC_CONSTANT_MACROS -#define __STDC_CONSTANT_MACROS -#endif #include #include diff --git a/extern/ogre-ffmpeg-videoplayer/audiofactory.hpp b/extern/osg-ffmpeg-videoplayer/audiofactory.hpp similarity index 100% rename from extern/ogre-ffmpeg-videoplayer/audiofactory.hpp rename to extern/osg-ffmpeg-videoplayer/audiofactory.hpp diff --git a/extern/ogre-ffmpeg-videoplayer/videodefs.hpp b/extern/osg-ffmpeg-videoplayer/videodefs.hpp similarity index 100% rename from extern/ogre-ffmpeg-videoplayer/videodefs.hpp rename to extern/osg-ffmpeg-videoplayer/videodefs.hpp diff --git a/extern/ogre-ffmpeg-videoplayer/videoplayer.cpp b/extern/osg-ffmpeg-videoplayer/videoplayer.cpp similarity index 73% rename from extern/ogre-ffmpeg-videoplayer/videoplayer.cpp rename to extern/osg-ffmpeg-videoplayer/videoplayer.cpp index c2daf35795..9ec8151300 100644 --- a/extern/ogre-ffmpeg-videoplayer/videoplayer.cpp +++ b/extern/osg-ffmpeg-videoplayer/videoplayer.cpp @@ -1,5 +1,9 @@ #include "videoplayer.hpp" +#include + +#include + #include "audiofactory.hpp" #include "videostate.hpp" @@ -23,7 +27,7 @@ void VideoPlayer::setAudioFactory(MovieAudioFactory *factory) mAudioFactory.reset(factory); } -void VideoPlayer::playVideo(const std::string &resourceName) +void VideoPlayer::playVideo(boost::shared_ptr inputstream, const std::string& name) { if(mState) close(); @@ -31,10 +35,10 @@ void VideoPlayer::playVideo(const std::string &resourceName) try { mState = new VideoState; mState->setAudioFactory(mAudioFactory.get()); - mState->init(resourceName); + mState->init(inputstream, name); // wait until we have the first picture - while (mState->video_st && mState->mTexture.isNull()) + while (mState->video_st && !mState->mTexture.get()) { if (!mState->update()) break; @@ -53,27 +57,26 @@ bool VideoPlayer::update () return false; } -std::string VideoPlayer::getTextureName() +osg::ref_ptr VideoPlayer::getVideoTexture() { - std::string name; - if (mState && !mState->mTexture.isNull()) - name = mState->mTexture->getName(); - return name; + if (mState) + return mState->mTexture; + return osg::ref_ptr(); } int VideoPlayer::getVideoWidth() { int width=0; - if (mState && !mState->mTexture.isNull()) - width = mState->mTexture->getWidth(); + if (mState && mState->mTexture.get() && mState->mTexture->getImage()) + width = mState->mTexture->getImage()->s(); return width; } int VideoPlayer::getVideoHeight() { int height=0; - if (mState && !mState->mTexture.isNull()) - height = mState->mTexture->getHeight(); + if (mState && mState->mTexture.get() && mState->mTexture->getImage()) + height = mState->mTexture->getImage()->t(); return height; } diff --git a/extern/ogre-ffmpeg-videoplayer/videoplayer.hpp b/extern/osg-ffmpeg-videoplayer/videoplayer.hpp similarity index 79% rename from extern/ogre-ffmpeg-videoplayer/videoplayer.hpp rename to extern/osg-ffmpeg-videoplayer/videoplayer.hpp index 2727ac6f08..c118ddb7f5 100644 --- a/extern/ogre-ffmpeg-videoplayer/videoplayer.hpp +++ b/extern/osg-ffmpeg-videoplayer/videoplayer.hpp @@ -1,9 +1,22 @@ #ifndef VIDEOPLAYER_H #define VIDEOPLAYER_H +#include + #include #include +#include + +#include + +#include + +namespace osg +{ + class Texture2D; +} + namespace Video { @@ -11,7 +24,7 @@ namespace Video class MovieAudioFactory; /** - * @brief Plays a video on an Ogre texture. + * @brief Plays a video on an osg texture. */ class VideoPlayer { @@ -30,7 +43,8 @@ namespace Video /// Play the given video. If a video is already playing, the old video is closed first. /// @note The video will be unpaused by default. Use the pause() and play() methods to control pausing. - void playVideo (const std::string& resourceName); + /// @param name A name for the video stream - only used for logging purposes. + void playVideo (boost::shared_ptr inputstream, const std::string& name); /// Get the current playback time position in the video, in seconds double getCurrentTime(); @@ -52,8 +66,9 @@ namespace Video /// Stop the currently playing video, if a video is playing. void close(); - /// Return the texture name of the currently playing video, or "" if no video is playing. - std::string getTextureName(); + /// Return the texture of the currently playing video, or a null pointer if no video is playing. + osg::ref_ptr getVideoTexture(); + /// Return the width of the currently playing video, or 0 if no video is playing. int getVideoWidth(); /// Return the height of the currently playing video, or 0 if no video is playing. diff --git a/extern/ogre-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp similarity index 83% rename from extern/ogre-ffmpeg-videoplayer/videostate.cpp rename to extern/osg-ffmpeg-videoplayer/videostate.cpp index 877906f6df..3c9279ea25 100644 --- a/extern/ogre-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -1,15 +1,8 @@ #include "videostate.hpp" -#ifndef __STDC_CONSTANT_MACROS -#define __STDC_CONSTANT_MACROS -#endif -#include +#include -// Has to be included *before* ffmpeg, due to a macro collision with ffmpeg (#define PixelFormat in avformat.h - grumble) -#include -#include -#include -#include +#include extern "C" { @@ -92,21 +85,14 @@ void VideoState::setAudioFactory(MovieAudioFactory *factory) void PacketQueue::put(AVPacket *pkt) { AVPacketList *pkt1; + if(pkt != &flush_pkt && !pkt->buf && av_dup_packet(pkt) < 0) + throw std::runtime_error("Failed to duplicate packet"); + pkt1 = (AVPacketList*)av_malloc(sizeof(AVPacketList)); if(!pkt1) throw std::bad_alloc(); pkt1->pkt = *pkt; pkt1->next = NULL; - if(pkt->data != flush_pkt.data && pkt1->pkt.destruct == NULL) - { - if(av_dup_packet(&pkt1->pkt) < 0) - { - av_free(pkt1); - throw std::runtime_error("Failed to duplicate packet"); - } - av_free_packet(pkt); - } - this->mutex.lock (); if(!last_pkt) @@ -174,12 +160,14 @@ void PacketQueue::clear() this->mutex.unlock (); } -int VideoState::OgreResource_Read(void *user_data, uint8_t *buf, int buf_size) +int VideoState::istream_read(void *user_data, uint8_t *buf, int buf_size) { - Ogre::DataStreamPtr stream = static_cast(user_data)->stream; try { - return stream->read(buf, buf_size); + std::istream& stream = *static_cast(user_data)->stream; + stream.clear(); + stream.read((char*)buf, buf_size); + return stream.gcount(); } catch (std::exception& ) { @@ -187,57 +175,59 @@ int VideoState::OgreResource_Read(void *user_data, uint8_t *buf, int buf_size) } } -int VideoState::OgreResource_Write(void *user_data, uint8_t *buf, int buf_size) +int VideoState::istream_write(void *, uint8_t *, int) { - Ogre::DataStreamPtr stream = static_cast(user_data)->stream; - try - { - return stream->write(buf, buf_size); - } - catch (std::exception& ) - { - return 0; - } + throw std::runtime_error("can't write to read-only stream"); } -int64_t VideoState::OgreResource_Seek(void *user_data, int64_t offset, int whence) +int64_t VideoState::istream_seek(void *user_data, int64_t offset, int whence) { - Ogre::DataStreamPtr stream = static_cast(user_data)->stream; + std::istream& stream = *static_cast(user_data)->stream; whence &= ~AVSEEK_FORCE; + + stream.clear(); + if(whence == AVSEEK_SIZE) - return stream->size(); + { + size_t prev = stream.tellg(); + stream.seekg(0, std::ios_base::end); + size_t size = stream.tellg(); + stream.seekg(prev, std::ios_base::beg); + return size; + } + if(whence == SEEK_SET) - stream->seek(static_cast(offset)); + stream.seekg(offset, std::ios_base::beg); else if(whence == SEEK_CUR) - stream->seek(static_cast(stream->tell()+offset)); + stream.seekg(offset, std::ios_base::cur); else if(whence == SEEK_END) - stream->seek(static_cast(stream->size() + offset)); + stream.seekg(offset, std::ios_base::end); else return -1; - return stream->tell(); + return stream.tellg(); } void VideoState::video_display(VideoPicture *vp) { if((*this->video_st)->codec->width != 0 && (*this->video_st)->codec->height != 0) { - if (mTexture.isNull()) + if (!mTexture.get()) { - static int i = 0; - mTexture = Ogre::TextureManager::getSingleton().createManual( - "ffmpeg/VideoTexture" + Ogre::StringConverter::toString(++i), - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, - (*this->video_st)->codec->width, (*this->video_st)->codec->height, - 0, - Ogre::PF_BYTE_RGBA, - Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); + mTexture = new osg::Texture2D; + mTexture->setDataVariance(osg::Object::DYNAMIC); + mTexture->setResizeNonPowerOfTwoHint(false); + mTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + mTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); } - Ogre::PixelBox pb((*this->video_st)->codec->width, (*this->video_st)->codec->height, 1, Ogre::PF_BYTE_RGBA, &vp->data[0]); - Ogre::HardwarePixelBufferSharedPtr buffer = mTexture->getBuffer(); - buffer->blitFromMemory(pb); + + osg::ref_ptr image = new osg::Image; + + image->setImage((*this->video_st)->codec->width, (*this->video_st)->codec->height, + 1, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, &vp->data[0], osg::Image::NO_DELETE); + + mTexture->setImage(image); } } @@ -252,7 +242,7 @@ void VideoState::video_refresh() VideoPicture* vp = &this->pictq[this->pictq_rindex]; this->video_display(vp); - this->pictq_rindex = (pictq_rindex+1) % VIDEO_PICTURE_QUEUE_SIZE; + this->pictq_rindex = (pictq_rindex+1) % VIDEO_PICTURE_ARRAY_SIZE; this->frame_last_pts = vp->pts; this->pictq_size--; this->pictq_cond.notify_one(); @@ -269,12 +259,12 @@ void VideoState::video_refresh() for (; ipictq_size-1; ++i) { if (this->pictq[pictq_rindex].pts + threshold <= this->get_master_clock()) - this->pictq_rindex = (this->pictq_rindex+1) % VIDEO_PICTURE_QUEUE_SIZE; // not enough time to show this picture + this->pictq_rindex = (this->pictq_rindex+1) % VIDEO_PICTURE_ARRAY_SIZE; // not enough time to show this picture else break; } - assert (this->pictq_rindex < VIDEO_PICTURE_QUEUE_SIZE); + assert (this->pictq_rindex < VIDEO_PICTURE_ARRAY_SIZE); VideoPicture* vp = &this->pictq[this->pictq_rindex]; this->video_display(vp); @@ -284,7 +274,7 @@ void VideoState::video_refresh() this->pictq_size -= i; // update queue for next picture this->pictq_size--; - this->pictq_rindex = (this->pictq_rindex+1) % VIDEO_PICTURE_QUEUE_SIZE; + this->pictq_rindex = (this->pictq_rindex+1) % VIDEO_PICTURE_ARRAY_SIZE; this->pictq_cond.notify_one(); } } @@ -308,7 +298,7 @@ int VideoState::queue_picture(AVFrame *pFrame, double pts) // windex is set to 0 initially vp = &this->pictq[this->pictq_windex]; - // Convert the image into RGBA format for Ogre + // Convert the image into RGBA format // TODO: we could do this in a pixel shader instead, if the source format // matches a commonly used format (ie YUV420P) if(this->sws_context == NULL) @@ -316,7 +306,7 @@ int VideoState::queue_picture(AVFrame *pFrame, double pts) int w = (*this->video_st)->codec->width; int h = (*this->video_st)->codec->height; this->sws_context = sws_getContext(w, h, (*this->video_st)->codec->pix_fmt, - w, h, PIX_FMT_RGBA, SWS_BICUBIC, + w, h, AV_PIX_FMT_RGBA, SWS_BICUBIC, NULL, NULL, NULL); if(this->sws_context == NULL) throw std::runtime_error("Cannot initialize the conversion context!\n"); @@ -330,7 +320,7 @@ int VideoState::queue_picture(AVFrame *pFrame, double pts) 0, (*this->video_st)->codec->height, &dst, this->rgbaFrame->linesize); // now we inform our display thread that we have a pic ready - this->pictq_windex = (this->pictq_windex+1) % VIDEO_PICTURE_QUEUE_SIZE; + this->pictq_windex = (this->pictq_windex+1) % VIDEO_PICTURE_ARRAY_SIZE; this->pictq_size++; this->pictq_mutex.unlock(); @@ -357,24 +347,28 @@ double VideoState::synchronize_video(AVFrame *src_frame, double pts) return pts; } - +static void our_free_buffer(void *opaque, uint8_t *data); /* These are called whenever we allocate a frame * buffer. We use this to store the global_pts in * a frame at the time it is allocated. */ static int64_t global_video_pkt_pts = AV_NOPTS_VALUE; -static int our_get_buffer(struct AVCodecContext *c, AVFrame *pic) +static int our_get_buffer(struct AVCodecContext *c, AVFrame *pic, int flags) { - int ret = avcodec_default_get_buffer(c, pic); + AVBufferRef *ref; + int ret = avcodec_default_get_buffer2(c, pic, flags); int64_t *pts = (int64_t*)av_malloc(sizeof(int64_t)); *pts = global_video_pkt_pts; pic->opaque = pts; + ref = av_buffer_create((uint8_t *)pic->opaque, sizeof(int64_t), our_free_buffer, pic->buf[0], flags); + pic->buf[0] = ref; return ret; } -static void our_release_buffer(struct AVCodecContext *c, AVFrame *pic) +static void our_free_buffer(void *opaque, uint8_t *data) { - if(pic) av_freep(&pic->opaque); - avcodec_default_release_buffer(c, pic); + AVBufferRef *ref = (AVBufferRef *)opaque; + av_buffer_unref(&ref); + av_free(data); } @@ -387,7 +381,7 @@ void VideoState::video_thread_loop(VideoState *self) pFrame = av_frame_alloc(); self->rgbaFrame = av_frame_alloc(); - avpicture_alloc((AVPicture*)self->rgbaFrame, PIX_FMT_RGBA, (*self->video_st)->codec->width, (*self->video_st)->codec->height); + avpicture_alloc((AVPicture*)self->rgbaFrame, AV_PIX_FMT_RGBA, (*self->video_st)->codec->width, (*self->video_st)->codec->height); while(self->videoq.get(packet, self) >= 0) { @@ -533,12 +527,9 @@ void VideoState::decode_thread_loop(VideoState *self) av_free_packet(packet); } } - catch(std::runtime_error& e) { + catch(std::exception& e) { std::cerr << "An error occured playing the video: " << e.what () << std::endl; } - catch(Ogre::Exception& e) { - std::cerr << "An error occured playing the video: " << e.getFullDescription () << std::endl; - } self->mQuit = true; } @@ -595,8 +586,7 @@ int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) case AVMEDIA_TYPE_VIDEO: this->video_st = pFormatCtx->streams + stream_index; - codecCtx->get_buffer = our_get_buffer; - codecCtx->release_buffer = our_release_buffer; + codecCtx->get_buffer2 = our_get_buffer; this->video_thread = boost::thread(video_thread_loop, this); break; @@ -607,7 +597,7 @@ int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) return 0; } -void VideoState::init(const std::string& resourceName) +void VideoState::init(boost::shared_ptr inputstream, const std::string &name) { int video_index = -1; int audio_index = -1; @@ -616,11 +606,11 @@ void VideoState::init(const std::string& resourceName) this->av_sync_type = AV_SYNC_DEFAULT; this->mQuit = false; - this->stream = Ogre::ResourceGroupManager::getSingleton().openResource(resourceName); - if(this->stream.isNull()) + this->stream = inputstream; + if(!this->stream.get()) throw std::runtime_error("Failed to open video resource"); - AVIOContext *ioCtx = avio_alloc_context(NULL, 0, 0, this, OgreResource_Read, OgreResource_Write, OgreResource_Seek); + AVIOContext *ioCtx = avio_alloc_context(NULL, 0, 0, this, istream_read, istream_write, istream_seek); if(!ioCtx) throw std::runtime_error("Failed to allocate AVIOContext"); this->format_ctx = avformat_alloc_context(); @@ -634,7 +624,7 @@ void VideoState::init(const std::string& resourceName) /// /// https://trac.ffmpeg.org/ticket/1357 /// - if(!this->format_ctx || avformat_open_input(&this->format_ctx, resourceName.c_str(), NULL, NULL)) + if(!this->format_ctx || avformat_open_input(&this->format_ctx, name.c_str(), NULL, NULL)) { if (this->format_ctx != NULL) { @@ -658,7 +648,7 @@ void VideoState::init(const std::string& resourceName) throw std::runtime_error("Failed to retrieve stream information"); // Dump information about file onto standard error - av_dump_format(this->format_ctx, 0, resourceName.c_str(), 0); + av_dump_format(this->format_ctx, 0, name.c_str(), 0); for(i = 0;i < this->format_ctx->nb_streams;i++) { @@ -726,10 +716,11 @@ void VideoState::deinit() avformat_close_input(&this->format_ctx); } - if (!mTexture.isNull()) + if (mTexture) { - Ogre::TextureManager::getSingleton().remove(mTexture->getName()); - mTexture.setNull(); + // reset Image separately, it's pointing to *this and there might still be outside references to mTexture + mTexture->setImage(NULL); + mTexture = NULL; } } diff --git a/extern/ogre-ffmpeg-videoplayer/videostate.hpp b/extern/osg-ffmpeg-videoplayer/videostate.hpp similarity index 80% rename from extern/ogre-ffmpeg-videoplayer/videostate.hpp rename to extern/osg-ffmpeg-videoplayer/videostate.hpp index cdeb2d0e39..72a2aab186 100644 --- a/extern/ogre-ffmpeg-videoplayer/videostate.hpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.hpp @@ -1,13 +1,21 @@ #ifndef VIDEOPLAYER_VIDEOSTATE_H #define VIDEOPLAYER_VIDEOSTATE_H +#include + #include -#include +#include +namespace osg +{ + class Texture2D; +} #include "videodefs.hpp" #define VIDEO_PICTURE_QUEUE_SIZE 50 +// allocate one extra to make sure we do not overwrite the osg::Image currently set on the texture +#define VIDEO_PICTURE_ARRAY_SIZE (VIDEO_PICTURE_QUEUE_SIZE+1) extern "C" { @@ -78,7 +86,7 @@ struct VideoState { void setAudioFactory(MovieAudioFactory* factory); - void init(const std::string& resourceName); + void init(boost::shared_ptr inputstream, const std::string& name); void deinit(); void setPaused(bool isPaused); @@ -104,18 +112,18 @@ struct VideoState { double get_external_clock(); double get_master_clock(); - static int OgreResource_Read(void *user_data, uint8_t *buf, int buf_size); - static int OgreResource_Write(void *user_data, uint8_t *buf, int buf_size); - static int64_t OgreResource_Seek(void *user_data, int64_t offset, int whence); + static int istream_read(void *user_data, uint8_t *buf, int buf_size); + static int istream_write(void *user_data, uint8_t *buf, int buf_size); + static int64_t istream_seek(void *user_data, int64_t offset, int whence); - Ogre::TexturePtr mTexture; + osg::ref_ptr mTexture; MovieAudioFactory* mAudioFactory; boost::shared_ptr mAudioDecoder; ExternalClock mExternalClock; - Ogre::DataStreamPtr stream; + boost::shared_ptr stream; AVFormatContext* format_ctx; int av_sync_type; @@ -130,7 +138,7 @@ struct VideoState { double video_clock; /// -#include - -#include -#include - -namespace SFO -{ -class CursorManager -{ -public: - virtual ~CursorManager(){} - - /// \brief Tell the manager that the cursor has changed, giving the - /// name of the cursor we changed to ("arrow", "ibeam", etc) - /// \return Whether the manager is interested in more information about the cursor - virtual bool cursorChanged(const std::string &name) = 0; - - /// \brief Follow up a cursorChanged() call with enough info to create an cursor. - virtual void receiveCursorInfo(const std::string &name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) = 0; - - /// \brief sets whether to actively manage cursors or not - virtual void setEnabled(bool enabled) = 0; -}; -} - -#endif diff --git a/extern/sdl4ogre/imagerotate.cpp b/extern/sdl4ogre/imagerotate.cpp deleted file mode 100644 index b825943fc1..0000000000 --- a/extern/sdl4ogre/imagerotate.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "imagerotate.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace Ogre; - -namespace SFO -{ - -void ImageRotate::rotate(const std::string& sourceImage, const std::string& destImage, const float angle) -{ - Root* root = Ogre::Root::getSingletonPtr(); - - std::string destImageRot = std::string(destImage) + std::string("_rot"); - - SceneManager* sceneMgr = root->createSceneManager(ST_GENERIC); - Camera* camera = sceneMgr->createCamera("ImageRotateCamera"); - - MaterialPtr material = MaterialManager::getSingleton().create("ImageRotateMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - material->getTechnique(0)->getPass(0)->setLightingEnabled(false); - material->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); - TextureUnitState* tus = material->getTechnique(0)->getPass(0)->createTextureUnitState(sourceImage); - Degree deg(angle); - tus->setTextureRotate(Radian(deg.valueRadians())); - tus->setTextureAddressingMode(TextureUnitState::TAM_BORDER); - tus->setTextureBorderColour(ColourValue(0, 0, 0, 0)); - - Rectangle2D* rect = new Rectangle2D(true); - rect->setCorners(-1.0, 1.0, 1.0, -1.0); - rect->setMaterial("ImageRotateMaterial"); - // Render the background before everything else - rect->setRenderQueueGroup(RENDER_QUEUE_BACKGROUND); - - // Use infinite AAB to always stay visible - AxisAlignedBox aabInf; - aabInf.setInfinite(); - rect->setBoundingBox(aabInf); - - // Attach background to the scene - SceneNode* node = sceneMgr->getRootSceneNode()->createChildSceneNode(); - node->attachObject(rect); - - // retrieve image width and height - TexturePtr sourceTexture = TextureManager::getSingleton().getByName(sourceImage); - unsigned int width = sourceTexture->getWidth(); - unsigned int height = sourceTexture->getHeight(); - - TexturePtr destTextureRot = TextureManager::getSingleton().createManual( - destImageRot, - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - TEX_TYPE_2D, - width, height, - 0, - PF_A8B8G8R8, - TU_RENDERTARGET); - - RenderTarget* rtt = destTextureRot->getBuffer()->getRenderTarget(); - rtt->setAutoUpdated(false); - Viewport* vp = rtt->addViewport(camera); - vp->setOverlaysEnabled(false); - vp->setShadowsEnabled(false); - vp->setBackgroundColour(ColourValue(0,0,0,0)); - - rtt->update(); - - //copy the rotated image to a static texture - TexturePtr destTexture = TextureManager::getSingleton().createManual( - destImage, - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - TEX_TYPE_2D, - width, height, - 0, - PF_A8B8G8R8, - Ogre::TU_STATIC); - - destTexture->getBuffer()->blit(destTextureRot->getBuffer()); - - // remove all the junk we've created - TextureManager::getSingleton().remove(destImageRot); - MaterialManager::getSingleton().remove("ImageRotateMaterial"); - root->destroySceneManager(sceneMgr); - delete rect; -} - -} diff --git a/extern/sdl4ogre/imagerotate.hpp b/extern/sdl4ogre/imagerotate.hpp deleted file mode 100644 index 7135a571ab..0000000000 --- a/extern/sdl4ogre/imagerotate.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef OENGINE_OGRE_IMAGEROTATE_HPP -#define OENGINE_OGRE_IMAGEROTATE_HPP - -#include - - -namespace SFO -{ - - /// Rotate an image by certain degrees and save as file, uses the GPU - /// Make sure Ogre Root is initialised before calling - class ImageRotate - { - public: - /** - * @param source image (file name - has to exist in an resource group) - * @param name of the destination texture to save to (in memory) - * @param angle in degrees to turn - */ - static void rotate(const std::string& sourceImage, const std::string& destImage, const float angle); - }; - -} - -#endif diff --git a/extern/sdl4ogre/osx_utils.h b/extern/sdl4ogre/osx_utils.h deleted file mode 100644 index 48149827ab..0000000000 --- a/extern/sdl4ogre/osx_utils.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef SDL4OGRE_OSX_UTILS_H -#define SDL4OGRE_OSX_UTILS_H - -#include - -namespace SFO { - -extern unsigned long WindowContentViewHandle(SDL_SysWMinfo &info); - -} - -#endif // SDL4OGRE_OSX_UTILS_H diff --git a/extern/sdl4ogre/osx_utils.mm b/extern/sdl4ogre/osx_utils.mm deleted file mode 100644 index 4069959cbd..0000000000 --- a/extern/sdl4ogre/osx_utils.mm +++ /dev/null @@ -1,15 +0,0 @@ -#include "osx_utils.h" -#import - - -namespace SFO { - -unsigned long WindowContentViewHandle(SDL_SysWMinfo &info) -{ - NSWindow *window = info.info.cocoa.window; - NSView *view = [window contentView]; - - return (unsigned long)view; -} - -} diff --git a/extern/sdl4ogre/sdlcursormanager.cpp b/extern/sdl4ogre/sdlcursormanager.cpp deleted file mode 100644 index 61d9c32dd6..0000000000 --- a/extern/sdl4ogre/sdlcursormanager.cpp +++ /dev/null @@ -1,161 +0,0 @@ -#include "sdlcursormanager.hpp" - -#include -#include -#include - -#include -#include - -#include "imagerotate.hpp" - -namespace SFO -{ - - SDLCursorManager::SDLCursorManager() : - mEnabled(false), - mInitialized(false) - { - } - - SDLCursorManager::~SDLCursorManager() - { - CursorMap::const_iterator curs_iter = mCursorMap.begin(); - - while(curs_iter != mCursorMap.end()) - { - SDL_FreeCursor(curs_iter->second); - ++curs_iter; - } - - mCursorMap.clear(); - } - - void SDLCursorManager::setEnabled(bool enabled) - { - if(mInitialized && enabled == mEnabled) - return; - - mInitialized = true; - mEnabled = enabled; - - //turn on hardware cursors - if(enabled) - { - _setGUICursor(mCurrentCursor); - } - //turn off hardware cursors - else - { - SDL_ShowCursor(SDL_FALSE); - } - } - - bool SDLCursorManager::cursorChanged(const std::string &name) - { - mCurrentCursor = name; - - CursorMap::const_iterator curs_iter = mCursorMap.find(name); - - //we have this cursor - if(curs_iter != mCursorMap.end()) - { - _setGUICursor(name); - - return false; - } - else - { - //they should get back to us with more info - return true; - } - } - - void SDLCursorManager::_setGUICursor(const std::string &name) - { - SDL_SetCursor(mCursorMap.find(name)->second); - } - - void SDLCursorManager::receiveCursorInfo(const std::string& name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) - { - _createCursorFromResource(name, rotDegrees, tex, size_x, size_y, hotspot_x, hotspot_y); - } - - /// \brief creates an SDL cursor from an Ogre texture - void SDLCursorManager::_createCursorFromResource(const std::string& name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) - { - if (mCursorMap.find(name) != mCursorMap.end()) - return; - - std::string tempName = tex->getName() + "_rotated"; - - // we use a render target to uncompress the DDS texture - // just blitting doesn't seem to work on D3D9 - ImageRotate::rotate(tex->getName(), tempName, static_cast(-rotDegrees)); - - Ogre::TexturePtr resultTexture = Ogre::TextureManager::getSingleton().getByName(tempName); - - // now blit to memory - Ogre::Image destImage; - resultTexture->convertToImage(destImage); - - SDL_Surface* surf = SDL_CreateRGBSurface(0,size_x,size_y,32,0xFF000000,0x00FF0000,0x0000FF00,0x000000FF); - - - //copy the Ogre texture to an SDL surface - for(size_t x = 0; x < size_x; ++x) - { - for(size_t y = 0; y < size_y; ++y) - { - Ogre::ColourValue clr = destImage.getColourAt(x, y, 0); - - //set the pixel on the SDL surface to the same value as the Ogre texture's - _putPixel(surf, x, y, SDL_MapRGBA(surf->format, static_cast(clr.r * 255), - static_cast(clr.g * 255), static_cast(clr.b * 255), static_cast(clr.a * 255))); - } - } - - //set the cursor and store it for later - SDL_Cursor* curs = SDL_CreateColorCursor(surf, hotspot_x, hotspot_y); - mCursorMap.insert(CursorMap::value_type(std::string(name), curs)); - - //clean up - SDL_FreeSurface(surf); - Ogre::TextureManager::getSingleton().remove(tempName); - - _setGUICursor(name); - } - - void SDLCursorManager::_putPixel(SDL_Surface *surface, int x, int y, Uint32 pixel) - { - int bpp = surface->format->BytesPerPixel; - /* Here p is the address to the pixel we want to set */ - Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; - - switch(bpp) { - case 1: - *p = pixel; - break; - - case 2: - *(Uint16 *)p = pixel; - break; - - case 3: - if(SDL_BYTEORDER == SDL_BIG_ENDIAN) { - p[0] = (pixel >> 16) & 0xff; - p[1] = (pixel >> 8) & 0xff; - p[2] = pixel & 0xff; - } else { - p[0] = pixel & 0xff; - p[1] = (pixel >> 8) & 0xff; - p[2] = (pixel >> 16) & 0xff; - } - break; - - case 4: - *(Uint32 *)p = pixel; - break; - } - } -} diff --git a/extern/sdl4ogre/sdlcursormanager.hpp b/extern/sdl4ogre/sdlcursormanager.hpp deleted file mode 100644 index 58324fc01c..0000000000 --- a/extern/sdl4ogre/sdlcursormanager.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef SDL4OGRE_CURSORMANAGER_H -#define SDL4OGRE_CURSORMANAGER_H - -#include "cursormanager.hpp" -#include - -struct SDL_Cursor; -struct SDL_Surface; - -namespace SFO -{ - class SDLCursorManager : - public CursorManager - { - public: - SDLCursorManager(); - virtual ~SDLCursorManager(); - - virtual void setEnabled(bool enabled); - - virtual bool cursorChanged(const std::string &name); - virtual void receiveCursorInfo(const std::string &name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); - - private: - void _createCursorFromResource(const std::string &name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); - void _putPixel(SDL_Surface *surface, int x, int y, Uint32 pixel); - - void _setGUICursor(const std::string& name); - - typedef std::map CursorMap; - CursorMap mCursorMap; - - std::string mCurrentCursor; - bool mEnabled; - bool mInitialized; - }; -} - -#endif diff --git a/extern/sdl4ogre/sdlwindowhelper.cpp b/extern/sdl4ogre/sdlwindowhelper.cpp deleted file mode 100644 index 6690e3caba..0000000000 --- a/extern/sdl4ogre/sdlwindowhelper.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "sdlwindowhelper.hpp" - -#include -#include -#include - -#include -#include -#include - -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE -#include "osx_utils.h" -#endif - -namespace SFO -{ - -SDLWindowHelper::SDLWindowHelper (SDL_Window* window, int w, int h, - const std::string& title, bool fullscreen, Ogre::NameValuePairList params) - : mSDLWindow(window) -{ - //get the native whnd - struct SDL_SysWMinfo wmInfo; - SDL_VERSION(&wmInfo.version); - - if (SDL_GetWindowWMInfo(mSDLWindow, &wmInfo) == SDL_FALSE) - throw std::runtime_error("Couldn't get WM Info!"); - - Ogre::String winHandle; - - switch (wmInfo.subsystem) - { -#ifdef _WIN32 - case SDL_SYSWM_WINDOWS: - // Windows code - winHandle = Ogre::StringConverter::toString((uintptr_t)wmInfo.info.win.window); - break; -#elif __MACOSX__ - case SDL_SYSWM_COCOA: - //required to make OGRE play nice with our window - params.insert(std::make_pair("macAPI", "cocoa")); - params.insert(std::make_pair("macAPICocoaUseNSView", "true")); - winHandle = Ogre::StringConverter::toString(WindowContentViewHandle(wmInfo)); - break; -#elif ANDROID - case SDL_SYSWM_ANDROID: - winHandle = Ogre::StringConverter::toString((unsigned long)wmInfo.info.android.window); - break; - #else - case SDL_SYSWM_X11: - winHandle = Ogre::StringConverter::toString((unsigned long)wmInfo.info.x11.window); - break; -#endif - default: - throw std::runtime_error("Unexpected WM!"); - break; - } - - /// \todo externalWindowHandle is deprecated according to the source code. Figure out a way to get parentWindowHandle - /// to work properly. On Linux/X11 it causes an occasional GLXBadDrawable error. - -#ifdef ANDROID - SDL_GLContext context= SDL_GL_CreateContext(window); - params.insert(std::make_pair("currentGLContext","True")); -#endif - params.insert(std::make_pair("externalWindowHandle", winHandle)); - - mWindow = Ogre::Root::getSingleton().createRenderWindow(title, w, h, fullscreen, ¶ms); -} - -void SDLWindowHelper::setWindowIcon(const std::string &name) -{ - Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().load(name, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); - if (texture.isNull()) - { - std::stringstream error; - error << "Window icon not found: " << name; - throw std::runtime_error(error.str()); - } - Ogre::Image image; - texture->convertToImage(image); - - SDL_Surface* surface = SDL_CreateRGBSurface(0,texture->getWidth(),texture->getHeight(),32,0xFF000000,0x00FF0000,0x0000FF00,0x000000FF); - - //copy the Ogre texture to an SDL surface - for(size_t x = 0; x < texture->getWidth(); ++x) - { - for(size_t y = 0; y < texture->getHeight(); ++y) - { - Ogre::ColourValue clr = image.getColourAt(x, y, 0); - - //set the pixel on the SDL surface to the same value as the Ogre texture's - int bpp = surface->format->BytesPerPixel; - /* Here p is the address to the pixel we want to set */ - Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; - Uint32 pixel = SDL_MapRGBA(surface->format, static_cast(clr.r * 255), - static_cast(clr.g * 255), static_cast(clr.b * 255), static_cast(clr.a * 255)); - switch(bpp) { - case 1: - *p = pixel; - break; - - case 2: - *(Uint16 *)p = pixel; - break; - - case 3: - if(SDL_BYTEORDER == SDL_BIG_ENDIAN) { - p[0] = (pixel >> 16) & 0xff; - p[1] = (pixel >> 8) & 0xff; - p[2] = pixel & 0xff; - } else { - p[0] = pixel & 0xff; - p[1] = (pixel >> 8) & 0xff; - p[2] = (pixel >> 16) & 0xff; - } - break; - - case 4: - *(Uint32 *)p = pixel; - break; - } - } - } - - SDL_SetWindowIcon(mSDLWindow, surface); - SDL_FreeSurface(surface); -} - -} diff --git a/extern/sdl4ogre/sdlwindowhelper.hpp b/extern/sdl4ogre/sdlwindowhelper.hpp deleted file mode 100644 index 834716b223..0000000000 --- a/extern/sdl4ogre/sdlwindowhelper.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef SDL4OGRE_SDLWINDOWHELPER_H -#define SDL4OGRE_SDLWINDOWHELPER_H - -#include - -namespace Ogre -{ - class RenderWindow; -} -struct SDL_Window; - -namespace SFO -{ - - /// @brief Creates an Ogre window from an SDL window and allows setting an Ogre texture as window icon - class SDLWindowHelper - { - public: - SDLWindowHelper (SDL_Window* window, int w, int h, const std::string& title, bool fullscreen, Ogre::NameValuePairList params); - void setWindowIcon(const std::string& name); - Ogre::RenderWindow* getWindow() { return mWindow; } - - private: - Ogre::RenderWindow* mWindow; - SDL_Window* mSDLWindow; - }; - -} - - -#endif diff --git a/extern/shiny/CMakeLists.txt b/extern/shiny/CMakeLists.txt deleted file mode 100644 index daf2a9df8d..0000000000 --- a/extern/shiny/CMakeLists.txt +++ /dev/null @@ -1,54 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -# This is NOT intended as a stand-alone build system! Instead, you should include this from the main CMakeLists of your project. -# Make sure to link against Ogre and boost::filesystem. - -option(SHINY_BUILD_OGRE_PLATFORM "build the Ogre platform" ON) - -set(SHINY_LIBRARY "shiny") -set(SHINY_OGREPLATFORM_LIBRARY "shiny.OgrePlatform") - -# Sources -set(SOURCE_FILES - Main/Factory.cpp - Main/MaterialInstance.cpp - Main/MaterialInstancePass.cpp - Main/MaterialInstanceTextureUnit.cpp - Main/Platform.cpp - Main/Preprocessor.cpp - Main/PropertyBase.cpp - Main/ScriptLoader.cpp - Main/ShaderInstance.cpp - Main/ShaderSet.cpp -) - -set(OGRE_PLATFORM_SOURCE_FILES - Platforms/Ogre/OgreGpuProgram.cpp - Platforms/Ogre/OgreMaterial.cpp - Platforms/Ogre/OgreMaterialSerializer.cpp - Platforms/Ogre/OgrePass.cpp - Platforms/Ogre/OgrePlatform.cpp - Platforms/Ogre/OgreTextureUnitState.cpp -) - -file(GLOB OGRE_PLATFORM_SOURCE_FILES Platforms/Ogre/*.cpp) - -add_library(${SHINY_LIBRARY} STATIC ${SOURCE_FILES}) - -set(SHINY_LIBRARIES ${SHINY_LIBRARY}) - -if (SHINY_BUILD_OGRE_PLATFORM) - add_library(${SHINY_OGREPLATFORM_LIBRARY} STATIC ${OGRE_PLATFORM_SOURCE_FILES}) - set(SHINY_LIBRARIES ${SHINY_LIBRARIES} ${SHINY_OGREPLATFORM_LIBRARY}) -endif() - -set(SHINY_LIBRARY ${SHINY_LIBRARY} PARENT_SCOPE) - -if (DEFINED SHINY_BUILD_MATERIAL_EDITOR) - add_subdirectory(Editor) - - set(SHINY_BUILD_EDITOR_FLAG ${SHINY_BUILD_EDITOR_FLAG} PARENT_SCOPE) -endif() - -link_directories(${CMAKE_CURRENT_BINARY_DIR}) -set(SHINY_LIBRARIES ${SHINY_LIBRARIES} PARENT_SCOPE) diff --git a/extern/shiny/Docs/Configurations.dox b/extern/shiny/Docs/Configurations.dox deleted file mode 100644 index 570e81d4af..0000000000 --- a/extern/shiny/Docs/Configurations.dox +++ /dev/null @@ -1,32 +0,0 @@ -/*! - - \page configurations Configurations - - A common task in shader development is to provide a different set of simpler shaders for all your materials. Some examples: - - When rendering cubic or planar reflection maps in real-time, you will want to disable shadows. - - For an in-game minimap render target, you don't want to have fog. - - For this task, the library provides a \a Configuration concept. - - A Configuration is a set of properties that can override global settings, as long as this Configuration is active. - - Here's an example. Say you have a global setting with the name 'shadows' that controls if your materials receive shadows. - - Now, lets create a configuration for our reflection render targets that disables shadows for all materials. Paste the following in a new file with the extension '.configuration': - - \code - configuration reflection_targets - { - shadows false - } - \endcode - - \note You may also create configurations using sh::Factory::createConfiguration. - - The active Configuration is controlled by the active material scheme in Ogre. So, in order to use the configuration "reflection_targets" for your reflection renders, simply call - \code - viewport->setMaterialScheme ("reflection_targets"); - \endcode - on the Ogre viewport of your reflection render! - -*/ diff --git a/extern/shiny/Docs/Doxyfile b/extern/shiny/Docs/Doxyfile deleted file mode 100644 index 3564c45f67..0000000000 --- a/extern/shiny/Docs/Doxyfile +++ /dev/null @@ -1,1826 +0,0 @@ -# Doxyfile 1.8.1.1 - -# 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 sequence of words) that should -# identify the project. Note that if you do not use Doxywizard you need -# to put quotes around the project name if it contains spaces. - -PROJECT_NAME = shiny - -# 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 = - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer -# a quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = - -# With the PROJECT_LOGO tag one can specify an logo or icon that is -# included in the documentation. The maximum height of the logo should not -# exceed 55 pixels and the maximum width should not exceed 200 pixels. -# Doxygen will copy the logo to the output directory. - -PROJECT_LOGO = - -# 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 = /home/scrawl/sh_doxy/generated - -# 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, Esperanto, 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-Cyrillic, Slovak, -# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. - -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 = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the - -# 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 if your file system -# 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 = - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding -# "class=itcl::class" will allow you to use the command class in the -# itcl::class meaning. - -TCL_SUBST = - -# 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, CSharp, 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. Note that for custom extensions -# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. - -EXTENSION_MAPPING = - -# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all -# comments according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you -# can mix doxygen, HTML, and XML commands with Markdown formatting. -# Disable only in case of backward compatibilities issues. - -MARKDOWN_SUPPORT = YES - -# 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 makes the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. - -BUILTIN_STL_SUPPORT = NO - -# 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 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 the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and -# unions are shown inside the group in which they are included (e.g. using -# @ingroup) instead of on a separate page (for HTML and Man pages) or -# section (for LaTeX and RTF). - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and -# unions with only public data fields will be shown inline in the documentation -# of the scope in which they are defined (i.e. file, namespace, or group -# documentation), provided this scope is documented. If set to NO (the default), -# structs, classes, and unions are shown on a separate page (for HTML and Man -# pages) or section (for LaTeX and RTF). - -INLINE_SIMPLE_STRUCTS = NO - -# 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 penalty. -# 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 roughly 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 - -# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be -# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given -# their name and scope. Since this can be an expensive process and often the -# same symbol appear multiple times in the code, doxygen keeps a cache of -# pre-resolved symbols. If the cache is too small doxygen will become slower. -# If the cache is too large, memory is wasted. The cache size is given by this -# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols. - -LOOKUP_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_PACKAGE tag is set to YES all members with package or internal -# scope will be included in the documentation. - -EXTRACT_PACKAGE = 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 namespaces 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 = NO - -# 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 FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen -# will list include files with double quotes in the documentation -# rather than with sharp brackets. - -FORCE_LOCAL_INCLUDES = NO - -# 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_MEMBERS_CTORS_1ST tag is set to YES then doxygen -# will sort the (brief and detailed) documentation of class members so that -# constructors and destructors are listed first. If set to NO (the default) -# the constructors will appear in the respective orders defined by -# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. -# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO -# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. - -SORT_MEMBERS_CTORS_1ST = 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 - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to -# do proper type resolution of all parameters of a function it will reject a -# match between the prototype and the implementation of a member function even -# if there is only one candidate or it is obvious which candidate to choose -# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen -# will still accept a match between prototype and implementation in such cases. - -STRICT_PROTO_MATCHING = 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 macro 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 macros 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 - -# 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. To 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 = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files -# containing the references data. This must be a list of .bib files. The -# .bib extension is automatically appended if omitted. Using this command -# requires the bibtex tool to be installed. See also -# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style -# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this -# feature you need bibtex and perl available in the search path. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# 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 - -# The WARN_NO_PARAMDOC option can be enabled 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 = ../ - -# 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++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh -# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py -# *.f90 *.f *.for *.vhd *.vhdl - -FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cpp \ - *.c++ \ - *.d \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.h \ - *.hh \ - *.hxx \ - *.hpp \ - *.h++ \ - *.idl \ - *.odl \ - *.cs \ - *.php \ - *.php3 \ - *.inc \ - *.m \ - *.markdown \ - *.md \ - *.mm \ - *.dox \ - *.py \ - *.f90 \ - *.f \ - *.for \ - *.vhd \ - *.vhdl - -# 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 be -# 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. -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system 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 = - -# 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 or if -# non of the patterns match the file name, INPUT_FILTER is applied. - -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 - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) -# and it is also possible to disable source filtering for a specific pattern -# using *.ext= (so without naming a filter). This option only has effect when -# FILTER_SOURCE_FILES is enabled. - -FILTER_SOURCE_PATTERNS = - -#--------------------------------------------------------------------------- -# 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, C++ and Fortran 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 = YES - -# 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 = html - -# 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. Note that when using a custom header you are responsible -# for the proper inclusion of any scripts and style sheets that doxygen -# needs, which is dependent on the configuration options used. -# It is advised to generate a default header using "doxygen -w html -# header.html footer.html stylesheet.css YourConfigFile" and then modify -# that header. Note that the header is subject to change so you typically -# have to redo this when upgrading to a newer version of doxygen or when -# changing the value of configuration settings such as GENERATE_TREEVIEW! - -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 -# style sheet in the HTML output directory as well, or it will be erased! - -HTML_STYLESHEET = - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that -# the files will be copied as-is; there are no commands or markers available. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. -# Doxygen will adjust the colors in the style sheet and background images -# according to this color. Hue is specified as an angle on a colorwheel, -# see http://en.wikipedia.org/wiki/Hue for more information. -# For instance the value 0 represents red, 60 is yellow, 120 is green, -# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. -# The allowed range is 0 to 359. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of -# the colors in the HTML output. For a value of 0 the output will use -# grayscales only. A value of 255 will produce the most vivid colors. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to -# the luminance component of the colors in the HTML output. Values below -# 100 gradually make the output lighter, whereas values above 100 make -# the output darker. The value divided by 100 is the actual gamma applied, -# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, -# and 100 does not change the gamma. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting -# this to NO can help when comparing the output of multiple runs. - -HTML_TIMESTAMP = 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. - -HTML_DYNAMIC_SECTIONS = NO - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of -# entries shown in the various tree structured indices initially; the user -# can expand and collapse entries dynamically later on. Doxygen will expand -# the tree to such a level that at most the specified number of entries are -# visible (unless a fully collapsed tree already exceeds this amount). -# So setting the number of entries 1 will produce a full collapsed tree by -# default. 0 is a special value representing an infinite number of entries -# and will result in a full expanded tree by default. - -HTML_INDEX_NUM_ENTRIES = 100 - -# 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 - -# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. - -DOCSET_PUBLISHER_NAME = Publisher - -# 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 = org.doxygen.Project - -# 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 = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files -# will be generated, which together with the HTML files, form an Eclipse help -# plugin. To install this plugin and make it available under the help contents -# menu in Eclipse, the contents of the directory containing the HTML and XML -# files needs to be copied into the plugins directory of eclipse. The name of -# the directory within the plugins directory should be the same as -# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before -# the help appears. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have -# this name. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) -# at top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. Since the tabs have the same information as the -# navigation tree you can set this option to NO if you already set -# GENERATE_TREEVIEW to YES. - -DISABLE_INDEX = NO - -# 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 YES, 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 (i.e. any modern browser). -# Windows users are probably better off using the HTML help feature. -# Since the tree basically has the same information as the tab index you -# could consider to set DISABLE_INDEX to NO when enabling this option. - -GENERATE_TREEVIEW = NO - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values -# (range [0,1..20]) that doxygen will group on one line in the generated HTML -# documentation. Note that a value of 0 will completely suppress the enum -# values from appearing in the overview section. - -ENUM_VALUES_PER_LINE = 4 - -# 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 - -# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open -# links to external symbols imported via tag files in a separate window. - -EXT_LINKS_IN_WINDOW = NO - -# 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 - -# Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are -# not supported properly for IE 6.0, but are supported on all modern browsers. -# Note that when changing this option you need to delete any form_*.png files -# in the HTML output before the changes have effect. - -FORMULA_TRANSPARENT = YES - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax -# (see http://www.mathjax.org) which uses client side Javascript for the -# rendering instead of using prerendered bitmaps. Use this if you do not -# have LaTeX installed or if you want to formulas look prettier in the HTML -# output. When enabled you may also need to install MathJax separately and -# configure the path to it using the MATHJAX_RELPATH option. - -USE_MATHJAX = NO - -# When MathJax is enabled you need to specify the location relative to the -# HTML output directory using the MATHJAX_RELPATH option. The destination -# directory should contain the MathJax.js script. For instance, if the mathjax -# directory is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to -# the MathJax Content Delivery Network so you can quickly see the result without -# installing MathJax. However, it is strongly recommended to install a local -# copy of MathJax from http://www.mathjax.org before deployment. - -MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest - -# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension -# names that should be enabled during MathJax rendering. - -MATHJAX_EXTENSIONS = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box -# for the HTML output. The underlying search engine uses javascript -# and DHTML and should work on any modern browser. Note that when using -# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets -# (GENERATE_DOCSET) there is already a search function so this one should -# typically be disabled. For large projects the javascript based search engine -# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. - -SEARCHENGINE = YES - -# When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a PHP enabled web server instead of at the web client -# using Javascript. Doxygen will generate the search PHP script and index -# file to put on the web server. The advantage of the server -# based approach is that it scales better to large projects and allows -# full text search. The disadvantages are that it is more difficult to setup -# and does not have live searching capabilities. - -SERVER_BASED_SEARCH = NO - -#--------------------------------------------------------------------------- -# 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 = YES - -# 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. -# Note that when enabling USE_PDFLATEX this option is only used for -# generating bitmaps for formulas in the HTML output, but not in the -# Makefile that is written to the output directory. - -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, letter, legal and -# executive. If left blank a4wide will be used. - -PAPER_TYPE = a4 - -# 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 = - -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for -# the generated latex document. The footer should contain everything after -# the last chapter. If it is left blank doxygen will generate a -# standard footer. Notice: only use this tag if you know what you are doing! - -LATEX_FOOTER = - -# 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 - -# If LATEX_SOURCE_CODE is set to YES then doxygen will include -# source code with syntax highlighting in the LaTeX output. -# Note that which sources are shown also depends on other settings -# such as SOURCE_BROWSER. - -LATEX_SOURCE_CODE = NO - -# The LATEX_BIB_STYLE tag can be used to specify the style to use for the -# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See -# http://en.wikipedia.org/wiki/BibTeX for more info. - -LATEX_BIB_STYLE = plain - -#--------------------------------------------------------------------------- -# 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 style sheet 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 -# pointed to by INCLUDE_PATH will be searched when 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 that -# overrules the definition found in the source code. - -EXPAND_AS_DEFINED = - -# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then -# doxygen's preprocessor will remove all references to function-like macros -# that are alone on a line, have an all uppercase name, and do not end with a -# semicolon, because these 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. For each -# tag file the location of the external documentation should be added. 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. 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 also works with HAVE_DOT disabled, but 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 = NO - -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is -# allowed to run in parallel. When set to 0 (the default) doxygen will -# base this on the number of processors available in the system. You can set it -# explicitly to a value larger than 0 to get control over the balance -# between CPU load and processing speed. - -DOT_NUM_THREADS = 0 - -# By default doxygen will use the Helvetica font for all dot files that -# doxygen generates. When you want a differently looking font you can specify -# the font name using DOT_FONTNAME. You 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 = Helvetica - -# 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 Helvetica font. -# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to -# set the path where dot can find it. - -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 -# 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 the UML_LOOK tag is enabled, the fields and methods are shown inside -# the class node. If there are many fields or methods and many nodes the -# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS -# threshold limits the number of items for each type to make the size more -# managable. Set this to 0 for no limit. Note that the threshold may be -# exceeded by 50% before the limit is enforced. - -UML_LIMIT_NUM_FIELDS = 10 - -# 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 generate a graphical hierarchy of all classes instead of a textual one. - -GRAPHICAL_HIERARCHY = YES - -# If the DIRECTORY_GRAPH 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 svg, png, jpg, or gif. -# If left blank png will be used. If you choose svg you need to set -# HTML_FILE_EXTENSION to xhtml in order to make the SVG files -# visible in IE 9+ (other browsers do not have this requirement). - -DOT_IMAGE_FORMAT = png - -# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to -# enable generation of interactive SVG images that allow zooming and panning. -# Note that this requires a modern browser other than Internet Explorer. -# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you -# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files -# visible. Older versions of IE do not have SVG support. - -INTERACTIVE_SVG = NO - -# 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 MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the -# \mscfile command). - -MSCFILE_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 diff --git a/extern/shiny/Docs/GettingStarted.dox b/extern/shiny/Docs/GettingStarted.dox deleted file mode 100644 index b9cf58e23a..0000000000 --- a/extern/shiny/Docs/GettingStarted.dox +++ /dev/null @@ -1,65 +0,0 @@ -/*! - \page getting-started Getting started - - \section download Download the source - - \code - git clone git@github.com:scrawl/shiny.git - \endcode - - \section building Build the source - - The source files you want to build are: - - Main/*.cpp - - Preprocessor/*.cpp (unless you are using the system install of boost::wave, more below) - - Platforms/Ogre/*.cpp - - You can either build the sources as a static library, or simply add the sources to the source tree of your project. - - If you use CMake, you might find the included CMakeLists.txt useful. It builds static libraries with the names "shiny" and "shiny.OgrePlatform". - - \note The CMakeLists.txt is not intended as a stand-alone build system! Instead, you should include this from the main CMakeLists of your project. - - Make sure to link against OGRE and the boost filesystem library. - - If your boost version is older than 1.49, you must set the SHINY_USE_WAVE_SYSTEM_INSTALL variable and additionally link against the boost wave library. - - \code - set(SHINY_USE_WAVE_SYSTEM_INSTALL "TRUE") - \endcode - - \section code Basic initialisation code - - Add the following code to your application: - - \code{cpp} - - #include - #include - - .... - - sh::OgrePlatform* platform = new sh::OgrePlatform( - "General", // OGRE Resource group to use for creating materials. - myApplication.getDataPath() + "/" + "materials" // Path to look for materials and shaders. NOTE: This does NOT use the Ogre resource system, so you have to specify an absolute path. - ); - - sh::Factory* factory = new sh::Factory(platform); - - // Set a language. Valid options: CG, HLSL, GLSL - factory->setCurrentLanguage(sh::Language_GLSL); - - factory->loadAllFiles(); - - .... - your application runs here - .... - - // don't forget to delete on exit - delete factory; - - \endcode - - That's it! Now you can start defining materials. Refer to the page \ref defining-materials-shaders . - -*/ diff --git a/extern/shiny/Docs/Lod.dox b/extern/shiny/Docs/Lod.dox deleted file mode 100644 index 37d004638e..0000000000 --- a/extern/shiny/Docs/Lod.dox +++ /dev/null @@ -1,49 +0,0 @@ -/*! - - \page lod Material LOD - - \section howitworks How it works - - When Ogre requests a technique for a specific LOD index, the Factory selects the appropriate LOD configuration which then temporarily overrides the global settings in the shaders. We can use this to disable shader features one by one at a lower LOD, resulting in simpler and faster techniques for distant objects. - - \section howtouseit How to use it - - - Create a file with the extension '.lod'. There you can specify shader features to disable at a specific LOD level. Higher LOD index refers to a lower LOD. Example contents: - - \code - lod_configuration 1 - { - specular_mapping false - } - - lod_configuration 2 - { - specular_mapping false - normal_mapping false - } - - lod_configuration 3 - { - terrain_composite_map true - specular_mapping false - normal_mapping false - } - \endcode - - \note You can also add LOD configurations by calling \a sh::Factory::registerLodConfiguration. - - \note Do not use an index of 0. LOD 0 refers to the highest LOD, and you will never want to disable features at the highest LOD level. - - - - In your materials, specify the distances at which a lower LOD kicks in. Note that the actual distance might also be affected by the viewport and current entity LOD bias. In this example, the first LOD level (lod index 1) would normally be applied at a distance of 100 units, the next after 300, and the last after 1000 units. - - \code - material sample_material - { - lod_values 100 300 1000 - - ... your passes, texture units etc ... - } - \endcode - -*/ diff --git a/extern/shiny/Docs/Macros.dox b/extern/shiny/Docs/Macros.dox deleted file mode 100644 index c04ebd3747..0000000000 --- a/extern/shiny/Docs/Macros.dox +++ /dev/null @@ -1,283 +0,0 @@ -/*! - \page macros Shader Macros - - \tableofcontents - - \section Shader Language - - These macros are automatically defined, depending on the shader language that has been set by the application using sh::Factory::setCurrentLanguage. - - - SH_GLSL - - SH_HLSL - - SH_CG - - Example: - - \code - #if SH_GLSL == 1 - // glsl porting code - #endif - - #if SH_CG == 1 || SH_HLSL == 1 - // cg / hlsl porting code (similiar syntax) - #endif - \endcode - - \note It is encouraged to use the shipped porting header (extra/core.h) by #include-ing it in your shaders. If you do that, you should not have to use the above macros directly. - - \section vertex-fragment Vertex / fragment shader - - These macros are automatically defined, depending on the type of shader that is currently being compiled. - - - SH_VERTEX_SHADER - - SH_FRAGMENT_SHADER - - If you use the same source file for both vertex and fragment shader, then it is advised to use these macros for blending out the unused source. This will reduce your compile time. - - \section passthrough Vertex -> Fragment passthrough - - In shader development, a common task is to pass variables from the vertex to the fragment shader. This is no problem if you have a deterministic shader source (i.e. no #ifdefs). - - However, as soon as you begin to have lots of permutations of the same shader source, a problem arises. All current GPUs have a limit of 8 vertex to fragment passthroughs (with 4 components each, for example a float4). - - A common optimization is to put several individual float values together in a float4 (so-called "Packing"). But if your shader has lots of permutations and the passthrough elements you actually need are not known beforehand, it can be very tedious to pack manually. With the following macros, packing can become easier. - - \subsection shAllocatePassthrough shAllocatePassthrough - - Usage: \@shAllocatePassthrough(num_components, name) - - Example: - \code - #if FRAGMENT_NEED_DEPTH - @shAllocatePassthrough(1, depth) - #endif - \endcode - - This is the first thing you should do before using any of the macros below. - - \subsection shPassthroughVertexOutputs shPassthroughVertexOutputs - - Usage: \@shPassthroughVertexOutputs - - Use this in the inputs/outputs section of your vertex shader, in order to declare all the outputs that are needed for packing the variables that you want passed to the fragment. - - \subsection shPassthroughFragmentInputs shPassthroughFragmentInputs - - Usage: \@shPassthroughFragmentInputs - - Use this in the inputs/outputs section of your fragment shader, in order to declare all the inputs that are needed for receiving the variables that you want passed to the fragment. - - \subsection shPassthroughAssign shPassthroughAssign - - Usage: \@shPassthroughAssign(name, value) - - Use this in the vertex shader for assigning a value to the variable you want passed to the fragment. - - Example: - \code - #if FRAGMENT_NEED_DEPTH - @shPassthroughAssign(depth, shOutputPosition.z); - #endif - - \endcode - - \subsection shPassthroughReceive shPassthroughReceive - - Usage: \@shPassthroughReceive(name) - - Use this in the fragment shader to receive the passed value. - - Example: - - \code - #if FRAGMENT_NEED_DEPTH - float depth = @shPassthroughReceive(depth); - #endif - \endcode - - \section texUnits Texture units - - \subsection shUseSampler shUseSampler - - Usage: \@shUseSampler(samplerName) - - Requests the texture unit with name \a samplerName to be available for use in this pass. - - Why is this necessary? If you have a derived material that does not use all of the texture units that its parent defines (for example, if an optional asset such as a normal map is not available), there would be no way to know which texture units are actually needed and which can be skipped in the creation process (those that are never referenced in the shader). - - \section properties Property retrieval / binding - - \subsection shPropertyHasValue shPropertyHasValue - - Usage: \@shPropertyHasValue(property) - - Gets replaced by 1 if the property's value is not empty, or 0 if it is empty. - Useful for checking whether an optional texture is present or not. - - Example: - \code - #if @shPropertyHasValue(specularMap) - // specular mapping code - #endif - #if @shPropertyHasValue(normalMap) - // normal mapping code - #endif - \endcode - - \subsection shUniformProperty shUniformProperty - - Usage: \@shUniformProperty<4f|3f|2f|1f|int> (uniformName, property) - - Binds the value of \a property (from the shader_properties of the pass this shader belongs to) to the uniform with name \a uniformName. - - The following variants are available, depending on the type of your uniform variable: - - \@shUniformProperty4f - - \@shUniformProperty3f - - \@shUniformProperty2f - - \@shUniformProperty1f - - \@shUniformPropertyInt - - Example: \@shUniformProperty1f (uFresnelScale, fresnelScale) - - \subsection shPropertyBool shPropertyBool - - Retrieve a boolean property of the pass that this shader belongs to, gets replaced with either 0 or 1. - - Usage: \@shPropertyBool(propertyName) - - Example: - \code - #if @shPropertyBool(has_vertex_colors) - ... - #endif - \endcode - - \subsection shPropertyString shPropertyString - - Retrieve a string property of the pass that this shader belongs to - - Usage: \@shPropertyString(propertyName) - - \subsection shPropertyEqual shPropertyEqual - - Check if the value of a property equals a specific value, gets replaced with either 0 or 1. This is useful because the preprocessor cannot compare strings, only numbers. - - Usage: \@shPropertyEqual(propertyName, value) - - Example: - \code - #if @shPropertyEqual(lighting_mode, phong) - ... - #endif - \endcode - - \section globalSettings Global settings - - \subsection shGlobalSettingBool shGlobalSettingBool - - Retrieves the boolean value of a specific global setting, gets replaced with either 0 or 1. The value can be set using sh::Factory::setGlobalSetting. - - Usage: \@shGlobalSettingBool(settingName) - - \subsection shGlobalSettingEqual shGlobalSettingEqual - - Check if the value of a global setting equals a specific value, gets replaced with either 0 or 1. This is useful because the preprocessor cannot compare strings, only numbers. - - Usage: \@shGlobalSettingEqual(settingName, value) - - \subsection shGlobalSettingString shGlobalSettingString - - Gets replaced with the current value of a given global setting. The value can be set using sh::Factory::setGlobalSetting. - - Usage: \@shGlobalSettingString(settingName) - - \section sharedParams Shared parameters - - \subsection shSharedParameter shSharedParameter - - Allows you to bind a custom value to a uniform parameter. - - Usage: \@shSharedParameter(sharedParameterName) - - Example: \@shSharedParameter(pssmSplitPoints) - now the uniform parameter 'pssmSplitPoints' can be altered in all shaders that use it by executing sh::Factory::setSharedParameter("pssmSplitPoints", value) - - \note You may use the same shared parameter in as many shaders as you want. But don't forget to add the \@shSharedParameter macro to every shader that uses this shared parameter. - - \section autoconstants Auto constants - - \subsection shAutoConstant shAutoConstant - - Usage: \@shAutoConstant(uniformName, autoConstantName, [extraData]) - - Example: \@shAutoConstant(uModelViewMatrix, worldviewproj_matrix) - - Example: \@shAutoConstant(uLightPosition4, light_position, 4) - - Binds auto constant with name \a autoConstantName to the uniform \a uniformName. Optionally, you may specify extra data (for example the light index), as required by some auto constants. - - The auto constant names are the same as Ogre's. Read the section "3.1.9 Using Vertex/Geometry/Fragment Programs in a Pass" of the Ogre manual for a list of all auto constant names. - - \section misc Misc - - \subsection shForeach shForeach - - Usage: \@shForeach(n) - - Repeats the content of this foreach block \a n times. The end of the block is marked via \@shEndForeach, and the current iteration number can be retrieved via \@shIterator. - - \note Nested foreach blocks are currently \a not supported. - - \note For technical reasons, you can only use constant numbers, properties (\@shPropertyString) or global settings (\@shGlobalSettingString) as \a n parameter. - - Example: - - \code - @shForeach(3) - this is iteration number @shIterator - @shEndForeach - - Gets replaced with: - - this is iteration number 0 - this is iteration number 1 - this is iteration number 2 - \endcode - - Optionally, you can pass a constant offset to \@shIterator. Example: - - \code - @shForeach(3) - this is iteration number @shIterator(7) - @shEndForeach - - Gets replaced with: - - this is iteration number 7 - this is iteration number 8 - this is iteration number 9 - \endcode - - \subsection shCounter shCounter - - Gets replaced after the preprocessing step with the number that equals the n-th occurence of counters of the same ID. - - Usage: \@shCounter(ID) - - Example: - \code - @shCounter(0) - @shCounter(0) - @shCounter(1) - @shCounter(0) - \endcode - - Gets replaced with: - - \code - 0 - 1 - 0 - 2 - \endcode - -*/ diff --git a/extern/shiny/Docs/Mainpage.dox b/extern/shiny/Docs/Mainpage.dox deleted file mode 100644 index fb8f596dc0..0000000000 --- a/extern/shiny/Docs/Mainpage.dox +++ /dev/null @@ -1,13 +0,0 @@ -/*! - - \mainpage - - - \ref getting-started - - \ref defining-materials-shaders - - \ref macros - - \ref configurations - - \ref lod - - - sh::Factory - the main interface class - -*/ diff --git a/extern/shiny/Docs/Materials.dox b/extern/shiny/Docs/Materials.dox deleted file mode 100644 index d08599a04a..0000000000 --- a/extern/shiny/Docs/Materials.dox +++ /dev/null @@ -1,131 +0,0 @@ -/*! - - \page defining-materials-shaders Defining materials and shaders - - \section first-material Your first material - - Create a file called "myFirstMaterial.mat" and place it in the path you have used in your initialisation code (see \ref getting-started). Paste the following: - - \code - - material my_first_material - { - diffuse 1.0 1.0 1.0 1.0 - specular 0.4 0.4 0.4 32 - ambient 1.0 1.0 1.0 - emissive 0.0 0.0 0.0 - diffuseMap black.png - - pass - { - diffuse $diffuse - specular $specular - ambient $ambient - emissive $emissive - - texture_unit diffuseMap - { - texture $diffuseMap - create_in_ffp true // use this texture unit for fixed function pipeline - } - } - } - - material material1 - { - parent my_first_material - diffuseMap texture1.png - } - - material material2 - { - parent my_first_material - diffuseMap texture2.png - } - - \endcode - - \section first-shader The first shader - - Change the 'pass' section to include some shaders: - - \code - pass - { - vertex_program my_first_shader_vertex - fragment_program my_first_shader_fragment - ... - } - \endcode - - \note This does \a not refer to a single shader with a fixed source code, but in fact will automatically create a new \a instance of this shader (if necessary), which can have its own uniform variables, compile-time macros and much more! - - Next, we're going to define our shaders. Paste this in a new file called 'myfirstshader.shaderset' - - \code - shader_set my_first_shader_vertex - { - source example.shader - type vertex - profiles_cg vs_2_0 arbvp1 - profiles_hlsl vs_2_0 - } - - shader_set my_first_shader_fragment - { - source example.shader - type fragment - profiles_cg ps_2_x ps_2_0 ps arbfp1 - profiles_hlsl ps_2_0 - } - \endcode - - Some notes: - - There is no entry_point property because the entry point is always \a main. - - Both profiles_cg and profiles_hlsl are a list of shader profiles. The first profile that is supported is automatically picked. GLSL does not have shader profiles. - - Now, let's get into writing our shader! As you can guess from above, the filename should be 'example.shader'. - Make sure to also copy the 'core.h' file to the same location. It is included in shiny's source tree under 'Extra/'. - - Important: a newline at the end of the file is required. Many editors do this automatically or can be configured to do so. If there is no newline at the end of the file, and the last line is '#endif', you will get the rather cryptic error message " ill formed preprocessor directive: #endif" from boost::wave. - - \code - #include "core.h" - - #ifdef SH_VERTEX_SHADER - - SH_BEGIN_PROGRAM - shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - shVertexInput(float2, uv0) - shOutput(float2, UV) - SH_START_PROGRAM - { - shOutputPosition = shMatrixMult(wvp, shInputPosition); - UV = uv0; - } - - #else - - SH_BEGIN_PROGRAM - // NOTE: It is important that the sampler name here (diffuseMap) matches - // the name of the texture unit in the material. This is necessary because the system - // skips texture units that are never "referenced" in the shader. This can be the case - // when your base material has optional assets (for example a normal map) that are not - // used by some derived materials. - shSampler2D(diffuseMap) - shInput(float2, UV) - SH_START_PROGRAM - { - shOutputColour(0) = shSample(diffuseMap, UV); - } - - #endif - - \endcode - - There you have it! This shader will compile in several languages thanks to the porting defines in "core.h". If you need more defines, feel free to add them and don't forget to send them to me! - - For a full list of macros available when writing your shaders, refer to the page \ref macros - - In the future, some more in-depth shader examples might follow. -*/ diff --git a/extern/shiny/Editor/Actions.cpp b/extern/shiny/Editor/Actions.cpp deleted file mode 100644 index 135e819878..0000000000 --- a/extern/shiny/Editor/Actions.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include "Actions.hpp" - -#include "../Main/Factory.hpp" - -namespace sh -{ - - void ActionDeleteMaterial::execute() - { - sh::Factory::getInstance().destroyMaterialInstance(mName); - } - - void ActionCloneMaterial::execute() - { - sh::MaterialInstance* sourceMaterial = sh::Factory::getInstance().getMaterialInstance(mSourceName); - std::string sourceMaterialParent = static_cast(sourceMaterial->getParent())->getName(); - sh::MaterialInstance* material = sh::Factory::getInstance().createMaterialInstance( - mDestName, sourceMaterialParent); - sourceMaterial->copyAll(material, sourceMaterial, false); - - material->setSourceFile(sourceMaterial->getSourceFile()); - } - - void ActionSaveAll::execute() - { - sh::Factory::getInstance().saveAll(); - } - - void ActionChangeGlobalSetting::execute() - { - sh::Factory::getInstance().setGlobalSetting(mName, mNewValue); - } - - void ActionCreateConfiguration::execute() - { - sh::Configuration newConfiguration; - sh::Factory::getInstance().createConfiguration(mName); - } - - void ActionDeleteConfiguration::execute() - { - sh::Factory::getInstance().destroyConfiguration(mName); - } - - void ActionChangeConfiguration::execute() - { - sh::Configuration* c = sh::Factory::getInstance().getConfiguration(mName); - c->setProperty(mKey, sh::makeProperty(new sh::StringValue(mValue))); - sh::Factory::getInstance().notifyConfigurationChanged(); - } - - void ActionDeleteConfigurationProperty::execute() - { - sh::Configuration* c = sh::Factory::getInstance().getConfiguration(mName); - c->deleteProperty(mKey); - sh::Factory::getInstance().notifyConfigurationChanged(); - } - - void ActionSetMaterialProperty::execute() - { - sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); - m->setProperty(mKey, sh::makeProperty(mValue)); - - sh::Factory::getInstance().notifyConfigurationChanged(); - } - - void ActionDeleteMaterialProperty::execute() - { - sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); - m->deleteProperty(mKey); - - sh::Factory::getInstance().notifyConfigurationChanged(); - } - - void ActionCreatePass::execute() - { - sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); - m->createPass(); - - sh::Factory::getInstance().notifyConfigurationChanged(); - } - - void ActionDeletePass::execute() - { - sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); - m->deletePass(mPassIndex); - - sh::Factory::getInstance().notifyConfigurationChanged(); - } - - void ActionSetPassProperty::execute() - { - sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); - assert (m->getPasses()->size() > mPassIndex); - m->getPasses()->at(mPassIndex).setProperty (mKey, sh::makeProperty(mValue)); - - sh::Factory::getInstance().notifyConfigurationChanged(); - } - - void ActionDeletePassProperty::execute() - { - sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); - assert (m->getPasses()->size() > mPassIndex); - m->getPasses()->at(mPassIndex).deleteProperty(mKey); - - sh::Factory::getInstance().notifyConfigurationChanged(); - } - - void ActionSetShaderProperty::execute() - { - sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); - assert (m->getPasses()->size() > mPassIndex); - m->getPasses()->at(mPassIndex).mShaderProperties.setProperty (mKey, sh::makeProperty(mValue)); - - sh::Factory::getInstance().notifyConfigurationChanged(); - } - - void ActionDeleteShaderProperty::execute() - { - sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); - assert (m->getPasses()->size() > mPassIndex); - m->getPasses()->at(mPassIndex).mShaderProperties.deleteProperty (mKey); - - sh::Factory::getInstance().notifyConfigurationChanged(); - } - - void ActionSetTextureProperty::execute() - { - sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); - assert (m->getPasses()->size() > mPassIndex); - assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex); - m->getPasses()->at(mPassIndex).mTexUnits.at(mTextureIndex).setProperty(mKey, sh::makeProperty(mValue)); - - sh::Factory::getInstance().notifyConfigurationChanged(); - } - - void ActionDeleteTextureProperty::execute() - { - sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); - assert (m->getPasses()->size() > mPassIndex); - assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex); - m->getPasses()->at(mPassIndex).mTexUnits.at(mTextureIndex).deleteProperty(mKey); - - sh::Factory::getInstance().notifyConfigurationChanged(); - } - - void ActionCreateTextureUnit::execute() - { - sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); - assert (m->getPasses()->size() > mPassIndex); - m->getPasses()->at(mPassIndex).createTextureUnit(mTexUnitName); - - sh::Factory::getInstance().notifyConfigurationChanged(); - } - - void ActionDeleteTextureUnit::execute() - { - sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); - assert (m->getPasses()->size() > mPassIndex); - assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex); - - m->getPasses()->at(mPassIndex).mTexUnits.erase(m->getPasses()->at(mPassIndex).mTexUnits.begin() + mTextureIndex); - - sh::Factory::getInstance().notifyConfigurationChanged(); - } - - void ActionMoveTextureUnit::execute() - { - sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); - assert (m->getPasses()->size() > mPassIndex); - assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex); - if (!mMoveUp) - assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex+1); - - std::vector textures = m->getPasses()->at(mPassIndex).mTexUnits; - if (mMoveUp) - std::swap(textures[mTextureIndex-1], textures[mTextureIndex]); - else - std::swap(textures[mTextureIndex+1], textures[mTextureIndex]); - m->getPasses()->at(mPassIndex).mTexUnits = textures; - - sh::Factory::getInstance().notifyConfigurationChanged(); - } - - void ActionChangeTextureUnitName::execute() - { - sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); - assert (m->getPasses()->size() > mPassIndex); - assert (m->getPasses()->at(mPassIndex).mTexUnits.size() > mTextureIndex); - - m->getPasses()->at(mPassIndex).mTexUnits[mTextureIndex].setName(mTexUnitName); - - sh::Factory::getInstance().notifyConfigurationChanged(); - } -} diff --git a/extern/shiny/Editor/Actions.hpp b/extern/shiny/Editor/Actions.hpp deleted file mode 100644 index 1bbdbe5a98..0000000000 --- a/extern/shiny/Editor/Actions.hpp +++ /dev/null @@ -1,307 +0,0 @@ -#ifndef SH_ACTIONS_H -#define SH_ACTIONS_H - -#include - -namespace sh -{ - - class Action - { - public: - virtual void execute() = 0; - virtual ~Action() {} - }; - - class ActionDeleteMaterial : public Action - { - public: - ActionDeleteMaterial(const std::string& name) - : mName(name) {} - - virtual void execute(); - private: - std::string mName; - }; - - class ActionCloneMaterial : public Action - { - public: - ActionCloneMaterial(const std::string& sourceName, const std::string& destName) - : mSourceName(sourceName), mDestName(destName) {} - - virtual void execute(); - private: - std::string mSourceName; - std::string mDestName; - }; - - class ActionSaveAll : public Action - { - public: - virtual void execute(); - }; - - class ActionChangeGlobalSetting : public Action - { - public: - ActionChangeGlobalSetting(const std::string& name, const std::string& newValue) - : mName(name), mNewValue(newValue) {} - - virtual void execute(); - private: - std::string mName; - std::string mNewValue; - }; - - // configuration - - class ActionCreateConfiguration : public Action - { - public: - ActionCreateConfiguration(const std::string& name) - : mName(name) {} - - virtual void execute(); - private: - std::string mName; - - }; - - class ActionDeleteConfiguration : public Action - { - public: - ActionDeleteConfiguration(const std::string& name) - : mName(name) {} - - virtual void execute(); - private: - std::string mName; - - }; - - class ActionChangeConfiguration : public Action - { - public: - ActionChangeConfiguration (const std::string& name, const std::string& key, const std::string& value) - : mName(name), mKey(key), mValue(value) {} - - virtual void execute(); - private: - std::string mName; - std::string mKey; - std::string mValue; - }; - - class ActionDeleteConfigurationProperty : public Action - { - public: - ActionDeleteConfigurationProperty (const std::string& name, const std::string& key) - : mName(name), mKey(key) {} - - virtual void execute(); - private: - std::string mName; - std::string mKey; - }; - - // material - - class ActionSetMaterialProperty : public Action - { - public: - ActionSetMaterialProperty (const std::string& name, const std::string& key, const std::string& value) - : mName(name), mKey(key), mValue(value) {} - - virtual void execute(); - private: - std::string mName; - std::string mKey; - std::string mValue; - }; - - class ActionDeleteMaterialProperty : public Action - { - public: - ActionDeleteMaterialProperty (const std::string& name, const std::string& key) - : mName(name), mKey(key) {} - - virtual void execute(); - private: - std::string mName; - std::string mKey; - }; - - // pass - - class ActionCreatePass : public Action - { - public: - ActionCreatePass (const std::string& name) - : mName(name) {} - - virtual void execute(); - private: - std::string mName; - }; - - class ActionDeletePass : public Action - { - public: - ActionDeletePass (const std::string& name, int passIndex) - : mName(name), mPassIndex(passIndex) {} - - virtual void execute(); - private: - std::string mName; - int mPassIndex; - }; - - class ActionSetPassProperty : public Action - { - public: - ActionSetPassProperty (const std::string& name, int passIndex, const std::string& key, const std::string& value) - : mName(name), mPassIndex(passIndex), mKey(key), mValue(value) {} - - virtual void execute(); - private: - std::string mName; - int mPassIndex; - std::string mKey; - std::string mValue; - }; - - class ActionDeletePassProperty : public Action - { - public: - ActionDeletePassProperty (const std::string& name, int passIndex, const std::string& key) - : mName(name), mPassIndex(passIndex), mKey(key) {} - - virtual void execute(); - private: - std::string mName; - int mPassIndex; - std::string mKey; - }; - - // shader - - class ActionSetShaderProperty : public Action - { - public: - ActionSetShaderProperty (const std::string& name, int passIndex, const std::string& key, const std::string& value) - : mName(name), mPassIndex(passIndex), mKey(key), mValue(value) {} - - virtual void execute(); - private: - std::string mName; - int mPassIndex; - std::string mKey; - std::string mValue; - }; - - class ActionDeleteShaderProperty : public Action - { - public: - ActionDeleteShaderProperty (const std::string& name, int passIndex, const std::string& key) - : mName(name), mPassIndex(passIndex), mKey(key) {} - - virtual void execute(); - private: - std::string mName; - int mPassIndex; - std::string mKey; - }; - - // texture unit - - class ActionChangeTextureUnitName : public Action - { - public: - ActionChangeTextureUnitName (const std::string& name, int passIndex, int textureIndex, const std::string& texUnitName) - : mName(name), mPassIndex(passIndex), mTextureIndex(textureIndex), mTexUnitName(texUnitName) {} - - virtual void execute(); - - private: - std::string mName; - int mPassIndex; - int mTextureIndex; - std::string mTexUnitName; - }; - - class ActionCreateTextureUnit : public Action - { - public: - ActionCreateTextureUnit (const std::string& name, int passIndex, const std::string& texUnitName) - : mName(name), mPassIndex(passIndex), mTexUnitName(texUnitName) {} - - virtual void execute(); - - private: - std::string mName; - int mPassIndex; - std::string mTexUnitName; - }; - - class ActionDeleteTextureUnit : public Action - { - public: - ActionDeleteTextureUnit (const std::string& name, int passIndex, int textureIndex) - : mName(name), mPassIndex(passIndex), mTextureIndex(textureIndex) {} - - virtual void execute(); - - private: - std::string mName; - int mPassIndex; - int mTextureIndex; - }; - - class ActionMoveTextureUnit : public Action - { - public: - ActionMoveTextureUnit (const std::string& name, int passIndex, int textureIndex, bool moveUp) - : mName(name), mPassIndex(passIndex), mTextureIndex(textureIndex), mMoveUp(moveUp) {} - - virtual void execute(); - - private: - std::string mName; - int mPassIndex; - int mTextureIndex; - bool mMoveUp; - }; - - class ActionSetTextureProperty : public Action - { - public: - ActionSetTextureProperty (const std::string& name, int passIndex, int textureIndex, const std::string& key, const std::string& value) - : mName(name), mPassIndex(passIndex), mTextureIndex(textureIndex), mKey(key), mValue(value) {} - - virtual void execute(); - private: - std::string mName; - int mPassIndex; - int mTextureIndex; - std::string mKey; - std::string mValue; - }; - - class ActionDeleteTextureProperty : public Action - { - public: - ActionDeleteTextureProperty (const std::string& name, int passIndex, int textureIndex, const std::string& key) - : mName(name), mPassIndex(passIndex), mTextureIndex(textureIndex), mKey(key) {} - - virtual void execute(); - private: - std::string mName; - int mPassIndex; - int mTextureIndex; - std::string mKey; - }; - -} - -#endif diff --git a/extern/shiny/Editor/AddPropertyDialog.cpp b/extern/shiny/Editor/AddPropertyDialog.cpp deleted file mode 100644 index 71b47feb19..0000000000 --- a/extern/shiny/Editor/AddPropertyDialog.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "AddPropertyDialog.hpp" -#include "ui_addpropertydialog.h" - -AddPropertyDialog::AddPropertyDialog(QWidget *parent) - : QDialog(parent) - , ui(new Ui::AddPropertyDialog) - , mType(0) -{ - ui->setupUi(this); - - connect(ui->buttonBox, SIGNAL(accepted()), - this, SLOT(accepted())); - connect(ui->buttonBox, SIGNAL(rejected()), - this, SLOT(rejected())); -} - -void AddPropertyDialog::accepted() -{ - mName = ui->lineEdit->text(); - mType = ui->comboBox->currentIndex(); -} - -void AddPropertyDialog::rejected() -{ - mName = ""; -} - -AddPropertyDialog::~AddPropertyDialog() -{ - delete ui; -} diff --git a/extern/shiny/Editor/AddPropertyDialog.h b/extern/shiny/Editor/AddPropertyDialog.h deleted file mode 100644 index c1d2c960b5..0000000000 --- a/extern/shiny/Editor/AddPropertyDialog.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef ADDPROPERTYDIALOG_H -#define ADDPROPERTYDIALOG_H - -#include - -namespace Ui { -class AddPropertyDialog; -} - -class AddPropertyDialog : public QDialog -{ - Q_OBJECT - -public: - explicit AddPropertyDialog(QWidget *parent = 0); - ~AddPropertyDialog(); - -private: - Ui::AddPropertyDialog *ui; -}; - -#endif // ADDPROPERTYDIALOG_H diff --git a/extern/shiny/Editor/AddPropertyDialog.hpp b/extern/shiny/Editor/AddPropertyDialog.hpp deleted file mode 100644 index b4e19b0870..0000000000 --- a/extern/shiny/Editor/AddPropertyDialog.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef ADDPROPERTYDIALOG_H -#define ADDPROPERTYDIALOG_H - -#include - -namespace Ui { -class AddPropertyDialog; -} - -class AddPropertyDialog : public QDialog -{ - Q_OBJECT - -public: - explicit AddPropertyDialog(QWidget *parent = 0); - ~AddPropertyDialog(); - - int mType; - QString mName; - -public slots: - void accepted(); - void rejected(); - -private: - Ui::AddPropertyDialog *ui; -}; - -#endif // ADDPROPERTYDIALOG_H diff --git a/extern/shiny/Editor/CMakeLists.txt b/extern/shiny/Editor/CMakeLists.txt deleted file mode 100644 index eead159f08..0000000000 --- a/extern/shiny/Editor/CMakeLists.txt +++ /dev/null @@ -1,61 +0,0 @@ -set(SHINY_EDITOR_LIBRARY "shiny.Editor") - -find_package(Qt4) - -if (QT_FOUND) - - add_definitions(-DSHINY_BUILD_MATERIAL_EDITOR=1) - set (SHINY_BUILD_EDITOR_FLAG -DSHINY_BUILD_MATERIAL_EDITOR=1 PARENT_SCOPE) - - set(QT_USE_QTGUI 1) - - # Headers that must be preprocessed - set(SHINY_EDITOR_HEADER_MOC - MainWindow.hpp - NewMaterialDialog.hpp - AddPropertyDialog.hpp - PropertySortModel.hpp - ) - - set(SHINY_EDITOR_UI - mainwindow.ui - newmaterialdialog.ui - addpropertydialog.ui - ) - - QT4_WRAP_CPP(MOC_SRCS ${SHINY_EDITOR_HEADER_MOC}) - QT4_WRAP_UI(UI_HDRS ${SHINY_EDITOR_UI}) - - set(SOURCE_FILES - NewMaterialDialog.cpp - AddPropertyDialog.cpp - ColoredTabWidget.hpp - MainWindow.cpp - Editor.cpp - Actions.cpp - Query.cpp - PropertySortModel.cpp - ${SHINY_EDITOR_UI} # Just to have them in the IDE's file explorer - ) - - include(${QT_USE_FILE}) - - set (CMAKE_INCLUDE_CURRENT_DIR "true") - - include_directories(${CMAKE_CURRENT_BINARY_DIR}) - - add_library(${SHINY_EDITOR_LIBRARY} STATIC ${SOURCE_FILES} ${MOC_SRCS} ${UI_HDRS}) - - set(SHINY_LIBRARIES ${SHINY_LIBRARIES} - ${SHINY_EDITOR_LIBRARY} - ${QT_LIBRARIES} - ) - set(SHINY_LIBRARIES ${SHINY_LIBRARIES} PARENT_SCOPE) - -else (QT_FOUND) - - add_definitions(-DSHINY_BUILD_MATERIAL_EDITOR=0) - set (SHINY_BUILD_EDITOR_FLAG -DSHINY_BUILD_MATERIAL_EDITOR=0 PARENT_SCOPE) - message(STATUS "QT4 was not found. You will not be able to use the material editor.") - -endif(QT_FOUND) diff --git a/extern/shiny/Editor/ColoredTabWidget.hpp b/extern/shiny/Editor/ColoredTabWidget.hpp deleted file mode 100644 index 0bf30f6dda..0000000000 --- a/extern/shiny/Editor/ColoredTabWidget.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef SHINY_EDITOR_COLOREDTABWIDGET_H -#define SHINY_EDITOR_COLOREDTABWIDGET_H - -#include - -namespace sh -{ - -/// Makes tabBar() public to allow changing tab title colors. -class ColoredTabWidget : public QTabWidget -{ -public: - ColoredTabWidget(QWidget* parent = 0) - : QTabWidget(parent) {} - - QTabBar* tabBar() - { - return QTabWidget::tabBar(); - } -}; - -} - -#endif diff --git a/extern/shiny/Editor/Editor.cpp b/extern/shiny/Editor/Editor.cpp deleted file mode 100644 index 8c58d0e66d..0000000000 --- a/extern/shiny/Editor/Editor.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "Editor.hpp" - - -#include -#include - -#include - -#include "../Main/Factory.hpp" - -#include "MainWindow.hpp" - -namespace sh -{ - - Editor::Editor() - : mMainWindow(NULL) - , mApplication(NULL) - , mInitialized(false) - , mThread(NULL) - { - } - - Editor::~Editor() - { - if (mMainWindow) - mMainWindow->mRequestExit = true; - - if (mThread) - mThread->join(); - delete mThread; - } - - void Editor::show() - { - if (!mInitialized) - { - mInitialized = true; - - mThread = new boost::thread(boost::bind(&Editor::runThread, this)); - } - else - { - if (mMainWindow) - mMainWindow->mRequestShowWindow = true; - } - } - - void Editor::runThread() - { - int argc = 0; - char** argv = NULL; - mApplication = new QApplication(argc, argv); - mApplication->setQuitOnLastWindowClosed(false); - mMainWindow = new MainWindow(); - mMainWindow->mSync = &mSync; - mMainWindow->show(); - - mApplication->exec(); - - delete mApplication; - } - - void Editor::update() - { - sh::Factory::getInstance().doMonitorShaderFiles(); - - if (!mMainWindow) - return; - - - { - boost::mutex::scoped_lock lock(mSync.mActionMutex); - - // execute pending actions - while (mMainWindow->mActionQueue.size()) - { - Action* action = mMainWindow->mActionQueue.front(); - action->execute(); - delete action; - mMainWindow->mActionQueue.pop(); - } - } - { - boost::mutex::scoped_lock lock(mSync.mQueryMutex); - - // execute pending queries - for (std::vector::iterator it = mMainWindow->mQueries.begin(); it != mMainWindow->mQueries.end(); ++it) - { - Query* query = *it; - if (!query->mDone) - query->execute(); - } - } - - boost::mutex::scoped_lock lock2(mSync.mUpdateMutex); - - // update the list of materials - mMainWindow->mState.mMaterialList.clear(); - sh::Factory::getInstance().listMaterials(mMainWindow->mState.mMaterialList); - - // update global settings - mMainWindow->mState.mGlobalSettingsMap.clear(); - sh::Factory::getInstance().listGlobalSettings(mMainWindow->mState.mGlobalSettingsMap); - - // update configuration list - mMainWindow->mState.mConfigurationList.clear(); - sh::Factory::getInstance().listConfigurationNames(mMainWindow->mState.mConfigurationList); - - // update shader list - mMainWindow->mState.mShaderSets.clear(); - sh::Factory::getInstance().listShaderSets(mMainWindow->mState.mShaderSets); - - mMainWindow->mState.mErrors += sh::Factory::getInstance().getErrorLog(); - } - -} diff --git a/extern/shiny/Editor/Editor.hpp b/extern/shiny/Editor/Editor.hpp deleted file mode 100644 index 2b1e8040d0..0000000000 --- a/extern/shiny/Editor/Editor.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef SH_EDITOR_H -#define SH_EDITOR_H - -#if SHINY_BUILD_MATERIAL_EDITOR -class QApplication; - -#include -#include - -namespace boost -{ - class thread; -} - -namespace sh -{ - class MainWindow; - - struct SynchronizationState - { - boost::mutex mUpdateMutex; - boost::mutex mActionMutex; - boost::mutex mQueryMutex; - }; - - class Editor - { - public: - - Editor(); - ~Editor(); - - void show(); - void update(); - - - private: - bool mInitialized; - - MainWindow* mMainWindow; - QApplication* mApplication; - - SynchronizationState mSync; - - boost::thread* mThread; - - void runThread(); - - void processShowWindow(); - }; - -} - -#else - -// Dummy implementation, so that the user's code does not have to be polluted with #ifdefs -namespace sh -{ - - class Editor - { - public: - Editor() {} - ~Editor() {} - void show() {} - void update() {} - - }; -} - -#endif - -#endif diff --git a/extern/shiny/Editor/MainWindow.cpp b/extern/shiny/Editor/MainWindow.cpp deleted file mode 100644 index a2c52dc2f8..0000000000 --- a/extern/shiny/Editor/MainWindow.cpp +++ /dev/null @@ -1,952 +0,0 @@ -#include "MainWindow.hpp" -#include "ui_mainwindow.h" - -#include - -#include -#include - -#include -#include - -#include "Editor.hpp" -#include "ColoredTabWidget.hpp" -#include "AddPropertyDialog.hpp" - -sh::MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent) - , ui(new Ui::MainWindow) - , mRequestShowWindow(false) - , mRequestExit(false) - , mIgnoreGlobalSettingChange(false) - , mIgnoreConfigurationChange(false) - , mIgnoreMaterialChange(false) - , mIgnoreMaterialPropertyChange(false) -{ - ui->setupUi(this); - - QTimer *timer = new QTimer(this); - connect(timer, SIGNAL(timeout()), this, SLOT(onIdle())); - timer->start(50); - - QList sizes; - sizes << 250; - sizes << 550; - ui->splitter->setSizes(sizes); - - mMaterialModel = new QStringListModel(this); - - mMaterialProxyModel = new QSortFilterProxyModel(this); - mMaterialProxyModel->setSourceModel(mMaterialModel); - mMaterialProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); - mMaterialProxyModel->setDynamicSortFilter(true); - mMaterialProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); - - ui->materialList->setModel(mMaterialProxyModel); - ui->materialList->setSelectionMode(QAbstractItemView::SingleSelection); - ui->materialList->setEditTriggers(QAbstractItemView::NoEditTriggers); - ui->materialList->setAlternatingRowColors(true); - - connect(ui->materialList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), - this, SLOT(onMaterialSelectionChanged(QModelIndex,QModelIndex))); - - mMaterialPropertyModel = new QStandardItemModel(0, 2, this); - mMaterialPropertyModel->setHorizontalHeaderItem(0, new QStandardItem(QString("Name"))); - mMaterialPropertyModel->setHorizontalHeaderItem(1, new QStandardItem(QString("Value"))); - connect(mMaterialPropertyModel, SIGNAL(itemChanged(QStandardItem*)), - this, SLOT(onMaterialPropertyChanged(QStandardItem*))); - - mMaterialSortModel = new PropertySortModel(this); - mMaterialSortModel->setSourceModel(mMaterialPropertyModel); - mMaterialSortModel->setDynamicSortFilter(true); - mMaterialSortModel->setSortCaseSensitivity(Qt::CaseInsensitive); - - ui->materialView->setModel(mMaterialSortModel); - ui->materialView->setContextMenuPolicy(Qt::CustomContextMenu); - ui->materialView->setAlternatingRowColors(true); - ui->materialView->setSortingEnabled(true); - connect(ui->materialView, SIGNAL(customContextMenuRequested(QPoint)), - this, SLOT(onContextMenuRequested(QPoint))); - - mGlobalSettingsModel = new QStandardItemModel(0, 2, this); - mGlobalSettingsModel->setHorizontalHeaderItem(0, new QStandardItem(QString("Name"))); - mGlobalSettingsModel->setHorizontalHeaderItem(1, new QStandardItem(QString("Value"))); - connect(mGlobalSettingsModel, SIGNAL(itemChanged(QStandardItem*)), - this, SLOT(onGlobalSettingChanged(QStandardItem*))); - - ui->globalSettingsView->setModel(mGlobalSettingsModel); - ui->globalSettingsView->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents); - ui->globalSettingsView->verticalHeader()->hide(); - ui->globalSettingsView->horizontalHeader()->setResizeMode(QHeaderView::Stretch); - ui->globalSettingsView->setSelectionMode(QAbstractItemView::SingleSelection); - - ui->configurationList->setSelectionMode(QAbstractItemView::SingleSelection); - ui->configurationList->setEditTriggers(QAbstractItemView::NoEditTriggers); - connect(ui->configurationList, SIGNAL(currentTextChanged(QString)), - this, SLOT(onConfigurationSelectionChanged(QString))); - - mConfigurationModel = new QStandardItemModel(0, 2, this); - mConfigurationModel->setHorizontalHeaderItem(0, new QStandardItem(QString("Name"))); - mConfigurationModel->setHorizontalHeaderItem(1, new QStandardItem(QString("Value"))); - connect(mConfigurationModel, SIGNAL(itemChanged(QStandardItem*)), - this, SLOT(onConfigurationChanged(QStandardItem*))); - - ui->configurationView->setModel(mConfigurationModel); - ui->configurationView->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents); - ui->configurationView->verticalHeader()->hide(); - ui->configurationView->horizontalHeader()->setResizeMode(QHeaderView::Stretch); - ui->configurationView->setSelectionMode(QAbstractItemView::SingleSelection); -} - -sh::MainWindow::~MainWindow() -{ - delete ui; -} - -void sh::MainWindow::closeEvent(QCloseEvent *event) -{ - this->hide(); - event->ignore(); -} - -void sh::MainWindow::onIdle() -{ - if (mRequestShowWindow) - { - mRequestShowWindow = false; - show(); - } - - if (mRequestExit) - { - QApplication::exit(); - return; - } - - boost::mutex::scoped_lock lock(mSync->mUpdateMutex); - - - mIgnoreMaterialChange = true; - QString selected; - - QModelIndex selectedIndex = ui->materialList->selectionModel()->currentIndex(); - if (selectedIndex.isValid()) - selected = mMaterialModel->data(selectedIndex, Qt::DisplayRole).toString(); - - QStringList list; - - for (std::vector::const_iterator it = mState.mMaterialList.begin(); it != mState.mMaterialList.end(); ++it) - { - list.push_back(QString::fromStdString(*it)); - } - - if (mMaterialModel->stringList() != list) - { - mMaterialModel->setStringList(list); - - // quick hack to keep our selection when the model has changed - if (!selected.isEmpty()) - for (int i=0; irowCount(); ++i) - { - const QModelIndex& index = mMaterialModel->index(i,0); - if (mMaterialModel->data(index, Qt::DisplayRole).toString() == selected) - { - ui->materialList->setCurrentIndex(index); - break; - } - } - } - mIgnoreMaterialChange = false; - - mIgnoreGlobalSettingChange = true; - for (std::map::const_iterator it = mState.mGlobalSettingsMap.begin(); - it != mState.mGlobalSettingsMap.end(); ++it) - { - QList list = mGlobalSettingsModel->findItems(QString::fromStdString(it->first)); - if (!list.empty()) // item was already there - { - // if it changed, set the value column - if (mGlobalSettingsModel->data(mGlobalSettingsModel->index(list.front()->row(), 1)).toString() - != QString::fromStdString(it->second)) - { - mGlobalSettingsModel->setItem(list.front()->row(), 1, new QStandardItem(QString::fromStdString(it->second))); - } - } - else // item wasn't there; insert new row - { - QList toAdd; - QStandardItem* name = new QStandardItem(QString::fromStdString(it->first)); - name->setFlags(name->flags() &= ~Qt::ItemIsEditable); - QStandardItem* value = new QStandardItem(QString::fromStdString(it->second)); - toAdd.push_back(name); - toAdd.push_back(value); - mGlobalSettingsModel->appendRow(toAdd); - } - } - mIgnoreGlobalSettingChange = false; - - - mIgnoreConfigurationChange = true; - QList selected_ = ui->configurationList->selectedItems(); - QString selectedStr; - if (selected_.size()) - selectedStr = selected_.front()->text(); - - ui->configurationList->clear(); - - for (std::vector::const_iterator it = mState.mConfigurationList.begin(); it != mState.mConfigurationList.end(); ++it) - ui->configurationList->addItem(QString::fromStdString(*it)); - - if (!selectedStr.isEmpty()) - for (int i=0; iconfigurationList->count(); ++i) - { - if (ui->configurationList->item(i)->text() == selectedStr) - { - ui->configurationList->setCurrentItem(ui->configurationList->item(i), QItemSelectionModel::ClearAndSelect); - } - } - - mIgnoreConfigurationChange = false; - - if (!mState.mErrors.empty()) - { - ui->errorLog->append(QString::fromStdString(mState.mErrors)); - mState.mErrors = ""; - QColor color = ui->tabWidget->palette().color(QPalette::Normal, QPalette::Link); - ui->tabWidget->tabBar()->setTabTextColor(3, color); - } - - - // process query results - boost::mutex::scoped_lock lock2(mSync->mQueryMutex); - for (std::vector::iterator it = mQueries.begin(); it != mQueries.end();) - { - if ((*it)->mDone) - { - if (typeid(**it) == typeid(ConfigurationQuery)) - buildConfigurationModel(static_cast(*it)); - else if (typeid(**it) == typeid(MaterialQuery)) - buildMaterialModel(static_cast(*it)); - else if (typeid(**it) == typeid(MaterialPropertyQuery)) - { - MaterialPropertyQuery* q = static_cast(*it); - mIgnoreMaterialPropertyChange = true; - if (getSelectedMaterial().toStdString() == q->mName) - { - for (int i=0; irowCount(); ++i) - { - if (mMaterialPropertyModel->item(i,0)->text() == QString::fromStdString(q->mPropertyName)) - { - mMaterialPropertyModel->item(i,1)->setText(QString::fromStdString(q->mValue)); - if (mMaterialPropertyModel->item(i,1)->isCheckable()) - mMaterialPropertyModel->item(i,1)->setCheckState ((q->mValue == "true") - ? Qt::Checked : Qt::Unchecked); - } - } - } - mIgnoreMaterialPropertyChange = false; - } - - delete *it; - it = mQueries.erase(it); - } - else - ++it; - } -} - -void sh::MainWindow::onMaterialSelectionChanged (const QModelIndex & current, const QModelIndex & previous) -{ - if (mIgnoreMaterialChange) - return; - - QString name = getSelectedMaterial(); - if (!name.isEmpty()) - requestQuery(new sh::MaterialQuery(name.toStdString())); -} - -QString sh::MainWindow::getSelectedMaterial() -{ - QModelIndex selectedIndex = ui->materialList->selectionModel()->currentIndex(); - if (!selectedIndex.isValid()) - return QString(""); - - return mMaterialProxyModel->data(selectedIndex, Qt::DisplayRole).toString(); -} - -void sh::MainWindow::onConfigurationSelectionChanged (const QString& current) -{ - if (mIgnoreConfigurationChange) - return; - requestQuery(new sh::ConfigurationQuery(current.toStdString())); -} - -void sh::MainWindow::onGlobalSettingChanged(QStandardItem *item) -{ - if (mIgnoreGlobalSettingChange) - return; // we are only interested in changes by the user, not by the backend. - - std::string name = mGlobalSettingsModel->data(mGlobalSettingsModel->index(item->row(), 0)).toString().toStdString(); - std::string value = mGlobalSettingsModel->data(mGlobalSettingsModel->index(item->row(), 1)).toString().toStdString(); - - queueAction(new sh::ActionChangeGlobalSetting(name, value)); -} - -void sh::MainWindow::onConfigurationChanged (QStandardItem* item) -{ - QList items = ui->configurationList->selectedItems(); - if (items.size()) - { - std::string name = items.front()->text().toStdString(); - std::string key = mConfigurationModel->data(mConfigurationModel->index(item->row(), 0)).toString().toStdString(); - std::string value = mConfigurationModel->data(mConfigurationModel->index(item->row(), 1)).toString().toStdString(); - - queueAction(new sh::ActionChangeConfiguration(name, key, value)); - - requestQuery(new sh::ConfigurationQuery(name)); - } -} - -void sh::MainWindow::on_lineEdit_textEdited(const QString &arg1) -{ - mMaterialProxyModel->setFilterFixedString(arg1); -} - -void sh::MainWindow::on_actionSave_triggered() -{ - queueAction (new sh::ActionSaveAll()); -} - -void sh::MainWindow::on_actionNewMaterial_triggered() -{ - -} - -void sh::MainWindow::on_actionDeleteMaterial_triggered() -{ - QModelIndex selectedIndex = ui->materialList->selectionModel()->currentIndex(); - QString name = mMaterialProxyModel->data(selectedIndex, Qt::DisplayRole).toString(); - - queueAction (new sh::ActionDeleteMaterial(name.toStdString())); -} - -void sh::MainWindow::queueAction(Action* action) -{ - boost::mutex::scoped_lock lock(mSync->mActionMutex); - mActionQueue.push(action); -} - -void sh::MainWindow::requestQuery(Query *query) -{ - boost::mutex::scoped_lock lock(mSync->mActionMutex); - mQueries.push_back(query); -} - -void sh::MainWindow::on_actionQuit_triggered() -{ - hide(); -} - -void sh::MainWindow::on_actionNewConfiguration_triggered() -{ - QInputDialog dialog(this); - - QString text = QInputDialog::getText(this, tr("New Configuration"), - tr("Configuration name:")); - - if (!text.isEmpty()) - { - queueAction(new ActionCreateConfiguration(text.toStdString())); - } -} - -void sh::MainWindow::on_actionDeleteConfiguration_triggered() -{ - QList items = ui->configurationList->selectedItems(); - if (items.size()) - queueAction(new ActionDeleteConfiguration(items.front()->text().toStdString())); -} - -void sh::MainWindow::on_actionDeleteConfigurationProperty_triggered() -{ - QList items = ui->configurationList->selectedItems(); - if (items.empty()) - return; - std::string configurationName = items.front()->text().toStdString(); - - QModelIndex current = ui->configurationView->currentIndex(); - if (!current.isValid()) - return; - - std::string propertyName = mConfigurationModel->data(mConfigurationModel->index(current.row(), 0)).toString().toStdString(); - - queueAction(new sh::ActionDeleteConfigurationProperty(configurationName, propertyName)); - requestQuery(new sh::ConfigurationQuery(configurationName)); -} - -void sh::MainWindow::on_actionCloneMaterial_triggered() -{ - QModelIndex selectedIndex = ui->materialList->selectionModel()->currentIndex(); - QString name = mMaterialProxyModel->data(selectedIndex, Qt::DisplayRole).toString(); - if (name.isEmpty()) - return; - - QInputDialog dialog(this); - - QString text = QInputDialog::getText(this, tr("Clone material"), - tr("Name:")); - - if (!text.isEmpty()) - { - queueAction(new ActionCloneMaterial(name.toStdString(), text.toStdString())); - } -} - -void sh::MainWindow::onContextMenuRequested(const QPoint &point) -{ - QPoint globalPos = ui->materialView->viewport()->mapToGlobal(point); - - QMenu menu; - - QList actions; - actions.push_back(ui->actionNewProperty); - actions.push_back(ui->actionDeleteProperty); - actions.push_back(ui->actionCreatePass); - actions.push_back(ui->actionCreateTextureUnit); - menu.addActions(actions); - - menu.exec(globalPos); -} - -void sh::MainWindow::getContext(QModelIndex index, int* passIndex, int* textureIndex, bool* isInPass, bool* isInTextureUnit) -{ - if (passIndex) - { - *passIndex = 0; - if (isInPass) - *isInPass = false; - QModelIndex passModelIndex = index; - // go up until we find the pass item. - while (getPropertyKey(passModelIndex) != "pass" && passModelIndex.isValid()) - passModelIndex = passModelIndex.parent(); - - if (passModelIndex.isValid()) - { - if (passModelIndex.column() != 0) - passModelIndex = passModelIndex.parent().child(passModelIndex.row(), 0); - for (int i=0; irowCount(); ++i) - { - if (mMaterialPropertyModel->data(mMaterialPropertyModel->index(i, 0)).toString() == QString("pass")) - { - if (mMaterialPropertyModel->index(i, 0) == passModelIndex) - { - if (isInPass) - *isInPass = true; - break; - } - ++(*passIndex); - } - } - } - } - if (textureIndex) - { - *textureIndex = 0; - if (isInTextureUnit) - *isInTextureUnit = false; - QModelIndex texModelIndex = index; - // go up until we find the texture_unit item. - while (getPropertyKey(texModelIndex) != "texture_unit" && texModelIndex.isValid()) - texModelIndex = texModelIndex.parent(); - if (texModelIndex.isValid()) - { - if (texModelIndex.column() != 0) - texModelIndex = texModelIndex.parent().child(texModelIndex.row(), 0); - for (int i=0; irowCount(texModelIndex.parent()); ++i) - { - if (texModelIndex.parent().child(i, 0).data().toString() == QString("texture_unit")) - { - if (texModelIndex.parent().child(i, 0) == texModelIndex) - { - if (isInTextureUnit) - *isInTextureUnit = true; - break; - } - ++(*textureIndex); - } - } - } - } -} - -std::string sh::MainWindow::getPropertyKey(QModelIndex index) -{ - if (!index.parent().isValid()) - return mMaterialPropertyModel->data(mMaterialPropertyModel->index(index.row(), 0)).toString().toStdString(); - else - return index.parent().child(index.row(), 0).data().toString().toStdString(); -} - -std::string sh::MainWindow::getPropertyValue(QModelIndex index) -{ - if (!index.parent().isValid()) - return mMaterialPropertyModel->data(mMaterialPropertyModel->index(index.row(), 1)).toString().toStdString(); - else - return index.parent().child(index.row(), 1).data().toString().toStdString(); -} - -void sh::MainWindow::onMaterialPropertyChanged(QStandardItem *item) -{ - if (mIgnoreMaterialPropertyChange) - return; - - QString material = getSelectedMaterial(); - if (material.isEmpty()) - return; - - // handle checkboxes being checked/unchecked - std::string value = getPropertyValue(item->index()); - if (item->data(Qt::UserRole).toInt() == MaterialProperty::Boolean) - { - if (item->checkState() == Qt::Checked && value != "true") - value = "true"; - else if (item->checkState() == Qt::Unchecked && value == "true") - value = "false"; - item->setText(QString::fromStdString(value)); - } - - // handle inherited properties being changed, i.e. overridden by the current (derived) material - if (item->data(Qt::UserRole+1).toInt() == MaterialProperty::Inherited_Unchanged) - { - QColor normalColor = ui->materialView->palette().color(QPalette::Normal, QPalette::WindowText); - mIgnoreMaterialPropertyChange = true; - mMaterialPropertyModel->item(item->index().row(), 0) - ->setData(QVariant(MaterialProperty::Inherited_Changed), Qt::UserRole+1); - mMaterialPropertyModel->item(item->index().row(), 0) - ->setData(normalColor, Qt::ForegroundRole); - mMaterialPropertyModel->item(item->index().row(), 1) - ->setData(QVariant(MaterialProperty::Inherited_Changed), Qt::UserRole+1); - mMaterialPropertyModel->item(item->index().row(), 1) - ->setData(normalColor, Qt::ForegroundRole); - mIgnoreMaterialPropertyChange = false; - - ui->materialView->scrollTo(mMaterialSortModel->mapFromSource(item->index())); - } - - if (!item->index().parent().isValid()) - { - // top level material property - queueAction(new ActionSetMaterialProperty( - material.toStdString(), getPropertyKey(item->index()), value)); - } - else if (getPropertyKey(item->index()) == "texture_unit") - { - // texture unit name changed - int passIndex, textureIndex; - getContext(item->index(), &passIndex, &textureIndex); - std::cout << "passIndex " << passIndex << " " << textureIndex << std::endl; - - queueAction(new ActionChangeTextureUnitName( - material.toStdString(), passIndex, textureIndex, value)); - - } - else if (item->index().parent().data().toString() == "pass") - { - // pass property - int passIndex; - getContext(item->index(), &passIndex, NULL); - /// \todo if shaders are changed, check that the material provides all properties needed by the shader - queueAction(new ActionSetPassProperty( - material.toStdString(), passIndex, getPropertyKey(item->index()), value)); - } - else if (item->index().parent().data().toString() == "shader_properties") - { - // shader property - int passIndex; - getContext(item->index(), &passIndex, NULL); - queueAction(new ActionSetShaderProperty( - material.toStdString(), passIndex, getPropertyKey(item->index()), value)); - } - else if (item->index().parent().data().toString() == "texture_unit") - { - // texture property - int passIndex, textureIndex; - getContext(item->index(), &passIndex, &textureIndex); - queueAction(new ActionSetTextureProperty( - material.toStdString(), passIndex, textureIndex, getPropertyKey(item->index()), value)); - } -} - -void sh::MainWindow::buildMaterialModel(MaterialQuery *data) -{ - mMaterialPropertyModel->clear(); - - mMaterialPropertyModel->setHorizontalHeaderItem(0, new QStandardItem(QString("Name"))); - mMaterialPropertyModel->setHorizontalHeaderItem(1, new QStandardItem(QString("Value"))); - - for (std::map::const_iterator it = data->mProperties.begin(); - it != data->mProperties.end(); ++it) - { - addProperty(mMaterialPropertyModel->invisibleRootItem(), it->first, it->second); - } - - for (std::vector::iterator it = data->mPasses.begin(); - it != data->mPasses.end(); ++it) - { - QStandardItem* passItem = new QStandardItem (QString("pass")); - passItem->setFlags(passItem->flags() &= ~Qt::ItemIsEditable); - passItem->setData(QVariant(static_cast(MaterialProperty::Object)), Qt::UserRole); - - if (it->mShaderProperties.size()) - { - QStandardItem* shaderPropertiesItem = new QStandardItem (QString("shader_properties")); - shaderPropertiesItem->setFlags(shaderPropertiesItem->flags() &= ~Qt::ItemIsEditable); - shaderPropertiesItem->setData(QVariant(static_cast(MaterialProperty::Object)), Qt::UserRole); - - for (std::map::iterator pit = it->mShaderProperties.begin(); - pit != it->mShaderProperties.end(); ++pit) - { - addProperty(shaderPropertiesItem, pit->first, pit->second); - } - passItem->appendRow(shaderPropertiesItem); - } - - for (std::map::iterator pit = it->mProperties.begin(); - pit != it->mProperties.end(); ++pit) - { - addProperty(passItem, pit->first, pit->second); - } - - for (std::vector::iterator tIt = it->mTextureUnits.begin(); - tIt != it->mTextureUnits.end(); ++tIt) - { - QStandardItem* unitItem = new QStandardItem (QString("texture_unit")); - unitItem->setFlags(unitItem->flags() &= ~Qt::ItemIsEditable); - unitItem->setData(QVariant(static_cast(MaterialProperty::Object)), Qt::UserRole); - QStandardItem* nameItem = new QStandardItem (QString::fromStdString(tIt->mName)); - nameItem->setData(QVariant(static_cast(MaterialProperty::Object)), Qt::UserRole); - - QList texUnit; - texUnit << unitItem << nameItem; - - for (std::map::iterator pit = tIt->mProperties.begin(); - pit != tIt->mProperties.end(); ++pit) - { - addProperty(unitItem, pit->first, pit->second); - } - - passItem->appendRow(texUnit); - } - - QList toAdd; - toAdd << passItem; - toAdd << new QStandardItem(QString("")); - mMaterialPropertyModel->appendRow(toAdd); - } - - ui->materialView->expandAll(); - ui->materialView->resizeColumnToContents(0); - ui->materialView->resizeColumnToContents(1); -} - -void sh::MainWindow::addProperty(QStandardItem *parent, const std::string &key, MaterialProperty value, bool scrollTo) -{ - QList toAdd; - QStandardItem* keyItem = new QStandardItem(QString::fromStdString(key)); - keyItem->setFlags(keyItem->flags() &= ~Qt::ItemIsEditable); - keyItem->setData(QVariant(value.mType), Qt::UserRole); - keyItem->setData(QVariant(value.mSource), Qt::UserRole+1); - toAdd.push_back(keyItem); - - QStandardItem* valueItem = NULL; - if (value.mSource != MaterialProperty::None) - { - valueItem = new QStandardItem(QString::fromStdString(value.mValue)); - valueItem->setData(QVariant(value.mType), Qt::UserRole); - valueItem->setData(QVariant(value.mSource), Qt::UserRole+1); - toAdd.push_back(valueItem); - } - - - if (value.mSource == MaterialProperty::Inherited_Unchanged) - { - QColor color = ui->configurationView->palette().color(QPalette::Disabled, QPalette::WindowText); - keyItem->setData(color, Qt::ForegroundRole); - if (valueItem) - valueItem->setData(color, Qt::ForegroundRole); - } - if (value.mType == MaterialProperty::Boolean && valueItem) - { - valueItem->setCheckable(true); - valueItem->setCheckState((value.mValue == "true") ? Qt::Checked : Qt::Unchecked); - } - - parent->appendRow(toAdd); - - if (scrollTo) - ui->materialView->scrollTo(mMaterialSortModel->mapFromSource(keyItem->index())); -} - -void sh::MainWindow::buildConfigurationModel(ConfigurationQuery *data) -{ - while (mConfigurationModel->rowCount()) - mConfigurationModel->removeRow(0); - for (std::map::iterator it = data->mProperties.begin(); - it != data->mProperties.end(); ++it) - { - QList toAdd; - QStandardItem* name = new QStandardItem(QString::fromStdString(it->first)); - name->setFlags(name->flags() &= ~Qt::ItemIsEditable); - QStandardItem* value = new QStandardItem(QString::fromStdString(it->second)); - toAdd.push_back(name); - toAdd.push_back(value); - mConfigurationModel->appendRow(toAdd); - } - - // add items that are in global settings, but not in this configuration (with a "inactive" color) - for (std::map::const_iterator it = mState.mGlobalSettingsMap.begin(); - it != mState.mGlobalSettingsMap.end(); ++it) - { - if (data->mProperties.find(it->first) == data->mProperties.end()) - { - QColor color = ui->configurationView->palette().color(QPalette::Disabled, QPalette::WindowText); - QList toAdd; - QStandardItem* name = new QStandardItem(QString::fromStdString(it->first)); - name->setFlags(name->flags() &= ~Qt::ItemIsEditable); - name->setData(color, Qt::ForegroundRole); - QStandardItem* value = new QStandardItem(QString::fromStdString(it->second)); - value->setData(color, Qt::ForegroundRole); - toAdd.push_back(name); - toAdd.push_back(value); - mConfigurationModel->appendRow(toAdd); - } - } -} - -void sh::MainWindow::on_actionCreatePass_triggered() -{ - QString material = getSelectedMaterial(); - if (!material.isEmpty()) - { - addProperty(mMaterialPropertyModel->invisibleRootItem(), - "pass", MaterialProperty("", MaterialProperty::Object, MaterialProperty::None), true); - - queueAction (new ActionCreatePass(material.toStdString())); - } -} - -void sh::MainWindow::on_actionDeleteProperty_triggered() -{ - QModelIndex selectedIndex = mMaterialSortModel->mapToSource(ui->materialView->selectionModel()->currentIndex()); - QString material = getSelectedMaterial(); - if (material.isEmpty()) - return; - - mIgnoreMaterialPropertyChange = true; - - if (getPropertyKey(selectedIndex) == "pass") - { - // delete whole pass - int passIndex; - getContext(selectedIndex, &passIndex, NULL); - if (passIndex == 0) - { - QMessageBox msgBox; - msgBox.setText("The first pass can not be deleted."); - msgBox.exec(); - } - else - { - queueAction(new ActionDeletePass(material.toStdString(), passIndex)); - mMaterialPropertyModel->removeRow(selectedIndex.row(), selectedIndex.parent()); - } - } - else if (getPropertyKey(selectedIndex) == "texture_unit") - { - // delete whole texture unit - int passIndex, textureIndex; - getContext(selectedIndex, &passIndex, &textureIndex); - queueAction(new ActionDeleteTextureUnit(material.toStdString(), passIndex, textureIndex)); - mMaterialPropertyModel->removeRow(selectedIndex.row(), selectedIndex.parent()); - } - else if (!selectedIndex.parent().isValid()) - { - // top level material property - MaterialProperty::Source source = static_cast( - mMaterialPropertyModel->itemFromIndex(selectedIndex)->data(Qt::UserRole+1).toInt()); - if (source == MaterialProperty::Inherited_Unchanged) - { - QMessageBox msgBox; - msgBox.setText("Inherited properties can not be deleted."); - msgBox.exec(); - } - else - { - queueAction(new ActionDeleteMaterialProperty( - material.toStdString(), getPropertyKey(selectedIndex))); - std::cout << "source is " << source << std::endl; - if (source == MaterialProperty::Inherited_Changed) - { - QColor inactiveColor = ui->materialView->palette().color(QPalette::Disabled, QPalette::WindowText); - mMaterialPropertyModel->item(selectedIndex.row(), 0) - ->setData(QVariant(MaterialProperty::Inherited_Unchanged), Qt::UserRole+1); - mMaterialPropertyModel->item(selectedIndex.row(), 0) - ->setData(inactiveColor, Qt::ForegroundRole); - mMaterialPropertyModel->item(selectedIndex.row(), 1) - ->setData(QVariant(MaterialProperty::Inherited_Unchanged), Qt::UserRole+1); - mMaterialPropertyModel->item(selectedIndex.row(), 1) - ->setData(inactiveColor, Qt::ForegroundRole); - - // make sure to update the property's value - requestQuery(new sh::MaterialPropertyQuery(material.toStdString(), getPropertyKey(selectedIndex))); - } - else - mMaterialPropertyModel->removeRow(selectedIndex.row()); - } - } - else if (selectedIndex.parent().data().toString() == "pass") - { - // pass property - int passIndex; - getContext(selectedIndex, &passIndex, NULL); - queueAction(new ActionDeletePassProperty( - material.toStdString(), passIndex, getPropertyKey(selectedIndex))); - mMaterialPropertyModel->removeRow(selectedIndex.row(), selectedIndex.parent()); - } - else if (selectedIndex.parent().data().toString() == "shader_properties") - { - // shader property - int passIndex; - getContext(selectedIndex, &passIndex, NULL); - queueAction(new ActionDeleteShaderProperty( - material.toStdString(), passIndex, getPropertyKey(selectedIndex))); - mMaterialPropertyModel->removeRow(selectedIndex.row(), selectedIndex.parent()); - } - else if (selectedIndex.parent().data().toString() == "texture_unit") - { - // texture property - int passIndex, textureIndex; - getContext(selectedIndex, &passIndex, &textureIndex); - queueAction(new ActionDeleteTextureProperty( - material.toStdString(), passIndex, textureIndex, getPropertyKey(selectedIndex))); - mMaterialPropertyModel->removeRow(selectedIndex.row(), selectedIndex.parent()); - } - mIgnoreMaterialPropertyChange = false; -} - -void sh::MainWindow::on_actionNewProperty_triggered() -{ - QModelIndex selectedIndex = mMaterialSortModel->mapToSource(ui->materialView->selectionModel()->currentIndex()); - QString material = getSelectedMaterial(); - if (material.isEmpty()) - return; - - AddPropertyDialog* dialog = new AddPropertyDialog(this); - dialog->exec(); - QString propertyName = dialog->mName; - QString defaultValue = ""; - - /// \todo check if this property name exists already - - if (!propertyName.isEmpty()) - { - int passIndex, textureIndex; - bool isInPass, isInTextureUnit; - getContext(selectedIndex, &passIndex, &textureIndex, &isInPass, &isInTextureUnit); - - QList items; - QStandardItem* keyItem = new QStandardItem(propertyName); - keyItem->setFlags(keyItem->flags() &= ~Qt::ItemIsEditable); - items << keyItem; - items << new QStandardItem(defaultValue); - - // figure out which item the new property should be a child of - QModelIndex parentIndex = selectedIndex; - if (selectedIndex.data(Qt::UserRole) != MaterialProperty::Object) - parentIndex = selectedIndex.parent(); - QStandardItem* parentItem; - if (!parentIndex.isValid()) - parentItem = mMaterialPropertyModel->invisibleRootItem(); - else - parentItem = mMaterialPropertyModel->itemFromIndex(parentIndex); - - if (isInTextureUnit) - { - queueAction(new ActionSetTextureProperty( - material.toStdString(), passIndex, textureIndex, propertyName.toStdString(), defaultValue.toStdString())); - } - else if (isInPass) - { - if (selectedIndex.parent().child(selectedIndex.row(),0).data().toString() == "shader_properties" - || selectedIndex.parent().data().toString() == "shader_properties") - { - queueAction(new ActionSetShaderProperty( - material.toStdString(), passIndex, propertyName.toStdString(), defaultValue.toStdString())); - } - else - { - queueAction(new ActionSetPassProperty( - material.toStdString(), passIndex, propertyName.toStdString(), defaultValue.toStdString())); - } - } - else - { - queueAction(new ActionSetMaterialProperty( - material.toStdString(), propertyName.toStdString(), defaultValue.toStdString())); - } - - addProperty(parentItem, propertyName.toStdString(), - MaterialProperty (defaultValue.toStdString(), MaterialProperty::Misc, MaterialProperty::Normal), true); - - /// \todo scroll to newly added property - } -} - -void sh::MainWindow::on_actionCreateTextureUnit_triggered() -{ - QString material = getSelectedMaterial(); - if (material.isEmpty()) - return; - - QInputDialog dialog(this); - - QString text = QInputDialog::getText(this, tr("New texture unit"), - tr("Texture unit name (for referencing in shaders):")); - if (!text.isEmpty()) - { - QModelIndex selectedIndex = mMaterialSortModel->mapToSource(ui->materialView->selectionModel()->currentIndex()); - int passIndex; - getContext(selectedIndex, &passIndex, NULL); - queueAction(new ActionCreateTextureUnit(material.toStdString(), passIndex, text.toStdString())); - - // add to model - int index = 0; - for (int i=0; irowCount(); ++i) - { - if (mMaterialPropertyModel->data(mMaterialPropertyModel->index(i, 0)).toString() == QString("pass")) - { - if (index == passIndex) - { - addProperty(mMaterialPropertyModel->itemFromIndex(mMaterialPropertyModel->index(i, 0)), - "texture_unit", MaterialProperty(text.toStdString(), MaterialProperty::Object), true); - break; - } - - ++index; - } - } - } -} - -void sh::MainWindow::on_clearButton_clicked() -{ - ui->errorLog->clear(); -} - -void sh::MainWindow::on_tabWidget_currentChanged(int index) -{ - QColor color = ui->tabWidget->palette().color(QPalette::Normal, QPalette::WindowText); - - if (index == 3) - ui->tabWidget->tabBar()->setTabTextColor(3, color); -} diff --git a/extern/shiny/Editor/MainWindow.hpp b/extern/shiny/Editor/MainWindow.hpp deleted file mode 100644 index 3f0dc295c0..0000000000 --- a/extern/shiny/Editor/MainWindow.hpp +++ /dev/null @@ -1,139 +0,0 @@ -#ifndef SHINY_EDITOR_MAINWINDOW_HPP -#define SHINY_EDITOR_MAINWINDOW_HPP - -#include - -#include -#include -#include - -#include - -#include "Actions.hpp" -#include "Query.hpp" - -#include "PropertySortModel.hpp" - -namespace Ui { -class MainWindow; -} - -namespace sh -{ - -struct SynchronizationState; - - -/** - * @brief A snapshot of the material system's state. Lock the mUpdateMutex before accessing. - */ -struct MaterialSystemState -{ - std::vector mMaterialList; - - std::map mGlobalSettingsMap; - - std::vector mConfigurationList; - - std::vector mMaterialFiles; - std::vector mConfigurationFiles; - - std::vector mShaderSets; - - std::string mErrors; -}; - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent = 0); - ~MainWindow(); - - // really should be an std::atomic - volatile bool mRequestShowWindow; - // dito - volatile bool mRequestExit; - - SynchronizationState* mSync; - - /// \todo Is there a better way to ignore manual model changes? - bool mIgnoreGlobalSettingChange; - bool mIgnoreConfigurationChange; - bool mIgnoreMaterialChange; - bool mIgnoreMaterialPropertyChange; - - std::queue mActionQueue; - std::vector mQueries; - - MaterialSystemState mState; - -private: - Ui::MainWindow *ui; - - // material tab - QStringListModel* mMaterialModel; - QSortFilterProxyModel* mMaterialProxyModel; - - QStandardItemModel* mMaterialPropertyModel; - PropertySortModel* mMaterialSortModel; - - // global settings tab - QStandardItemModel* mGlobalSettingsModel; - - // configuration tab - QStandardItemModel* mConfigurationModel; - - void queueAction(Action* action); - void requestQuery(Query* query); - - void buildMaterialModel (MaterialQuery* data); - void buildConfigurationModel (ConfigurationQuery* data); - - QString getSelectedMaterial(); - - /// get the context of an index in the material property model - void getContext(QModelIndex index, int* passIndex, int* textureIndex, bool* isInPass=NULL, bool* isInTextureUnit=NULL); - - std::string getPropertyKey(QModelIndex index); - std::string getPropertyValue(QModelIndex index); - - void addProperty (QStandardItem* parent, const std::string& key, MaterialProperty value, bool scrollTo=false); - -protected: - void closeEvent(QCloseEvent *event); - -public slots: - void onIdle(); - - void onMaterialSelectionChanged (const QModelIndex & current, const QModelIndex & previous); - void onConfigurationSelectionChanged (const QString& current); - - void onGlobalSettingChanged (QStandardItem* item); - void onConfigurationChanged (QStandardItem* item); - void onMaterialPropertyChanged (QStandardItem* item); - - void onContextMenuRequested(const QPoint& point); - -private slots: - void on_lineEdit_textEdited(const QString &arg1); - void on_actionSave_triggered(); - void on_actionNewMaterial_triggered(); - void on_actionDeleteMaterial_triggered(); - void on_actionQuit_triggered(); - void on_actionNewConfiguration_triggered(); - void on_actionDeleteConfiguration_triggered(); - void on_actionDeleteConfigurationProperty_triggered(); - void on_actionCloneMaterial_triggered(); - void on_actionCreatePass_triggered(); - void on_actionDeleteProperty_triggered(); - void on_actionNewProperty_triggered(); - void on_actionCreateTextureUnit_triggered(); - void on_clearButton_clicked(); - void on_tabWidget_currentChanged(int index); -}; - -} - -#endif // MAINWINDOW_HPP diff --git a/extern/shiny/Editor/NewMaterialDialog.cpp b/extern/shiny/Editor/NewMaterialDialog.cpp deleted file mode 100644 index f1a716a9f0..0000000000 --- a/extern/shiny/Editor/NewMaterialDialog.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "NewMaterialDialog.hpp" -#include "ui_newmaterialdialog.h" - -NewMaterialDialog::NewMaterialDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::NewMaterialDialog) -{ - ui->setupUi(this); -} - -NewMaterialDialog::~NewMaterialDialog() -{ - delete ui; -} diff --git a/extern/shiny/Editor/NewMaterialDialog.hpp b/extern/shiny/Editor/NewMaterialDialog.hpp deleted file mode 100644 index 2a20bbb395..0000000000 --- a/extern/shiny/Editor/NewMaterialDialog.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef NEWMATERIALDIALOG_HPP -#define NEWMATERIALDIALOG_HPP - -#include - -namespace Ui { -class NewMaterialDialog; -} - -class NewMaterialDialog : public QDialog -{ - Q_OBJECT - -public: - explicit NewMaterialDialog(QWidget *parent = 0); - ~NewMaterialDialog(); - -private: - Ui::NewMaterialDialog *ui; -}; - -#endif // NEWMATERIALDIALOG_HPP diff --git a/extern/shiny/Editor/PropertySortModel.cpp b/extern/shiny/Editor/PropertySortModel.cpp deleted file mode 100644 index 637fe11b02..0000000000 --- a/extern/shiny/Editor/PropertySortModel.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "PropertySortModel.hpp" - -#include "Query.hpp" - -#include -sh::PropertySortModel::PropertySortModel(QObject *parent) - : QSortFilterProxyModel(parent) -{ -} - -bool sh::PropertySortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const -{ - if (left.data(Qt::UserRole+1).toInt() != 0 && right.data(Qt::UserRole+1).toInt() != 0) - { - int sourceL = left.data(Qt::UserRole+1).toInt(); - int sourceR = right.data(Qt::UserRole+1).toInt(); - - if (sourceL > sourceR) - return true; - else if (sourceR > sourceL) - return false; - } - - int typeL = left.data(Qt::UserRole).toInt(); - int typeR = right.data(Qt::UserRole).toInt(); - - if (typeL > typeR) - return true; - else if (typeR > typeL) - return false; - - QString nameL = left.data().toString(); - QString nameR = right.data().toString(); - return nameL > nameR; -} diff --git a/extern/shiny/Editor/PropertySortModel.hpp b/extern/shiny/Editor/PropertySortModel.hpp deleted file mode 100644 index f96927764e..0000000000 --- a/extern/shiny/Editor/PropertySortModel.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef SHINY_EDITOR_PROPERTYSORTMODEL_H -#define SHINY_EDITOR_PROPERTYSORTMODEL_H - -#include - -namespace sh -{ - - class PropertySortModel : public QSortFilterProxyModel - { - Q_OBJECT - - public: - PropertySortModel(QObject* parent); - protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const; - }; - -} - -#endif diff --git a/extern/shiny/Editor/Query.cpp b/extern/shiny/Editor/Query.cpp deleted file mode 100644 index ccf07b62bc..0000000000 --- a/extern/shiny/Editor/Query.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#include "Query.hpp" - -#include "../Main/Factory.hpp" - -namespace sh -{ - -void Query::execute() -{ - executeImpl(); - mDone = true; -} - -ConfigurationQuery::ConfigurationQuery(const std::string &name) - : mName(name) -{ -} - -void ConfigurationQuery::executeImpl() -{ - sh::Factory::getInstance().listConfigurationSettings(mName, mProperties); -} - -void MaterialQuery::executeImpl() -{ - sh::MaterialInstance* instance = sh::Factory::getInstance().getMaterialInstance(mName); - - if (instance->getParent()) - mParent = static_cast(instance->getParent())->getName(); - - // add the inherited properties - sh::PropertySetGet* parent = instance; - std::vector inheritedPropertiesVector; - while (parent->getParent()) - { - parent = parent->getParent(); - const sh::PropertyMap& parentProperties = parent->listProperties(); - - for (PropertyMap::const_iterator it = parentProperties.begin(); it != parentProperties.end(); ++it) - { - MaterialProperty::Source source = MaterialProperty::Inherited_Unchanged; - MaterialProperty::Type type = getType(it->first, parent->getProperty(it->first)); - mProperties[it->first] = MaterialProperty ( - retrieveValue(parent->getProperty(it->first), NULL).get(), - type, source); - inheritedPropertiesVector.push_back(it->first); - } - } - - // add our properties - const sh::PropertyMap& ourProperties = instance->listProperties(); - for (PropertyMap::const_iterator it = ourProperties.begin(); it != ourProperties.end(); ++it) - { - MaterialProperty::Source source = - (std::find(inheritedPropertiesVector.begin(), inheritedPropertiesVector.end(), it->first) - != inheritedPropertiesVector.end()) ? - MaterialProperty::Inherited_Changed : MaterialProperty::Normal; - MaterialProperty::Type type = getType(it->first, instance->getProperty(it->first)); - mProperties[it->first] = MaterialProperty ( - retrieveValue(instance->getProperty(it->first), NULL).get(), - type, source); - } - - std::vector* passes = instance->getPasses(); - for (std::vector::iterator it = passes->begin(); it != passes->end(); ++it) - { - mPasses.push_back(PassInfo()); - - const sh::PropertyMap& passProperties = it->listProperties(); - for (PropertyMap::const_iterator pit = passProperties.begin(); pit != passProperties.end(); ++pit) - { - PropertyValuePtr property = it->getProperty(pit->first); - MaterialProperty::Type type = getType(pit->first, property); - if (typeid(*property).name() == typeid(sh::LinkedValue).name()) - mPasses.back().mProperties[pit->first] = MaterialProperty("$" + property->_getStringValue(), type); - else - mPasses.back().mProperties[pit->first] = MaterialProperty( - retrieveValue(property, NULL).get(), type); - } - - const sh::PropertyMap& shaderProperties = it->mShaderProperties.listProperties(); - for (PropertyMap::const_iterator pit = shaderProperties.begin(); pit != shaderProperties.end(); ++pit) - { - PropertyValuePtr property = it->mShaderProperties.getProperty(pit->first); - MaterialProperty::Type type = getType(pit->first, property); - if (typeid(*property).name() == typeid(sh::LinkedValue).name()) - mPasses.back().mShaderProperties[pit->first] = MaterialProperty("$" + property->_getStringValue(), type); - else - mPasses.back().mShaderProperties[pit->first] = MaterialProperty( - retrieveValue(property, NULL).get(), type); - } - - std::vector* texUnits = &it->mTexUnits; - for (std::vector::iterator tIt = texUnits->begin(); tIt != texUnits->end(); ++tIt) - { - mPasses.back().mTextureUnits.push_back(TextureUnitInfo()); - mPasses.back().mTextureUnits.back().mName = tIt->getName(); - const sh::PropertyMap& unitProperties = tIt->listProperties(); - for (PropertyMap::const_iterator pit = unitProperties.begin(); pit != unitProperties.end(); ++pit) - { - PropertyValuePtr property = tIt->getProperty(pit->first); - MaterialProperty::Type type = getType(pit->first, property); - if (typeid(*property).name() == typeid(sh::LinkedValue).name()) - mPasses.back().mTextureUnits.back().mProperties[pit->first] = MaterialProperty( - "$" + property->_getStringValue(), MaterialProperty::Linked); - else - mPasses.back().mTextureUnits.back().mProperties[pit->first] = MaterialProperty( - retrieveValue(property, NULL).get(), type); - } - } - } -} - -MaterialProperty::Type MaterialQuery::getType(const std::string &key, PropertyValuePtr value) -{ - if (typeid(*value).name() == typeid(sh::LinkedValue).name()) - return MaterialProperty::Linked; - - if (key == "vertex_program" || key == "fragment_program") - return MaterialProperty::Shader; - - std::string valueStr = retrieveValue(value, NULL).get(); - - if (valueStr == "false" || valueStr == "true") - return MaterialProperty::Boolean; -} - -void MaterialPropertyQuery::executeImpl() -{ - sh::MaterialInstance* m = sh::Factory::getInstance().getMaterialInstance(mName); - mValue = retrieveValue(m->getProperty(mPropertyName), m).get(); -} - -} diff --git a/extern/shiny/Editor/Query.hpp b/extern/shiny/Editor/Query.hpp deleted file mode 100644 index d98c8c9b27..0000000000 --- a/extern/shiny/Editor/Query.hpp +++ /dev/null @@ -1,121 +0,0 @@ -#ifndef SH_QUERY_H -#define SH_QUERY_H - -#include -#include -#include - -#include "../Main/PropertyBase.hpp" - -namespace sh -{ - -class Query -{ -public: - Query() - : mDone(false) {} - virtual ~Query() {} - - void execute(); - - bool mDone; - -protected: - virtual void executeImpl() = 0; -}; - -class ConfigurationQuery : public Query -{ -public: - ConfigurationQuery(const std::string& name); - - std::map mProperties; -protected: - std::string mName; - virtual void executeImpl(); -}; - - -struct MaterialProperty -{ - - enum Type - { - Texture, - Color, - Boolean, - Shader, - Misc, - Linked, - Object // child object, i.e. pass, texture unit, shader properties - }; - - enum Source - { - Normal, - Inherited_Changed, - Inherited_Unchanged, - None // there is no property source (e.g. a pass, which does not have a name) - }; - - MaterialProperty() {} - MaterialProperty (const std::string& value, Type type, Source source=Normal) - : mValue(value), mType(type), mSource(source) {} - - std::string mValue; - Type mType; - Source mSource; -}; - - -struct TextureUnitInfo -{ - std::string mName; - std::map mProperties; -}; - -struct PassInfo -{ - std::map mShaderProperties; - - std::map mProperties; - std::vector mTextureUnits; -}; - -class MaterialQuery : public Query -{ -public: - MaterialQuery(const std::string& name) - : mName(name) {} - - std::string mParent; - std::vector mPasses; - std::map mProperties; - -protected: - std::string mName; - virtual void executeImpl(); - - MaterialProperty::Type getType (const std::string& key, PropertyValuePtr value); -}; - -class MaterialPropertyQuery : public Query -{ -public: - MaterialPropertyQuery(const std::string& name, const std::string& propertyName) - : mName(name), mPropertyName(propertyName) - { - } - - std::string mValue; - - std::string mName; - std::string mPropertyName; -protected: - virtual void executeImpl(); -}; - -} - -#endif diff --git a/extern/shiny/Editor/addpropertydialog.ui b/extern/shiny/Editor/addpropertydialog.ui deleted file mode 100644 index 63de7d1415..0000000000 --- a/extern/shiny/Editor/addpropertydialog.ui +++ /dev/null @@ -1,118 +0,0 @@ - - - AddPropertyDialog - - - - 0 - 0 - 257 - 133 - - - - Dialog - - - - - - - - - - - - - Property name - - - - - - - Editing widget - - - - - - - - Checkbox - - - - - Shader - - - - - Color - - - - - Texture - - - - - Other - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - - buttonBox - accepted() - AddPropertyDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - AddPropertyDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/extern/shiny/Editor/mainwindow.ui b/extern/shiny/Editor/mainwindow.ui deleted file mode 100644 index b27c8357de..0000000000 --- a/extern/shiny/Editor/mainwindow.ui +++ /dev/null @@ -1,420 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 647 - 512 - - - - MainWindow - - - - - - - 0 - - - - - - - Materials - - - - - - Qt::Horizontal - - - - - - - - - - Search - - - - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - - 16 - 16 - - - - - - - - - - - - - - - - 1 - 0 - - - - - - - - - 1 - 0 - - - - - 16 - 16 - - - - Qt::ToolButtonIconOnly - - - - - - - - - - - - - - Global settings - - - - - - - - - - Configurations - - - - - - Qt::Horizontal - - - - - - - - - - - 16 - 16 - - - - - - - - - - - - - - - - - - 16 - 16 - - - - - - - - - - - - - - Errors - - - - - - - - true - - - - - - - - 0 - 0 - - - - Clear - - - - - - - - - - - - - - - - - - - - - 0 - 0 - 647 - 25 - - - - false - - - - File - - - - - - - Material - - - - - - - - - History - - - - - - - - - - - - - - Quit - - - - - - - - - - Save - - - Save all - - - - - - - - - - Delete - - - Delete selected material - - - - - Change parent... - - - - - - - - - - New - - - Create a new material - - - - - - - - - - Clone - - - Clone selected material - - - - - - - - - - Delete - - - Delete selected configuration - - - Del - - - - - - - - - - New - - - Create a new configuration - - - - - - - - - - Delete - - - Delete property - - - - - - - - - - Delete - - - Delete item - - - - - - - - - - New property - - - - - - - - - - Create pass - - - - - - - - - - Create texture unit - - - - - - sh::ColoredTabWidget - QTabWidget -
ColoredTabWidget.hpp
- 1 -
-
- - -
diff --git a/extern/shiny/Editor/newmaterialdialog.ui b/extern/shiny/Editor/newmaterialdialog.ui deleted file mode 100644 index f24561cf79..0000000000 --- a/extern/shiny/Editor/newmaterialdialog.ui +++ /dev/null @@ -1,98 +0,0 @@ - - - NewMaterialDialog - - - - 0 - 0 - 385 - 198 - - - - Dialog - - - - - - - - Name - - - - - - - Parent material - - - - - - - - - - - - - File - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - NewMaterialDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - NewMaterialDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/extern/shiny/Extra/core.h b/extern/shiny/Extra/core.h deleted file mode 100644 index 1687d5ed11..0000000000 --- a/extern/shiny/Extra/core.h +++ /dev/null @@ -1,174 +0,0 @@ -#if SH_HLSL == 1 || SH_CG == 1 - - #define shTexture2D sampler2D - #define shTexture3D sampler3D - #define shSample(tex, coord) tex2D(tex, coord) - #define shCubicSample(tex, coord) texCUBE(tex, coord) - #define shLerp(a, b, t) lerp(a, b, t) - #define shSaturate(a) saturate(a) - - #define shSampler2D(name) , uniform sampler2D name : register(s@shCounter(0)) @shUseSampler(name) - - #define shSampler3D(name) , uniform sampler3D name : register(s@shCounter(0)) @shUseSampler(name) - - #define shSamplerCube(name) , uniform samplerCUBE name : register(s@shCounter(0)) @shUseSampler(name) - - #define shMatrixMult(m, v) mul(m, v) - - #define shUniform(type, name) , uniform type name - - #define shTangentInput(type) , in type tangent : TANGENT - #define shVertexInput(type, name) , in type name : TEXCOORD@shCounter(1) - #define shInput(type, name) , in type name : TEXCOORD@shCounter(1) - #define shOutput(type, name) , out type name : TEXCOORD@shCounter(2) - - #define shNormalInput(type) , in type normal : NORMAL - - #define shColourInput(type) , in type colour : COLOR - - #ifdef SH_VERTEX_SHADER - - #define shOutputPosition oPosition - #define shInputPosition iPosition - - - #define SH_BEGIN_PROGRAM \ - void main( \ - float4 iPosition : POSITION \ - , out float4 oPosition : POSITION - - #define SH_START_PROGRAM \ - ) \ - - #endif - - #ifdef SH_FRAGMENT_SHADER - - #define shOutputColour(num) oColor##num - - #define shDeclareMrtOutput(num) , out float4 oColor##num : COLOR##num - - #define SH_BEGIN_PROGRAM \ - void main( \ - out float4 oColor0 : COLOR - - #define SH_START_PROGRAM \ - ) \ - - #endif - -#endif - -#if SH_GLSL == 1 - - @version 120 - - #define float2 vec2 - #define float3 vec3 - #define float4 vec4 - #define int2 ivec2 - #define int3 ivec3 - #define int4 ivec4 - #define shTexture2D sampler2D - #define shTexture3D sampler3D - #define shSample(tex, coord) texture2D(tex, coord) - #define shCubicSample(tex, coord) textureCube(tex, coord) - #define shLerp(a, b, t) mix(a, b, t) - #define shSaturate(a) clamp(a, 0.0, 1.0) - - #define shUniform(type, name) uniform type name; - - #define shSampler2D(name) uniform sampler2D name; @shUseSampler(name) - - #define shSampler3D(name) uniform sampler3D name; @shUseSampler(name) - - #define shSamplerCube(name) uniform samplerCube name; @shUseSampler(name) - - #define shMatrixMult(m, v) (m * v) - - #define shOutputPosition gl_Position - - #define float4x4 mat4 - #define float3x3 mat3 - - // GLSL 1.3 - #if 0 - - // automatically recognized by ogre when the input name equals this - #define shInputPosition vertex - - #define shOutputColour(num) oColor##num - - #define shTangentInput(type) in type tangent; - #define shVertexInput(type, name) in type name; - #define shInput(type, name) in type name; - #define shOutput(type, name) out type name; - - // automatically recognized by ogre when the input name equals this - #define shNormalInput(type) in type normal; - #define shColourInput(type) in type colour; - - #ifdef SH_VERTEX_SHADER - - #define SH_BEGIN_PROGRAM \ - in float4 vertex; - #define SH_START_PROGRAM \ - void main(void) - - #endif - - #ifdef SH_FRAGMENT_SHADER - - #define shDeclareMrtOutput(num) out vec4 oColor##num; - - #define SH_BEGIN_PROGRAM \ - out float4 oColor0; - #define SH_START_PROGRAM \ - void main(void) - - - #endif - - #endif - - // GLSL 1.2 - - #if 1 - - // automatically recognized by ogre when the input name equals this - #define shInputPosition vertex - - #define shOutputColour(num) gl_FragData[num] - - #define shTangentInput(type) attribute type tangent; - #define shVertexInput(type, name) attribute type name; - #define shInput(type, name) varying type name; - #define shOutput(type, name) varying type name; - - // automatically recognized by ogre when the input name equals this - #define shNormalInput(type) attribute type normal; - #define shColourInput(type) attribute type colour; - - #ifdef SH_VERTEX_SHADER - - #define SH_BEGIN_PROGRAM \ - attribute vec4 vertex; - #define SH_START_PROGRAM \ - void main(void) - - #endif - - #ifdef SH_FRAGMENT_SHADER - - #define shDeclareMrtOutput(num) - - #define SH_BEGIN_PROGRAM - - #define SH_START_PROGRAM \ - void main(void) - - - #endif - - #endif -#endif diff --git a/extern/shiny/License.txt b/extern/shiny/License.txt deleted file mode 100644 index d89bcf3ad2..0000000000 --- a/extern/shiny/License.txt +++ /dev/null @@ -1,9 +0,0 @@ -Copyright (c) 2012 - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - diff --git a/extern/shiny/Main/Factory.cpp b/extern/shiny/Main/Factory.cpp deleted file mode 100644 index d0b86cbf94..0000000000 --- a/extern/shiny/Main/Factory.cpp +++ /dev/null @@ -1,817 +0,0 @@ -#include "Factory.hpp" - -#include -#include - -#include -#include -#include - -#include "Platform.hpp" -#include "ScriptLoader.hpp" -#include "ShaderSet.hpp" -#include "MaterialInstanceTextureUnit.hpp" - -namespace sh -{ - Factory* Factory::sThis = 0; - const std::string Factory::mBinaryCacheName = "binaryCache"; - - Factory& Factory::getInstance() - { - assert (sThis); - return *sThis; - } - - Factory* Factory::getInstancePtr() - { - return sThis; - } - - Factory::Factory (Platform* platform) - : mShadersEnabled(true) - , mShaderDebugOutputEnabled(false) - , mReadMicrocodeCache(false) - , mWriteMicrocodeCache(false) - , mReadSourceCache(false) - , mWriteSourceCache(false) - , mCurrentConfiguration(NULL) - , mCurrentLodConfiguration(NULL) - , mCurrentLanguage(Language_None) - , mListener(NULL) - , mPlatform(platform) - { - assert (!sThis); - sThis = this; - - mPlatform->setFactory(this); - } - - void Factory::loadAllFiles() - { - assert(mCurrentLanguage != Language_None); - - try - { - if (boost::filesystem::exists (mPlatform->getCacheFolder () + "/lastModified.txt")) - { - std::ifstream file; - file.open(std::string(mPlatform->getCacheFolder () + "/lastModified.txt").c_str()); - - std::string line; - while (getline(file, line)) - { - std::string sourceFile = line; - - if (!getline(file, line)) - assert(0); - - int modified = boost::lexical_cast(line); - - mShadersLastModified[sourceFile] = modified; - } - } - } - catch (std::exception& e) - { - std::cerr << "Failed to load shader modification index: " << e.what() << std::endl; - mShadersLastModified.clear(); - } - - // load configurations - { - ScriptLoader shaderSetLoader(".configuration"); - ScriptLoader::loadAllFiles (&shaderSetLoader, mPlatform->getBasePath()); - std::map nodes = shaderSetLoader.getAllConfigScripts(); - for (std::map ::const_iterator it = nodes.begin(); - it != nodes.end(); ++it) - { - if (!(it->second->getName() == "configuration")) - { - std::cerr << "sh::Factory: Warning: Unsupported root node type \"" << it->second->getName() << "\" for file type .configuration" << std::endl; - break; - } - - Configuration newConfiguration; - newConfiguration.setParent(&mGlobalSettings); - newConfiguration.setSourceFile (it->second->mFileName); - - std::vector props = it->second->getChildren(); - for (std::vector::const_iterator propIt = props.begin(); propIt != props.end(); ++propIt) - { - std::string name = (*propIt)->getName(); - std::string val = (*propIt)->getValue(); - - newConfiguration.setProperty (name, makeProperty(val)); - } - - mConfigurations[it->first] = newConfiguration; - } - } - - // load lod configurations - { - ScriptLoader lodLoader(".lod"); - ScriptLoader::loadAllFiles (&lodLoader, mPlatform->getBasePath()); - std::map nodes = lodLoader.getAllConfigScripts(); - for (std::map ::const_iterator it = nodes.begin(); - it != nodes.end(); ++it) - { - if (!(it->second->getName() == "lod_configuration")) - { - std::cerr << "sh::Factory: Warning: Unsupported root node type \"" << it->second->getName() << "\" for file type .lod" << std::endl; - break; - } - - if (it->first == "0") - { - throw std::runtime_error("lod level 0 (max lod) can't have a configuration"); - } - - PropertySetGet newLod; - - std::vector props = it->second->getChildren(); - for (std::vector::const_iterator propIt = props.begin(); propIt != props.end(); ++propIt) - { - std::string name = (*propIt)->getName(); - std::string val = (*propIt)->getValue(); - - newLod.setProperty (name, makeProperty(val)); - } - - mLodConfigurations[boost::lexical_cast(it->first)] = newLod; - } - } - - // load shader sets - bool removeBinaryCache = reloadShaders(); - - // load materials - { - ScriptLoader materialLoader(".mat"); - ScriptLoader::loadAllFiles (&materialLoader, mPlatform->getBasePath()); - - std::map nodes = materialLoader.getAllConfigScripts(); - for (std::map ::const_iterator it = nodes.begin(); - it != nodes.end(); ++it) - { - if (!(it->second->getName() == "material")) - { - std::cerr << "sh::Factory: Warning: Unsupported root node type \"" << it->second->getName() << "\" for file type .mat" << std::endl; - break; - } - - MaterialInstance newInstance(it->first, this); - newInstance.create(mPlatform); - if (!mShadersEnabled) - newInstance.setShadersEnabled (false); - - newInstance.setSourceFile (it->second->mFileName); - - std::vector props = it->second->getChildren(); - for (std::vector::const_iterator propIt = props.begin(); propIt != props.end(); ++propIt) - { - std::string name = (*propIt)->getName(); - - std::string val = (*propIt)->getValue(); - - if (name == "pass") - { - MaterialInstancePass* newPass = newInstance.createPass(); - std::vector props2 = (*propIt)->getChildren(); - for (std::vector::const_iterator propIt2 = props2.begin(); propIt2 != props2.end(); ++propIt2) - { - std::string name2 = (*propIt2)->getName(); - std::string val2 = (*propIt2)->getValue(); - - if (name2 == "shader_properties") - { - std::vector shaderProps = (*propIt2)->getChildren(); - for (std::vector::const_iterator shaderPropIt = shaderProps.begin(); shaderPropIt != shaderProps.end(); ++shaderPropIt) - { - std::string val = (*shaderPropIt)->getValue(); - newPass->mShaderProperties.setProperty((*shaderPropIt)->getName(), makeProperty(val)); - } - } - else if (name2 == "texture_unit") - { - MaterialInstanceTextureUnit* newTex = newPass->createTextureUnit(val2); - std::vector texProps = (*propIt2)->getChildren(); - for (std::vector::const_iterator texPropIt = texProps.begin(); texPropIt != texProps.end(); ++texPropIt) - { - std::string val = (*texPropIt)->getValue(); - newTex->setProperty((*texPropIt)->getName(), makeProperty(val)); - } - } - else - newPass->setProperty((*propIt2)->getName(), makeProperty(val2)); - } - } - else if (name == "parent") - newInstance.setParentInstance(val); - else - newInstance.setProperty((*propIt)->getName(), makeProperty(val)); - } - - if (newInstance.hasProperty("create_configuration")) - { - std::string config = retrieveValue(newInstance.getProperty("create_configuration"), NULL).get(); - newInstance.createForConfiguration (config, 0); - } - - mMaterials.insert (std::make_pair(it->first, newInstance)); - } - - // now that all materials are loaded, replace the parent names with the actual pointers to parent - for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it) - { - std::string parent = it->second.getParentInstance(); - if (parent != "") - { - if (mMaterials.find (it->second.getParentInstance()) == mMaterials.end()) - throw std::runtime_error ("Unable to find parent for material instance \"" + it->first + "\""); - it->second.setParent(&mMaterials.find(parent)->second); - } - } - } - - if (mPlatform->supportsShaderSerialization () && mReadMicrocodeCache && !removeBinaryCache) - { - std::string file = mPlatform->getCacheFolder () + "/" + mBinaryCacheName; - if (boost::filesystem::exists(file)) - { - mPlatform->deserializeShaders (file); - } - } - } - - Factory::~Factory () - { - mShaderSets.clear(); - - if (mPlatform->supportsShaderSerialization () && mWriteMicrocodeCache) - { - std::string file = mPlatform->getCacheFolder () + "/" + mBinaryCacheName; - mPlatform->serializeShaders (file); - } - - if (mReadSourceCache) - { - // save the last modified time of shader sources (as of when they were loaded) - std::ofstream file; - file.open(std::string(mPlatform->getCacheFolder () + "/lastModified.txt").c_str()); - - for (LastModifiedMap::const_iterator it = mShadersLastModifiedNew.begin(); it != mShadersLastModifiedNew.end(); ++it) - { - file << it->first << "\n" << it->second << std::endl; - } - - file.close(); - } - - delete mPlatform; - sThis = 0; - } - - MaterialInstance* Factory::searchInstance (const std::string& name) - { - MaterialMap::iterator it = mMaterials.find(name); - if (it != mMaterials.end()) - return &(it->second); - else - return NULL; - } - - MaterialInstance* Factory::findInstance (const std::string& name) - { - MaterialInstance* m = searchInstance(name); - assert (m); - return m; - } - - MaterialInstance* Factory::requestMaterial (const std::string& name, const std::string& configuration, unsigned short lodIndex) - { - MaterialInstance* m = searchInstance (name); - - if (configuration != "Default" && mConfigurations.find(configuration) == mConfigurations.end()) - return NULL; - - if (m) - { - if (m->createForConfiguration (configuration, 0)) - { - if (mListener) - mListener->materialCreated (m, configuration, 0); - } - else - return NULL; - - for (LodConfigurationMap::iterator it = mLodConfigurations.begin(); it != mLodConfigurations.end(); ++it) - { - if (m->createForConfiguration (configuration, it->first)) - { - if (mListener) - mListener->materialCreated (m, configuration, it->first); - } - else - return NULL; - } - } - return m; - } - - MaterialInstance* Factory::createMaterialInstance (const std::string& name, const std::string& parentInstance) - { - if (parentInstance != "" && mMaterials.find(parentInstance) == mMaterials.end()) - throw std::runtime_error ("trying to clone material that does not exist"); - - MaterialInstance newInstance(name, this); - - if (!mShadersEnabled) - newInstance.setShadersEnabled(false); - - if (parentInstance != "") - newInstance.setParent (&mMaterials.find(parentInstance)->second); - - newInstance.create(mPlatform); - - mMaterials.insert (std::make_pair(name, newInstance)); - - return &mMaterials.find(name)->second; - } - - void Factory::destroyMaterialInstance (const std::string& name) - { - if (mMaterials.find(name) != mMaterials.end()) - mMaterials.erase(name); - } - - void Factory::setShadersEnabled (bool enabled) - { - mShadersEnabled = enabled; - for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it) - { - it->second.setShadersEnabled(enabled); - } - } - - void Factory::setGlobalSetting (const std::string& name, const std::string& value) - { - bool changed = true; - if (mGlobalSettings.hasProperty(name)) - changed = (retrieveValue(mGlobalSettings.getProperty(name), NULL).get() != value); - - mGlobalSettings.setProperty (name, makeProperty(new StringValue(value))); - - if (changed) - { - for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it) - { - it->second.destroyAll(); - } - } - } - - void Factory::setSharedParameter (const std::string& name, PropertyValuePtr value) - { - mPlatform->setSharedParameter(name, value); - } - - ShaderSet* Factory::getShaderSet (const std::string& name) - { - if (mShaderSets.find(name) == mShaderSets.end()) - { - std::stringstream msg; - msg << "Shader '" << name << "' not found"; - throw std::runtime_error(msg.str()); - } - return &mShaderSets.find(name)->second; - } - - Platform* Factory::getPlatform () - { - return mPlatform; - } - - Language Factory::getCurrentLanguage () - { - return mCurrentLanguage; - } - - void Factory::setCurrentLanguage (Language lang) - { - bool changed = (mCurrentLanguage != lang); - mCurrentLanguage = lang; - - if (changed) - { - for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it) - { - it->second.destroyAll(); - } - } - } - - void Factory::notifyConfigurationChanged() - { - for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it) - { - it->second.destroyAll(); - } - } - - MaterialInstance* Factory::getMaterialInstance (const std::string& name) - { - return findInstance(name); - } - - void Factory::setTextureAlias (const std::string& alias, const std::string& realName) - { - mTextureAliases[alias] = realName; - - // update the already existing texture units - for (std::map::iterator it = mTextureAliasInstances.begin(); it != mTextureAliasInstances.end(); ++it) - { - if (it->second == alias) - { - it->first->setTextureName(realName); - } - } - } - - std::string Factory::retrieveTextureAlias (const std::string& name) - { - TextureAliasMap::iterator it = mTextureAliases.find(name); - if (it != mTextureAliases.end()) - return it->second; - else - return ""; - } - - Configuration* Factory::getConfiguration (const std::string& name) - { - return &mConfigurations[name]; - } - - void Factory::createConfiguration (const std::string& name) - { - mConfigurations[name].setParent (&mGlobalSettings); - } - - void Factory::destroyConfiguration(const std::string &name) - { - mConfigurations.erase(name); - } - - void Factory::registerLodConfiguration (int index, PropertySetGet configuration) - { - mLodConfigurations[index] = configuration; - } - - void Factory::setMaterialListener (MaterialListener* listener) - { - mListener = listener; - } - - void Factory::addTextureAliasInstance (const std::string& name, TextureUnitState* t) - { - mTextureAliasInstances[t] = name; - } - - void Factory::removeTextureAliasInstances (TextureUnitState* t) - { - mTextureAliasInstances.erase(t); - } - - void Factory::setActiveConfiguration (const std::string& configuration) - { - if (configuration == "Default") - mCurrentConfiguration = 0; - else - { - assert (mConfigurations.find(configuration) != mConfigurations.end()); - mCurrentConfiguration = &mConfigurations[configuration]; - } - } - - void Factory::setActiveLodLevel (int level) - { - if (level == 0) - mCurrentLodConfiguration = 0; - else - { - assert (mLodConfigurations.find(level) != mLodConfigurations.end()); - mCurrentLodConfiguration = &mLodConfigurations[level]; - } - } - - void Factory::setShaderDebugOutputEnabled (bool enabled) - { - mShaderDebugOutputEnabled = enabled; - } - - PropertySetGet* Factory::getCurrentGlobalSettings() - { - PropertySetGet* p = &mGlobalSettings; - - // current global settings are affected by active configuration & active lod configuration - - if (mCurrentConfiguration) - { - p = mCurrentConfiguration; - } - - if (mCurrentLodConfiguration) - { - mCurrentLodConfiguration->setParent(p); - p = mCurrentLodConfiguration; - } - - return p; - } - - void Factory::saveAll () - { - std::map files; - for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it) - { - if (it->second.getSourceFile().empty()) - continue; - if (files.find(it->second.getSourceFile()) == files.end()) - { - /// \todo check if this is actually the same file, since there can be different paths to the same file - std::ofstream* stream = new std::ofstream(); - stream->open (it->second.getSourceFile().c_str()); - - files[it->second.getSourceFile()] = stream; - } - it->second.save (*files[it->second.getSourceFile()]); - } - - for (std::map::iterator it = files.begin(); it != files.end(); ++it) - { - delete it->second; - } - files.clear(); - - for (ConfigurationMap::iterator it = mConfigurations.begin(); it != mConfigurations.end(); ++it) - { - if (it->second.getSourceFile().empty()) - continue; - if (files.find(it->second.getSourceFile()) == files.end()) - { - /// \todo check if this is actually the same file, since there can be different paths to the same file - std::ofstream* stream = new std::ofstream(); - stream->open (it->second.getSourceFile().c_str()); - - files[it->second.getSourceFile()] = stream; - } - it->second.save (it->first, *files[it->second.getSourceFile()]); - } - - for (std::map::iterator it = files.begin(); it != files.end(); ++it) - { - delete it->second; - } - } - - void Factory::listMaterials(std::vector &out) - { - for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it) - { - out.push_back(it->first); - } - } - - void Factory::listGlobalSettings(std::map &out) - { - const PropertyMap& properties = mGlobalSettings.listProperties(); - - for (PropertyMap::const_iterator it = properties.begin(); it != properties.end(); ++it) - { - out[it->first] = retrieveValue(mGlobalSettings.getProperty(it->first), NULL).get(); - } - } - - void Factory::listConfigurationSettings(const std::string& name, std::map &out) - { - const PropertyMap& properties = mConfigurations[name].listProperties(); - - for (PropertyMap::const_iterator it = properties.begin(); it != properties.end(); ++it) - { - out[it->first] = retrieveValue(mConfigurations[name].getProperty(it->first), NULL).get(); - } - } - - void Factory::listConfigurationNames(std::vector &out) - { - for (ConfigurationMap::const_iterator it = mConfigurations.begin(); it != mConfigurations.end(); ++it) - { - out.push_back(it->first); - } - } - - void Factory::listShaderSets(std::vector &out) - { - for (ShaderSetMap::const_iterator it = mShaderSets.begin(); it != mShaderSets.end(); ++it) - { - out.push_back(it->first); - } - } - - void Factory::_ensureMaterial(const std::string& name, const std::string& configuration) - { - MaterialInstance* m = searchInstance (name); - assert(m); - - m->createForConfiguration (configuration, 0); - - for (LodConfigurationMap::iterator it = mLodConfigurations.begin(); it != mLodConfigurations.end(); ++it) - { - m->createForConfiguration (configuration, it->first); - } - } - - bool Factory::removeCache(const std::string& pattern) - { - bool ret = false; - if ( boost::filesystem::exists(mPlatform->getCacheFolder()) - && boost::filesystem::is_directory(mPlatform->getCacheFolder())) - { - boost::filesystem::directory_iterator end_iter; - for( boost::filesystem::directory_iterator dir_iter(mPlatform->getCacheFolder()) ; dir_iter != end_iter ; ++dir_iter) - { - if (boost::filesystem::is_regular_file(dir_iter->status()) ) - { - boost::filesystem::path file = dir_iter->path(); - - std::string pathname = file.filename().string(); - - // get first part of filename, e.g. main_fragment_546457654 -> main_fragment - // there is probably a better method for this... - std::vector tokens; - boost::split(tokens, pathname, boost::is_any_of("_")); - tokens.erase(--tokens.end()); - std::string shaderName; - for (std::vector::const_iterator vector_iter = tokens.begin(); vector_iter != tokens.end();) - { - shaderName += *(vector_iter++); - if (vector_iter != tokens.end()) - shaderName += "_"; - } - - if (shaderName == pattern) - { - boost::filesystem::remove(file); - ret = true; - std::cout << "Removing outdated shader: " << file << std::endl; - } - } - } - } - return ret; - } - - bool Factory::reloadShaders() - { - mShaderSets.clear(); - notifyConfigurationChanged(); - - bool removeBinaryCache = false; - ScriptLoader shaderSetLoader(".shaderset"); - ScriptLoader::loadAllFiles (&shaderSetLoader, mPlatform->getBasePath()); - std::map nodes = shaderSetLoader.getAllConfigScripts(); - for (std::map ::const_iterator it = nodes.begin(); - it != nodes.end(); ++it) - { - if (!(it->second->getName() == "shader_set")) - { - std::cerr << "sh::Factory: Warning: Unsupported root node type \"" << it->second->getName() << "\" for file type .shaderset" << std::endl; - break; - } - - if (!it->second->findChild("profiles_cg")) - throw std::runtime_error ("missing \"profiles_cg\" field for \"" + it->first + "\""); - if (!it->second->findChild("profiles_hlsl")) - throw std::runtime_error ("missing \"profiles_hlsl\" field for \"" + it->first + "\""); - if (!it->second->findChild("source")) - throw std::runtime_error ("missing \"source\" field for \"" + it->first + "\""); - if (!it->second->findChild("type")) - throw std::runtime_error ("missing \"type\" field for \"" + it->first + "\""); - - std::vector profiles_cg; - boost::split (profiles_cg, it->second->findChild("profiles_cg")->getValue(), boost::is_any_of(" ")); - std::string cg_profile; - for (std::vector::iterator it2 = profiles_cg.begin(); it2 != profiles_cg.end(); ++it2) - { - if (mPlatform->isProfileSupported(*it2)) - { - cg_profile = *it2; - break; - } - } - - std::vector profiles_hlsl; - boost::split (profiles_hlsl, it->second->findChild("profiles_hlsl")->getValue(), boost::is_any_of(" ")); - std::string hlsl_profile; - for (std::vector::iterator it2 = profiles_hlsl.begin(); it2 != profiles_hlsl.end(); ++it2) - { - if (mPlatform->isProfileSupported(*it2)) - { - hlsl_profile = *it2; - break; - } - } - - std::string sourceAbsolute = mPlatform->getBasePath() + "/" + it->second->findChild("source")->getValue(); - std::string sourceRelative = it->second->findChild("source")->getValue(); - - ShaderSet newSet (it->second->findChild("type")->getValue(), cg_profile, hlsl_profile, - sourceAbsolute, - mPlatform->getBasePath(), - it->first, - &mGlobalSettings); - - int lastModified = boost::filesystem::last_write_time (boost::filesystem::path(sourceAbsolute)); - mShadersLastModifiedNew[sourceRelative] = lastModified; - if (mShadersLastModified.find(sourceRelative) != mShadersLastModified.end()) - { - if (mShadersLastModified[sourceRelative] != lastModified) - { - // delete any outdated shaders based on this shader set - if (removeCache (it->first)) - removeBinaryCache = true; - } - } - else - { - // if we get here, this is either the first run or a new shader file was added - // in both cases we can safely delete - if (removeCache (it->first)) - removeBinaryCache = true; - } - mShaderSets.insert(std::make_pair(it->first, newSet)); - } - - // new is now current - mShadersLastModified = mShadersLastModifiedNew; - - return removeBinaryCache; - } - - void Factory::doMonitorShaderFiles() - { - bool reload=false; - ScriptLoader shaderSetLoader(".shaderset"); - ScriptLoader::loadAllFiles (&shaderSetLoader, mPlatform->getBasePath()); - std::map nodes = shaderSetLoader.getAllConfigScripts(); - for (std::map ::const_iterator it = nodes.begin(); - it != nodes.end(); ++it) - { - - std::string sourceAbsolute = mPlatform->getBasePath() + "/" + it->second->findChild("source")->getValue(); - std::string sourceRelative = it->second->findChild("source")->getValue(); - - int lastModified = boost::filesystem::last_write_time (boost::filesystem::path(sourceAbsolute)); - if (mShadersLastModified.find(sourceRelative) != mShadersLastModified.end()) - { - if (mShadersLastModified[sourceRelative] != lastModified) - { - reload=true; - break; - } - } - } - if (reload) - reloadShaders(); - } - - void Factory::logError(const std::string &msg) - { - mErrorLog << msg << '\n'; - } - - std::string Factory::getErrorLog() - { - std::string errors = mErrorLog.str(); - mErrorLog.str(""); - return errors; - } - - void Factory::unloadUnreferencedMaterials() - { - for (MaterialMap::iterator it = mMaterials.begin(); it != mMaterials.end(); ++it) - { - if (it->second.getMaterial()->isUnreferenced()) - it->second.getMaterial()->unreferenceTextures(); - } - } - - void Configuration::save(const std::string& name, std::ofstream &stream) - { - stream << "configuration " << name << '\n'; - stream << "{\n"; - PropertySetGet::save(stream, "\t"); - stream << "}\n"; - } -} diff --git a/extern/shiny/Main/Factory.hpp b/extern/shiny/Main/Factory.hpp deleted file mode 100644 index 721b4af7d9..0000000000 --- a/extern/shiny/Main/Factory.hpp +++ /dev/null @@ -1,271 +0,0 @@ -#ifndef SH_FACTORY_H -#define SH_FACTORY_H - -#include -#include -#include - -#include "MaterialInstance.hpp" -#include "ShaderSet.hpp" -#include "Language.hpp" - -namespace sh -{ - class Platform; - - class Configuration : public PropertySetGet - { - public: - void setSourceFile (const std::string& file) { mSourceFile = file ; } - std::string getSourceFile () { return mSourceFile; } - - void save(const std::string& name, std::ofstream &stream); - - private: - std::string mSourceFile; - }; - - typedef std::map MaterialMap; - typedef std::map ShaderSetMap; - typedef std::map ConfigurationMap; - typedef std::map LodConfigurationMap; - typedef std::map LastModifiedMap; - - typedef std::map TextureAliasMap; - - /** - * @brief - * Allows you to be notified when a certain material was just created. Useful for changing material properties that you can't - * do in a .mat script (for example a series of animated textures) \n - * When receiving the event, you can get the platform material by calling m->getMaterial() - * and casting that to the platform specific material (e.g. for Ogre, sh::OgreMaterial) - */ - class MaterialListener - { - public: - virtual void materialCreated (MaterialInstance* m, const std::string& configuration, unsigned short lodIndex) = 0; - }; - - /** - * @brief - * The main interface class - */ - class Factory - { - public: - Factory(Platform* platform); - ///< @note Ownership of \a platform is transferred to this class, so you don't have to delete it. - - ~Factory(); - - /** - * Create a MaterialInstance, optionally copying all properties from \a parentInstance - * @param name name of the new instance - * @param name of the parent (optional) - * @return newly created instance - */ - MaterialInstance* createMaterialInstance (const std::string& name, const std::string& parentInstance = ""); - - /// @note It is safe to call this if the instance does not exist - void destroyMaterialInstance (const std::string& name); - - /// Use this to enable or disable shaders on-the-fly - void setShadersEnabled (bool enabled); - - /// write generated shaders to current directory, useful for debugging - void setShaderDebugOutputEnabled (bool enabled); - - /// Use this to manage user settings. \n - /// Global settings can be retrieved in shaders through a macro. \n - /// When a global setting is changed, the shaders that depend on them are recompiled automatically. - void setGlobalSetting (const std::string& name, const std::string& value); - - /// Adjusts the given shared parameter. \n - /// Internally, this will change all uniform parameters of this name marked with the macro \@shSharedParameter \n - /// @param name of the shared parameter - /// @param value of the parameter, use sh::makeProperty to construct this value - void setSharedParameter (const std::string& name, PropertyValuePtr value); - - Language getCurrentLanguage (); - - /// Switch between different shader languages (cg, glsl, hlsl) - void setCurrentLanguage (Language lang); - - /// Get a MaterialInstance by name - MaterialInstance* getMaterialInstance (const std::string& name); - - /// Create a configuration, which can then be altered by using Factory::getConfiguration - void createConfiguration (const std::string& name); - - /// Register a lod configuration, which can then be used by setting up lod distance values for the material \n - /// 0 refers to highest lod, so use 1 or higher as index parameter - void registerLodConfiguration (int index, PropertySetGet configuration); - - /// Set an alias name for a texture, the real name can then be retrieved with the "texture_alias" - /// property in a texture unit - this is useful if you don't know the name of your texture beforehand. \n - /// Example: \n - /// - In the material definition: texture_alias ReflectionMap \n - /// - At runtime: factory->setTextureAlias("ReflectionMap", "rtt_654654"); \n - /// You can call factory->setTextureAlias as many times as you want, and if the material was already created, its texture will be updated! - void setTextureAlias (const std::string& alias, const std::string& realName); - - /// Retrieve the real texture name for a texture alias (the real name is set by the user) - std::string retrieveTextureAlias (const std::string& name); - - /// Attach a listener for material created events - void setMaterialListener (MaterialListener* listener); - - /// Call this after you have set up basic stuff, like the shader language. - void loadAllFiles (); - - /// Controls writing of generated shader source code to the cache folder, so that the - /// (rather expensive) preprocessing step can be skipped on the next run. See Factory::setReadSourceCache \n - /// \note The default is off (no cache writing) - void setWriteSourceCache(bool write) { mWriteSourceCache = write; } - - /// Controls reading of generated shader sources from the cache folder - /// \note The default is off (no cache reading) - /// \note Even if microcode caching is enabled, generating (or caching) the source is still required due to the macros. - void setReadSourceCache(bool read) { mReadSourceCache = read; } - - /// Controls writing the microcode of the generated shaders to the cache folder. Microcode is machine independent - /// and loads very fast compared to regular compilation. Note that the availability of this feature depends on the \a Platform. - /// \note The default is off (no cache writing) - void setWriteMicrocodeCache(bool write) { mWriteMicrocodeCache = write; } - - /// Controls reading of shader microcode from the cache folder. Microcode is machine independent - /// and loads very fast compared to regular compilation. Note that the availability of this feature depends on the \a Platform. - /// \note The default is off (no cache reading) - void setReadMicrocodeCache(bool read) { mReadMicrocodeCache = read; } - - /// Lists all materials currently registered with the factory. Whether they are - /// loaded or not does not matter. - void listMaterials (std::vector& out); - - /// Lists current name & value of all global settings. - void listGlobalSettings (std::map& out); - - /// Lists configuration names. - void listConfigurationNames (std::vector& out); - - /// Lists current name & value of settings for a given configuration. - void listConfigurationSettings (const std::string& name, std::map& out); - - /// Lists shader sets. - void listShaderSets (std::vector& out); - - /// \note This only works if microcode caching is disabled, as there is currently no way to remove the cache - /// through the Ogre API. Luckily, this is already fixed in Ogre 1.9. - bool reloadShaders(); - - /// Calls reloadShaders() if shader files have been modified since the last reload. - /// \note This only works if microcode caching is disabled, as there is currently no way to remove the cache - /// through the Ogre API. Luckily, this is already fixed in Ogre 1.9. - void doMonitorShaderFiles(); - - /// Unloads all materials that are currently not referenced. This will not unload the textures themselves, - /// but it will let go of the SharedPtr's to the textures, so that you may unload them if you so desire. \n - /// A good time to call this would be after a new level has been loaded, but just calling it occasionally after a period - /// of time should work just fine too. - void unloadUnreferencedMaterials(); - - void destroyConfiguration (const std::string& name); - - void notifyConfigurationChanged(); - - /// Saves all materials and configurations, by default to the file they were loaded from. - /// If you wish to save them elsewhere, use setSourceFile first. - void saveAll (); - - /// Returns the error log as a string, then clears it. - /// Note: Errors are also written to the standard error output, or thrown if they are fatal. - std::string getErrorLog (); - - static Factory& getInstance(); - ///< Return instance of this class. - - static Factory* getInstancePtr(); - - /// Make sure a material technique is loaded.\n - /// You will probably never have to use this. - void _ensureMaterial(const std::string& name, const std::string& configuration); - - - Configuration* getConfiguration (const std::string& name); - - private: - - MaterialInstance* requestMaterial (const std::string& name, const std::string& configuration, unsigned short lodIndex); - ShaderSet* getShaderSet (const std::string& name); - Platform* getPlatform (); - - PropertySetGet* getCurrentGlobalSettings(); - - void addTextureAliasInstance (const std::string& name, TextureUnitState* t); - void removeTextureAliasInstances (TextureUnitState* t); - - std::string getCacheFolder () { return mPlatform->getCacheFolder (); } - bool getReadSourceCache() { return mReadSourceCache; } - bool getWriteSourceCache() { return mWriteSourceCache; } - public: - bool getWriteMicrocodeCache() { return mWriteMicrocodeCache; } // Fixme - - private: - void setActiveConfiguration (const std::string& configuration); - void setActiveLodLevel (int level); - - bool getShaderDebugOutputEnabled() { return mShaderDebugOutputEnabled; } - - std::map mTextureAliasInstances; - - void logError (const std::string& msg); - - friend class Platform; - friend class MaterialInstance; - friend class ShaderInstance; - friend class ShaderSet; - friend class TextureUnitState; - - private: - static Factory* sThis; - - bool mShadersEnabled; - bool mShaderDebugOutputEnabled; - - bool mReadMicrocodeCache; - bool mWriteMicrocodeCache; - bool mReadSourceCache; - bool mWriteSourceCache; - std::stringstream mErrorLog; - - MaterialMap mMaterials; - ShaderSetMap mShaderSets; - ConfigurationMap mConfigurations; - LodConfigurationMap mLodConfigurations; - LastModifiedMap mShadersLastModified; - LastModifiedMap mShadersLastModifiedNew; - - PropertySetGet mGlobalSettings; - - PropertySetGet* mCurrentConfiguration; - PropertySetGet* mCurrentLodConfiguration; - - TextureAliasMap mTextureAliases; - - Language mCurrentLanguage; - - MaterialListener* mListener; - - Platform* mPlatform; - - MaterialInstance* findInstance (const std::string& name); - MaterialInstance* searchInstance (const std::string& name); - - /// @return was anything removed? - bool removeCache (const std::string& pattern); - - static const std::string mBinaryCacheName; - }; -} - -#endif diff --git a/extern/shiny/Main/Language.hpp b/extern/shiny/Main/Language.hpp deleted file mode 100644 index 6b271cb86a..0000000000 --- a/extern/shiny/Main/Language.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef SH_LANGUAGE_H -#define SH_LANGUAGE_H - -namespace sh -{ - enum Language - { - Language_CG, - Language_HLSL, - Language_GLSL, - Language_GLSLES, - Language_Count, - Language_None - }; -} - -#endif diff --git a/extern/shiny/Main/MaterialInstance.cpp b/extern/shiny/Main/MaterialInstance.cpp deleted file mode 100644 index 5d1a8e7f9d..0000000000 --- a/extern/shiny/Main/MaterialInstance.cpp +++ /dev/null @@ -1,261 +0,0 @@ -#include "MaterialInstance.hpp" - -#include -#include - -#include "Factory.hpp" -#include "ShaderSet.hpp" - -namespace sh -{ - MaterialInstance::MaterialInstance (const std::string& name, Factory* f) - : mFailedToCreate(false) - , mListener(NULL) - , mName(name) - , mShadersEnabled(true) - , mFactory(f) - { - } - - MaterialInstance::~MaterialInstance () - { - } - - void MaterialInstance::setParentInstance (const std::string& name) - { - mParentInstance = name; - } - - std::string MaterialInstance::getParentInstance () - { - return mParentInstance; - } - - void MaterialInstance::create (Platform* platform) - { - mMaterial = platform->createMaterial(mName); - - if (hasProperty ("shadow_caster_material")) - mMaterial->setShadowCasterMaterial (retrieveValue(getProperty("shadow_caster_material"), NULL).get()); - - if (hasProperty ("lod_values")) - mMaterial->setLodLevels (retrieveValue(getProperty("lod_values"), NULL).get()); - } - - void MaterialInstance::destroyAll () - { - if (hasProperty("create_configuration")) - return; - mMaterial->removeAll(); - mTexUnits.clear(); - mFailedToCreate = false; - } - - void MaterialInstance::setProperty (const std::string& name, PropertyValuePtr value) - { - PropertySetGet::setProperty (name, value); - destroyAll(); // trigger updates - } - - bool MaterialInstance::createForConfiguration (const std::string& configuration, unsigned short lodIndex) - { - if (mFailedToCreate) - return false; - try{ - mMaterial->ensureLoaded(); - bool res = mMaterial->createConfiguration(configuration, lodIndex); - if (!res) - return false; // listener was false positive - - if (mListener) - mListener->requestedConfiguration (this, configuration); - - mFactory->setActiveConfiguration (configuration); - mFactory->setActiveLodLevel (lodIndex); - - bool allowFixedFunction = true; - if (!mShadersEnabled && hasProperty("allow_fixed_function")) - { - allowFixedFunction = retrieveValue(getProperty("allow_fixed_function"), NULL).get(); - } - - bool useShaders = mShadersEnabled || !allowFixedFunction; - - // get passes of the top-most parent - PassVector* passes = getParentPasses(); - if (passes->empty()) - throw std::runtime_error ("material \"" + mName + "\" does not have any passes"); - - for (PassVector::iterator it = passes->begin(); it != passes->end(); ++it) - { - boost::shared_ptr pass = mMaterial->createPass (configuration, lodIndex); - it->copyAll (pass.get(), this); - - // texture samplers used in the shaders - std::vector usedTextureSamplersVertex; - std::vector usedTextureSamplersFragment; - - PropertySetGet* context = this; - - // create or retrieve shaders - bool hasVertex = it->hasProperty("vertex_program") - && !retrieveValue(it->getProperty("vertex_program"), context).get().empty(); - bool hasFragment = it->hasProperty("fragment_program") - && !retrieveValue(it->getProperty("fragment_program"), context).get().empty(); - if (useShaders) - { - it->setContext(context); - it->mShaderProperties.setContext(context); - if (hasVertex) - { - ShaderSet* vertex = mFactory->getShaderSet(retrieveValue(it->getProperty("vertex_program"), context).get()); - ShaderInstance* v = vertex->getInstance(&it->mShaderProperties); - if (v) - { - pass->assignProgram (GPT_Vertex, v->getName()); - v->setUniformParameters (pass, &it->mShaderProperties); - - std::vector sharedParams = v->getSharedParameters (); - for (std::vector::iterator it2 = sharedParams.begin(); it2 != sharedParams.end(); ++it2) - { - pass->addSharedParameter (GPT_Vertex, *it2); - } - - std::vector vector = v->getUsedSamplers (); - usedTextureSamplersVertex.insert(usedTextureSamplersVertex.end(), vector.begin(), vector.end()); - } - } - if (hasFragment) - { - ShaderSet* fragment = mFactory->getShaderSet(retrieveValue(it->getProperty("fragment_program"), context).get()); - ShaderInstance* f = fragment->getInstance(&it->mShaderProperties); - if (f) - { - pass->assignProgram (GPT_Fragment, f->getName()); - f->setUniformParameters (pass, &it->mShaderProperties); - - std::vector sharedParams = f->getSharedParameters (); - for (std::vector::iterator it2 = sharedParams.begin(); it2 != sharedParams.end(); ++it2) - { - pass->addSharedParameter (GPT_Fragment, *it2); - } - - std::vector vector = f->getUsedSamplers (); - usedTextureSamplersFragment.insert(usedTextureSamplersFragment.end(), vector.begin(), vector.end()); - } - } - } - - // create texture units - std::vector* texUnits = &it->mTexUnits; - int i=0; - for (std::vector::iterator texIt = texUnits->begin(); texIt != texUnits->end(); ++texIt ) - { - // only create those that are needed by the shader, OR those marked to be created in fixed function pipeline if shaders are disabled - bool foundVertex = std::find(usedTextureSamplersVertex.begin(), usedTextureSamplersVertex.end(), texIt->getName()) != usedTextureSamplersVertex.end(); - bool foundFragment = std::find(usedTextureSamplersFragment.begin(), usedTextureSamplersFragment.end(), texIt->getName()) != usedTextureSamplersFragment.end(); - if ( (foundVertex || foundFragment) - || (((!useShaders || (!hasVertex || !hasFragment)) && allowFixedFunction) && texIt->hasProperty("create_in_ffp") && retrieveValue(texIt->getProperty("create_in_ffp"), this).get())) - { - boost::shared_ptr texUnit = pass->createTextureUnitState (texIt->getName()); - texIt->copyAll (texUnit.get(), context); - - mTexUnits.push_back(texUnit); - - // set texture unit indices (required by GLSL) - if (useShaders && ((hasVertex && foundVertex) || (hasFragment && foundFragment)) && (mFactory->getCurrentLanguage () == Language_GLSL - || mFactory->getCurrentLanguage() == Language_GLSLES)) - { - pass->setTextureUnitIndex (foundVertex ? GPT_Vertex : GPT_Fragment, texIt->getName(), i); - - ++i; - } - } - } - } - - if (mListener) - mListener->createdConfiguration (this, configuration); - return true; - - } catch (std::runtime_error& e) - { - destroyAll(); - mFailedToCreate = true; - std::stringstream msg; - msg << "Error while creating material " << mName << ": " << e.what(); - std::cerr << msg.str() << std::endl; - mFactory->logError(msg.str()); - return false; - } - } - - Material* MaterialInstance::getMaterial () - { - return mMaterial.get(); - } - - MaterialInstancePass* MaterialInstance::createPass () - { - mPasses.push_back (MaterialInstancePass()); - mPasses.back().setContext(this); - return &mPasses.back(); - } - - void MaterialInstance::deletePass(unsigned int index) - { - assert(mPasses.size() > index); - mPasses.erase(mPasses.begin()+index); - } - - PassVector* MaterialInstance::getParentPasses() - { - if (mParent) - return static_cast(mParent)->getParentPasses(); - else - return &mPasses; - } - - PassVector* MaterialInstance::getPasses() - { - return &mPasses; - } - - void MaterialInstance::setShadersEnabled (bool enabled) - { - if (enabled == mShadersEnabled) - return; - mShadersEnabled = enabled; - - // trigger updates - if (mMaterial.get()) - destroyAll(); - } - - void MaterialInstance::save (std::ofstream& stream) - { - stream << "material " << mName << "\n" - << "{\n"; - - if (mParent) - { - stream << "\t" << "parent " << static_cast(mParent)->getName() << "\n"; - } - - const PropertyMap& properties = listProperties (); - for (PropertyMap::const_iterator it = properties.begin(); it != properties.end(); ++it) - { - stream << "\t" << it->first << " " << retrieveValue(getProperty(it->first), NULL).get() << "\n"; - } - - for (PassVector::iterator it = mPasses.begin(); it != mPasses.end(); ++it) - { - stream << "\tpass" << '\n'; - stream << "\t{" << '\n'; - it->save(stream); - stream << "\t}" << '\n'; - } - - stream << "}\n"; - } -} diff --git a/extern/shiny/Main/MaterialInstance.hpp b/extern/shiny/Main/MaterialInstance.hpp deleted file mode 100644 index 72c78c7b7c..0000000000 --- a/extern/shiny/Main/MaterialInstance.hpp +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef SH_MATERIALINSTANCE_H -#define SH_MATERIALINSTANCE_H - -#include -#include - -#include "PropertyBase.hpp" -#include "Platform.hpp" -#include "MaterialInstancePass.hpp" - -namespace sh -{ - class Factory; - - typedef std::vector PassVector; - - /** - * @brief - * Allows you to be notified when a certain configuration for a material was just about to be created. \n - * Useful for adjusting some properties prior to the material being created (Or you could also re-create - * the whole material from scratch, i.e. use this as a method to create this material entirely in code) - */ - class MaterialInstanceListener - { - public: - virtual void requestedConfiguration (MaterialInstance* m, const std::string& configuration) = 0; ///< called before creating - virtual void createdConfiguration (MaterialInstance* m, const std::string& configuration) = 0; ///< called after creating - virtual ~MaterialInstanceListener(){} - }; - - /** - * @brief - * A specific material instance, which has all required properties set - * (for example the diffuse & normal map, ambient/diffuse/specular values). \n - * Depending on these properties, the system will automatically select a shader permutation - * that suits these and create the backend materials / passes (provided by the \a Platform class). - */ - class MaterialInstance : public PropertySetGet - { - public: - MaterialInstance (const std::string& name, Factory* f); - virtual ~MaterialInstance (); - - PassVector* getParentPasses(); ///< gets the passes of the top-most parent - - PassVector* getPasses(); ///< get our passes (for derived materials, none) - - MaterialInstancePass* createPass (); - void deletePass (unsigned int index); - - /// @attention Because the backend material passes are created on demand, the returned material here might not contain anything yet! - /// The only place where you should use this method, is for the MaterialInstance given by the MaterialListener::materialCreated event! - Material* getMaterial(); - - /// attach a \a MaterialInstanceListener to this specific material (as opposed to \a MaterialListener, which listens to all materials) - void setListener (MaterialInstanceListener* l) { mListener = l; } - - std::string getName() { return mName; } - - virtual void setProperty (const std::string& name, PropertyValuePtr value); - - void setSourceFile(const std::string& sourceFile) { mSourceFile = sourceFile; } - - std::string getSourceFile() { return mSourceFile; } - ///< get the name of the file this material was read from, or empty if it was created dynamically by code - - private: - void setParentInstance (const std::string& name); - std::string getParentInstance (); - - void create (Platform* platform); - bool createForConfiguration (const std::string& configuration, unsigned short lodIndex); - - void destroyAll (); - - void setShadersEnabled (bool enabled); - - void save (std::ofstream& stream); - - bool mFailedToCreate; - - friend class Factory; - - - private: - std::string mParentInstance; - ///< this is only used during the file-loading phase. an instance could be loaded before its parent is loaded, - /// so initially only the parent's name is written to this member. - /// once all instances are loaded, the actual mParent pointer (from PropertySetGet class) can be set - - std::vector< boost::shared_ptr > mTexUnits; - - MaterialInstanceListener* mListener; - - PassVector mPasses; - - std::string mName; - - std::string mSourceFile; - - boost::shared_ptr mMaterial; - - bool mShadersEnabled; - - Factory* mFactory; - }; -} - -#endif diff --git a/extern/shiny/Main/MaterialInstancePass.cpp b/extern/shiny/Main/MaterialInstancePass.cpp deleted file mode 100644 index a628cd64c7..0000000000 --- a/extern/shiny/Main/MaterialInstancePass.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "MaterialInstancePass.hpp" - -#include - -namespace sh -{ - - MaterialInstanceTextureUnit* MaterialInstancePass::createTextureUnit (const std::string& name) - { - mTexUnits.push_back(MaterialInstanceTextureUnit(name)); - return &mTexUnits.back(); - } - - void MaterialInstancePass::save(std::ofstream &stream) - { - if (mShaderProperties.listProperties().size()) - { - stream << "\t\t" << "shader_properties" << '\n'; - stream << "\t\t{\n"; - mShaderProperties.save(stream, "\t\t\t"); - stream << "\t\t}\n"; - } - - PropertySetGet::save(stream, "\t\t"); - - for (std::vector ::iterator it = mTexUnits.begin(); - it != mTexUnits.end(); ++it) - { - stream << "\t\ttexture_unit " << it->getName() << '\n'; - stream << "\t\t{\n"; - it->save(stream, "\t\t\t"); - stream << "\t\t}\n"; - } - } -} diff --git a/extern/shiny/Main/MaterialInstancePass.hpp b/extern/shiny/Main/MaterialInstancePass.hpp deleted file mode 100644 index 3d83d8fa35..0000000000 --- a/extern/shiny/Main/MaterialInstancePass.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef SH_MATERIALINSTANCEPASS_H -#define SH_MATERIALINSTANCEPASS_H - -#include - -#include "PropertyBase.hpp" -#include "MaterialInstanceTextureUnit.hpp" - -namespace sh -{ - /** - * @brief - * Holds properties of a single texture unit in a \a MaterialInstancePass. \n - * No inheritance here for now. - */ - class MaterialInstancePass : public PropertySetGet - { - public: - MaterialInstanceTextureUnit* createTextureUnit (const std::string& name); - - void save (std::ofstream& stream); - - PropertySetGet mShaderProperties; - - std::vector mTexUnits; - }; -} - -#endif diff --git a/extern/shiny/Main/MaterialInstanceTextureUnit.cpp b/extern/shiny/Main/MaterialInstanceTextureUnit.cpp deleted file mode 100644 index 0e3078af34..0000000000 --- a/extern/shiny/Main/MaterialInstanceTextureUnit.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "MaterialInstanceTextureUnit.hpp" - -namespace sh -{ - MaterialInstanceTextureUnit::MaterialInstanceTextureUnit (const std::string& name) - : mName(name) - { - } - - std::string MaterialInstanceTextureUnit::getName() const - { - return mName; - } -} diff --git a/extern/shiny/Main/MaterialInstanceTextureUnit.hpp b/extern/shiny/Main/MaterialInstanceTextureUnit.hpp deleted file mode 100644 index 5ca400fd49..0000000000 --- a/extern/shiny/Main/MaterialInstanceTextureUnit.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef SH_MATERIALINSTANCETEXTUREUNIT_H -#define SH_MATERIALINSTANCETEXTUREUNIT_H - -#include "PropertyBase.hpp" - -namespace sh -{ - /** - * @brief - * A single texture unit state that belongs to a \a MaterialInstancePass \n - * this is not the real "backend" \a TextureUnitState (provided by \a Platform), - * it is merely a placeholder for properties. \n - * @note The backend \a TextureUnitState will only be created if this texture unit is - * actually used (i.e. referenced in the shader, or marked with property create_in_ffp = true). - */ - class MaterialInstanceTextureUnit : public PropertySetGet - { - public: - MaterialInstanceTextureUnit (const std::string& name); - std::string getName() const; - void setName (const std::string& name) { mName = name; } - private: - std::string mName; - }; -} - -#endif diff --git a/extern/shiny/Main/Platform.cpp b/extern/shiny/Main/Platform.cpp deleted file mode 100644 index f09956e0fd..0000000000 --- a/extern/shiny/Main/Platform.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include "Platform.hpp" - -#include - -#include "Factory.hpp" - -namespace sh -{ - Platform::Platform (const std::string& basePath) - : mCacheFolder("./") - , mFactory(NULL) - , mBasePath(basePath) - { - } - - Platform::~Platform () - { - } - - void Platform::setFactory (Factory* factory) - { - mFactory = factory; - } - - std::string Platform::getBasePath () - { - return mBasePath; - } - - bool Platform::supportsMaterialQueuedListener () - { - return false; - } - - bool Platform::supportsShaderSerialization () - { - return false; - } - - MaterialInstance* Platform::fireMaterialRequested (const std::string& name, const std::string& configuration, unsigned short lodIndex) - { - return mFactory->requestMaterial (name, configuration, lodIndex); - } - - void Platform::serializeShaders (const std::string& file) - { - throw std::runtime_error ("Shader serialization not supported by this platform"); - } - - void Platform::deserializeShaders (const std::string& file) - { - throw std::runtime_error ("Shader serialization not supported by this platform"); - } - - void Platform::setCacheFolder (const std::string& folder) - { - mCacheFolder = folder; - } - - std::string Platform::getCacheFolder() const - { - return mCacheFolder; - } - - // ------------------------------------------------------------------------------ - - bool TextureUnitState::setPropertyOverride (const std::string& name, PropertyValuePtr& value, PropertySetGet *context) - { - if (name == "texture_alias") - { - std::string aliasName = retrieveValue(value, context).get(); - - Factory::getInstance().addTextureAliasInstance (aliasName, this); - - setTextureName (Factory::getInstance().retrieveTextureAlias (aliasName)); - - return true; - } - else - return false; - } - - TextureUnitState::~TextureUnitState() - { - Factory* f = Factory::getInstancePtr (); - if (f) - f->removeTextureAliasInstances (this); - } -} diff --git a/extern/shiny/Main/Platform.hpp b/extern/shiny/Main/Platform.hpp deleted file mode 100644 index d3156e6801..0000000000 --- a/extern/shiny/Main/Platform.hpp +++ /dev/null @@ -1,147 +0,0 @@ -#ifndef SH_PLATFORM_H -#define SH_PLATFORM_H - -#include - -#include - -#include "Language.hpp" -#include "PropertyBase.hpp" - -namespace sh -{ - class Factory; - class MaterialInstance; - - enum GpuProgramType - { - GPT_Vertex, - GPT_Fragment - // GPT_Geometry - }; - - // These classes are supposed to be filled by the platform implementation - class GpuProgram - { - public: - virtual ~GpuProgram() {} - virtual bool getSupported () = 0; ///< @return true if the compilation was successful - - /// @param name name of the uniform in the shader - /// @param autoConstantName name of the auto constant (for example world_viewproj_matrix) - /// @param extraInfo if any extra info is needed (e.g. light index), put it here - virtual void setAutoConstant (const std::string& name, const std::string& autoConstantName, const std::string& extraInfo = "") = 0; - }; - - class TextureUnitState : public PropertySet - { - public: - virtual ~TextureUnitState(); - virtual void setTextureName (const std::string& textureName) = 0; - - protected: - virtual bool setPropertyOverride (const std::string& name, PropertyValuePtr& value, PropertySetGet *context); - }; - - class Pass : public PropertySet - { - public: - virtual boost::shared_ptr createTextureUnitState (const std::string& name) = 0; - virtual void assignProgram (GpuProgramType type, const std::string& name) = 0; - - /// @param type gpu program type - /// @param name name of the uniform in the shader - /// @param vt type of value, e.g. vector4 - /// @param value value to set - /// @param context used for retrieving linked values - virtual void setGpuConstant (int type, const std::string& name, ValueType vt, PropertyValuePtr value, PropertySetGet* context) = 0; - - virtual void setTextureUnitIndex (int programType, const std::string& name, int index) = 0; - - virtual void addSharedParameter (int type, const std::string& name) = 0; - }; - - class Material : public PropertySet - { - public: - virtual boost::shared_ptr createPass (const std::string& configuration, unsigned short lodIndex) = 0; - virtual bool createConfiguration (const std::string& name, unsigned short lodIndex) = 0; ///< @return false if already exists - virtual void removeAll () = 0; ///< remove all configurations - - virtual bool isUnreferenced() = 0; - virtual void unreferenceTextures() = 0; - virtual void ensureLoaded() = 0; - - virtual void setLodLevels (const std::string& lodLevels) = 0; - - virtual void setShadowCasterMaterial (const std::string& name) = 0; - }; - - class Platform - { - public: - Platform (const std::string& basePath); - virtual ~Platform (); - - /// set the folder to use for shader caching - void setCacheFolder (const std::string& folder); - - private: - virtual boost::shared_ptr createMaterial (const std::string& name) = 0; - - virtual boost::shared_ptr createGpuProgram ( - GpuProgramType type, - const std::string& compileArguments, - const std::string& name, const std::string& profile, - const std::string& source, Language lang) = 0; - - virtual void destroyGpuProgram (const std::string& name) = 0; - - virtual void setSharedParameter (const std::string& name, PropertyValuePtr value) = 0; - - virtual bool isProfileSupported (const std::string& profile) = 0; - - virtual void serializeShaders (const std::string& file); - virtual void deserializeShaders (const std::string& file); - - std::string getCacheFolder () const; - - friend class Factory; - friend class MaterialInstance; - friend class ShaderInstance; - friend class ShaderSet; - - protected: - /** - * this will be \a true if the platform supports serialization (writing shader microcode - * to disk) and deserialization (create gpu program from saved microcode) - */ - virtual bool supportsShaderSerialization (); - - /** - * this will be \a true if the platform supports a listener that notifies the system - * whenever a material is requested for rendering. if this is supported, shaders can be - * compiled on-demand when needed (and not earlier) - * @todo the Factory is not designed yet to handle the case where this method returns false - */ - virtual bool supportsMaterialQueuedListener (); - - /** - * fire event: material requested for rendering - * @param name material name - * @param configuration requested configuration - */ - MaterialInstance* fireMaterialRequested (const std::string& name, const std::string& configuration, unsigned short lodIndex); - - std::string mCacheFolder; - Factory* mFactory; - - private: - void setFactory (Factory* factory); - - std::string mBasePath; - std::string getBasePath(); - }; -} - -#endif diff --git a/extern/shiny/Main/Preprocessor.cpp b/extern/shiny/Main/Preprocessor.cpp deleted file mode 100644 index 26481aa036..0000000000 --- a/extern/shiny/Main/Preprocessor.cpp +++ /dev/null @@ -1,151 +0,0 @@ -#include "Preprocessor.hpp" - -#include -#include -#include - -#include - -/* - Almost exact copy of load_file_to_string policy found in - boost::wave headers with the only change that it uses - boost::filesystem facility to handle UTF-8 paths properly on windows. - - Original namespace is used due to required bost::wave - internal symbols. -*/ -namespace boost { -namespace wave { -namespace iteration_context_policies { - - struct load_utf8_path_to_string - { - template - class inner - { - public: - template - static void init_iterators(IterContextT &iter_ctx, - PositionT const &act_pos, language_support language) - { - typedef typename IterContextT::iterator_type iterator_type; - namespace bfs = boost::filesystem; - - // read in the file - bfs::ifstream instream(bfs::path(iter_ctx.filename.c_str())); - if (!instream.is_open()) { - BOOST_WAVE_THROW_CTX(iter_ctx.ctx, preprocess_exception, - bad_include_file, iter_ctx.filename.c_str(), act_pos); - return; - } - instream.unsetf(std::ios::skipws); - - iter_ctx.instring.assign( - std::istreambuf_iterator(instream.rdbuf()), - std::istreambuf_iterator()); - - iter_ctx.first = iterator_type( - iter_ctx.instring.begin(), iter_ctx.instring.end(), - PositionT(iter_ctx.filename), language); - iter_ctx.last = iterator_type(); - } - - private: - std::string instring; - }; - }; -} } } - -namespace sh -{ - std::string Preprocessor::preprocess (std::string source, const std::string& includePath, std::vector definitions, const std::string& name) - { - std::stringstream returnString; - - // current file position is saved for exception handling - boost::wave::util::file_position_type current_position; - - try - { - // This token type is one of the central types used throughout the library. - // It is a template parameter to some of the public classes and instances - // of this type are returned from the iterators. - typedef boost::wave::cpplexer::lex_token<> token_type; - - // The template boost::wave::cpplexer::lex_iterator<> is the lexer type to - // to use as the token source for the preprocessing engine. It is - // parametrized with the token type. - typedef boost::wave::cpplexer::lex_iterator lex_iterator_type; - - // This is the resulting context type. The first template parameter should - // match the iterator type used during construction of the context - // instance (see below). It is the type of the underlying input stream. - typedef boost::wave::context - context_type; - - // The preprocessor iterator shouldn't be constructed directly. It is - // generated through a wave::context<> object. This wave:context<> object - // is additionally used to initialize and define different parameters of - // the actual preprocessing. - // - // The preprocessing of the input stream is done on the fly behind the - // scenes during iteration over the range of context_type::iterator_type - // instances. - context_type ctx (source.begin(), source.end(), name.c_str()); - ctx.add_include_path(includePath.c_str()); - for (std::vector::iterator it = definitions.begin(); it != definitions.end(); ++it) - { - ctx.add_macro_definition(*it); - } - - // Get the preprocessor iterators and use them to generate the token - // sequence. - context_type::iterator_type first = ctx.begin(); - context_type::iterator_type last = ctx.end(); - - // The input stream is preprocessed for you while iterating over the range - // [first, last). The dereferenced iterator returns tokens holding - // information about the preprocessed input stream, such as token type, - // token value, and position. - while (first != last) - { - current_position = (*first).get_position(); - returnString << (*first).get_value(); - ++first; - } - } - catch (boost::wave::cpp_exception const& e) - { - // some preprocessing error - std::stringstream error; - error - << e.file_name() << "(" << e.line_no() << "): " - << e.description(); - throw std::runtime_error(error.str()); - } - catch (std::exception const& e) - { - // use last recognized token to retrieve the error position - std::stringstream error; - error - << current_position.get_file() - << "(" << current_position.get_line() << "): " - << "exception caught: " << e.what(); - throw std::runtime_error(error.str()); - } - catch (...) - { - // use last recognized token to retrieve the error position - std::stringstream error; - error - << current_position.get_file() - << "(" << current_position.get_line() << "): " - << "unexpected exception caught."; - throw std::runtime_error(error.str()); - } - - return returnString.str(); - } -} diff --git a/extern/shiny/Main/Preprocessor.hpp b/extern/shiny/Main/Preprocessor.hpp deleted file mode 100644 index 7ee30ae7fc..0000000000 --- a/extern/shiny/Main/Preprocessor.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef SH_PREPROCESSOR_H -#define SH_PREPROCESSOR_H - -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include - -namespace sh -{ - /** - * @brief A simple interface for the boost::wave preprocessor - */ - class Preprocessor - { - public: - /** - * @brief Run a shader source string through the preprocessor - * @param source source string - * @param includePath path to search for includes (that are included with #include) - * @param definitions macros to predefine (vector of strings of the format MACRO=value, or just MACRO to define it as 1) - * @param name name to use for error messages - * @return processed string - */ - static std::string preprocess (std::string source, const std::string& includePath, std::vector definitions, const std::string& name); - }; - - - - class emit_custom_line_directives_hooks - : public boost::wave::context_policies::default_preprocessing_hooks - { - public: - - template - bool - emit_line_directive(ContextT const& ctx, ContainerT &pending, - typename ContextT::token_type const& act_token) - { - // emit a #line directive showing the relative filename instead - typename ContextT::position_type pos = act_token.get_position(); - unsigned int column = 1; - - typedef typename ContextT::token_type result_type; - - // no line directives for now - pos.set_column(column); - pending.push_back(result_type(boost::wave::T_GENERATEDNEWLINE, "\n", pos)); - - return true; - } - }; - - -} - -#endif diff --git a/extern/shiny/Main/PropertyBase.cpp b/extern/shiny/Main/PropertyBase.cpp deleted file mode 100644 index 8592712fa9..0000000000 --- a/extern/shiny/Main/PropertyBase.cpp +++ /dev/null @@ -1,302 +0,0 @@ -#include "PropertyBase.hpp" - -#include -#include - -#include -#include - -#include - -namespace sh -{ - - IntValue::IntValue(int in) - : mValue(in) - { - } - - IntValue::IntValue(const std::string& in) - { - mValue = boost::lexical_cast(in); - } - - std::string IntValue::serialize() - { - return boost::lexical_cast(mValue); - } - - // ------------------------------------------------------------------------------ - - BooleanValue::BooleanValue (bool in) - : mValue(in) - { - } - - BooleanValue::BooleanValue (const std::string& in) - { - if (in == "true") - mValue = true; - else if (in == "false") - mValue = false; - else - { - std::stringstream msg; - msg << "sh::BooleanValue: Warning: Unrecognized value \"" << in << "\" for property value of type BooleanValue"; - throw std::runtime_error(msg.str()); - } - } - - std::string BooleanValue::serialize () - { - if (mValue) - return "true"; - else - return "false"; - } - - // ------------------------------------------------------------------------------ - - StringValue::StringValue (const std::string& in) - { - mStringValue = in; - } - - std::string StringValue::serialize() - { - return mStringValue; - } - - // ------------------------------------------------------------------------------ - - LinkedValue::LinkedValue (const std::string& in) - { - mStringValue = in; - mStringValue.erase(0, 1); - } - - std::string LinkedValue::serialize() - { - throw std::runtime_error ("can't directly get a linked value"); - } - - std::string LinkedValue::get(PropertySetGet* context) const - { - PropertyValuePtr p = context->getProperty(mStringValue); - return retrieveValue(p, NULL).get(); - } - - // ------------------------------------------------------------------------------ - - FloatValue::FloatValue (float in) - { - mValue = in; - } - - FloatValue::FloatValue (const std::string& in) - { - mValue = boost::lexical_cast(in); - } - - std::string FloatValue::serialize () - { - return boost::lexical_cast(mValue); - } - - // ------------------------------------------------------------------------------ - - Vector2::Vector2 (float x, float y) - : mX(x) - , mY(y) - { - } - - Vector2::Vector2 (const std::string& in) - { - std::vector tokens; - boost::split(tokens, in, boost::is_any_of(" ")); - assert ((tokens.size() == 2) && "Invalid Vector2 conversion"); - mX = boost::lexical_cast (tokens[0]); - mY = boost::lexical_cast (tokens[1]); - } - - std::string Vector2::serialize () - { - return boost::lexical_cast(mX) + " " - + boost::lexical_cast(mY); - } - - // ------------------------------------------------------------------------------ - - Vector3::Vector3 (float x, float y, float z) - : mX(x) - , mY(y) - , mZ(z) - { - } - - Vector3::Vector3 (const std::string& in) - { - std::vector tokens; - boost::split(tokens, in, boost::is_any_of(" ")); - assert ((tokens.size() == 3) && "Invalid Vector3 conversion"); - mX = boost::lexical_cast (tokens[0]); - mY = boost::lexical_cast (tokens[1]); - mZ = boost::lexical_cast (tokens[2]); - } - - std::string Vector3::serialize () - { - return boost::lexical_cast(mX) + " " - + boost::lexical_cast(mY) + " " - + boost::lexical_cast(mZ); - } - - // ------------------------------------------------------------------------------ - - Vector4::Vector4 (float x, float y, float z, float w) - : mX(x) - , mY(y) - , mZ(z) - , mW(w) - { - } - - Vector4::Vector4 (const std::string& in) - { - std::vector tokens; - boost::split(tokens, in, boost::is_any_of(" ")); - assert ((tokens.size() == 4) && "Invalid Vector4 conversion"); - mX = boost::lexical_cast (tokens[0]); - mY = boost::lexical_cast (tokens[1]); - mZ = boost::lexical_cast (tokens[2]); - mW = boost::lexical_cast (tokens[3]); - } - - std::string Vector4::serialize () - { - return boost::lexical_cast(mX) + " " - + boost::lexical_cast(mY) + " " - + boost::lexical_cast(mZ) + " " - + boost::lexical_cast(mW); - } - - // ------------------------------------------------------------------------------ - - void PropertySet::setProperty (const std::string& name, PropertyValuePtr &value, PropertySetGet* context) - { - if (!setPropertyOverride (name, value, context)) - { - std::stringstream msg; - msg << "sh::PropertySet: Warning: No match for property with name '" << name << "'"; - throw std::runtime_error(msg.str()); - } - } - - bool PropertySet::setPropertyOverride (const std::string& name, PropertyValuePtr &value, PropertySetGet* context) - { - // if we got here, none of the sub-classes were able to make use of the property - return false; - } - - // ------------------------------------------------------------------------------ - - PropertySetGet::PropertySetGet (PropertySetGet* parent) - : mParent(parent) - , mContext(NULL) - { - } - - PropertySetGet::PropertySetGet () - : mParent(NULL) - , mContext(NULL) - { - } - - void PropertySetGet::setParent (PropertySetGet* parent) - { - mParent = parent; - } - - void PropertySetGet::setContext (PropertySetGet* context) - { - mContext = context; - } - - PropertySetGet* PropertySetGet::getContext() - { - return mContext; - } - - void PropertySetGet::setProperty (const std::string& name, PropertyValuePtr value) - { - mProperties [name] = value; - } - - void PropertySetGet::deleteProperty(const std::string &name) - { - mProperties.erase(name); - } - - PropertyValuePtr& PropertySetGet::getProperty (const std::string& name) - { - bool found = (mProperties.find(name) != mProperties.end()); - - if (!found) - { - if (!mParent) - throw std::runtime_error ("Trying to retrieve property \"" + name + "\" that does not exist"); - else - return mParent->getProperty (name); - } - else - return mProperties[name]; - } - - bool PropertySetGet::hasProperty (const std::string& name) const - { - bool found = (mProperties.find(name) != mProperties.end()); - - if (!found) - { - if (!mParent) - return false; - else - return mParent->hasProperty (name); - } - else - return true; - } - - void PropertySetGet::copyAll (PropertySet* target, PropertySetGet* context, bool copyParent) - { - if (mParent && copyParent) - mParent->copyAll (target, context); - for (PropertyMap::iterator it = mProperties.begin(); it != mProperties.end(); ++it) - { - target->setProperty(it->first, it->second, context); - } - } - - void PropertySetGet::copyAll (PropertySetGet* target, PropertySetGet* context, bool copyParent) - { - if (mParent && copyParent) - mParent->copyAll (target, context); - for (PropertyMap::iterator it = mProperties.begin(); it != mProperties.end(); ++it) - { - std::string val = retrieveValue(it->second, this).get(); - target->setProperty(it->first, sh::makeProperty(new sh::StringValue(val))); - } - } - - void PropertySetGet::save(std::ofstream &stream, const std::string& indentation) - { - for (PropertyMap::iterator it = mProperties.begin(); it != mProperties.end(); ++it) - { - if (typeid( *(it->second) ) == typeid(LinkedValue)) - stream << indentation << it->first << " " << "$" + static_cast(&*(it->second))->_getStringValue() << '\n'; - else - stream << indentation << it->first << " " << retrieveValue(it->second, this).get() << '\n'; - } - } -} diff --git a/extern/shiny/Main/PropertyBase.hpp b/extern/shiny/Main/PropertyBase.hpp deleted file mode 100644 index 13809443e2..0000000000 --- a/extern/shiny/Main/PropertyBase.hpp +++ /dev/null @@ -1,244 +0,0 @@ -#ifndef SH_PROPERTYBASE_H -#define SH_PROPERTYBASE_H - -#include -#include - -#include - -namespace sh -{ - class StringValue; - class PropertySetGet; - class LinkedValue; - - enum ValueType - { - VT_String, - VT_Int, - VT_Float, - VT_Vector2, - VT_Vector3, - VT_Vector4 - }; - - class PropertyValue - { - public: - PropertyValue() {} - - virtual ~PropertyValue() {} - - std::string _getStringValue() { return mStringValue; } - - virtual std::string serialize() = 0; - - protected: - std::string mStringValue; ///< this will possibly not contain anything in the specialised classes - }; - typedef boost::shared_ptr PropertyValuePtr; - - class StringValue : public PropertyValue - { - public: - StringValue (const std::string& in); - std::string get() const { return mStringValue; } - - virtual std::string serialize(); - }; - - /** - * @brief Used for retrieving a named property from a context - */ - class LinkedValue : public PropertyValue - { - public: - LinkedValue (const std::string& in); - - std::string get(PropertySetGet* context) const; - - virtual std::string serialize(); - }; - - class FloatValue : public PropertyValue - { - public: - FloatValue (float in); - FloatValue (const std::string& in); - float get() const { return mValue; } - - virtual std::string serialize(); - private: - float mValue; - }; - - class IntValue : public PropertyValue - { - public: - IntValue (int in); - IntValue (const std::string& in); - int get() const { return mValue; } - - virtual std::string serialize(); - private: - int mValue; - }; - - class BooleanValue : public PropertyValue - { - public: - BooleanValue (bool in); - BooleanValue (const std::string& in); - bool get() const { return mValue; } - - virtual std::string serialize(); - private: - bool mValue; - }; - - class Vector2 : public PropertyValue - { - public: - Vector2 (float x, float y); - Vector2 (const std::string& in); - - float mX, mY; - - virtual std::string serialize(); - }; - - class Vector3 : public PropertyValue - { - public: - Vector3 (float x, float y, float z); - Vector3 (const std::string& in); - - float mX, mY, mZ; - - virtual std::string serialize(); - }; - - class Vector4 : public PropertyValue - { - public: - Vector4 (float x, float y, float z, float w); - Vector4 (const std::string& in); - - float mX, mY, mZ, mW; - - virtual std::string serialize(); - }; - - /// \brief base class that allows setting properties with any kind of value-type - class PropertySet - { - public: - virtual ~PropertySet() {} - void setProperty (const std::string& name, PropertyValuePtr& value, PropertySetGet* context); - - protected: - virtual bool setPropertyOverride (const std::string& name, PropertyValuePtr& value, PropertySetGet* context); - ///< @return \a true if the specified property was found, or false otherwise - }; - - typedef std::map PropertyMap; - - /// \brief base class that allows setting properties with any kind of value-type and retrieving them - class PropertySetGet - { - public: - PropertySetGet (PropertySetGet* parent); - PropertySetGet (); - - virtual ~PropertySetGet() {} - - void save (std::ofstream& stream, const std::string& indentation); - - void copyAll (PropertySet* target, PropertySetGet* context, bool copyParent=true); - ///< call setProperty for each property/value pair stored in \a this - void copyAll (PropertySetGet* target, PropertySetGet* context, bool copyParent=true); - ///< call setProperty for each property/value pair stored in \a this - - void setParent (PropertySetGet* parent); - PropertySetGet* getParent () { return mParent; } - void setContext (PropertySetGet* context); - PropertySetGet* getContext(); - - virtual void setProperty (const std::string& name, PropertyValuePtr value); - PropertyValuePtr& getProperty (const std::string& name); - - void deleteProperty (const std::string& name); - - const PropertyMap& listProperties() { return mProperties; } - - bool hasProperty (const std::string& name) const; - - private: - PropertyMap mProperties; - - protected: - PropertySetGet* mParent; - ///< the parent can provide properties as well (when they are retrieved via getProperty) \n - /// multiple levels of inheritance are also supported \n - /// children can override properties of their parents - - PropertySetGet* mContext; - ///< used to retrieve linked property values - }; - - template - static T retrieveValue (boost::shared_ptr& value, PropertySetGet* context) - { - if (typeid(*value).name() == typeid(LinkedValue).name()) - { - std::string v = static_cast(value.get())->get(context); - PropertyValuePtr newVal = PropertyValuePtr (new StringValue(v)); - return retrieveValue(newVal, NULL); - } - if (typeid(T).name() == typeid(*value).name()) - { - // requested type is the same as source type, only have to cast it - return *static_cast(value.get()); - } - - if ((typeid(T).name() == typeid(StringValue).name()) - && typeid(*value).name() != typeid(StringValue).name()) - { - // if string type is requested and value is not string, use serialize method to convert to string - T* ptr = new T (value->serialize()); // note that T is always StringValue here, but we can't use it here - value = boost::shared_ptr (static_cast(ptr)); - return *ptr; - } - - { - // remaining case: deserialization from string by passing the string to constructor of class T - T* ptr = new T(value->_getStringValue()); - PropertyValuePtr newVal (static_cast(ptr)); - value = newVal; - return *ptr; - } - } - ///< - /// @brief alternate version that supports linked values (use of $variables in parent material) - /// @note \a value is changed in-place to the converted object - /// @return converted object \n - - /// Create a property from a string - inline PropertyValuePtr makeProperty (const std::string& prop) - { - if (prop.size() > 1 && prop[0] == '$') - return PropertyValuePtr (static_cast(new LinkedValue(prop))); - else - return PropertyValuePtr (static_cast (new StringValue(prop))); - } - - template - /// Create a property of any type - /// Example: sh::makeProperty (new sh::Vector4(1, 1, 1, 1)) - inline PropertyValuePtr makeProperty (T* p) - { - return PropertyValuePtr ( static_cast(p) ); - } -} - -#endif diff --git a/extern/shiny/Main/ScriptLoader.cpp b/extern/shiny/Main/ScriptLoader.cpp deleted file mode 100644 index bafe07fc8e..0000000000 --- a/extern/shiny/Main/ScriptLoader.cpp +++ /dev/null @@ -1,414 +0,0 @@ -#include "ScriptLoader.hpp" - -#include -#include -#include -#include - -#include - -namespace sh -{ - void ScriptLoader::loadAllFiles(ScriptLoader* c, const std::string& path) - { - for ( boost::filesystem::recursive_directory_iterator end, dir(path); dir != end; ++dir ) - { - boost::filesystem::path p(*dir); - if(p.extension() == c->mFileEnding) - { - c->mCurrentFileName = (*dir).path().string(); - std::ifstream in((*dir).path().string().c_str(), std::ios::binary); - c->parseScript(in); - } - } - } - - ScriptLoader::ScriptLoader(const std::string& fileEnding) - : mLoadOrder(0) - , mToken(TOKEN_NewLine) - , mLastToken(TOKEN_NewLine) - - { - mFileEnding = fileEnding; - } - - ScriptLoader::~ScriptLoader() - { - clearScriptList(); - } - - void ScriptLoader::clearScriptList() - { - std::map ::iterator i; - for (i = m_scriptList.begin(); i != m_scriptList.end(); ++i) - { - delete i->second; - } - m_scriptList.clear(); - } - - ScriptNode *ScriptLoader::getConfigScript(const std::string &name) - { - std::map ::iterator i; - - std::string key = name; - i = m_scriptList.find(key); - - //If found.. - if (i != m_scriptList.end()) - { - return i->second; - } - else - { - return NULL; - } - } - - std::map ScriptLoader::getAllConfigScripts () - { - return m_scriptList; - } - - void ScriptLoader::parseScript(std::ifstream &stream) - { - //Get first token - _nextToken(stream); - if (mToken == TOKEN_EOF) - { - stream.close(); - return; - } - - //Parse the script - _parseNodes(stream, 0); - - stream.close(); - } - - void ScriptLoader::_nextToken(std::ifstream &stream) - { - //EOF token - if (!stream.good()) - { - mToken = TOKEN_EOF; - return; - } - - //(Get next character) - int ch = stream.get(); - - while ((ch == ' ' || ch == 9) && !stream.eof()) - { //Skip leading spaces / tabs - ch = stream.get(); - } - - if (!stream.good()) - { - mToken = TOKEN_EOF; - return; - } - - //Newline token - if (ch == '\r' || ch == '\n') - { - do - { - ch = stream.get(); - } while ((ch == '\r' || ch == '\n') && !stream.eof()); - - stream.unget(); - - mToken = TOKEN_NewLine; - return; - } - - //Open brace token - else if (ch == '{') - { - mToken = TOKEN_OpenBrace; - return; - } - - //Close brace token - else if (ch == '}') - { - mToken = TOKEN_CloseBrace; - return; - } - - //Text token - if (ch < 32 || ch > 122) //Verify valid char - { - throw std::runtime_error("Parse Error: Invalid character, ConfigLoader::load()"); - } - - mTokenValue = ""; - mToken = TOKEN_Text; - do - { - //Skip comments - if (ch == '/') - { - int ch2 = stream.peek(); - - //C++ style comment (//) - if (ch2 == '/') - { - stream.get(); - do - { - ch = stream.get(); - } while (ch != '\r' && ch != '\n' && !stream.eof()); - - mToken = TOKEN_NewLine; - return; - } - } - - //Add valid char to tokVal - mTokenValue += (char)ch; - - //Next char - ch = stream.get(); - - } while (ch > 32 && ch <= 122 && !stream.eof()); - - stream.unget(); - - return; - } - - void ScriptLoader::_skipNewLines(std::ifstream &stream) - { - while (mToken == TOKEN_NewLine) - { - _nextToken(stream); - } - } - - void ScriptLoader::_parseNodes(std::ifstream &stream, ScriptNode *parent) - { - typedef std::pair ScriptItem; - - while (true) - { - switch (mToken) - { - //Node - case TOKEN_Text: - { - //Add the new node - ScriptNode *newNode; - if (parent) - { - newNode = parent->addChild(mTokenValue); - } - else - { - newNode = new ScriptNode(0, mTokenValue); - } - - //Get values - _nextToken(stream); - std::string valueStr; - int i=0; - while (mToken == TOKEN_Text) - { - if (i == 0) - valueStr += mTokenValue; - else - valueStr += " " + mTokenValue; - _nextToken(stream); - ++i; - } - newNode->setValue(valueStr); - - //Add root nodes to scriptList - if (!parent) - { - std::string key; - - if (newNode->getValue() == "") - throw std::runtime_error("Root node must have a name (\"" + newNode->getName() + "\")"); - key = newNode->getValue(); - - m_scriptList.insert(ScriptItem(key, newNode)); - } - - _skipNewLines(stream); - - //Add any sub-nodes - if (mToken == TOKEN_OpenBrace) - { - //Parse nodes - _nextToken(stream); - _parseNodes(stream, newNode); - //Check for matching closing brace - if (mToken != TOKEN_CloseBrace) - { - throw std::runtime_error("Parse Error: Expecting closing brace"); - } - _nextToken(stream); - _skipNewLines(stream); - } - - newNode->mFileName = mCurrentFileName; - - break; - } - - //Out of place brace - case TOKEN_OpenBrace: - throw std::runtime_error("Parse Error: Opening brace out of plane"); - break; - - //Return if end of nodes have been reached - case TOKEN_CloseBrace: - return; - - //Return if reached end of file - case TOKEN_EOF: - return; - - case TOKEN_NewLine: - _nextToken(stream); - break; - } - }; - } - - ScriptNode::ScriptNode(ScriptNode *parent, const std::string &name) - { - mName = name; - mParent = parent; - mRemoveSelf = true; //For proper destruction - mLastChildFound = -1; - - //Add self to parent's child list (unless this is the root node being created) - if (parent != NULL) - { - mParent->mChildren.push_back(this); - mIter = --(mParent->mChildren.end()); - } - } - - ScriptNode::~ScriptNode() - { - //Delete all children - std::vector::iterator i; - for (i = mChildren.begin(); i != mChildren.end(); ++i) - { - ScriptNode *node = *i; - node->mRemoveSelf = false; - delete node; - } - mChildren.clear(); - - //Remove self from parent's child list - if (mRemoveSelf && mParent != NULL) - { - mParent->mChildren.erase(mIter); - } - } - - ScriptNode *ScriptNode::addChild(const std::string &name, bool replaceExisting) - { - if (replaceExisting) - { - ScriptNode *node = findChild(name, false); - if (node) - { - return node; - } - } - return new ScriptNode(this, name); - } - - ScriptNode *ScriptNode::findChild(const std::string &name, bool recursive) - { - int indx; - int childCount = (int)mChildren.size(); - - if (mLastChildFound != -1) - { - //If possible, try checking the nodes neighboring the last successful search - //(often nodes searched for in sequence, so this will boost search speeds). - int prevC = mLastChildFound-1; - if (prevC < 0) - prevC = 0; - else if (prevC >= childCount) - prevC = childCount-1; - int nextC = mLastChildFound+1; - if (nextC < 0) - nextC = 0; - else if (nextC >= childCount) - nextC = childCount-1; - - for (indx = prevC; indx <= nextC; ++indx) - { - ScriptNode *node = mChildren[indx]; - if (node->mName == name) - { - mLastChildFound = indx; - return node; - } - } - - //If not found that way, search for the node from start to finish, avoiding the - //already searched area above. - for (indx = nextC + 1; indx < childCount; ++indx) - { - ScriptNode *node = mChildren[indx]; - if (node->mName == name) { - mLastChildFound = indx; - return node; - } - } - for (indx = 0; indx < prevC; ++indx) - { - ScriptNode *node = mChildren[indx]; - if (node->mName == name) { - mLastChildFound = indx; - return node; - } - } - } - else - { - //Search for the node from start to finish - for (indx = 0; indx < childCount; ++indx){ - ScriptNode *node = mChildren[indx]; - if (node->mName == name) { - mLastChildFound = indx; - return node; - } - } - } - - //If not found, search child nodes (if recursive == true) - if (recursive) - { - for (indx = 0; indx < childCount; ++indx) - { - mChildren[indx]->findChild(name, recursive); - } - } - - //Not found anywhere - return NULL; - } - - void ScriptNode::setParent(ScriptNode *newParent) - { - //Remove self from current parent - mParent->mChildren.erase(mIter); - - //Set new parent - mParent = newParent; - - //Add self to new parent - mParent->mChildren.push_back(this); - mIter = --(mParent->mChildren.end()); - } -} diff --git a/extern/shiny/Main/ScriptLoader.hpp b/extern/shiny/Main/ScriptLoader.hpp deleted file mode 100644 index 89720fb5d0..0000000000 --- a/extern/shiny/Main/ScriptLoader.hpp +++ /dev/null @@ -1,134 +0,0 @@ -#ifndef SH_CONFIG_LOADER_H__ -#define SH_CONFIG_LOADER_H__ - -#include -#include -#include -#include - -namespace sh -{ - class ScriptNode; - - /** - * @brief The base class of loaders that read Ogre style script files to get configuration and settings. - * Heavily inspired by: http://www.ogre3d.org/tikiwiki/All-purpose+script+parser - * ( "Non-ogre version") - */ - class ScriptLoader - { - public: - static void loadAllFiles(ScriptLoader* c, const std::string& path); - - ScriptLoader(const std::string& fileEnding); - virtual ~ScriptLoader(); - - std::string mFileEnding; - - // For a line like - // entity animals/dog - // { - // ... - // } - // The type is "entity" and the name is "animals/dog" - // Or if animal/dog was not there then name is "" - ScriptNode *getConfigScript (const std::string &name); - - std::map getAllConfigScripts (); - - void parseScript(std::ifstream &stream); - - std::string mCurrentFileName; - - protected: - - float mLoadOrder; - // like "*.object" - - std::map m_scriptList; - - enum Token - { - TOKEN_Text, - TOKEN_NewLine, - TOKEN_OpenBrace, - TOKEN_CloseBrace, - TOKEN_EOF - }; - - Token mToken, mLastToken; - std::string mTokenValue; - - void _parseNodes(std::ifstream &stream, ScriptNode *parent); - void _nextToken(std::ifstream &stream); - void _skipNewLines(std::ifstream &stream); - - void clearScriptList(); - }; - - class ScriptNode - { - public: - ScriptNode(ScriptNode *parent, const std::string &name = "untitled"); - ~ScriptNode(); - - inline void setName(const std::string &name) - { - this->mName = name; - } - - inline std::string &getName() - { - return mName; - } - - inline void setValue(const std::string &value) - { - mValue = value; - } - - inline std::string &getValue() - { - return mValue; - } - - ScriptNode *addChild(const std::string &name = "untitled", bool replaceExisting = false); - ScriptNode *findChild(const std::string &name, bool recursive = false); - - inline std::vector &getChildren() - { - return mChildren; - } - - inline ScriptNode *getChild(unsigned int index = 0) - { - assert(index < mChildren.size()); - return mChildren[index]; - } - - void setParent(ScriptNode *newParent); - - inline ScriptNode *getParent() - { - return mParent; - } - - std::string mFileName; - - - private: - std::string mName; - std::string mValue; - std::vector mChildren; - ScriptNode *mParent; - - - int mLastChildFound; //The last child node's index found with a call to findChild() - - std::vector::iterator mIter; - bool mRemoveSelf; - }; - -} - -#endif diff --git a/extern/shiny/Main/ShaderInstance.cpp b/extern/shiny/Main/ShaderInstance.cpp deleted file mode 100644 index 270aaba68f..0000000000 --- a/extern/shiny/Main/ShaderInstance.cpp +++ /dev/null @@ -1,698 +0,0 @@ -#include "ShaderInstance.hpp" - -#include -#include -#include - -#include -#include -#include - -#include - -#include "Preprocessor.hpp" -#include "Factory.hpp" -#include "ShaderSet.hpp" - -namespace -{ - std::string convertLang (sh::Language lang) - { - if (lang == sh::Language_CG) - return "SH_CG"; - else if (lang == sh::Language_HLSL) - return "SH_HLSL"; - else if (lang == sh::Language_GLSL) - return "SH_GLSL"; - else if (lang == sh::Language_GLSLES) - return "SH_GLSLES"; - throw std::runtime_error("invalid language"); - } - - char getComponent(int num) - { - if (num == 0) - return 'x'; - else if (num == 1) - return 'y'; - else if (num == 2) - return 'z'; - else if (num == 3) - return 'w'; - else - throw std::runtime_error("invalid component"); - } - - std::string getFloat(sh::Language lang, int num_components) - { - if (lang == sh::Language_CG || lang == sh::Language_HLSL) - return (num_components == 1) ? "float" : "float" + boost::lexical_cast(num_components); - else - return (num_components == 1) ? "float" : "vec" + boost::lexical_cast(num_components); - } - - bool isCmd (const std::string& source, size_t pos, const std::string& cmd) - { - return (source.size() >= pos + cmd.size() && source.substr(pos, cmd.size()) == cmd); - } - - void writeDebugFile (const std::string& content, const std::string& filename) - { - boost::filesystem::path full_path(boost::filesystem::current_path()); - std::ofstream of ((full_path / filename ).string().c_str() , std::ios_base::out); - of.write(content.c_str(), content.size()); - of.close(); - } -} - -namespace sh -{ - std::string Passthrough::expand_assign(std::string toAssign) - { - std::string res; - - int i = 0; - int current_passthrough = passthrough_number; - int current_component_left = component_start; - int current_component_right = 0; - int components_left = num_components; - int components_at_once; - while (i < num_components) - { - if (components_left + current_component_left <= 4) - components_at_once = components_left; - else - components_at_once = 4 - current_component_left; - - std::string componentStr = "."; - for (int j = 0; j < components_at_once; ++j) - componentStr += getComponent(j + current_component_left); - std::string componentStr2 = "."; - for (int j = 0; j < components_at_once; ++j) - componentStr2 += getComponent(j + current_component_right); - if (num_components == 1) - { - componentStr2 = ""; - } - res += "passthrough" + boost::lexical_cast(current_passthrough) + componentStr + " = " + toAssign + componentStr2; - - current_component_left += components_at_once; - current_component_right += components_at_once; - components_left -= components_at_once; - - i += components_at_once; - - if (components_left == 0) - { - // finished - return res; - } - else - { - // add semicolon to every instruction but the last - res += "; "; - } - - if (current_component_left == 4) - { - current_passthrough++; - current_component_left = 0; - } - } - throw std::runtime_error("expand_assign error"); // this should never happen, but gets us rid of the "control reaches end of non-void function" warning - } - - std::string Passthrough::expand_receive() - { - std::string res; - - res += getFloat(lang, num_components) + "("; - - int i = 0; - int current_passthrough = passthrough_number; - int current_component = component_start; - int components_left = num_components; - while (i < num_components) - { - int components_at_once = std::min(components_left, 4 - current_component); - - std::string componentStr; - for (int j = 0; j < components_at_once; ++j) - componentStr += getComponent(j + current_component); - - res += "passthrough" + boost::lexical_cast(current_passthrough) + "." + componentStr; - - current_component += components_at_once; - - components_left -= components_at_once; - - i += components_at_once; - - if (components_left == 0) - { - // finished - return res + ")"; -; - } - else - { - // add comma to every variable but the last - res += ", "; - } - - if (current_component == 4) - { - current_passthrough++; - current_component = 0; - } - } - - throw std::runtime_error("expand_receive error"); // this should never happen, but gets us rid of the "control reaches end of non-void function" warning - } - - // ------------------------------------------------------------------------------ - - void ShaderInstance::parse (std::string& source, PropertySetGet* properties) - { - size_t pos = 0; - while (true) - { - pos = source.find("@", pos); - if (pos == std::string::npos) - break; - - if (isCmd(source, pos, "@shProperty")) - { - std::vector args = extractMacroArguments (pos, source); - - size_t start = source.find("(", pos); - size_t end = source.find(")", pos); - std::string cmd = source.substr(pos+1, start-(pos+1)); - - std::string replaceValue; - if (cmd == "shPropertyBool") - { - std::string propertyName = args[0]; - PropertyValuePtr value = properties->getProperty(propertyName); - bool val = retrieveValue(value, properties->getContext()).get(); - replaceValue = val ? "1" : "0"; - } - else if (cmd == "shPropertyString") - { - std::string propertyName = args[0]; - PropertyValuePtr value = properties->getProperty(propertyName); - replaceValue = retrieveValue(value, properties->getContext()).get(); - } - else if (cmd == "shPropertyEqual") - { - std::string propertyName = args[0]; - std::string comparedAgainst = args[1]; - std::string value = retrieveValue(properties->getProperty(propertyName), properties->getContext()).get(); - replaceValue = (value == comparedAgainst) ? "1" : "0"; - } - else if (isCmd(source, pos, "@shPropertyHasValue")) - { - assert(args.size() == 1); - std::string propertyName = args[0]; - PropertyValuePtr value = properties->getProperty(propertyName); - std::string val = retrieveValue(value, properties->getContext()).get(); - replaceValue = (val.empty() ? "0" : "1"); - } - else - throw std::runtime_error ("unknown command \"" + cmd + "\""); - source.replace(pos, (end+1)-pos, replaceValue); - } - else if (isCmd(source, pos, "@shGlobalSetting")) - { - std::vector args = extractMacroArguments (pos, source); - - std::string cmd = source.substr(pos+1, source.find("(", pos)-(pos+1)); - std::string replaceValue; - if (cmd == "shGlobalSettingBool") - { - std::string settingName = args[0]; - std::string value = retrieveValue(mParent->getCurrentGlobalSettings()->getProperty(settingName), NULL).get(); - replaceValue = (value == "true" || value == "1") ? "1" : "0"; - } - else if (cmd == "shGlobalSettingEqual") - { - std::string settingName = args[0]; - std::string comparedAgainst = args[1]; - std::string value = retrieveValue(mParent->getCurrentGlobalSettings()->getProperty(settingName), NULL).get(); - replaceValue = (value == comparedAgainst) ? "1" : "0"; - } - else if (cmd == "shGlobalSettingString") - { - std::string settingName = args[0]; - replaceValue = retrieveValue(mParent->getCurrentGlobalSettings()->getProperty(settingName), NULL).get(); - } - else - throw std::runtime_error ("unknown command \"" + cmd + "\""); - - source.replace(pos, (source.find(")", pos)+1)-pos, replaceValue); - } - else if (isCmd(source, pos, "@shForeach")) - { - - assert(source.find("@shEndForeach", pos) != std::string::npos); - size_t block_end = source.find("@shEndForeach", pos); - - // get the argument for parsing - size_t start = source.find("(", pos); - size_t end = start; - int brace_depth = 1; - while (brace_depth > 0) - { - ++end; - if (source[end] == '(') - ++brace_depth; - else if (source[end] == ')') - --brace_depth; - } - std::string arg = source.substr(start+1, end-(start+1)); - parse(arg, properties); - - int num = boost::lexical_cast(arg); - - // get the content of the inner block - std::string content = source.substr(end+1, block_end - (end+1)); - - // replace both outer and inner block with content of inner block num times - std::string replaceStr; - for (int i=0; i 0) - { - ++_end; - if (addStr[_end] == '(') - ++_brace_depth; - else if (addStr[_end] == ')') - --_brace_depth; - } - std::string arg = addStr.substr(_start+1, _end-(_start+1)); - parse(arg, properties); - - int offset = boost::lexical_cast (arg); - addStr.replace(pos2, (_end+1)-pos2, boost::lexical_cast(i+offset)); - } - else - { - addStr.replace(pos2, std::string("@shIterator").length(), boost::lexical_cast(i)); - } - } - - replaceStr += addStr; - } - source.replace(pos, (block_end+std::string("@shEndForeach").length())-pos, replaceStr); - } - else if (source.size() > pos+1) - ++pos; // skip - } - - } - - ShaderInstance::ShaderInstance (ShaderSet* parent, const std::string& name, PropertySetGet* properties) - : mName(name) - , mParent(parent) - , mSupported(true) - , mCurrentPassthrough(0) - , mCurrentComponent(0) - { - std::string source = mParent->getSource(); - int type = mParent->getType(); - std::string basePath = mParent->getBasePath(); - size_t pos; - - bool readCache = Factory::getInstance ().getReadSourceCache () && boost::filesystem::exists( - Factory::getInstance ().getCacheFolder () + "/" + mName); - bool writeCache = Factory::getInstance ().getWriteSourceCache (); - - - if (readCache) - { - std::ifstream ifs( std::string(Factory::getInstance ().getCacheFolder () + "/" + mName).c_str() ); - std::stringstream ss; - ss << ifs.rdbuf(); - source = ss.str(); - } - else - { - std::vector definitions; - - if (mParent->getType() == GPT_Vertex) - definitions.push_back("SH_VERTEX_SHADER"); - else - definitions.push_back("SH_FRAGMENT_SHADER"); - definitions.push_back(convertLang(Factory::getInstance().getCurrentLanguage())); - - parse(source, properties); - - if (Factory::getInstance ().getShaderDebugOutputEnabled ()) - writeDebugFile(source, name + ".pre"); - - // why do we need our own preprocessor? there are several custom commands available in the shader files - // (for example for binding uniforms to properties or auto constants) - more below. it is important that these - // commands are _only executed if the specific code path actually "survives" the compilation. - // thus, we run the code through a preprocessor first to remove the parts that are unused because of - // unmet #if conditions (or other preprocessor directives). - source = Preprocessor::preprocess(source, basePath, definitions, name); - - // parse counter - std::map counters; - while (true) - { - pos = source.find("@shCounter"); - if (pos == std::string::npos) - break; - - size_t end = source.find(")", pos); - - std::vector args = extractMacroArguments (pos, source); - assert(args.size()); - - int index = boost::lexical_cast(args[0]); - - if (counters.find(index) == counters.end()) - counters[index] = 0; - - source.replace(pos, (end+1)-pos, boost::lexical_cast(counters[index]++)); - } - - // parse passthrough declarations - while (true) - { - pos = source.find("@shAllocatePassthrough"); - if (pos == std::string::npos) - break; - - if (mCurrentPassthrough > 7) - throw std::runtime_error ("too many passthrough's requested (max 8)"); - - std::vector args = extractMacroArguments (pos, source); - assert(args.size() == 2); - - size_t end = source.find(")", pos); - - Passthrough passthrough; - - passthrough.num_components = boost::lexical_cast(args[0]); - assert (passthrough.num_components != 0); - - std::string passthroughName = args[1]; - passthrough.lang = Factory::getInstance().getCurrentLanguage (); - passthrough.component_start = mCurrentComponent; - passthrough.passthrough_number = mCurrentPassthrough; - - mPassthroughMap[passthroughName] = passthrough; - - mCurrentComponent += passthrough.num_components; - if (mCurrentComponent > 3) - { - mCurrentComponent -= 4; - ++mCurrentPassthrough; - } - - source.erase(pos, (end+1)-pos); - } - - // passthrough assign - while (true) - { - pos = source.find("@shPassthroughAssign"); - if (pos == std::string::npos) - break; - - std::vector args = extractMacroArguments (pos, source); - assert(args.size() == 2); - - size_t end = source.find(")", pos); - - std::string passthroughName = args[0]; - std::string assignTo = args[1]; - - assert(mPassthroughMap.find(passthroughName) != mPassthroughMap.end()); - Passthrough& p = mPassthroughMap[passthroughName]; - - source.replace(pos, (end+1)-pos, p.expand_assign(assignTo)); - } - - // passthrough receive - while (true) - { - pos = source.find("@shPassthroughReceive"); - if (pos == std::string::npos) - break; - - std::vector args = extractMacroArguments (pos, source); - assert(args.size() == 1); - - size_t end = source.find(")", pos); - std::string passthroughName = args[0]; - - assert(mPassthroughMap.find(passthroughName) != mPassthroughMap.end()); - Passthrough& p = mPassthroughMap[passthroughName]; - - source.replace(pos, (end+1)-pos, p.expand_receive()); - } - - // passthrough vertex outputs - while (true) - { - pos = source.find("@shPassthroughVertexOutputs"); - if (pos == std::string::npos) - break; - - std::string result; - for (int i = 0; i < mCurrentPassthrough+1; ++i) - { - // not using newlines here, otherwise the line numbers reported by compiler would be messed up.. - if (Factory::getInstance().getCurrentLanguage () == Language_CG || Factory::getInstance().getCurrentLanguage () == Language_HLSL) - result += ", out float4 passthrough" + boost::lexical_cast(i) + " : TEXCOORD" + boost::lexical_cast(i); - - /* - else - result += "out vec4 passthrough" + boost::lexical_cast(i) + "; "; - */ - else - result += "varying vec4 passthrough" + boost::lexical_cast(i) + "; "; - } - - source.replace(pos, std::string("@shPassthroughVertexOutputs").length(), result); - } - - // passthrough fragment inputs - while (true) - { - pos = source.find("@shPassthroughFragmentInputs"); - if (pos == std::string::npos) - break; - - std::string result; - for (int i = 0; i < mCurrentPassthrough+1; ++i) - { - // not using newlines here, otherwise the line numbers reported by compiler would be messed up.. - if (Factory::getInstance().getCurrentLanguage () == Language_CG || Factory::getInstance().getCurrentLanguage () == Language_HLSL) - result += ", in float4 passthrough" + boost::lexical_cast(i) + " : TEXCOORD" + boost::lexical_cast(i); - /* - else - result += "in vec4 passthrough" + boost::lexical_cast(i) + "; "; - */ - else - result += "varying vec4 passthrough" + boost::lexical_cast(i) + "; "; - } - - source.replace(pos, std::string("@shPassthroughFragmentInputs").length(), result); - } - } - - // save to cache _here_ - we want to preserve some macros - if (writeCache && !readCache) - { - std::ofstream of (std::string(Factory::getInstance ().getCacheFolder () + "/" + mName).c_str(), std::ios_base::out); - of.write(source.c_str(), source.size()); - of.close(); - } - - - // parse shared parameters - while (true) - { - pos = source.find("@shSharedParameter"); - if (pos == std::string::npos) - break; - - std::vector args = extractMacroArguments (pos, source); - assert(args.size()); - - size_t end = source.find(")", pos); - - mSharedParameters.push_back(args[0]); - - source.erase(pos, (end+1)-pos); - } - - // parse auto constants - typedef std::map< std::string, std::pair > AutoConstantMap; - AutoConstantMap autoConstants; - while (true) - { - pos = source.find("@shAutoConstant"); - if (pos == std::string::npos) - break; - - std::vector args = extractMacroArguments (pos, source); - assert(args.size() >= 2); - - size_t end = source.find(")", pos); - - std::string autoConstantName, uniformName; - std::string extraData; - - uniformName = args[0]; - autoConstantName = args[1]; - if (args.size() > 2) - extraData = args[2]; - - autoConstants[uniformName] = std::make_pair(autoConstantName, extraData); - - source.erase(pos, (end+1)-pos); - } - - // parse uniform properties - while (true) - { - pos = source.find("@shUniformProperty"); - if (pos == std::string::npos) - break; - - std::vector args = extractMacroArguments (pos, source); - assert(args.size() == 2); - - size_t start = source.find("(", pos); - size_t end = source.find(")", pos); - std::string cmd = source.substr(pos, start-pos); - - ValueType vt; - if (cmd == "@shUniformProperty4f") - vt = VT_Vector4; - else if (cmd == "@shUniformProperty3f") - vt = VT_Vector3; - else if (cmd == "@shUniformProperty2f") - vt = VT_Vector2; - else if (cmd == "@shUniformProperty1f") - vt = VT_Float; - else if (cmd == "@shUniformPropertyInt") - vt = VT_Int; - else - throw std::runtime_error ("unsupported command \"" + cmd + "\""); - - - std::string propertyName, uniformName; - uniformName = args[0]; - propertyName = args[1]; - mUniformProperties[uniformName] = std::make_pair(propertyName, vt); - - source.erase(pos, (end+1)-pos); - } - - // parse texture samplers used - while (true) - { - pos = source.find("@shUseSampler"); - if (pos == std::string::npos) - break; - - size_t end = source.find(")", pos); - - mUsedSamplers.push_back(extractMacroArguments (pos, source)[0]); - source.erase(pos, (end+1)-pos); - } - - // convert any left-over @'s to # - boost::algorithm::replace_all(source, "@", "#"); - - Platform* platform = Factory::getInstance().getPlatform(); - - std::string profile; - if (Factory::getInstance ().getCurrentLanguage () == Language_CG) - profile = mParent->getCgProfile (); - else if (Factory::getInstance ().getCurrentLanguage () == Language_HLSL) - profile = mParent->getHlslProfile (); - - - if (type == GPT_Vertex) - mProgram = boost::shared_ptr(platform->createGpuProgram(GPT_Vertex, "", mName, profile, source, Factory::getInstance().getCurrentLanguage())); - else if (type == GPT_Fragment) - mProgram = boost::shared_ptr(platform->createGpuProgram(GPT_Fragment, "", mName, profile, source, Factory::getInstance().getCurrentLanguage())); - - - if (Factory::getInstance ().getShaderDebugOutputEnabled ()) - writeDebugFile(source, name); - - if (!mProgram->getSupported()) - { - std::cerr << " Full source code below: \n" << source << std::endl; - mSupported = false; - return; - } - - // set auto constants - for (AutoConstantMap::iterator it = autoConstants.begin(); it != autoConstants.end(); ++it) - { - mProgram->setAutoConstant(it->first, it->second.first, it->second.second); - } - } - - std::string ShaderInstance::getName () - { - return mName; - } - - bool ShaderInstance::getSupported () const - { - return mSupported; - } - - std::vector ShaderInstance::getUsedSamplers() - { - return mUsedSamplers; - } - - void ShaderInstance::setUniformParameters (boost::shared_ptr pass, PropertySetGet* properties) - { - for (UniformMap::iterator it = mUniformProperties.begin(); it != mUniformProperties.end(); ++it) - { - pass->setGpuConstant(mParent->getType(), it->first, it->second.second, properties->getProperty(it->second.first), properties->getContext()); - } - } - - std::vector ShaderInstance::extractMacroArguments (size_t pos, const std::string& source) - { - size_t start = source.find("(", pos); - size_t end = source.find(")", pos); - std::string args = source.substr(start+1, end-(start+1)); - std::vector results; - boost::algorithm::split(results, args, boost::is_any_of(",")); - std::for_each(results.begin(), results.end(), - boost::bind(&boost::trim, - _1, std::locale() )); - return results; - } -} diff --git a/extern/shiny/Main/ShaderInstance.hpp b/extern/shiny/Main/ShaderInstance.hpp deleted file mode 100644 index 76326ce0c3..0000000000 --- a/extern/shiny/Main/ShaderInstance.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef SH_SHADERINSTANCE_H -#define SH_SHADERINSTANCE_H - -#include - -#include "Platform.hpp" - -namespace sh -{ - class ShaderSet; - - typedef std::map< std::string, std::pair > UniformMap; - - struct Passthrough - { - Language lang; ///< language to generate for - - int num_components; ///< e.g. 4 for a float4 - - int passthrough_number; - int component_start; ///< 0 = x - - std::string expand_assign(std::string assignTo); - std::string expand_receive(); - }; - typedef std::map PassthroughMap; - - /** - * @brief A specific instance of a \a ShaderSet with a deterministic shader source - */ - class ShaderInstance - { - public: - ShaderInstance (ShaderSet* parent, const std::string& name, PropertySetGet* properties); - - std::string getName(); - - bool getSupported () const; - - std::vector getUsedSamplers(); - std::vector getSharedParameters() { return mSharedParameters; } - - void setUniformParameters (boost::shared_ptr pass, PropertySetGet* properties); - - private: - boost::shared_ptr mProgram; - std::string mName; - ShaderSet* mParent; - bool mSupported; ///< shader compilation was sucessful? - - std::vector mUsedSamplers; - ///< names of the texture samplers that are used by this shader - - std::vector mSharedParameters; - - UniformMap mUniformProperties; - ///< uniforms that this depends on, and their property names / value-types - /// @note this lists shared uniform parameters as well - - int mCurrentPassthrough; ///< 0 - x - int mCurrentComponent; ///< 0:x, 1:y, 2:z, 3:w - - PassthroughMap mPassthroughMap; - - std::vector extractMacroArguments (size_t pos, const std::string& source); ///< take a macro invocation and return vector of arguments - - void parse (std::string& source, PropertySetGet* properties); - }; -} - -#endif diff --git a/extern/shiny/Main/ShaderSet.cpp b/extern/shiny/Main/ShaderSet.cpp deleted file mode 100644 index 4e19948ad9..0000000000 --- a/extern/shiny/Main/ShaderSet.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include "ShaderSet.hpp" - -#include -#include - -#include -#include -#include -#include - -#include "Factory.hpp" - -namespace sh -{ - ShaderSet::ShaderSet (const std::string& type, const std::string& cgProfile, const std::string& hlslProfile, const std::string& sourceFile, const std::string& basePath, - const std::string& name, PropertySetGet* globalSettingsPtr) - : mBasePath(basePath) - , mCgProfile(cgProfile) - , mHlslProfile(hlslProfile) - , mName(name) - { - if (type == "vertex") - mType = GPT_Vertex; - else // if (type == "fragment") - mType = GPT_Fragment; - - std::ifstream stream(sourceFile.c_str(), std::ifstream::in); - std::stringstream buffer; - - boost::filesystem::path p (sourceFile); - p = p.branch_path(); - mBasePath = p.string(); - - buffer << stream.rdbuf(); - stream.close(); - mSource = buffer.str(); - parse(); - } - - ShaderSet::~ShaderSet() - { - for (ShaderInstanceMap::iterator it = mInstances.begin(); it != mInstances.end(); ++it) - { - sh::Factory::getInstance().getPlatform()->destroyGpuProgram(it->second.getName()); - } - } - - void ShaderSet::parse() - { - std::string currentToken; - bool tokenIsRecognized = false; - bool isInBraces = false; - for (std::string::const_iterator it = mSource.begin(); it != mSource.end(); ++it) - { - char c = *it; - if (((c == ' ') && !isInBraces) || (c == '\n') || - ( ((c == '(') || (c == ')')) - && !tokenIsRecognized)) - { - if (tokenIsRecognized) - { - if (boost::starts_with(currentToken, "@shGlobalSetting")) - { - assert ((currentToken.find('(') != std::string::npos) && (currentToken.find(')') != std::string::npos)); - size_t start = currentToken.find('(')+1; - mGlobalSettings.push_back(currentToken.substr(start, currentToken.find(')')-start)); - } - else if (boost::starts_with(currentToken, "@shPropertyHasValue")) - { - assert ((currentToken.find('(') != std::string::npos) && (currentToken.find(')') != std::string::npos)); - size_t start = currentToken.find('(')+1; - mPropertiesToExist.push_back(currentToken.substr(start, currentToken.find(')')-start)); - } - else if (boost::starts_with(currentToken, "@shPropertyEqual")) - { - assert ((currentToken.find('(') != std::string::npos) && (currentToken.find(')') != std::string::npos) - && (currentToken.find(',') != std::string::npos)); - size_t start = currentToken.find('(')+1; - size_t end = currentToken.find(','); - mProperties.push_back(currentToken.substr(start, end-start)); - } - else if (boost::starts_with(currentToken, "@shProperty")) - { - assert ((currentToken.find('(') != std::string::npos) && (currentToken.find(')') != std::string::npos)); - size_t start = currentToken.find('(')+1; - std::string propertyName = currentToken.substr(start, currentToken.find(')')-start); - // if the property name is constructed dynamically (e.g. through an iterator) then there is nothing we can do - if (propertyName.find("@") == std::string::npos) - mProperties.push_back(propertyName); - } - } - - currentToken = ""; - } - else - { - if (currentToken == "") - { - if (c == '@') - tokenIsRecognized = true; - else - tokenIsRecognized = false; - } - else - { - if (c == '@') - { - // ouch, there are nested macros - // ( for example @shForeach(@shPropertyString(foobar)) ) - currentToken = ""; - } - } - - if (c == '(' && tokenIsRecognized) - isInBraces = true; - else if (c == ')' && tokenIsRecognized) - isInBraces = false; - - currentToken += c; - - } - } - } - - ShaderInstance* ShaderSet::getInstance (PropertySetGet* properties) - { - size_t h = buildHash (properties); - if (std::find(mFailedToCompile.begin(), mFailedToCompile.end(), h) != mFailedToCompile.end()) - return NULL; - if (mInstances.find(h) == mInstances.end()) - { - ShaderInstance newInstance(this, mName + "_" + boost::lexical_cast(h), properties); - if (!newInstance.getSupported()) - { - mFailedToCompile.push_back(h); - return NULL; - } - mInstances.insert(std::make_pair(h, newInstance)); - } - return &mInstances.find(h)->second; - } - - size_t ShaderSet::buildHash (PropertySetGet* properties) - { - size_t seed = 0; - PropertySetGet* currentGlobalSettings = getCurrentGlobalSettings (); - - for (std::vector::iterator it = mProperties.begin(); it != mProperties.end(); ++it) - { - std::string v = retrieveValue(properties->getProperty(*it), properties->getContext()).get(); - boost::hash_combine(seed, v); - } - for (std::vector ::iterator it = mGlobalSettings.begin(); it != mGlobalSettings.end(); ++it) - { - boost::hash_combine(seed, retrieveValue(currentGlobalSettings->getProperty(*it), NULL).get()); - } - for (std::vector::iterator it = mPropertiesToExist.begin(); it != mPropertiesToExist.end(); ++it) - { - std::string v = retrieveValue(properties->getProperty(*it), properties->getContext()).get(); - boost::hash_combine(seed, static_cast(v != "")); - } - boost::hash_combine(seed, static_cast(Factory::getInstance().getCurrentLanguage())); - return seed; - } - - PropertySetGet* ShaderSet::getCurrentGlobalSettings() const - { - return Factory::getInstance ().getCurrentGlobalSettings (); - } - - std::string ShaderSet::getBasePath() const - { - return mBasePath; - } - - std::string ShaderSet::getSource() const - { - return mSource; - } - - std::string ShaderSet::getCgProfile() const - { - return mCgProfile; - } - - std::string ShaderSet::getHlslProfile() const - { - return mHlslProfile; - } - - int ShaderSet::getType() const - { - return mType; - } -} diff --git a/extern/shiny/Main/ShaderSet.hpp b/extern/shiny/Main/ShaderSet.hpp deleted file mode 100644 index 988c6769fa..0000000000 --- a/extern/shiny/Main/ShaderSet.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef SH_SHADERSET_H -#define SH_SHADERSET_H - -#include -#include -#include - -#include "ShaderInstance.hpp" - -namespace sh -{ - class PropertySetGet; - - typedef std::map ShaderInstanceMap; - - /** - * @brief Contains possible shader permutations of a single uber-shader (represented by one source file) - */ - class ShaderSet - { - public: - ShaderSet (const std::string& type, const std::string& cgProfile, const std::string& hlslProfile, const std::string& sourceFile, const std::string& basePath, - const std::string& name, PropertySetGet* globalSettingsPtr); - ~ShaderSet(); - - /// Retrieve a shader instance for the given properties. \n - /// If a \a ShaderInstance with the same properties exists already, simply returns this instance. \n - /// Otherwise, creates a new \a ShaderInstance (i.e. compiles a new shader). \n - /// Might also return NULL if the shader failed to compile. \n - /// @note Only the properties that actually affect the shader source are taken into consideration here, - /// so it does not matter if you pass any extra properties that the shader does not care about. - ShaderInstance* getInstance (PropertySetGet* properties); - - private: - PropertySetGet* getCurrentGlobalSettings() const; - std::string getBasePath() const; - std::string getSource() const; - std::string getCgProfile() const; - std::string getHlslProfile() const; - int getType() const; - - friend class ShaderInstance; - - private: - GpuProgramType mType; - std::string mSource; - std::string mBasePath; - std::string mCgProfile; - std::string mHlslProfile; - std::string mName; - - std::vector mFailedToCompile; - - std::vector mGlobalSettings; ///< names of the global settings that affect the shader source - std::vector mProperties; ///< names of the per-material properties that affect the shader source - - std::vector mPropertiesToExist; - ///< same as mProperties, however in this case, it is only relevant if the property is empty or not - /// (we don't care about the value) - - ShaderInstanceMap mInstances; ///< maps permutation ID (generated from the properties) to \a ShaderInstance - - void parse(); ///< find out which properties and global settings affect the shader source - - size_t buildHash (PropertySetGet* properties); - }; -} - -#endif diff --git a/extern/shiny/Platforms/Ogre/OgreGpuProgram.cpp b/extern/shiny/Platforms/Ogre/OgreGpuProgram.cpp deleted file mode 100644 index e718540197..0000000000 --- a/extern/shiny/Platforms/Ogre/OgreGpuProgram.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include - -#include "OgreGpuProgram.hpp" - -#include - -#include -#include -#include - -namespace sh -{ - OgreGpuProgram::OgreGpuProgram( - GpuProgramType type, - const std::string& compileArguments, - const std::string& name, const std::string& profile, - const std::string& source, const std::string& lang, - const std::string& resourceGroup) - : GpuProgram() - { - Ogre::HighLevelGpuProgramManager& mgr = Ogre::HighLevelGpuProgramManager::getSingleton(); - assert (mgr.getByName(name).isNull() && "Vertex program already exists"); - - Ogre::GpuProgramType t; - if (type == GPT_Vertex) - t = Ogre::GPT_VERTEX_PROGRAM; - else - t = Ogre::GPT_FRAGMENT_PROGRAM; - - mProgram = mgr.createProgram(name, resourceGroup, lang, t); - if (lang != "glsl" && lang != "glsles") - mProgram->setParameter("entry_point", "main"); - if (lang == "hlsl") - mProgram->setParameter("target", profile); - else if (lang == "cg") - mProgram->setParameter("profiles", profile); - - mProgram->setSource(source); - mProgram->load(); - - if (mProgram.isNull() || !mProgram->isSupported()) - std::cerr << "Failed to compile shader \"" << name << "\". Consider the OGRE log for more information." << std::endl; - } - - bool OgreGpuProgram::getSupported() - { - return (!mProgram.isNull() && mProgram->isSupported()); - } - - void OgreGpuProgram::setAutoConstant (const std::string& name, const std::string& autoConstantName, const std::string& extraInfo) - { - assert (!mProgram.isNull() && mProgram->isSupported()); - const Ogre::GpuProgramParameters::AutoConstantDefinition* d = Ogre::GpuProgramParameters::getAutoConstantDefinition(autoConstantName); - - if (!d) - throw std::runtime_error ("can't find auto constant with name \"" + autoConstantName + "\""); - Ogre::GpuProgramParameters::AutoConstantType t = d->acType; - - // this simplifies debugging for CG a lot. - mProgram->getDefaultParameters()->setIgnoreMissingParams(true); - - if (d->dataType == Ogre::GpuProgramParameters::ACDT_NONE) - mProgram->getDefaultParameters()->setNamedAutoConstant (name, t, 0); - else if (d->dataType == Ogre::GpuProgramParameters::ACDT_INT) - mProgram->getDefaultParameters()->setNamedAutoConstant (name, t, extraInfo == "" ? 0 : boost::lexical_cast(extraInfo)); - else if (d->dataType == Ogre::GpuProgramParameters::ACDT_REAL) - mProgram->getDefaultParameters()->setNamedAutoConstantReal (name, t, extraInfo == "" ? 0.f : boost::lexical_cast(extraInfo)); - } -} diff --git a/extern/shiny/Platforms/Ogre/OgreGpuProgram.hpp b/extern/shiny/Platforms/Ogre/OgreGpuProgram.hpp deleted file mode 100644 index 42673ed9b4..0000000000 --- a/extern/shiny/Platforms/Ogre/OgreGpuProgram.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef SH_OGREGPUPROGRAM_H -#define SH_OGREGPUPROGRAM_H - -#include - -#include - -#include "../../Main/Platform.hpp" - -namespace sh -{ - class OgreGpuProgram : public GpuProgram - { - public: - OgreGpuProgram ( - GpuProgramType type, - const std::string& compileArguments, - const std::string& name, const std::string& profile, - const std::string& source, const std::string& lang, - const std::string& resourceGroup); - - virtual bool getSupported(); - - virtual void setAutoConstant (const std::string& name, const std::string& autoConstantName, const std::string& extraInfo = ""); - - private: - Ogre::HighLevelGpuProgramPtr mProgram; - }; -} - -#endif diff --git a/extern/shiny/Platforms/Ogre/OgreMaterial.cpp b/extern/shiny/Platforms/Ogre/OgreMaterial.cpp deleted file mode 100644 index 04560e1f9e..0000000000 --- a/extern/shiny/Platforms/Ogre/OgreMaterial.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "OgreMaterial.hpp" - -#include -#include -#include - -#include "OgrePass.hpp" -#include "OgreMaterialSerializer.hpp" -#include "OgrePlatform.hpp" - -namespace sh -{ - static const std::string sDefaultTechniqueName = "SH_DefaultTechnique"; - - OgreMaterial::OgreMaterial (const std::string& name, const std::string& resourceGroup) - : Material() - { - mName = name; - assert (Ogre::MaterialManager::getSingleton().getByName(name).isNull() && "Material already exists"); - mMaterial = Ogre::MaterialManager::getSingleton().create (name, resourceGroup); - mMaterial->removeAllTechniques(); - mMaterial->createTechnique()->setSchemeName (sDefaultTechniqueName); - mMaterial->compile(); - } - - void OgreMaterial::ensureLoaded() - { - if (mMaterial.isNull()) - mMaterial = Ogre::MaterialManager::getSingleton().getByName(mName); - } - - bool OgreMaterial::isUnreferenced() - { - // Resource system internals hold 3 shared pointers, we hold one, so usecount of 4 means unused - return (!mMaterial.isNull() && mMaterial.useCount() <= Ogre::ResourceGroupManager::RESOURCE_SYSTEM_NUM_REFERENCE_COUNTS+1); - } - - void OgreMaterial::unreferenceTextures() - { - mMaterial->unload(); - } - - OgreMaterial::~OgreMaterial() - { - if (!mMaterial.isNull()) - Ogre::MaterialManager::getSingleton().remove(mMaterial->getName()); - } - - boost::shared_ptr OgreMaterial::createPass (const std::string& configuration, unsigned short lodIndex) - { - return boost::shared_ptr (new OgrePass (this, configuration, lodIndex)); - } - - void OgreMaterial::removeAll () - { - if (mMaterial.isNull()) - return; - mMaterial->removeAllTechniques(); - mMaterial->createTechnique()->setSchemeName (sDefaultTechniqueName); - mMaterial->compile(); - } - - void OgreMaterial::setLodLevels (const std::string& lodLevels) - { - OgreMaterialSerializer& s = OgrePlatform::getSerializer(); - - s.setMaterialProperty ("lod_values", lodLevels, mMaterial); - } - - bool OgreMaterial::createConfiguration (const std::string& name, unsigned short lodIndex) - { - for (int i=0; igetNumTechniques(); ++i) - { - if (mMaterial->getTechnique(i)->getSchemeName() == name && mMaterial->getTechnique(i)->getLodIndex() == lodIndex) - return false; - } - - Ogre::Technique* t = mMaterial->createTechnique(); - t->setSchemeName (name); - t->setLodIndex (lodIndex); - if (mShadowCasterMaterial != "") - t->setShadowCasterMaterial(mShadowCasterMaterial); - - mMaterial->compile(); - - return true; - } - - Ogre::MaterialPtr OgreMaterial::getOgreMaterial () - { - return mMaterial; - } - - Ogre::Technique* OgreMaterial::getOgreTechniqueForConfiguration (const std::string& configurationName, unsigned short lodIndex) - { - for (int i=0; igetNumTechniques(); ++i) - { - if (mMaterial->getTechnique(i)->getSchemeName() == configurationName && mMaterial->getTechnique(i)->getLodIndex() == lodIndex) - { - return mMaterial->getTechnique(i); - } - } - - // Prepare and throw error message - std::stringstream message; - message << "Could not find configurationName '" << configurationName - << "' and lodIndex " << lodIndex; - - throw std::runtime_error(message.str()); - } - - void OgreMaterial::setShadowCasterMaterial (const std::string& name) - { - mShadowCasterMaterial = name; - for (int i=0; igetNumTechniques(); ++i) - { - mMaterial->getTechnique(i)->setShadowCasterMaterial(mShadowCasterMaterial); - } - } -} diff --git a/extern/shiny/Platforms/Ogre/OgreMaterial.hpp b/extern/shiny/Platforms/Ogre/OgreMaterial.hpp deleted file mode 100644 index 67a2c26e86..0000000000 --- a/extern/shiny/Platforms/Ogre/OgreMaterial.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef SH_OGREMATERIAL_H -#define SH_OGREMATERIAL_H - -#include - -#include - -#include "../../Main/Platform.hpp" - -namespace sh -{ - class OgreMaterial : public Material - { - public: - OgreMaterial (const std::string& name, const std::string& resourceGroup); - virtual ~OgreMaterial(); - - virtual boost::shared_ptr createPass (const std::string& configuration, unsigned short lodIndex); - virtual bool createConfiguration (const std::string& name, unsigned short lodIndex); - - virtual bool isUnreferenced(); - virtual void unreferenceTextures(); - virtual void ensureLoaded(); - - virtual void removeAll (); - - Ogre::MaterialPtr getOgreMaterial(); - - virtual void setLodLevels (const std::string& lodLevels); - - Ogre::Technique* getOgreTechniqueForConfiguration (const std::string& configurationName, unsigned short lodIndex = 0); - - virtual void setShadowCasterMaterial (const std::string& name); - - private: - Ogre::MaterialPtr mMaterial; - std::string mName; - - std::string mShadowCasterMaterial; - }; -} - -#endif diff --git a/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.cpp b/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.cpp deleted file mode 100644 index f45e641557..0000000000 --- a/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "OgreMaterialSerializer.hpp" - -#include - -#include - -namespace sh -{ - void OgreMaterialSerializer::reset() - { - mScriptContext.section = Ogre::MSS_NONE; - mScriptContext.material.setNull(); - mScriptContext.technique = 0; - mScriptContext.pass = 0; - mScriptContext.textureUnit = 0; - mScriptContext.program.setNull(); - mScriptContext.lineNo = 0; - mScriptContext.filename.clear(); - mScriptContext.techLev = -1; - mScriptContext.passLev = -1; - mScriptContext.stateLev = -1; - } - - bool OgreMaterialSerializer::setPassProperty (const std::string& param, std::string value, Ogre::Pass* pass) - { - // workaround https://ogre3d.atlassian.net/browse/OGRE-158 - if (param == "transparent_sorting" && value == "force") - { - pass->setTransparentSortingForced(true); - return true; - } - - reset(); - - mScriptContext.section = Ogre::MSS_PASS; - mScriptContext.pass = pass; - - if (mPassAttribParsers.find (param) == mPassAttribParsers.end()) - return false; - else - { - mPassAttribParsers.find(param)->second(value, mScriptContext); - return true; - } - } - - bool OgreMaterialSerializer::setTextureUnitProperty (const std::string& param, std::string value, Ogre::TextureUnitState* t) - { - // quick access to automip setting, without having to use 'texture' which doesn't like spaces in filenames - if (param == "num_mipmaps") - { - t->setNumMipmaps(Ogre::StringConverter::parseInt(value)); - return true; - } - - reset(); - - mScriptContext.section = Ogre::MSS_TEXTUREUNIT; - mScriptContext.textureUnit = t; - - if (mTextureUnitAttribParsers.find (param) == mTextureUnitAttribParsers.end()) - return false; - else - { - mTextureUnitAttribParsers.find(param)->second(value, mScriptContext); - return true; - } - } - - bool OgreMaterialSerializer::setMaterialProperty (const std::string& param, std::string value, Ogre::MaterialPtr m) - { - reset(); - - mScriptContext.section = Ogre::MSS_MATERIAL; - mScriptContext.material = m; - - if (mMaterialAttribParsers.find (param) == mMaterialAttribParsers.end()) - return false; - else - { - mMaterialAttribParsers.find(param)->second(value, mScriptContext); - return true; - } - } -} diff --git a/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.hpp b/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.hpp deleted file mode 100644 index acfc5a362f..0000000000 --- a/extern/shiny/Platforms/Ogre/OgreMaterialSerializer.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef SH_OGREMATERIALSERIALIZER_H -#define SH_OGREMATERIALSERIALIZER_H - -#include - -namespace Ogre -{ - class Pass; -} - -namespace sh -{ - /** - * @brief This class allows me to let Ogre handle the pass & texture unit properties - */ - class OgreMaterialSerializer : public Ogre::MaterialSerializer - { - public: - bool setPassProperty (const std::string& param, std::string value, Ogre::Pass* pass); - bool setTextureUnitProperty (const std::string& param, std::string value, Ogre::TextureUnitState* t); - bool setMaterialProperty (const std::string& param, std::string value, Ogre::MaterialPtr m); - - private: - void reset(); - }; - -} - -#endif diff --git a/extern/shiny/Platforms/Ogre/OgrePass.cpp b/extern/shiny/Platforms/Ogre/OgrePass.cpp deleted file mode 100644 index 5cd501094f..0000000000 --- a/extern/shiny/Platforms/Ogre/OgrePass.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include - -#include "OgrePass.hpp" - -#include -#include - -#include "OgreTextureUnitState.hpp" -#include "OgreGpuProgram.hpp" -#include "OgreMaterial.hpp" -#include "OgreMaterialSerializer.hpp" -#include "OgrePlatform.hpp" - -namespace sh -{ - OgrePass::OgrePass (OgreMaterial* parent, const std::string& configuration, unsigned short lodIndex) - : Pass() - { - Ogre::Technique* t = parent->getOgreTechniqueForConfiguration(configuration, lodIndex); - mPass = t->createPass(); - } - - boost::shared_ptr OgrePass::createTextureUnitState (const std::string& name) - { - return boost::shared_ptr (new OgreTextureUnitState (this, name)); - } - - void OgrePass::assignProgram (GpuProgramType type, const std::string& name) - { - if (type == GPT_Vertex) - mPass->setVertexProgram (name); - else if (type == GPT_Fragment) - mPass->setFragmentProgram (name); - else - throw std::runtime_error("unsupported GpuProgramType"); - } - - Ogre::Pass* OgrePass::getOgrePass () - { - return mPass; - } - - bool OgrePass::setPropertyOverride (const std::string &name, PropertyValuePtr& value, PropertySetGet* context) - { - if (((typeid(*value) == typeid(StringValue)) || typeid(*value) == typeid(LinkedValue)) - && retrieveValue(value, context).get() == "default") - return true; - - if (name == "vertex_program") - return true; // handled already - else if (name == "fragment_program") - return true; // handled already - else - { - OgreMaterialSerializer& s = OgrePlatform::getSerializer(); - - return s.setPassProperty (name, retrieveValue(value, context).get(), mPass); - } - } - - void OgrePass::setGpuConstant (int type, const std::string& name, ValueType vt, PropertyValuePtr value, PropertySetGet* context) - { - Ogre::GpuProgramParametersSharedPtr params; - if (type == GPT_Vertex) - { - if (!mPass->hasVertexProgram ()) - return; - params = mPass->getVertexProgramParameters(); - } - else if (type == GPT_Fragment) - { - if (!mPass->hasFragmentProgram ()) - return; - params = mPass->getFragmentProgramParameters(); - } - - if (vt == VT_Float) - params->setNamedConstant (name, retrieveValue(value, context).get()); - else if (vt == VT_Int) - params->setNamedConstant (name, retrieveValue(value, context).get()); - else if (vt == VT_Vector4) - { - Vector4 v = retrieveValue(value, context); - params->setNamedConstant (name, Ogre::Vector4(v.mX, v.mY, v.mZ, v.mW)); - } - else if (vt == VT_Vector3) - { - Vector3 v = retrieveValue(value, context); - params->setNamedConstant (name, Ogre::Vector4(v.mX, v.mY, v.mZ, 1.0)); - } - else if (vt == VT_Vector2) - { - Vector2 v = retrieveValue(value, context); - params->setNamedConstant (name, Ogre::Vector4(v.mX, v.mY, 1.0, 1.0)); - } - else - throw std::runtime_error ("unsupported constant type"); - } - - void OgrePass::addSharedParameter (int type, const std::string& name) - { - Ogre::GpuProgramParametersSharedPtr params; - if (type == GPT_Vertex) - params = mPass->getVertexProgramParameters(); - else if (type == GPT_Fragment) - params = mPass->getFragmentProgramParameters(); - - try - { - params->addSharedParameters (name); - } - catch (Ogre::Exception& ) - { - std::stringstream msg; - msg << "Could not create a shared parameter instance for '" - << name << "'. Make sure this shared parameter has a value set (via Factory::setSharedParameter)!"; - throw std::runtime_error(msg.str()); - } - } - - void OgrePass::setTextureUnitIndex (int programType, const std::string& name, int index) - { - Ogre::GpuProgramParametersSharedPtr params; - if (programType == GPT_Vertex) - params = mPass->getVertexProgramParameters(); - else if (programType == GPT_Fragment) - params = mPass->getFragmentProgramParameters(); - - params->setNamedConstant(name, index); - } -} diff --git a/extern/shiny/Platforms/Ogre/OgrePass.hpp b/extern/shiny/Platforms/Ogre/OgrePass.hpp deleted file mode 100644 index e7cfd4e337..0000000000 --- a/extern/shiny/Platforms/Ogre/OgrePass.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef SH_OGREPASS_H -#define SH_OGREPASS_H - -#include - -#include "../../Main/Platform.hpp" - -namespace sh -{ - class OgreMaterial; - - class OgrePass : public Pass - { - public: - OgrePass (OgreMaterial* parent, const std::string& configuration, unsigned short lodIndex); - - virtual boost::shared_ptr createTextureUnitState (const std::string& name); - virtual void assignProgram (GpuProgramType type, const std::string& name); - - Ogre::Pass* getOgrePass(); - - virtual void setGpuConstant (int type, const std::string& name, ValueType vt, PropertyValuePtr value, PropertySetGet* context); - - virtual void addSharedParameter (int type, const std::string& name); - virtual void setTextureUnitIndex (int programType, const std::string& name, int index); - - private: - Ogre::Pass* mPass; - - protected: - virtual bool setPropertyOverride (const std::string &name, PropertyValuePtr& value, PropertySetGet* context); - }; -} - -#endif diff --git a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp deleted file mode 100644 index aa01c8ba14..0000000000 --- a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp +++ /dev/null @@ -1,193 +0,0 @@ -#include - -#include "OgrePlatform.hpp" - -#include -#include -#include -#include - -#include "OgreMaterial.hpp" -#include "OgreGpuProgram.hpp" -#include "OgreMaterialSerializer.hpp" - -#include "../../Main/MaterialInstance.hpp" -#include "../../Main/Factory.hpp" - -namespace -{ - std::string convertLang (sh::Language lang) - { - if (lang == sh::Language_CG) - return "cg"; - else if (lang == sh::Language_HLSL) - return "hlsl"; - else if (lang == sh::Language_GLSL) - return "glsl"; - else if (lang == sh::Language_GLSLES) - return "glsles"; - throw std::runtime_error ("invalid language, valid are: cg, hlsl, glsl, glsles"); - } -} - -namespace sh -{ - OgreMaterialSerializer* OgrePlatform::sSerializer = 0; - - OgrePlatform::OgrePlatform(const std::string& resourceGroupName, const std::string& basePath) - : Platform(basePath) - , mResourceGroup(resourceGroupName) - { - Ogre::MaterialManager::getSingleton().addListener(this); - - if (supportsShaderSerialization()) - Ogre::GpuProgramManager::getSingletonPtr()->setSaveMicrocodesToCache(true); - - sSerializer = new OgreMaterialSerializer(); - } - - OgreMaterialSerializer& OgrePlatform::getSerializer() - { - assert(sSerializer); - return *sSerializer; - } - - OgrePlatform::~OgrePlatform () - { - Ogre::MaterialManager::getSingleton().removeListener(this); - - delete sSerializer; - } - - bool OgrePlatform::isProfileSupported (const std::string& profile) - { - return Ogre::GpuProgramManager::getSingleton().isSyntaxSupported(profile); - } - - bool OgrePlatform::supportsShaderSerialization () - { - #if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0) - return true; - #else - return Ogre::Root::getSingleton ().getRenderSystem ()->getName ().find("OpenGL") == std::string::npos; - #endif - } - - bool OgrePlatform::supportsMaterialQueuedListener () - { - return true; - } - - boost::shared_ptr OgrePlatform::createMaterial (const std::string& name) - { - OgreMaterial* material = new OgreMaterial(name, mResourceGroup); - return boost::shared_ptr (material); - } - - void OgrePlatform::destroyGpuProgram(const std::string &name) - { - Ogre::HighLevelGpuProgramManager::getSingleton().remove(name); - } - - boost::shared_ptr OgrePlatform::createGpuProgram ( - GpuProgramType type, - const std::string& compileArguments, - const std::string& name, const std::string& profile, - const std::string& source, Language lang) - { - OgreGpuProgram* prog = new OgreGpuProgram (type, compileArguments, name, profile, source, convertLang(lang), mResourceGroup); - return boost::shared_ptr (static_cast(prog)); - } - - Ogre::Technique* OgrePlatform::handleSchemeNotFound ( - unsigned short schemeIndex, const Ogre::String &schemeName, Ogre::Material *originalMaterial, - unsigned short lodIndex, const Ogre::Renderable *rend) - { - MaterialInstance* m = fireMaterialRequested(originalMaterial->getName(), schemeName, lodIndex); - if (m) - { - OgreMaterial* _m = static_cast(m->getMaterial()); - return _m->getOgreTechniqueForConfiguration (schemeName, lodIndex); - } - else - return 0; // material does not belong to us - } - - void OgrePlatform::serializeShaders (const std::string& file) - { - #if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0) - if (Ogre::GpuProgramManager::getSingleton().isCacheDirty()) - #endif - { - std::fstream output; - output.open(file.c_str(), std::ios::out | std::ios::binary); - Ogre::DataStreamPtr shaderCache (OGRE_NEW Ogre::FileStreamDataStream(file, &output, false)); - Ogre::GpuProgramManager::getSingleton().saveMicrocodeCache(shaderCache); - } - } - - void OgrePlatform::deserializeShaders (const std::string& file) - { - std::ifstream inp; - inp.open(file.c_str(), std::ios::in | std::ios::binary); - Ogre::DataStreamPtr shaderCache(OGRE_NEW Ogre::FileStreamDataStream(file, &inp, false)); - Ogre::GpuProgramManager::getSingleton().loadMicrocodeCache(shaderCache); - } - - void OgrePlatform::setSharedParameter (const std::string& name, PropertyValuePtr value) - { - Ogre::GpuSharedParametersPtr params; - if (mSharedParameters.find(name) == mSharedParameters.end()) - { - params = Ogre::GpuProgramManager::getSingleton().createSharedParameters(name); - - Ogre::GpuConstantType type; - if (typeid(*value) == typeid(Vector4)) - type = Ogre::GCT_FLOAT4; - else if (typeid(*value) == typeid(Vector3)) - type = Ogre::GCT_FLOAT3; - else if (typeid(*value) == typeid(Vector2)) - type = Ogre::GCT_FLOAT2; - else if (typeid(*value) == typeid(FloatValue)) - type = Ogre::GCT_FLOAT1; - else if (typeid(*value) == typeid(IntValue)) - type = Ogre::GCT_INT1; - else - throw std::runtime_error("unexpected type"); - params->addConstantDefinition(name, type); - mSharedParameters[name] = params; - } - else - params = mSharedParameters.find(name)->second; - - Ogre::Vector4 v (1.0, 1.0, 1.0, 1.0); - if (typeid(*value) == typeid(Vector4)) - { - Vector4 vec = retrieveValue(value, NULL); - v.x = vec.mX; - v.y = vec.mY; - v.z = vec.mZ; - v.w = vec.mW; - } - else if (typeid(*value) == typeid(Vector3)) - { - Vector3 vec = retrieveValue(value, NULL); - v.x = vec.mX; - v.y = vec.mY; - v.z = vec.mZ; - } - else if (typeid(*value) == typeid(Vector2)) - { - Vector2 vec = retrieveValue(value, NULL); - v.x = vec.mX; - v.y = vec.mY; - } - else if (typeid(*value) == typeid(FloatValue)) - v.x = retrieveValue(value, NULL).get(); - else if (typeid(*value) == typeid(IntValue)) - v.x = static_cast(retrieveValue(value, NULL).get()); - else - throw std::runtime_error ("unsupported property type for shared parameter \"" + name + "\""); - params->setNamedConstant(name, v); - } -} diff --git a/extern/shiny/Platforms/Ogre/OgrePlatform.hpp b/extern/shiny/Platforms/Ogre/OgrePlatform.hpp deleted file mode 100644 index d3a1a83608..0000000000 --- a/extern/shiny/Platforms/Ogre/OgrePlatform.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef SH_OGREPLATFORM_H -#define SH_OGREPLATFORM_H - -/** - * @addtogroup Platforms - * @{ - */ - -/** - * @addtogroup Ogre - * A set of classes to interact with Ogre's material system - * @{ - */ - -#include "../../Main/Platform.hpp" - -#include -#include - -namespace sh -{ - class OgreMaterialSerializer; - - class OgrePlatform : public Platform, public Ogre::MaterialManager::Listener - { - public: - OgrePlatform (const std::string& resourceGroupName, const std::string& basePath); - virtual ~OgrePlatform (); - - virtual Ogre::Technique* handleSchemeNotFound ( - unsigned short schemeIndex, const Ogre::String &schemeName, Ogre::Material *originalMaterial, - unsigned short lodIndex, const Ogre::Renderable *rend); - - static OgreMaterialSerializer& getSerializer(); - - private: - virtual bool isProfileSupported (const std::string& profile); - - virtual void serializeShaders (const std::string& file); - virtual void deserializeShaders (const std::string& file); - - virtual boost::shared_ptr createMaterial (const std::string& name); - - virtual boost::shared_ptr createGpuProgram ( - GpuProgramType type, - const std::string& compileArguments, - const std::string& name, const std::string& profile, - const std::string& source, Language lang); - - virtual void destroyGpuProgram (const std::string& name); - - virtual void setSharedParameter (const std::string& name, PropertyValuePtr value); - - friend class ShaderInstance; - friend class Factory; - - protected: - virtual bool supportsShaderSerialization (); - virtual bool supportsMaterialQueuedListener (); - - std::string mResourceGroup; - - static OgreMaterialSerializer* sSerializer; - - std::map mSharedParameters; - }; -} - -/** - * @} - * @} - */ - -#endif diff --git a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp deleted file mode 100644 index ad8e6d2b03..0000000000 --- a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "OgreTextureUnitState.hpp" - -#include - -#include -#include - -#include "OgrePass.hpp" -#include "OgrePlatform.hpp" -#include "OgreMaterialSerializer.hpp" - -namespace sh -{ - OgreTextureUnitState::OgreTextureUnitState (OgrePass* parent, const std::string& name) - : TextureUnitState() - { - mTextureUnitState = parent->getOgrePass()->createTextureUnitState(""); - mTextureUnitState->setName(name); - } - - bool OgreTextureUnitState::setPropertyOverride (const std::string &name, PropertyValuePtr& value, PropertySetGet* context) - { - OgreMaterialSerializer& s = OgrePlatform::getSerializer(); - - if (name == "texture_alias") - { - // texture alias in this library refers to something else than in ogre - // delegate up - return TextureUnitState::setPropertyOverride (name, value, context); - } - else if (name == "direct_texture") - { - setTextureName (retrieveValue(value, context).get()); - return true; - } - else if (name == "anim_texture2") - { - std::string val = retrieveValue(value, context).get(); - std::vector tokens; - boost::split(tokens, val, boost::is_any_of(" ")); - assert(tokens.size() == 3); - std::string texture = tokens[0]; - int frames = boost::lexical_cast(tokens[1]); - float duration = boost::lexical_cast(tokens[2]); - - std::vector frameTextures; - for (int i=0; isetAnimatedTextureName(&frameTextures[0], frames, duration); - return true; - } - else if (name == "create_in_ffp") - return true; // handled elsewhere - - return s.setTextureUnitProperty (name, retrieveValue(value, context).get(), mTextureUnitState); - } - - void OgreTextureUnitState::setTextureName (const std::string& textureName) - { - mTextureUnitState->setTextureName(textureName); - } -} diff --git a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.hpp b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.hpp deleted file mode 100644 index 0f1914f62e..0000000000 --- a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef SH_OGRETEXTUREUNITSTATE_H -#define SH_OGRETEXTUREUNITSTATE_H - -#include - -#include "../../Main/Platform.hpp" - -namespace sh -{ - class OgrePass; - - class OgreTextureUnitState : public TextureUnitState - { - public: - OgreTextureUnitState (OgrePass* parent, const std::string& name); - - virtual void setTextureName (const std::string& textureName); - - private: - Ogre::TextureUnitState* mTextureUnitState; - - protected: - virtual bool setPropertyOverride (const std::string &name, PropertyValuePtr& value, PropertySetGet* context); - }; -} - -#endif diff --git a/extern/shiny/Readme.txt b/extern/shiny/Readme.txt deleted file mode 100644 index 6133219906..0000000000 --- a/extern/shiny/Readme.txt +++ /dev/null @@ -1,33 +0,0 @@ -shiny - a shader and material management library for OGRE - -FEATURES - -- High-level layer on top of OGRE's material system. It allows you to generate multiple techniques for all your materials from a set of high-level per-material properties. - -- Several available Macros in shader source files. Just a few examples of the possibilities: binding OGRE auto constants, binding uniforms to material properties, foreach loops (repeat shader source a given number of times), retrieving per-material properties in an #if condition, automatic packing for vertex to fragment passthroughs. These macros allow you to generate even very complex shaders (for example the Ogre::Terrain shader) without assembling them in C++ code. - -- Integrated preprocessor (no, I didn't reinvent the wheel, I used boost::wave which turned out to be an excellent choice) that allows me to blend out macros that shouldn't be in use because e.g. the shader permutation doesn't need this specific feature. - -- User settings integration. They can be set by a C++ interface and retrieved through a macro in shader files. - -- Automatic handling of shader permutations, i.e. shaders are shared between materials in a smart way. - -- An optional "meta-language" (well, actually it's just a small header with some conditional defines) that you may use to compile the same shader source for different target languages. If you don't like it, you can still code in GLSL / CG etc separately. You can also switch between the languages at runtime. - -- On-demand material and shader creation. It uses Ogre's material listener to compile the shaders as soon as they are needed for rendering, and not earlier. - -- Shader changes are fully dynamic and real-time. Changing a user setting will recompile all shaders affected by this setting when they are next needed. - -- Serialization system that extends Ogre's material script system, it uses Ogre's script parser, but also adds some additional properties that are not available in Ogre's material system. - -- A concept called "Configuration" allowing you to create a different set of your shaders, doing the same thing except for some minor differences: the properties that are overridden by the active configuration. Possible uses for this are using simpler shaders (no shadows, no fog etc) when rendering for example realtime reflections or a minimap. You can easily switch between configurations by changing the active Ogre material scheme (for example on a viewport level). - -- Fixed function support. You can globally enable or disable shaders at any time, and for texture units you can specify if they're only needed for the shader-based path (e.g. normal maps) or if they should also be created in the fixed function path. - -LICENSE - -see License.txt - -AUTHOR - -scrawl diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt index da3451d93e..75cb6a9b0d 100644 --- a/files/CMakeLists.txt +++ b/files/CMakeLists.txt @@ -1,49 +1,2 @@ -project(resources) - -set(WATER_FILES - water_nm.png - circle.png -) - -set(MATERIAL_FILES - atmosphere.shader - atmosphere.shaderset - clouds.shader - clouds.shaderset - core.h - moon.shader - moon.shaderset - objects.mat - objects.shader - objects.shaderset - openmw.configuration - quad.mat - quad.shader - quad.shaderset - shadowcaster.mat - shadowcaster.shader - shadowcaster.shaderset - shadows.h - sky.mat - stars.shader - stars.shaderset - sun.shader - sun.shaderset - terrain.shader - terrain.shaderset - underwater.h - water.mat - water.shader - water.shaderset - selection.mat - selection.shader - selection.shaderset - mygui.mat - mygui.shader - mygui.shaderset - ripples.particle -) - -copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/water "${OpenMW_BINARY_DIR}/resources/water/" "${WATER_FILES}") - -copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/materials "${OpenMW_BINARY_DIR}/resources/materials/" "${MATERIAL_FILES}") +add_subdirectory(mygui) +add_subdirectory(shaders) diff --git a/files/mac/Info.plist b/files/mac/openmw-Info.plist.in similarity index 95% rename from files/mac/Info.plist rename to files/mac/openmw-Info.plist.in index f832b09c9d..efd55ee5c3 100644 --- a/files/mac/Info.plist +++ b/files/mac/openmw-Info.plist.in @@ -26,5 +26,7 @@ NSHumanReadableCopyright + NSHighResolutionCapable + diff --git a/files/mac/openmw-cs-Info.plist.in b/files/mac/openmw-cs-Info.plist.in new file mode 100644 index 0000000000..684ad79087 --- /dev/null +++ b/files/mac/openmw-cs-Info.plist.in @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + LSRequiresCarbon + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + NSHighResolutionCapable + + + diff --git a/files/materials/atmosphere.shader b/files/materials/atmosphere.shader deleted file mode 100644 index 3eaa73b1ca..0000000000 --- a/files/materials/atmosphere.shader +++ /dev/null @@ -1,39 +0,0 @@ -#include "core.h" - -#ifdef SH_VERTEX_SHADER - - SH_BEGIN_PROGRAM - shUniform(float4x4, view) @shAutoConstant(view, view_matrix) - shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix) - - shOutput(float, alphaFade) - - SH_START_PROGRAM - { - float4x4 viewFixed = view; -#if !SH_GLSL && !SH_GLSLES - viewFixed[0][3] = 0.0; - viewFixed[1][3] = 0.0; - viewFixed[2][3] = 0.0; -#else - viewFixed[3][0] = 0.0; - viewFixed[3][1] = 0.0; - viewFixed[3][2] = 0.0; -#endif - shOutputPosition = shMatrixMult(projection, shMatrixMult(viewFixed, shInputPosition)); - alphaFade = shInputPosition.z < 150.0 ? 0.0 : 1.0; - } - -#else - - SH_BEGIN_PROGRAM - shInput(float, alphaFade) - shUniform(float4, atmosphereColour) @shSharedParameter(atmosphereColour) - shUniform(float4, horizonColour) @shSharedParameter(horizonColour, horizonColour) - - SH_START_PROGRAM - { - shOutputColour(0) = alphaFade * atmosphereColour + (1.0 - alphaFade) * horizonColour; - } - -#endif diff --git a/files/materials/atmosphere.shaderset b/files/materials/atmosphere.shaderset deleted file mode 100644 index 54108dbba6..0000000000 --- a/files/materials/atmosphere.shaderset +++ /dev/null @@ -1,15 +0,0 @@ -shader_set atmosphere_vertex -{ - source atmosphere.shader - type vertex - profiles_cg vs_2_0 arbvp1 - profiles_hlsl vs_2_0 -} - -shader_set atmosphere_fragment -{ - source atmosphere.shader - type fragment - profiles_cg ps_2_x ps_2_0 ps arbfp1 - profiles_hlsl ps_2_0 -} diff --git a/files/materials/clouds.shader b/files/materials/clouds.shader deleted file mode 100644 index 5902d2fdcc..0000000000 --- a/files/materials/clouds.shader +++ /dev/null @@ -1,55 +0,0 @@ -#include "core.h" - -#ifdef SH_VERTEX_SHADER - - SH_BEGIN_PROGRAM - shUniform(float4x4, worldview) @shAutoConstant(worldview, worldview_matrix) - shUniform(float4x4, proj) @shAutoConstant(proj, projection_matrix) - shVertexInput(float2, uv0) - shOutput(float2, UV) - shOutput(float, alphaFade) - - SH_START_PROGRAM - { - float4x4 worldviewFixed = worldview; - -#if !SH_GLSL && !SH_GLSLES - worldviewFixed[0][3] = 0.0; - worldviewFixed[1][3] = 0.0; - worldviewFixed[2][3] = 0.0; -#else - worldviewFixed[3][0] = 0.0; - worldviewFixed[3][1] = 0.0; - worldviewFixed[3][2] = 0.0; -#endif - - shOutputPosition = shMatrixMult(proj, shMatrixMult(worldviewFixed, shInputPosition)); - UV = uv0; - alphaFade = (shInputPosition.z <= 200.0) ? ((shInputPosition.z <= 100.0) ? 0.0 : 0.25) : 1.0; - } - -#else - - SH_BEGIN_PROGRAM - shInput(float2, UV) - shInput(float, alphaFade) - - shSampler2D(diffuseMap1) - shSampler2D(diffuseMap2) - - shUniform(float, cloudBlendFactor) @shSharedParameter(cloudBlendFactor) - shUniform(float, cloudAnimationTimer) @shSharedParameter(cloudAnimationTimer) - shUniform(float, cloudOpacity) @shSharedParameter(cloudOpacity) - shUniform(float3, cloudColour) @shSharedParameter(cloudColour) - - SH_START_PROGRAM - { - // Scroll in y direction - float2 scrolledUV = UV + float2(0,1) * cloudAnimationTimer * 0.003; - - float4 albedo = shSample(diffuseMap1, scrolledUV) * (1.0-cloudBlendFactor) + shSample(diffuseMap2, scrolledUV) * cloudBlendFactor; - - shOutputColour(0) = float4(cloudColour, 1) * albedo * float4(1,1,1, cloudOpacity * alphaFade); - } - -#endif diff --git a/files/materials/clouds.shaderset b/files/materials/clouds.shaderset deleted file mode 100644 index 5fffb5658d..0000000000 --- a/files/materials/clouds.shaderset +++ /dev/null @@ -1,15 +0,0 @@ -shader_set clouds_vertex -{ - source clouds.shader - type vertex - profiles_cg vs_2_0 arbvp1 - profiles_hlsl vs_2_0 -} - -shader_set clouds_fragment -{ - source clouds.shader - type fragment - profiles_cg ps_2_x ps_2_0 ps arbfp1 - profiles_hlsl ps_2_0 -} diff --git a/files/materials/core.h b/files/materials/core.h deleted file mode 100644 index e6cde4bdae..0000000000 --- a/files/materials/core.h +++ /dev/null @@ -1,181 +0,0 @@ -#if SH_HLSL == 1 || SH_CG == 1 - - #define shTexture2D sampler2D - #define shSample(tex, coord) tex2D(tex, coord) - #define shCubicSample(tex, coord) texCUBE(tex, coord) - #define shLerp(a, b, t) lerp(a, b, t) - #define shSaturate(a) saturate(a) - - #define shSampler2D(name) , uniform sampler2D name : register(s@shCounter(0)) @shUseSampler(name) - - #define shSamplerCube(name) , uniform samplerCUBE name : register(s@shCounter(0)) @shUseSampler(name) - - #define shMatrixMult(m, v) mul(m, v) - - #define shUniform(type, name) , uniform type name - - #define shTangentInput(type) , in type tangent : TANGENT - #define shVertexInput(type, name) , in type name : TEXCOORD@shCounter(1) - #define shInput(type, name) , in type name : TEXCOORD@shCounter(1) - #define shOutput(type, name) , out type name : TEXCOORD@shCounter(2) - - #define shNormalInput(type) , in type normal : NORMAL - - #define shColourInput(type) , in type colour : COLOR - - #define shFract(val) frac(val) - - #ifdef SH_VERTEX_SHADER - - #define shOutputPosition oPosition - #define shInputPosition iPosition - - - #define SH_BEGIN_PROGRAM \ - void main( \ - float4 iPosition : POSITION \ - , out float4 oPosition : POSITION - - #define SH_START_PROGRAM \ - ) \ - - #endif - - #ifdef SH_FRAGMENT_SHADER - - #define shOutputColour(num) oColor##num - - #define shDeclareMrtOutput(num) , out float4 oColor##num : COLOR##num - - #define SH_BEGIN_PROGRAM \ - void main( \ - out float4 oColor0 : COLOR - - #define SH_START_PROGRAM \ - ) \ - - #endif - -#endif - -#if SH_GLSL == 1 || SH_GLSLES == 1 - - #define shFract(val) fract(val) - -#if SH_GLSLES == 1 - @version 100 -#else - @version 120 -#endif - -#if SH_GLSLES == 1 -precision mediump int; -precision mediump float; -#endif - - #define float2 vec2 - #define float3 vec3 - #define float4 vec4 - #define int2 ivec2 - #define int3 ivec3 - #define int4 ivec4 - #define shTexture2D sampler2D - #define shSample(tex, coord) texture2D(tex, coord) - #define shCubicSample(tex, coord) textureCube(tex, coord) - #define shLerp(a, b, t) mix(a, b, t) - #define shSaturate(a) clamp(a, 0.0, 1.0) - - #define shUniform(type, name) uniform type name; - - #define shSampler2D(name) uniform sampler2D name; @shUseSampler(name) - - #define shSamplerCube(name) uniform samplerCube name; @shUseSampler(name) - - #define shMatrixMult(m, v) ((m) * (v)) - - #define shOutputPosition gl_Position - - #define float4x4 mat4 - #define float3x3 mat3 - - // GLSL 1.3 - #if 0 - - // automatically recognized by ogre when the input name equals this - #define shInputPosition vertex - - #define shOutputColour(num) oColor##num - - #define shTangentInput(type) in type tangent; - #define shVertexInput(type, name) in type name; - #define shInput(type, name) in type name; - #define shOutput(type, name) out type name; - - // automatically recognized by ogre when the input name equals this - #define shNormalInput(type) in type normal; - #define shColourInput(type) in type colour; - - #ifdef SH_VERTEX_SHADER - - #define SH_BEGIN_PROGRAM \ - in float4 vertex; - #define SH_START_PROGRAM \ - void main(void) - - #endif - - #ifdef SH_FRAGMENT_SHADER - - #define shDeclareMrtOutput(num) out vec4 oColor##num; - - #define SH_BEGIN_PROGRAM \ - out float4 oColor0; - #define SH_START_PROGRAM \ - void main(void) - - - #endif - - #endif - - // GLSL 1.2 - - #if 1 - - // automatically recognized by ogre when the input name equals this - #define shInputPosition vertex - - #define shOutputColour(num) gl_FragData[num] - - #define shTangentInput(type) attribute type tangent; - #define shVertexInput(type, name) attribute type name; - #define shInput(type, name) varying type name; - #define shOutput(type, name) varying type name; - - // automatically recognized by ogre when the input name equals this - #define shNormalInput(type) attribute type normal; - #define shColourInput(type) attribute type colour; - - #ifdef SH_VERTEX_SHADER - - #define SH_BEGIN_PROGRAM \ - attribute vec4 vertex; - #define SH_START_PROGRAM \ - void main(void) - - #endif - - #ifdef SH_FRAGMENT_SHADER - - #define shDeclareMrtOutput(num) - - #define SH_BEGIN_PROGRAM - - #define SH_START_PROGRAM \ - void main(void) - - - #endif - - #endif -#endif diff --git a/files/materials/moon.shader b/files/materials/moon.shader deleted file mode 100644 index 151b94180f..0000000000 --- a/files/materials/moon.shader +++ /dev/null @@ -1,51 +0,0 @@ -#include "core.h" - -#ifdef SH_VERTEX_SHADER - - SH_BEGIN_PROGRAM - shUniform(float4x4, world) @shAutoConstant(world, world_matrix) - shUniform(float4x4, view) @shAutoConstant(view, view_matrix) -shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix) - shVertexInput(float2, uv0) - shOutput(float2, UV) - - SH_START_PROGRAM - { - float4x4 viewFixed = view; -#if !SH_GLSL && !SH_GLSLES - viewFixed[0][3] = 0.0; - viewFixed[1][3] = 0.0; - viewFixed[2][3] = 0.0; -#else - viewFixed[3][0] = 0.0; - viewFixed[3][1] = 0.0; - viewFixed[3][2] = 0.0; -#endif - shOutputPosition = shMatrixMult(projection, shMatrixMult(viewFixed, shMatrixMult(world, shInputPosition))); - UV = uv0; - } - -#else - - SH_BEGIN_PROGRAM - shSampler2D(diffuseMap) - shSampler2D(alphaMap) - shInput(float2, UV) - shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) - shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) - - shUniform(float4, atmosphereColour) @shSharedParameter(atmosphereColour) - - SH_START_PROGRAM - { - float4 phaseTex = shSample(diffuseMap, UV); - float4 fullCircleTex = shSample(alphaMap, UV); - - shOutputColour(0).a = max(phaseTex.a, fullCircleTex.a) * materialDiffuse.a; - - shOutputColour(0).xyz = fullCircleTex.xyz * atmosphereColour.xyz; - shOutputColour(0).xyz = shLerp(shOutputColour(0).xyz, phaseTex.xyz, phaseTex.a); - shOutputColour(0).xyz *= materialEmissive.xyz; - } - -#endif diff --git a/files/materials/moon.shaderset b/files/materials/moon.shaderset deleted file mode 100644 index 659481a961..0000000000 --- a/files/materials/moon.shaderset +++ /dev/null @@ -1,15 +0,0 @@ -shader_set moon_vertex -{ - source moon.shader - type vertex - profiles_cg vs_2_0 arbvp1 - profiles_hlsl vs_2_0 -} - -shader_set moon_fragment -{ - source moon.shader - type fragment - profiles_cg ps_2_x ps_2_0 ps arbfp1 - profiles_hlsl ps_2_0 -} diff --git a/files/materials/mygui.mat b/files/materials/mygui.mat deleted file mode 100644 index 78cba3f897..0000000000 --- a/files/materials/mygui.mat +++ /dev/null @@ -1,25 +0,0 @@ -material MyGUI/NoTexture -{ - pass - { - vertex_program mygui_vertex - fragment_program mygui_fragment - shader_properties - { - has_texture false - } - } -} - -material MyGUI/OneTexture -{ - pass - { - vertex_program mygui_vertex - fragment_program mygui_fragment - shader_properties - { - has_texture true - } - } -} diff --git a/files/materials/mygui.shader b/files/materials/mygui.shader deleted file mode 100644 index 4d12eba90e..0000000000 --- a/files/materials/mygui.shader +++ /dev/null @@ -1,45 +0,0 @@ -#include "core.h" - -#define TEXTURE @shPropertyBool(has_texture) - -#ifdef SH_VERTEX_SHADER - - SH_BEGIN_PROGRAM -#if TEXTURE - shVertexInput(float2, uv0) - shOutput(float2, UV) -#endif - shColourInput(float4) - shOutput(float4, colourPassthrough) - - SH_START_PROGRAM - { - shOutputPosition = float4(shInputPosition.xyz, 1.0); -#if TEXTURE - UV.xy = uv0; -#endif - colourPassthrough = colour; - } - -#else - - - SH_BEGIN_PROGRAM - -#if TEXTURE - shSampler2D(diffuseMap) - shInput(float2, UV) -#endif - - shInput(float4, colourPassthrough) - - SH_START_PROGRAM - { -#if TEXTURE - shOutputColour(0) = shSample(diffuseMap, UV.xy) * colourPassthrough; -#else - shOutputColour(0) = colourPassthrough; -#endif - } - -#endif diff --git a/files/materials/mygui.shaderset b/files/materials/mygui.shaderset deleted file mode 100644 index 980cd4caf4..0000000000 --- a/files/materials/mygui.shaderset +++ /dev/null @@ -1,15 +0,0 @@ -shader_set mygui_vertex -{ - source mygui.shader - type vertex - profiles_cg vs_2_0 vp40 arbvp1 - profiles_hlsl vs_3_0 vs_2_0 -} - -shader_set mygui_fragment -{ - source mygui.shader - type fragment - profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1 - profiles_hlsl ps_3_0 ps_2_0 -} diff --git a/files/materials/objects.mat b/files/materials/objects.mat deleted file mode 100644 index 7d3085b0f2..0000000000 --- a/files/materials/objects.mat +++ /dev/null @@ -1,147 +0,0 @@ -material openmw_objects_base -{ - diffuse 1.0 1.0 1.0 1.0 - specular 0 0 0 0 1 - ambient 1.0 1.0 1.0 - emissive 0.0 0.0 0.0 - vertmode 0 - diffuseMap black.png - normalMap - emissiveMap - specMap - darkMap - use_emissive_map false - use_detail_map false - use_diffuse_map false - use_dark_map false - emissiveMapUVSet 0 - detailMapUVSet 0 - diffuseMapUVSet 0 - darkMapUVSet 0 - use_parallax false - - scene_blend default - depth_write default - depth_check default - alpha_rejection default - transparent_sorting default - polygon_mode default - env_map false - env_map_color 1 1 1 - - alphaTestMode 0 - alphaTestValue 0 - - pass - { - vertex_program openmw_objects_vertex - fragment_program openmw_objects_fragment - - shader_properties - { - vertexcolor_mode $vertmode - normalMap $normalMap - emissiveMapUVSet $emissiveMapUVSet - detailMapUVSet $detailMapUVSet - diffuseMapUVSet $diffuseMapUVSet - darkMapUVSet $darkMapUVSet - emissiveMap $emissiveMap - detailMap $detailMap - diffuseMap $diffuseMap - specMap $specMap - darkMap $darkMap - env_map $env_map - env_map_color $env_map_color - use_parallax $use_parallax - alphaTestMode $alphaTestMode - alphaTestValue $alphaTestValue - } - - diffuse $diffuse - specular $specular - ambient $ambient - emissive $emissive - scene_blend $scene_blend - alpha_rejection $alpha_rejection - depth_write $depth_write - depth_check $depth_check - transparent_sorting $transparent_sorting - polygon_mode $polygon_mode - cull_hardware $cullmode - - texture_unit diffuseMap - { - direct_texture $diffuseMap - create_in_ffp $use_diffuse_map - tex_coord_set $diffuseMapUVSet - tex_address_mode $diffuseMapClampMode - } - - texture_unit normalMap - { - direct_texture $normalMap - // force automips here for now - num_mipmaps 4 - } - - texture_unit darkMap - { - create_in_ffp $use_dark_map - colour_op_ex modulate src_current src_texture - alpha_op_ex modulate src_current src_texture - direct_texture $darkMap - tex_coord_set $darkMapUVSet - tex_address_mode $darkMapClampMode - } - - texture_unit detailMap - { - create_in_ffp $use_detail_map - colour_op_ex modulate_x2 src_current src_texture - direct_texture $detailMap - tex_coord_set $detailMapUVSet - tex_address_mode $detailMapClampMode - } - - texture_unit emissiveMap - { - create_in_ffp $use_emissive_map - colour_op add - direct_texture $emissiveMap - tex_coord_set $emissiveMapUVSet - tex_address_mode $emissiveMapClampMode - } - - texture_unit envMap - { - create_in_ffp $env_map - env_map spherical - anim_texture2 textures\magicitem\caust.dds 32 2 - colour_op add - } - - texture_unit specMap - { - direct_texture $specMap - } - - texture_unit shadowMap0 - { - content_type shadow - tex_address_mode clamp - filtering none - } - texture_unit shadowMap1 - { - content_type shadow - tex_address_mode clamp - filtering none - } - texture_unit shadowMap2 - { - content_type shadow - tex_address_mode clamp - filtering none - } - } -} diff --git a/files/materials/objects.shader b/files/materials/objects.shader deleted file mode 100644 index 5c74b11393..0000000000 --- a/files/materials/objects.shader +++ /dev/null @@ -1,591 +0,0 @@ -#include "core.h" - - -#define FOG @shGlobalSettingBool(fog) - -#define SHADOWS_PSSM @shGlobalSettingBool(shadows_pssm) -#define SHADOWS @shGlobalSettingBool(shadows) - -#if SHADOWS || SHADOWS_PSSM - #include "shadows.h" -#endif - -#if FOG || SHADOWS_PSSM -#define NEED_DEPTH -#endif - -#define SPECULAR 1 - -#define NORMAL_MAP @shPropertyHasValue(normalMap) -#define EMISSIVE_MAP @shPropertyHasValue(emissiveMap) -#define DETAIL_MAP @shPropertyHasValue(detailMap) -#define DIFFUSE_MAP @shPropertyHasValue(diffuseMap) -#define DARK_MAP @shPropertyHasValue(darkMap) -#define SPEC_MAP @shPropertyHasValue(specMap) && SPECULAR - -#define ALPHATEST_MODE @shPropertyString(alphaTestMode) - -#define PARALLAX @shPropertyBool(use_parallax) -#define PARALLAX_SCALE 0.04 -#define PARALLAX_BIAS -0.02 - -// right now we support 2 UV sets max. implementing them is tedious, and we're probably not going to need more -#define SECOND_UV_SET (@shPropertyString(emissiveMapUVSet) || @shPropertyString(detailMapUVSet) || @shPropertyString(diffuseMapUVSet) || @shPropertyString(darkMapUVSet)) - -// if normal mapping is enabled, we force pixel lighting -#define VERTEX_LIGHTING (!@shPropertyHasValue(normalMap)) - -#define UNDERWATER @shGlobalSettingBool(render_refraction) - -#define VERTEXCOLOR_MODE @shPropertyString(vertexcolor_mode) - -#define VIEWPROJ_FIX @shGlobalSettingBool(viewproj_fix) - -#define ENV_MAP @shPropertyBool(env_map) - -#define NEED_NORMAL (!VERTEX_LIGHTING || ENV_MAP) || SPECULAR - -#ifdef SH_VERTEX_SHADER - - // ------------------------------------- VERTEX --------------------------------------- - - SH_BEGIN_PROGRAM - shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - - shUniform(float4x4, textureMatrix0) @shAutoConstant(textureMatrix0, texture_matrix, 0) - -#if (VIEWPROJ_FIX) || (SHADOWS) - shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) -#endif - -#if VIEWPROJ_FIX - shUniform(float4, vpRow2Fix) @shSharedParameter(vpRow2Fix, vpRow2Fix) - shUniform(float4x4, vpMatrix) @shAutoConstant(vpMatrix, viewproj_matrix) -#endif - - shVertexInput(float2, uv0) -#if SECOND_UV_SET - shVertexInput(float2, uv1) -#endif - shOutput(float4, UV) - - shNormalInput(float4) - -#if NORMAL_MAP - shTangentInput(float4) - shOutput(float3, tangentPassthrough) -#endif - -#if NEED_NORMAL - shOutput(float3, normalPassthrough) -#endif - - // Depth in w - shOutput(float4, objSpacePositionPassthrough) - -#if VERTEXCOLOR_MODE != 0 - shColourInput(float4) -#endif - -#if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING - shOutput(float4, colourPassthrough) -#endif - -#if ENV_MAP || VERTEX_LIGHTING - shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix) -#endif - -#if VERTEX_LIGHTING - shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_space_array, @shGlobalSettingString(num_lights)) - shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) - shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) - shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) -#if VERTEXCOLOR_MODE != 2 - shUniform(float4, materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) -#endif - shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) -#if VERTEXCOLOR_MODE != 1 - shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) -#endif - -#endif - -#if SHADOWS - shOutput(float4, lightSpacePos0) - shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) -#endif - -#if SHADOWS_PSSM - @shForeach(3) - shOutput(float4, lightSpacePos@shIterator) - shUniform(float4x4, texViewProjMatrix@shIterator) @shAutoConstant(texViewProjMatrix@shIterator, texture_viewproj_matrix, @shIterator) - @shEndForeach -#if !VIEWPROJ_FIX - shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) -#endif -#endif - -#if VERTEX_LIGHTING - shOutput(float4, lightResult) - shOutput(float3, directionalResult) -#endif - SH_START_PROGRAM - { - shOutputPosition = shMatrixMult(wvp, shInputPosition); - - UV.xy = shMatrixMult (textureMatrix0, float4(uv0,0,1)).xy; -#if SECOND_UV_SET - UV.zw = uv1; -#endif - -#if ENV_MAP || VERTEX_LIGHTING - float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz); -#endif - -#if ENV_MAP - float3 viewVec = normalize( shMatrixMult(worldView, shInputPosition).xyz); - - float3 r = reflect( viewVec, viewNormal ); - float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) ); - UV.z = r.x/m + 0.5; - UV.w = r.y/m + 0.5; -#endif - -#if NORMAL_MAP - tangentPassthrough = tangent.xyz; -#endif -#if NEED_NORMAL - normalPassthrough = normal.xyz; -#endif -#if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING - colourPassthrough = colour; -#endif - -#ifdef NEED_DEPTH - - -#if VIEWPROJ_FIX - float4x4 vpFixed = vpMatrix; -#if !SH_GLSL && !SH_GLSLES - vpFixed[2] = vpRow2Fix; -#else - vpFixed[0][2] = vpRow2Fix.x; - vpFixed[1][2] = vpRow2Fix.y; - vpFixed[2][2] = vpRow2Fix.z; - vpFixed[3][2] = vpRow2Fix.w; -#endif - - float4x4 fixedWVP = shMatrixMult(vpFixed, worldMatrix); - - objSpacePositionPassthrough.w = shMatrixMult(fixedWVP, shInputPosition).z; -#else - objSpacePositionPassthrough.w = shOutputPosition.z; -#endif - -#endif - - objSpacePositionPassthrough.xyz = shInputPosition.xyz; - -#if SHADOWS - lightSpacePos0 = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition)); -#endif -#if SHADOWS_PSSM - float4 wPos = shMatrixMult(worldMatrix, shInputPosition); - @shForeach(3) - lightSpacePos@shIterator = shMatrixMult(texViewProjMatrix@shIterator, wPos); - @shEndForeach -#endif - - -#if VERTEX_LIGHTING - float3 viewPos = shMatrixMult(worldView, shInputPosition).xyz; - - float3 lightDir; - float d; - lightResult = float4(0,0,0,1); - @shForeach(@shGlobalSettingString(num_lights)) - lightDir = lightPosition[@shIterator].xyz - (viewPos * lightPosition[@shIterator].w); - d = length(lightDir); - lightDir = normalize(lightDir); - - -#if VERTEXCOLOR_MODE == 2 - lightResult.xyz += colour.xyz * lightDiffuse[@shIterator].xyz - * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(viewNormal.xyz, lightDir), 0.0); -#else - lightResult.xyz += materialDiffuse.xyz * lightDiffuse[@shIterator].xyz - * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(viewNormal.xyz, lightDir), 0.0); -#endif - -#if @shIterator == 0 - directionalResult = lightResult.xyz; -#endif - - @shEndForeach - - -#if VERTEXCOLOR_MODE == 2 - lightResult.xyz += lightAmbient.xyz * colour.xyz + materialEmissive.xyz; - lightResult.a *= colour.a; -#endif -#if VERTEXCOLOR_MODE == 1 - lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + colour.xyz; -#endif -#if VERTEXCOLOR_MODE == 0 - lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + materialEmissive.xyz; -#endif - - lightResult.a *= materialDiffuse.a; - -#endif - } - -#else -#if NORMAL_MAP && SH_GLSLES - mat3 transpose( mat3 m); -#endif - // ----------------------------------- FRAGMENT ------------------------------------------ - -#if UNDERWATER - #include "underwater.h" -#endif - - SH_BEGIN_PROGRAM -#if DIFFUSE_MAP - shSampler2D(diffuseMap) -#endif - -#if NORMAL_MAP - shSampler2D(normalMap) -#endif - -#if DARK_MAP - shSampler2D(darkMap) -#endif - -#if DETAIL_MAP - shSampler2D(detailMap) -#endif - -#if EMISSIVE_MAP - shSampler2D(emissiveMap) -#endif - -#if ENV_MAP - shSampler2D(envMap) - shUniform(float3, env_map_color) @shUniformProperty3f(env_map_color, env_map_color) -#endif - -#if SPEC_MAP - shSampler2D(specMap) -#endif - -#if ENV_MAP || SPECULAR || PARALLAX - shUniform(float3, cameraPosObjSpace) @shAutoConstant(cameraPosObjSpace, camera_position_object_space) -#endif -#if SPECULAR - shUniform(float3, lightSpec0) @shAutoConstant(lightSpec0, light_specular_colour, 0) - shUniform(float3, lightPosObjSpace0) @shAutoConstant(lightPosObjSpace0, light_position_object_space, 0) - shUniform(float, matShininess) @shAutoConstant(matShininess, surface_shininess) - shUniform(float3, matSpec) @shAutoConstant(matSpec, surface_specular_colour) -#endif - - shInput(float4, UV) - -#if NORMAL_MAP - shInput(float3, tangentPassthrough) -#endif -#if NEED_NORMAL - shInput(float3, normalPassthrough) -#endif - - shInput(float4, objSpacePositionPassthrough) - -#if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING - shInput(float4, colourPassthrough) -#endif - -#if FOG - shUniform(float3, fogColour) @shAutoConstant(fogColour, fog_colour) - shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params) -#endif - -#if SHADOWS - shInput(float4, lightSpacePos0) - shSampler2D(shadowMap0) - shUniform(float2, invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, 1) -#endif -#if SHADOWS_PSSM - @shForeach(3) - shInput(float4, lightSpacePos@shIterator) - shSampler2D(shadowMap@shIterator) - shUniform(float2, invShadowmapSize@shIterator) @shAutoConstant(invShadowmapSize@shIterator, inverse_texture_size, @shIterator(1)) - @shEndForeach - shUniform(float3, pssmSplitPoints) @shSharedParameter(pssmSplitPoints) -#endif - -#if SHADOWS || SHADOWS_PSSM - shUniform(float4, shadowFar_fadeStart) @shSharedParameter(shadowFar_fadeStart) -#endif - -#if (UNDERWATER) || (FOG) - shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) - shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) -#endif - -#if UNDERWATER - shUniform(float, waterLevel) @shSharedParameter(waterLevel) - shUniform(float, waterEnabled) @shSharedParameter(waterEnabled) -#endif - -#if VERTEX_LIGHTING - shInput(float4, lightResult) - shInput(float3, directionalResult) -#else - shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_space_array, @shGlobalSettingString(num_lights)) - shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) - shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) - shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) - shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix) - #if VERTEXCOLOR_MODE != 2 - shUniform(float4, materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) - #endif - shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) - #if VERTEXCOLOR_MODE != 1 - shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) - #endif -#endif - -#if ALPHATEST_MODE != 0 - shUniform(float, alphaTestValue) @shUniformProperty1f(alphaTestValue, alphaTestValue) -#endif - - SH_START_PROGRAM - { - float4 newUV = UV; - -#ifdef NEED_DEPTH - float depthPassthrough = objSpacePositionPassthrough.w; -#endif - -#if NEED_NORMAL - float3 normal = normalPassthrough; -#endif - -#if NORMAL_MAP - float3 binormal = cross(tangentPassthrough.xyz, normal.xyz); - float3x3 tbn = float3x3(tangentPassthrough.xyz, binormal, normal.xyz); - - #if SH_GLSL || SH_GLSLES - tbn = transpose(tbn); - #endif - - float4 normalTex = shSample(normalMap, UV.xy); - - normal = normalize (shMatrixMult( transpose(tbn), normalTex.xyz * 2.0 - 1.0 )); -#endif - -#if ENV_MAP || SPECULAR || PARALLAX - float3 eyeDir = normalize(cameraPosObjSpace.xyz - objSpacePositionPassthrough.xyz); -#endif - -#if PARALLAX - float3 TSeyeDir = normalize(shMatrixMult(tbn, eyeDir)); - - newUV += (TSeyeDir.xyxy * ( normalTex.a * PARALLAX_SCALE + PARALLAX_BIAS )).xyxy; -#endif - -#if DIFFUSE_MAP - #if @shPropertyString(diffuseMapUVSet) - float4 diffuse = shSample(diffuseMap, newUV.zw); - #else - float4 diffuse = shSample(diffuseMap, newUV.xy); - #endif -#else - float4 diffuse = float4(1,1,1,1); -#endif - -#if ALPHATEST_MODE == 1 - if (diffuse.a >= alphaTestValue) - discard; -#elif ALPHATEST_MODE == 2 - if (diffuse.a != alphaTestValue) - discard; -#elif ALPHATEST_MODE == 3 - if (diffuse.a > alphaTestValue) - discard; -#elif ALPHATEST_MODE == 4 - if (diffuse.a <= alphaTestValue) - discard; -#elif ALPHATEST_MODE == 5 - if (diffuse.a == alphaTestValue) - discard; -#elif ALPHATEST_MODE == 6 - if (diffuse.a < alphaTestValue) - discard; -#elif ALPHATEST_MODE == 7 - discard; -#endif - - -#if DETAIL_MAP -#if @shPropertyString(detailMapUVSet) - diffuse *= shSample(detailMap, newUV.zw)*2; -#else - diffuse *= shSample(detailMap, newUV.xy)*2; -#endif -#endif - -#if DARK_MAP -#if @shPropertyString(darkMapUVSet) - diffuse *= shSample(darkMap, newUV.zw); -#else - diffuse *= shSample(darkMap, newUV.xy); -#endif -#endif - - shOutputColour(0) = diffuse; - -#if !VERTEX_LIGHTING - float3 viewPos = shMatrixMult(worldView, float4(objSpacePositionPassthrough.xyz,1)).xyz; - float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz); - - float3 lightDir; - float d; - float4 lightResult = float4(0,0,0,1); - @shForeach(@shGlobalSettingString(num_lights)) - lightDir = lightPosition[@shIterator].xyz - (viewPos * lightPosition[@shIterator].w); - d = length(lightDir); - lightDir = normalize(lightDir); - -#if VERTEXCOLOR_MODE == 2 - lightResult.xyz += colourPassthrough.xyz * lightDiffuse[@shIterator].xyz - * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(viewNormal.xyz, lightDir), 0.0); -#else - lightResult.xyz += materialDiffuse.xyz * lightDiffuse[@shIterator].xyz - * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(viewNormal.xyz, lightDir), 0.0); -#endif - -#if @shIterator == 0 - float3 directionalResult = lightResult.xyz; -#endif - - @shEndForeach - - -#if VERTEXCOLOR_MODE == 2 - lightResult.xyz += lightAmbient.xyz * colourPassthrough.xyz + materialEmissive.xyz; - lightResult.a *= colourPassthrough.a; -#endif -#if VERTEXCOLOR_MODE == 1 - lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + colourPassthrough.xyz; -#endif -#if VERTEXCOLOR_MODE == 0 - lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + materialEmissive.xyz; -#endif - - lightResult.a *= materialDiffuse.a; -#endif - - // shadows only for the first (directional) light -#if SHADOWS - float shadow = depthShadowPCF (shadowMap0, lightSpacePos0, invShadowmapSize0); -#endif -#if SHADOWS_PSSM - float shadow = pssmDepthShadow (lightSpacePos0, invShadowmapSize0, shadowMap0, lightSpacePos1, invShadowmapSize1, shadowMap1, lightSpacePos2, invShadowmapSize2, shadowMap2, depthPassthrough, pssmSplitPoints); -#endif - -#if SHADOWS || SHADOWS_PSSM - float fadeRange = shadowFar_fadeStart.x - shadowFar_fadeStart.y; - float fade = 1.0-((depthPassthrough - shadowFar_fadeStart.y) / fadeRange); - shadow = (depthPassthrough > shadowFar_fadeStart.x) ? 1.0 : ((depthPassthrough > shadowFar_fadeStart.y) ? 1.0-((1.0-shadow)*fade) : shadow); -#endif - -#if !SHADOWS && !SHADOWS_PSSM - float shadow = 1.0; -#endif - - - -#if (UNDERWATER) || (FOG) - float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough.xyz,1)).xyz; -#endif - -#if UNDERWATER - float3 waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0.0,0.0,1.0), waterLevel); -#endif - -#if SHADOWS || SHADOWS_PSSM - shOutputColour(0) *= (lightResult - float4(directionalResult * (1.0-shadow),0.0)); -#else - shOutputColour(0) *= lightResult; -#endif - -#if EMISSIVE_MAP - #if @shPropertyString(emissiveMapUVSet) - shOutputColour(0).xyz += shSample(emissiveMap, newUV.zw).xyz; - #else - shOutputColour(0).xyz += shSample(emissiveMap, newUV.xy).xyz; - #endif -#endif - -#if ENV_MAP - // Everything looks better with fresnel - float facing = 1.0 - max(abs(dot(-eyeDir, normal)), 0.0); - float envFactor = shSaturate(0.25 + 0.75 * pow(facing, 1.0)); - - shOutputColour(0).xyz += shSample(envMap, UV.zw).xyz * envFactor * env_map_color; -#endif - -#if SPECULAR - float3 light0Dir = normalize(lightPosObjSpace0.xyz); - - float NdotL = max(dot(normal, light0Dir), 0.0); - float3 halfVec = normalize (light0Dir + eyeDir); - - float shininess = matShininess; -#if SPEC_MAP - float4 specTex = shSample(specMap, UV.xy); - shininess *= (specTex.a); -#endif - - float3 specular = pow(max(dot(normal, halfVec), 0.0), shininess) * lightSpec0 * matSpec; -#if SPEC_MAP - specular *= specTex.xyz; -#else - specular *= diffuse.a; -#endif - - shOutputColour(0).xyz += specular * shadow; -#endif - -#if FOG - float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); - - -#if UNDERWATER - shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, UNDERWATER_COLOUR, shSaturate(length(waterEyePos-worldPos) / VISIBILITY)); -#else - shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColour, fogValue); -#endif - -#endif - - // prevent negative colour output (for example with negative lights) - shOutputColour(0).xyz = max(shOutputColour(0).xyz, float3(0.0,0.0,0.0)); - } -#if NORMAL_MAP && SH_GLSLES - mat3 transpose(mat3 m){ - return mat3( - m[0][0],m[1][0],m[2][0], - m[0][1],m[1][1],m[2][1], - m[0][2],m[1][2],m[2][2] - ); - } -#endif - -#endif diff --git a/files/materials/objects.shaderset b/files/materials/objects.shaderset deleted file mode 100644 index 028c15ce8a..0000000000 --- a/files/materials/objects.shaderset +++ /dev/null @@ -1,15 +0,0 @@ -shader_set openmw_objects_vertex -{ - source objects.shader - type vertex - profiles_cg vs_2_0 vp40 arbvp1 - profiles_hlsl vs_3_0 vs_2_0 -} - -shader_set openmw_objects_fragment -{ - source objects.shader - type fragment - profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1 - profiles_hlsl ps_3_0 ps_2_0 -} diff --git a/files/materials/openmw.configuration b/files/materials/openmw.configuration deleted file mode 100644 index b953a91311..0000000000 --- a/files/materials/openmw.configuration +++ /dev/null @@ -1,20 +0,0 @@ -configuration water_reflection -{ - shadows false - shadows_pssm false - viewproj_fix true -} - -configuration water_refraction -{ - viewproj_fix true - render_refraction true -} - -configuration local_map -{ - fog false - shadows false - shadows_pssm false - simple_water true -} diff --git a/files/materials/quad.mat b/files/materials/quad.mat deleted file mode 100644 index 77a2c0c340..0000000000 --- a/files/materials/quad.mat +++ /dev/null @@ -1,22 +0,0 @@ -material quad -{ - depth_write on - - pass - { - vertex_program transform_vertex - fragment_program quad_fragment - - depth_write $depth_write - - texture_unit SceneBuffer - { - } - } -} - -material quad_noDepthWrite -{ - parent quad - depth_write off -} diff --git a/files/materials/quad.shader b/files/materials/quad.shader deleted file mode 100644 index 4620588c3c..0000000000 --- a/files/materials/quad.shader +++ /dev/null @@ -1,25 +0,0 @@ -#include "core.h" - -#ifdef SH_VERTEX_SHADER - - SH_BEGIN_PROGRAM - shVertexInput(float2, uv0) - shOutput(float2, UV) - shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - SH_START_PROGRAM - { - shOutputPosition = shMatrixMult(wvp, shInputPosition); - UV = uv0; - } - -#else - - SH_BEGIN_PROGRAM - shInput(float2, UV) - shSampler2D(SceneBuffer) - SH_START_PROGRAM - { - shOutputColour(0) = shSample(SceneBuffer, UV); - } - -#endif diff --git a/files/materials/quad.shaderset b/files/materials/quad.shaderset deleted file mode 100644 index 71fd82da44..0000000000 --- a/files/materials/quad.shaderset +++ /dev/null @@ -1,15 +0,0 @@ -shader_set transform_vertex -{ - source quad.shader - type vertex - profiles_cg vs_2_0 vp40 arbvp1 - profiles_hlsl vs_2_0 -} - -shader_set quad_fragment -{ - source quad.shader - type fragment - profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1 - profiles_hlsl ps_2_0 -} diff --git a/files/materials/ripples.particle b/files/materials/ripples.particle deleted file mode 100644 index 58045f6d73..0000000000 --- a/files/materials/ripples.particle +++ /dev/null @@ -1,26 +0,0 @@ -particle_system openmw/Ripples -{ - material openmw/Ripple - particle_width 30 - particle_height 30 - // To make the particles move with the scene node when the waterlevel changes - local_space true - quota 300 - billboard_type perpendicular_common - common_up_vector 0 1 0 - common_direction 0 0 1 - - affector ColourFader - { - alpha -0.33 - } - - affector Scaler - { - rate 120 - } - - affector Rotator - { - } -} diff --git a/files/materials/selection.mat b/files/materials/selection.mat deleted file mode 100644 index 2cb92f8843..0000000000 --- a/files/materials/selection.mat +++ /dev/null @@ -1,9 +0,0 @@ -material SelectionColour -{ - allow_fixed_function false - pass - { - vertex_program selection_vertex - fragment_program selection_fragment - } -} diff --git a/files/materials/selection.shader b/files/materials/selection.shader deleted file mode 100644 index 095a31259d..0000000000 --- a/files/materials/selection.shader +++ /dev/null @@ -1,24 +0,0 @@ -#include "core.h" - -#ifdef SH_VERTEX_SHADER - - SH_BEGIN_PROGRAM - shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - - SH_START_PROGRAM - { - shOutputPosition = shMatrixMult(wvp, shInputPosition); - } - -#else - - SH_BEGIN_PROGRAM - shUniform(float4, colour) @shAutoConstant(colour, custom, 1) - - SH_START_PROGRAM - { - shOutputColour(0) = colour; - //shOutputColour(0) = float4(1,0,0,1); - } - -#endif diff --git a/files/materials/selection.shaderset b/files/materials/selection.shaderset deleted file mode 100644 index c90826282e..0000000000 --- a/files/materials/selection.shaderset +++ /dev/null @@ -1,15 +0,0 @@ -shader_set selection_vertex -{ - source selection.shader - type vertex - profiles_cg vs_2_0 arbvp1 - profiles_hlsl vs_2_0 -} - -shader_set selection_fragment -{ - source selection.shader - type fragment - profiles_cg ps_2_x ps_2_0 ps arbfp1 - profiles_hlsl ps_2_0 -} diff --git a/files/materials/shadowcaster.mat b/files/materials/shadowcaster.mat deleted file mode 100644 index 5c5c8e088d..0000000000 --- a/files/materials/shadowcaster.mat +++ /dev/null @@ -1,35 +0,0 @@ -material openmw_shadowcaster_default -{ - create_configuration Default - allow_fixed_function false - pass - { - fog_override true - - vertex_program openmw_shadowcaster_vertex - fragment_program openmw_shadowcaster_fragment - - shader_properties - { - shadow_transparency true - } - } -} - -material openmw_shadowcaster_noalpha -{ - create_configuration Default - allow_fixed_function false - pass - { - fog_override true - - vertex_program openmw_shadowcaster_vertex - fragment_program openmw_shadowcaster_fragment - - shader_properties - { - shadow_transparency false - } - } -} diff --git a/files/materials/shadowcaster.shader b/files/materials/shadowcaster.shader deleted file mode 100644 index 8f7911553a..0000000000 --- a/files/materials/shadowcaster.shader +++ /dev/null @@ -1,55 +0,0 @@ -#include "core.h" - -#define ALPHA @shPropertyBool(shadow_transparency) - -#ifdef SH_VERTEX_SHADER - - SH_BEGIN_PROGRAM -#if ALPHA - shVertexInput(float2, uv0) - shOutput(float2, UV) -#endif - shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - shOutput(float2, depth) - SH_START_PROGRAM - { - // this is the view space position - shOutputPosition = shMatrixMult(wvp, shInputPosition); - - // depth info for the fragment. - depth.x = shOutputPosition.z; - depth.y = shOutputPosition.w; - - // clamp z to zero. seem to do the trick. :-/ - shOutputPosition.z = max(shOutputPosition.z, 0.0); - -#if ALPHA - UV = uv0; -#endif - } - -#else - - SH_BEGIN_PROGRAM -#if ALPHA - shInput(float2, UV) - shSampler2D(texture1) -#endif - shInput(float2, depth) - SH_START_PROGRAM - { - float finalDepth = depth.x / depth.y; - - -#if ALPHA - // use alpha channel of the first texture - float alpha = shSample(texture1, UV).a; - - if (alpha < 0.5) - discard; -#endif - - shOutputColour(0) = float4(finalDepth, finalDepth, finalDepth, 1.0); - } - -#endif diff --git a/files/materials/shadowcaster.shaderset b/files/materials/shadowcaster.shaderset deleted file mode 100644 index 5f4990ed11..0000000000 --- a/files/materials/shadowcaster.shaderset +++ /dev/null @@ -1,15 +0,0 @@ -shader_set openmw_shadowcaster_vertex -{ - source shadowcaster.shader - type vertex - profiles_cg vs_2_0 arbvp1 - profiles_hlsl vs_2_0 -} - -shader_set openmw_shadowcaster_fragment -{ - source shadowcaster.shader - type fragment - profiles_cg ps_2_x ps_2_0 ps arbfp1 - profiles_hlsl ps_2_0 -} diff --git a/files/materials/shadows.h b/files/materials/shadows.h deleted file mode 100644 index eba3a3ea74..0000000000 --- a/files/materials/shadows.h +++ /dev/null @@ -1,51 +0,0 @@ - -#define FIXED_BIAS 0.0003 - -float depthShadowPCF (shTexture2D shadowMap, float4 shadowMapPos, float2 offset) -{ - shadowMapPos /= shadowMapPos.w; - float3 o = float3(offset.xy, -offset.x) * 0.3; - //float3 o = float3(0,0,0); - float c = (shadowMapPos.z <= FIXED_BIAS + shSample(shadowMap, shadowMapPos.xy - o.xy).r) ? 1.0 : 0.0; // top left - c += (shadowMapPos.z <= FIXED_BIAS + shSample(shadowMap, shadowMapPos.xy + o.xy).r) ? 1.0 : 0.0; // bottom right - c += (shadowMapPos.z <= FIXED_BIAS + shSample(shadowMap, shadowMapPos.xy + o.zy).r) ? 1.0 : 0.0; // bottom left - c += (shadowMapPos.z <= FIXED_BIAS + shSample(shadowMap, shadowMapPos.xy - o.zy).r) ? 1.0 : 0.0; // top right - return c / 4.0; -} - - - -float pssmDepthShadow ( - - - float4 lightSpacePos0, - float2 invShadowmapSize0, - shTexture2D shadowMap0, - - float4 lightSpacePos1, - float2 invShadowmapSize1, - shTexture2D shadowMap1, - - float4 lightSpacePos2, - float2 invShadowmapSize2, - shTexture2D shadowMap2, - - float depth, - float3 pssmSplitPoints) - -{ - float shadow; - - float pcf1 = depthShadowPCF(shadowMap0, lightSpacePos0, invShadowmapSize0); - float pcf2 = depthShadowPCF(shadowMap1, lightSpacePos1, invShadowmapSize1); - float pcf3 = depthShadowPCF(shadowMap2, lightSpacePos2, invShadowmapSize2); - - if (depth < pssmSplitPoints.x) - shadow = pcf1; - else if (depth < pssmSplitPoints.y) - shadow = pcf2; - else - shadow = pcf3; - - return shadow; -} diff --git a/files/materials/sky.mat b/files/materials/sky.mat deleted file mode 100644 index c2e8ddeb09..0000000000 --- a/files/materials/sky.mat +++ /dev/null @@ -1,140 +0,0 @@ -material QueryTotalPixels -{ - allow_fixed_function false - pass - { - vertex_program sun_vertex - fragment_program sun_fragment - cull_hardware none - polygon_mode_overrideable off - depth_check off - depth_write off - colour_write off - } -} - -material QueryVisiblePixels -{ - allow_fixed_function false - pass - { - vertex_program sun_vertex - fragment_program sun_fragment - cull_hardware none - cull_software none - polygon_mode_overrideable off - depth_check on - depth_write off - colour_write off - } -} - -material openmw_moon -{ - allow_fixed_function false - pass - { - vertex_program moon_vertex - fragment_program moon_fragment - cull_hardware none - - polygon_mode_overrideable off - depth_write off - depth_check off - scene_blend alpha_blend - - texture_unit diffuseMap - { - texture_alias $texture - } - - texture_unit alphaMap - { - texture_alias $alphatexture - } - } -} - -material openmw_clouds -{ - allow_fixed_function false - pass - { - vertex_program clouds_vertex - fragment_program clouds_fragment - - polygon_mode_overrideable off - - scene_blend alpha_blend - depth_write off - - // second diffuse map is used for weather transitions - texture_unit diffuseMap1 - { - texture_alias cloud_texture_1 - } - - texture_unit diffuseMap2 - { - texture_alias cloud_texture_2 - } - } -} - -material openmw_atmosphere -{ - allow_fixed_function false - pass - { - vertex_program atmosphere_vertex - fragment_program atmosphere_fragment - - polygon_mode_overrideable off - - depth_write off - } -} - -material openmw_stars -{ - allow_fixed_function false - pass - { - vertex_program stars_vertex - fragment_program stars_fragment - - polygon_mode_overrideable off - - depth_check off - depth_write off - scene_blend alpha_blend - - texture_unit diffuseMap - { - direct_texture $texture - } - } -} - -// used for both sun and sun glare -material openmw_sun -{ - allow_fixed_function false - pass - { - vertex_program sun_vertex - fragment_program sun_fragment - cull_hardware none - - polygon_mode_overrideable off - - depth_check off - depth_write off - scene_blend alpha_blend - - texture_unit diffuseMap - { - direct_texture $texture - } - } -} diff --git a/files/materials/stars.shader b/files/materials/stars.shader deleted file mode 100644 index 830be862a4..0000000000 --- a/files/materials/stars.shader +++ /dev/null @@ -1,48 +0,0 @@ -#include "core.h" - -#ifdef SH_VERTEX_SHADER - - SH_BEGIN_PROGRAM - shUniform(float4x4, worldview) @shAutoConstant(worldview, worldview_matrix) - shUniform(float4x4, proj) @shAutoConstant(proj, projection_matrix) - - shVertexInput(float2, uv0) - shOutput(float2, UV) - shOutput(float, fade) - - SH_START_PROGRAM - { - float4x4 worldviewFixed = worldview; -#if !SH_GLSL && !SH_GLSLES - worldviewFixed[0][3] = 0.0; - worldviewFixed[1][3] = 0.0; - worldviewFixed[2][3] = 0.0; -#else - worldviewFixed[3][0] = 0.0; - worldviewFixed[3][1] = 0.0; - worldviewFixed[3][2] = 0.0; -#endif - - shOutputPosition = shMatrixMult(proj, shMatrixMult(worldviewFixed, shInputPosition)); - UV = uv0; - - fade = (shInputPosition.z > 50.0) ? 1.0 : 0.0; - } - -#else - - SH_BEGIN_PROGRAM - - shInput(float2, UV) - shInput(float, fade) - - shSampler2D(diffuseMap) - shUniform(float, nightFade) @shSharedParameter(nightFade) - - - SH_START_PROGRAM - { - shOutputColour(0) = shSample(diffuseMap, UV) * float4(1,1,1, nightFade * fade); - } - -#endif diff --git a/files/materials/stars.shaderset b/files/materials/stars.shaderset deleted file mode 100644 index 0f8803450b..0000000000 --- a/files/materials/stars.shaderset +++ /dev/null @@ -1,15 +0,0 @@ -shader_set stars_vertex -{ - source stars.shader - type vertex - profiles_cg vs_2_0 arbvp1 - profiles_hlsl vs_2_0 -} - -shader_set stars_fragment -{ - source stars.shader - type fragment - profiles_cg ps_2_x ps_2_0 ps arbfp1 - profiles_hlsl ps_2_0 -} diff --git a/files/materials/sun.shader b/files/materials/sun.shader deleted file mode 100644 index 72e49d1a71..0000000000 --- a/files/materials/sun.shader +++ /dev/null @@ -1,41 +0,0 @@ -#include "core.h" - -#ifdef SH_VERTEX_SHADER - - SH_BEGIN_PROGRAM - shUniform(float4x4, world) @shAutoConstant(world, world_matrix) - shUniform(float4x4, view) @shAutoConstant(view, view_matrix) -shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix) - shVertexInput(float2, uv0) - shOutput(float2, UV) - - SH_START_PROGRAM - { - float4x4 viewFixed = view; -#if !SH_GLSL && !SH_GLSLES - viewFixed[0][3] = 0.0; - viewFixed[1][3] = 0.0; - viewFixed[2][3] = 0.0; -#else - viewFixed[3][0] = 0.0; - viewFixed[3][1] = 0.0; - viewFixed[3][2] = 0.0; -#endif - shOutputPosition = shMatrixMult(projection, shMatrixMult(viewFixed, shMatrixMult(world, shInputPosition))); - UV = uv0; - } - -#else - - SH_BEGIN_PROGRAM - shSampler2D(diffuseMap) - shInput(float2, UV) - shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) - //shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) - - SH_START_PROGRAM - { - shOutputColour(0) = float4(1,1,1,materialDiffuse.a) * shSample(diffuseMap, UV); - } - -#endif diff --git a/files/materials/sun.shaderset b/files/materials/sun.shaderset deleted file mode 100644 index 1b9e92a439..0000000000 --- a/files/materials/sun.shaderset +++ /dev/null @@ -1,15 +0,0 @@ -shader_set sun_vertex -{ - source sun.shader - type vertex - profiles_cg vs_2_0 arbvp1 - profiles_hlsl vs_2_0 -} - -shader_set sun_fragment -{ - source sun.shader - type fragment - profiles_cg ps_2_x ps_2_0 ps arbfp1 - profiles_hlsl ps_2_0 -} diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader deleted file mode 100644 index f20fce5063..0000000000 --- a/files/materials/terrain.shader +++ /dev/null @@ -1,507 +0,0 @@ -/* - * Copyright (c) 2015 scrawl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "core.h" - -#define IS_FIRST_PASS (@shPropertyString(pass_index) == 0) - -#define FOG (@shGlobalSettingBool(fog) && !@shPropertyBool(render_composite_map)) - -#define SHADOWS_PSSM @shGlobalSettingBool(shadows_pssm) -#define SHADOWS @shGlobalSettingBool(shadows) - -#if SHADOWS || SHADOWS_PSSM -#include "shadows.h" -#endif - -#define NUM_LAYERS @shPropertyString(num_layers) - -#if FOG || SHADOWS_PSSM -#define NEED_DEPTH 1 -#endif - -#define UNDERWATER @shGlobalSettingBool(render_refraction) - -#define VIEWPROJ_FIX @shGlobalSettingBool(viewproj_fix) - -#define RENDERCMP @shPropertyBool(render_composite_map) - -#define LIGHTING !RENDERCMP - -#define COMPOSITE_MAP @shPropertyBool(display_composite_map) - -#define NORMAL_MAP @shPropertyBool(normal_map_enabled) -#define PARALLAX @shPropertyBool(parallax_enabled) - -#define VERTEX_LIGHTING (!NORMAL_MAP) - -#define PARALLAX_SCALE 0.04 -#define PARALLAX_BIAS -0.02 - -// This is just for the permutation handler -#define NORMAL_MAPS @shPropertyString(normal_maps) - -#if NEED_DEPTH -@shAllocatePassthrough(1, depth) -#endif - -@shAllocatePassthrough(2, UV) - -@shAllocatePassthrough(3, worldPos) - -#if LIGHTING -@shAllocatePassthrough(3, normalPassthrough) -#if VERTEX_LIGHTING -@shAllocatePassthrough(3, lightResult) -@shAllocatePassthrough(3, directionalResult) -#else -@shAllocatePassthrough(3, colourPassthrough) -#endif - -#if SHADOWS -@shAllocatePassthrough(4, lightSpacePos0) -#endif -#if SHADOWS_PSSM -@shForeach(3) - @shAllocatePassthrough(4, lightSpacePos@shIterator) -@shEndForeach -#endif -#endif - -#ifdef SH_VERTEX_SHADER - - // ------------------------------------- VERTEX --------------------------------------- - - SH_BEGIN_PROGRAM - shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) - shUniform(float4x4, viewProjMatrix) @shAutoConstant(viewProjMatrix, viewproj_matrix) - -#if VIEWPROJ_FIX - shUniform(float4, vpRow2Fix) @shSharedParameter(vpRow2Fix, vpRow2Fix) -#endif - - shVertexInput(float2, uv0) - -#if LIGHTING - shNormalInput(float4) - shColourInput(float4) - -#if VERTEX_LIGHTING - shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_object_space_array, @shGlobalSettingString(num_lights)) - shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) - shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) - shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) -#endif - -#if SHADOWS - shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) -#endif - -#if SHADOWS_PSSM - @shForeach(3) - shUniform(float4x4, texViewProjMatrix@shIterator) @shAutoConstant(texViewProjMatrix@shIterator, texture_viewproj_matrix, @shIterator) - @shEndForeach -#endif - -#endif - - - @shPassthroughVertexOutputs - - SH_START_PROGRAM - { - float4 worldPos = shMatrixMult(worldMatrix, shInputPosition); - - shOutputPosition = shMatrixMult(viewProjMatrix, worldPos); - -#if NEED_DEPTH -#if VIEWPROJ_FIX - float4x4 vpFixed = viewProjMatrix; -#if !SH_GLSL && !SH_GLSLES - vpFixed[2] = vpRow2Fix; -#else - vpFixed[0][2] = vpRow2Fix.x; - vpFixed[1][2] = vpRow2Fix.y; - vpFixed[2][2] = vpRow2Fix.z; - vpFixed[3][2] = vpRow2Fix.w; -#endif - - float4x4 fixedWVP = shMatrixMult(vpFixed, worldMatrix); - - float depth = shMatrixMult(fixedWVP, shInputPosition).z; - @shPassthroughAssign(depth, depth); -#else - @shPassthroughAssign(depth, shOutputPosition.z); -#endif - -#endif - - @shPassthroughAssign(UV, uv0); - - @shPassthroughAssign(worldPos, worldPos.xyz); - -#if LIGHTING - @shPassthroughAssign(normalPassthrough, normal.xyz); -#endif -#if LIGHTING && !VERTEX_LIGHTING - @shPassthroughAssign(colourPassthrough, colour.xyz); -#endif - -#if LIGHTING - -#if SHADOWS - float4 lightSpacePos = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition)); - @shPassthroughAssign(lightSpacePos0, lightSpacePos); -#endif -#if SHADOWS_PSSM - float4 wPos = shMatrixMult(worldMatrix, shInputPosition); - - float4 lightSpacePos; - @shForeach(3) - lightSpacePos = shMatrixMult(texViewProjMatrix@shIterator, wPos); - @shPassthroughAssign(lightSpacePos@shIterator, lightSpacePos); - @shEndForeach -#endif - - -#if VERTEX_LIGHTING - // Lighting - float3 lightDir; - float d; - float3 lightResult = float3(0,0,0); - float3 directionalResult = float3(0,0,0); - @shForeach(@shGlobalSettingString(num_lights)) - lightDir = lightPosition[@shIterator].xyz - (shInputPosition.xyz * lightPosition[@shIterator].w); - d = length(lightDir); - lightDir = normalize(lightDir); - - - lightResult.xyz += lightDiffuse[@shIterator].xyz - * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(normal.xyz, lightDir), 0.0); - -#if @shIterator == 0 - directionalResult = lightResult.xyz; -#endif - @shEndForeach - lightResult.xyz += lightAmbient.xyz; - lightResult.xyz *= colour.xyz; - directionalResult.xyz *= colour.xyz; - - @shPassthroughAssign(lightResult, lightResult); - @shPassthroughAssign(directionalResult, directionalResult); -#endif - -#endif - } - -#else - - // ----------------------------------- FRAGMENT ------------------------------------------ - -#if UNDERWATER - #include "underwater.h" -#endif -#if NORMAL_MAP && SH_GLSLES - mat3 transpose(mat3 m); -#endif - - SH_BEGIN_PROGRAM - - -#if COMPOSITE_MAP - shSampler2D(compositeMap) -#else - -@shForeach(@shPropertyString(num_blendmaps)) - shSampler2D(blendMap@shIterator) -@shEndForeach - -@shForeach(@shPropertyString(num_layers)) - shSampler2D(diffuseMap@shIterator) -#if @shPropertyBool(use_normal_map_@shIterator) - shSampler2D(normalMap@shIterator) -#endif -@shEndForeach - -#endif - -#if FOG - shUniform(float3, fogColour) @shAutoConstant(fogColour, fog_colour) - shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params) -#endif - - @shPassthroughFragmentInputs - -#if LIGHTING - -#if !VERTEX_LIGHTING -shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_array, @shGlobalSettingString(num_lights)) -shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) -shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) -shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) -shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix) -#endif - -#if SHADOWS - shSampler2D(shadowMap0) - shUniform(float2, invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, @shPropertyString(shadowtexture_offset)) -#endif -#if SHADOWS_PSSM - @shForeach(3) - shSampler2D(shadowMap@shIterator) - shUniform(float2, invShadowmapSize@shIterator) @shAutoConstant(invShadowmapSize@shIterator, inverse_texture_size, @shIterator(@shPropertyString(shadowtexture_offset))) - @shEndForeach - shUniform(float3, pssmSplitPoints) @shSharedParameter(pssmSplitPoints) -#endif - -#if SHADOWS || SHADOWS_PSSM - shUniform(float4, shadowFar_fadeStart) @shSharedParameter(shadowFar_fadeStart) -#endif -#endif - -#if (UNDERWATER) || (FOG) - shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) -#endif - -#if UNDERWATER - shUniform(float, waterLevel) @shSharedParameter(waterLevel) -#endif - - -// For specular -#if LIGHTING - shUniform(float3, lightSpec0) @shAutoConstant(lightSpec0, light_specular_colour, 0) - shUniform(float3, lightPos0) @shAutoConstant(lightPos0, light_position, 0) -#endif - -shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) - - SH_START_PROGRAM - { - -#if NEED_DEPTH - float depth = @shPassthroughReceive(depth); -#endif - - float2 UV = @shPassthroughReceive(UV); - - float3 worldPos = @shPassthroughReceive(worldPos); - -#if LIGHTING - float3 normal = @shPassthroughReceive(normalPassthrough); -#endif - -#if LIGHTING && !VERTEX_LIGHTING - -#if NORMAL_MAP - // derive the tangent space basis - float3 tangent = float3(1,0, 0); - - float3 binormal = normalize(cross(tangent, normal)); - tangent = normalize(cross(normal, binormal)); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal - - // derive final matrix - float3x3 tbn = float3x3(tangent, binormal, normal); - #if SH_GLSL || SH_GLSLES - tbn = transpose(tbn); - #endif -#endif - -#endif - -#if UNDERWATER - float3 waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,0,1), waterLevel); -#endif - -#if !IS_FIRST_PASS -// Opacity the previous passes should have, i.e. 1 - (opacity of this pass) -float previousAlpha = 1.0; -#endif - - -shOutputColour(0) = float4(1,1,1,1); - -float3 TSnormal = float3(0,0,1); - -#if COMPOSITE_MAP - shOutputColour(0).xyz = shSample(compositeMap, UV).xyz; -#else - - // Layer calculations -// rescale UV to directly map edge vertices to texel centers - this is -// important to get correct blending at cell transitions -// TODO: parameterize texel size -float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; -@shForeach(@shPropertyString(num_blendmaps)) - float4 blendValues@shIterator = shSaturate(shSample(blendMap@shIterator, blendUV)); -@shEndForeach - - - float4 albedo = float4(0,0,0,1); - - float2 layerUV = float2(UV.x, 1.0-UV.y) * 16.0; // Reverse Y, required to get proper tangents - float2 thisLayerUV; - float4 normalTex; - float4 diffuseTex; - - float3 eyeDir = normalize(cameraPos.xyz - worldPos); -#if PARALLAX - float3 TSeyeDir = normalize(shMatrixMult(tbn, eyeDir)); -#endif - -@shForeach(@shPropertyString(num_layers)) - thisLayerUV = layerUV; -#if @shPropertyBool(use_normal_map_@shIterator) - normalTex = shSample(normalMap@shIterator, thisLayerUV); -#if @shIterator == 0 && IS_FIRST_PASS - TSnormal = normalize(normalTex.xyz * 2.0 - 1.0); -#else - TSnormal = shLerp(TSnormal, normalTex.xyz * 2.0 - 1.0, blendValues@shPropertyString(blendmap_component_@shIterator)); -#endif -#endif - -#if @shPropertyBool(use_parallax_@shIterator) - thisLayerUV += TSeyeDir.xy * ( normalTex.a * PARALLAX_SCALE + PARALLAX_BIAS ); -#endif - - diffuseTex = shSample(diffuseMap@shIterator, layerUV); -#if !@shPropertyBool(use_specular_@shIterator) - diffuseTex.a = 0.0; -#endif - -#if @shIterator == 0 -albedo = diffuseTex; -#else -albedo = shLerp(albedo, diffuseTex, blendValues@shPropertyString(blendmap_component_@shIterator)); -#endif - -#if !IS_FIRST_PASS - previousAlpha *= 1.0-blendValues@shPropertyString(blendmap_component_@shIterator); -#endif - - -@shEndForeach - - shOutputColour(0).rgb *= albedo.xyz; - -#endif - -#if LIGHTING - -#if VERTEX_LIGHTING - // Lighting - float3 lightResult = @shPassthroughReceive(lightResult); - float3 directionalResult = @shPassthroughReceive(directionalResult); -#else - -#if NORMAL_MAP - normal = normalize (shMatrixMult( transpose(tbn), TSnormal )); -#endif - - float3 colour = @shPassthroughReceive(colourPassthrough); - float3 lightDir; - float d; - float3 lightResult = float3(0,0,0); - @shForeach(@shGlobalSettingString(num_lights)) - lightDir = lightPosition[@shIterator].xyz - (worldPos * lightPosition[@shIterator].w); - d = length(lightDir); - lightDir = normalize(lightDir); - - lightResult.xyz += lightDiffuse[@shIterator].xyz - * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) - * max(dot(normal.xyz, lightDir), 0.0); -#if @shIterator == 0 - float3 directionalResult = lightResult.xyz; -#endif - @shEndForeach - lightResult.xyz += lightAmbient.xyz; - lightResult.xyz *= colour.xyz; - directionalResult.xyz *= colour.xyz; -#endif - - // shadows only for the first (directional) light -#if SHADOWS - float4 lightSpacePos0 = @shPassthroughReceive(lightSpacePos0); - float shadow = depthShadowPCF (shadowMap0, lightSpacePos0, invShadowmapSize0); -#endif -#if SHADOWS_PSSM - @shForeach(3) - float4 lightSpacePos@shIterator = @shPassthroughReceive(lightSpacePos@shIterator); - @shEndForeach - - float shadow = pssmDepthShadow (lightSpacePos0, invShadowmapSize0, shadowMap0, lightSpacePos1, invShadowmapSize1, shadowMap1, lightSpacePos2, invShadowmapSize2, shadowMap2, depth, pssmSplitPoints); -#endif - -#if SHADOWS || SHADOWS_PSSM - float fadeRange = shadowFar_fadeStart.x - shadowFar_fadeStart.y; - float fade = 1-((depth - shadowFar_fadeStart.y) / fadeRange); - shadow = (depth > shadowFar_fadeStart.x) ? 1.0 : ((depth > shadowFar_fadeStart.y) ? 1.0-((1.0-shadow)*fade) : shadow); -#endif - -#if !SHADOWS && !SHADOWS_PSSM - float shadow = 1.0; -#endif - - shOutputColour(0).xyz *= (lightResult - directionalResult * (1.0-shadow)); -#endif - -#if LIGHTING && !COMPOSITE_MAP - // Specular - float3 light0Dir = normalize(lightPos0.xyz); - - float NdotL = max(dot(normal, light0Dir), 0.0); - float3 halfVec = normalize (light0Dir + eyeDir); - - float3 specular = pow(max(dot(normal, halfVec), 0.0), 32.0) * lightSpec0; - shOutputColour(0).xyz += specular * (albedo.a) * shadow; -#endif - -#if FOG - float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); - - #if UNDERWATER - shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, UNDERWATER_COLOUR, shSaturate(length(waterEyePos-worldPos) / VISIBILITY)); - #else - shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColour, fogValue); - #endif -#endif - - // prevent negative colour output (for example with negative lights) - shOutputColour(0).xyz = max(shOutputColour(0).xyz, float3(0,0,0)); - -#if IS_FIRST_PASS - shOutputColour(0).a = 1.0; -#else - shOutputColour(0).a = 1.0-previousAlpha; -#endif - } -#if NORMAL_MAP && SH_GLSLES - mat3 transpose(mat3 m){ - return mat3( - m[0][0],m[1][0],m[2][0], - m[0][1],m[1][1],m[2][1], - m[0][2],m[1][2],m[2][2] - ); - } -#endif -#endif diff --git a/files/materials/terrain.shaderset b/files/materials/terrain.shaderset deleted file mode 100644 index a72f2358fd..0000000000 --- a/files/materials/terrain.shaderset +++ /dev/null @@ -1,15 +0,0 @@ -shader_set terrain_vertex -{ - source terrain.shader - type vertex - profiles_cg vs_2_0 vp40 arbvp1 - profiles_hlsl vs_3_0 vs_2_0 -} - -shader_set terrain_fragment -{ - source terrain.shader - type fragment - profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1 - profiles_hlsl ps_3_0 ps_2_0 -} diff --git a/files/materials/underwater.h b/files/materials/underwater.h deleted file mode 100644 index 2f38f65461..0000000000 --- a/files/materials/underwater.h +++ /dev/null @@ -1,121 +0,0 @@ -#define UNDERWATER_COLOUR float3(0.090195, 0.115685, 0.12745) - -#define VISIBILITY 1000.0 // how far you can look through water - -#define BIG_WAVES_X 0.3 // strength of big waves -#define BIG_WAVES_Y 0.3 - -#define MID_WAVES_X 0.3 // strength of middle sized waves -#define MID_WAVES_Y 0.15 - -#define SMALL_WAVES_X 0.15 // strength of small waves -#define SMALL_WAVES_Y 0.1 - -#define WAVE_CHOPPYNESS 0.15 // wave choppyness -#define WAVE_SCALE 0.01 // overall wave scale - -#define ABBERATION 0.001 // chromatic abberation amount - -#define SUN_EXT float3(0.45, 0.55, 0.68) //sunlight extinction - -float3 intercept(float3 lineP, - float3 lineN, - float3 planeN, - float planeD) -{ - - float distance = (planeD - dot(planeN, lineP)) / dot(lineN, planeN); - return lineP + lineN * distance; -} - -float3 perturb1(shTexture2D tex, float2 coords, float bend, float2 windDir, float windSpeed, float timer) -{ - float2 nCoord = float2(0,0); - bend *= WAVE_CHOPPYNESS; - nCoord = coords * (WAVE_SCALE * 0.05) + windDir * timer * (windSpeed*0.04); - float3 normal0 = 2.0 * shSample(tex, nCoord + float2(-timer*0.015,-timer*0.05)).rgb - 1.0; - nCoord = coords * (WAVE_SCALE * 0.1) + windDir * timer * (windSpeed*0.08)-normal0.xy*bend; - float3 normal1 = 2.0 * shSample(tex, nCoord + float2(+timer*0.020,+timer*0.015)).rgb - 1.0; - - nCoord = coords * (WAVE_SCALE * 0.25) + windDir * timer * (windSpeed*0.07)-normal1.xy*bend; - float3 normal2 = 2.0 * shSample(tex, nCoord + float2(-timer*0.04,-timer*0.03)).rgb - 1.0; - nCoord = coords * (WAVE_SCALE * 0.5) + windDir * timer * (windSpeed*0.09)-normal2.xy*bend; - float3 normal3 = 2.0 * shSample(tex, nCoord + float2(+timer*0.03,+timer*0.04)).rgb - 1.0; - - nCoord = coords * (WAVE_SCALE* 1.0) + windDir * timer * (windSpeed*0.4)-normal3.xy*bend; - float3 normal4 = 2.0 * shSample(tex, nCoord + float2(-timer*0.2,+timer*0.1)).rgb - 1.0; - nCoord = coords * (WAVE_SCALE * 2.0) + windDir * timer * (windSpeed*0.7)-normal4.xy*bend; - float3 normal5 = 2.0 * shSample(tex, nCoord + float2(+timer*0.1,-timer*0.06)).rgb - 1.0; - - - float3 normal = normalize(normal0 * BIG_WAVES_X + normal1 * BIG_WAVES_Y + - normal2 * MID_WAVES_X + normal3 * MID_WAVES_Y + - normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y); - return normal; -} - -float3 perturb(shTexture2D tex, float2 coords, float bend, float2 windDir, float windSpeed, float timer) -{ - bend *= WAVE_CHOPPYNESS; - float3 col = float3(0,0,0); - float2 nCoord = float2(0,0); //normal coords - - nCoord = coords * (WAVE_SCALE * 0.025) + windDir * timer * (windSpeed*0.03); - col += shSample(tex,nCoord + float2(-timer*0.005,-timer*0.01)).rgb*0.20; - nCoord = coords * (WAVE_SCALE * 0.1) + windDir * timer * (windSpeed*0.05)-(col.xy/col.zz)*bend; - col += shSample(tex,nCoord + float2(+timer*0.01,+timer*0.005)).rgb*0.20; - - nCoord = coords * (WAVE_SCALE * 0.2) + windDir * timer * (windSpeed*0.1)-(col.xy/col.zz)*bend; - col += shSample(tex,nCoord + float2(-timer*0.02,-timer*0.03)).rgb*0.20; - nCoord = coords * (WAVE_SCALE * 0.5) + windDir * timer * (windSpeed*0.2)-(col.xy/col.zz)*bend; - col += shSample(tex,nCoord + float2(+timer*0.03,+timer*0.02)).rgb*0.15; - - nCoord = coords * (WAVE_SCALE* 0.8) + windDir * timer * (windSpeed*1.0)-(col.xy/col.zz)*bend; - col += shSample(tex, nCoord + float2(-timer*0.06,+timer*0.08)).rgb*0.15; - nCoord = coords * (WAVE_SCALE * 1.0) + windDir * timer * (windSpeed*1.3)-(col.xy/col.zz)*bend; - col += shSample(tex,nCoord + float2(+timer*0.08,-timer*0.06)).rgb*0.10; - - return col; -} - - -float3 getCaustics (shTexture2D causticMap, float3 worldPos, float3 waterEyePos, float3 worldNormal, float3 lightDirectionWS0, float waterLevel, float waterTimer, float3 windDir_windSpeed) -{ - float waterDepth = shSaturate((waterEyePos.z - worldPos.z) / 50.0); - - float3 causticPos = intercept(worldPos.xyz, lightDirectionWS0.xyz, float3(0,0,1), waterLevel); - - ///\ todo clean this up - float causticdepth = length(causticPos-worldPos.xyz); - causticdepth = 1.0-shSaturate(causticdepth / VISIBILITY); - causticdepth = shSaturate(causticdepth); - - // NOTE: the original shader calculated a tangent space basis here, - // but using only the world normal is cheaper and i couldn't see a visual difference - // also, if this effect gets moved to screen-space some day, it's unlikely to have tangent information - float3 causticNorm = worldNormal.xyz * perturb(causticMap, causticPos.xy, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).xyz * 2.0 - 1.0; - causticNorm = float3(causticNorm.x, causticNorm.y, -causticNorm.z); - - //float fresnel = pow(clamp(dot(LV,causticnorm),0.0,1.0),2.0); - - float NdotL = max(dot(worldNormal.xyz, lightDirectionWS0.xyz),0.0); - - float causticR = 1.0-perturb(causticMap, causticPos.xy, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; - - /// \todo sunFade - - // float3 caustics = clamp(pow(float3(causticR)*5.5,float3(5.5*causticdepth)),0.0,1.0)*NdotL*sunFade*causticdepth; - float3 caustics = clamp(pow(float3(causticR,causticR,causticR)*5.5,float3(5.5*causticdepth,5.5*causticdepth,5.5*causticdepth)),0.0,1.0)*NdotL*causticdepth; - float causticG = 1.0-perturb(causticMap,causticPos.xy+(1.0-causticdepth)*ABBERATION, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; - float causticB = 1.0-perturb(causticMap,causticPos.xy+(1.0-causticdepth)*ABBERATION*2.0, causticdepth, windDir_windSpeed.xy, windDir_windSpeed.z, waterTimer).z; - //caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth)))*NdotL*sunFade*causticdepth; - caustics = shSaturate(pow(float3(causticR,causticG,causticB)*5.5,float3(5.5*causticdepth,5.5*causticdepth,5.5*causticdepth)))*NdotL*causticdepth; - - caustics *= 3.0; - - // shore transition - caustics = shLerp (float3(1,1,1), caustics, waterDepth); - - return caustics; -} - diff --git a/files/materials/water.mat b/files/materials/water.mat deleted file mode 100644 index cf03be39e5..0000000000 --- a/files/materials/water.mat +++ /dev/null @@ -1,101 +0,0 @@ -material Water -{ - allow_fixed_function false - - pass - { - emissive 1.0 1.0 1.0 - ambient 0 0 0 - diffuse 0 0 0 1 - specular 0 0 0 32 - - vertex_program water_vertex - fragment_program water_fragment - - cull_hardware none - - scene_blend alpha_blend - depth_write off - - texture_unit reflectionMap - { - texture_alias WaterReflection - tex_address_mode clamp - } - - texture_unit refractionMap - { - direct_texture WaterRefraction - tex_address_mode clamp - } - - texture_unit depthMap - { - texture_alias SceneDepth - tex_address_mode clamp - } - - texture_unit normalMap - { - texture water_nm.png - } - - texture_unit rippleNormalMap - { - direct_texture RippleNormal - tex_address_mode border - tex_border_colour 0.5 0.5 1.0 - } - - // for simple_water - texture_unit animatedTexture - { - create_in_ffp true - scale 0.1 0.1 - alpha_op_ex source1 src_manual src_current 0.7 - } - - texture_unit shadowMap0 - { - content_type shadow - tex_address_mode clamp - filtering none - } - texture_unit shadowMap1 - { - content_type shadow - tex_address_mode clamp - filtering none - } - texture_unit shadowMap2 - { - content_type shadow - tex_address_mode clamp - filtering none - } - } -} - -material openmw/Ripple -{ - // this will be overridden by Water_RippleFrameCount fallback setting - anim_texture2 textures\water\ripple.dds 4 0.25 - pass - { - scene_blend alpha_blend - depth_write off - cull_hardware none - diffuse vertexcolour - emissive 1 1 1 - ambient 0 0 0 - texture_unit diffuseMap - { - create_in_ffp true - anim_texture2 $anim_texture2 - - // to make sure rotating doesn't cause the texture to repeat - tex_address_mode border - tex_border_colour 0 0 0 0 - } - } -} diff --git a/files/materials/water.shader b/files/materials/water.shader deleted file mode 100644 index eff245b5ed..0000000000 --- a/files/materials/water.shader +++ /dev/null @@ -1,354 +0,0 @@ -#include "core.h" - - -#define SIMPLE_WATER @shGlobalSettingBool(simple_water) - -#if SIMPLE_WATER - // --------------------------------------- SIMPLE WATER --------------------------------------------------- - -#define FOG @shGlobalSettingBool(fog) - -#ifdef SH_VERTEX_SHADER - - SH_BEGIN_PROGRAM - shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - shVertexInput(float2, uv0) - shOutput(float2, UV) - -#if FOG - shOutput(float, depth) -#endif - SH_START_PROGRAM - { - shOutputPosition = shMatrixMult(wvp, shInputPosition); - UV = uv0; -#if FOG - depth = shOutputPosition.z; -#endif - } - -#else - - SH_BEGIN_PROGRAM - shSampler2D(animatedTexture) - shInput(float2, UV) - shInput(float, depth) - - shUniform(float3, fogColor) @shAutoConstant(fogColor, fog_colour) - shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params) - - - SH_START_PROGRAM - { - shOutputColour(0).xyz = shSample(animatedTexture, UV * float2(15.0, 15.0)).xyz * float3(1.0, 1.0, 1.0); - shOutputColour(0).w = 0.7; - -#if FOG - float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); - shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); -#endif - } - -#endif - -#else - - - -// Inspired by Blender GLSL Water by martinsh ( http://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html ) - -#define SHADOWS_PSSM @shGlobalSettingBool(shadows_pssm) -#define SHADOWS @shGlobalSettingBool(shadows) - -#if SHADOWS || SHADOWS_PSSM - #include "shadows.h" -#endif - -#define REFRACTION @shGlobalSettingBool(refraction) - -#ifdef SH_VERTEX_SHADER - - SH_BEGIN_PROGRAM - shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - - shOutput(float3, screenCoordsPassthrough) - shOutput(float4, position) - shOutput(float, depthPassthrough) - - -#if SHADOWS - shOutput(float4, lightSpacePos0) - shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) -#endif - -#if SHADOWS_PSSM - @shForeach(3) - shOutput(float4, lightSpacePos@shIterator) - shUniform(float4x4, texViewProjMatrix@shIterator) @shAutoConstant(texViewProjMatrix@shIterator, texture_viewproj_matrix, @shIterator) - @shEndForeach -#endif - -#if SHADOWS || SHADOWS_PSSM - shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) -#endif - - SH_START_PROGRAM - { - shOutputPosition = shMatrixMult(wvp, shInputPosition); - - - #if !SH_GLSL && !SH_GLSLES - float4x4 scalemat = float4x4( 0.5, 0.0, 0.0, 0.5, - 0.0, -0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 ); - #else - mat4 scalemat = mat4(0.5, 0.0, 0.0, 0.0, - 0.0, -0.5, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.0, - 0.5, 0.5, 0.5, 1.0); - #endif - - float4 texcoordProj = shMatrixMult(scalemat, shOutputPosition); - screenCoordsPassthrough = float3(texcoordProj.x, texcoordProj.y, texcoordProj.w); - - position = shInputPosition; - - depthPassthrough = shOutputPosition.z; - - -#if SHADOWS - lightSpacePos0 = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition)); -#endif -#if SHADOWS_PSSM - float4 wPos = shMatrixMult(worldMatrix, shInputPosition); - @shForeach(3) - lightSpacePos@shIterator = shMatrixMult(texViewProjMatrix@shIterator, wPos); - @shEndForeach -#endif - } - -#else - - // tweakables ---------------------------------------------------- - - #define VISIBILITY 1500.0 // how far you can look through water - - #define BIG_WAVES_X 0.1 // strength of big waves - #define BIG_WAVES_Y 0.1 - - #define MID_WAVES_X 0.1 // strength of middle sized waves - #define MID_WAVES_Y 0.1 - - #define SMALL_WAVES_X 0.1 // strength of small waves - #define SMALL_WAVES_Y 0.1 - - #define WAVE_CHOPPYNESS 0.05 // wave choppyness - #define WAVE_SCALE 75.0 // overall wave scale - - #define BUMP 0.5 // overall water surface bumpiness - #define REFL_BUMP 0.15 // reflection distortion amount - #define REFR_BUMP 0.06 // refraction distortion amount - - #define SCATTER_AMOUNT 0.3 // amount of sunlight scattering - #define SCATTER_COLOUR float3(0.0,1.0,0.95) // colour of sunlight scattering - - #define SUN_EXT float3(0.45, 0.55, 0.68) //sunlight extinction - - #define SPEC_HARDNESS 256.0 // specular highlights hardness - - // --------------------------------------------------------------- - - - - float fresnel_dielectric(float3 Incoming, float3 Normal, float eta) - { - /* compute fresnel reflectance without explicitly computing - the refracted direction */ - float c = abs(dot(Incoming, Normal)); - float g = eta * eta - 1.0 + c * c; - float result; - - if(g > 0.0) { - g = sqrt(g); - float A =(g - c)/(g + c); - float B =(c *(g + c)- 1.0)/(c *(g - c)+ 1.0); - result = 0.5 * A * A *(1.0 + B * B); - } - else - result = 1.0; /* TIR (no refracted component) */ - - return result; - } - - SH_BEGIN_PROGRAM - shInput(float3, screenCoordsPassthrough) - shInput(float4, position) - shInput(float, depthPassthrough) - - shUniform(float, far) @shAutoConstant(far, far_clip_distance) - - shSampler2D(reflectionMap) -#if REFRACTION - shSampler2D(refractionMap) -#endif - shSampler2D(depthMap) - shSampler2D(normalMap) - - shUniform(float4x4, wMat) @shAutoConstant(wMat, world_matrix) - shUniform(float3, windDir_windSpeed) @shSharedParameter(windDir_windSpeed) - #define WIND_SPEED windDir_windSpeed.z - #define WIND_DIR windDir_windSpeed.xy - - shUniform(float, waterTimer) @shSharedParameter(waterTimer) - shUniform(float2, waterSunFade_sunHeight) @shSharedParameter(waterSunFade_sunHeight) - - shUniform(float4, sunPosition) @shAutoConstant(sunPosition, light_position, 0) - shUniform(float4, sunSpecular) @shAutoConstant(sunSpecular, light_specular_colour, 0) - - shUniform(float, renderTargetFlipping) @shAutoConstant(renderTargetFlipping, render_target_flipping) - - - shUniform(float3, fogColor) @shAutoConstant(fogColor, fog_colour) - shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params) - - shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position_object_space) - - -#if SHADOWS - shInput(float4, lightSpacePos0) - shSampler2D(shadowMap0) - shUniform(float2, invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, 1) -#endif -#if SHADOWS_PSSM - @shForeach(3) - shInput(float4, lightSpacePos@shIterator) - shSampler2D(shadowMap@shIterator) - shUniform(float2, invShadowmapSize@shIterator) @shAutoConstant(invShadowmapSize@shIterator, inverse_texture_size, @shIterator(1)) - @shEndForeach - shUniform(float3, pssmSplitPoints) @shSharedParameter(pssmSplitPoints) -#endif - -#if SHADOWS || SHADOWS_PSSM - shUniform(float4, shadowFar_fadeStart) @shSharedParameter(shadowFar_fadeStart) -#endif - - - SH_START_PROGRAM - { - float3 worldPos = shMatrixMult (wMat, position).xyz; - float2 UV = worldPos.xy / (8192.0*5.0) * 3.0; - UV.y *= -1.0; - -#if SHADOWS - float shadow = depthShadowPCF (shadowMap0, lightSpacePos0, invShadowmapSize0); -#endif -#if SHADOWS_PSSM - float shadow = pssmDepthShadow (lightSpacePos0, invShadowmapSize0, shadowMap0, lightSpacePos1, invShadowmapSize1, shadowMap1, lightSpacePos2, invShadowmapSize2, shadowMap2, depthPassthrough, pssmSplitPoints); -#endif - -#if SHADOWS || SHADOWS_PSSM - float fadeRange = shadowFar_fadeStart.x - shadowFar_fadeStart.y; - float fade = 1.0-((depthPassthrough - shadowFar_fadeStart.y) / fadeRange); - shadow = (depthPassthrough > shadowFar_fadeStart.x) ? 1.0 : ((depthPassthrough > shadowFar_fadeStart.y) ? 1.0-((1.0-shadow)*fade) : shadow); -#endif - -#if !SHADOWS && !SHADOWS_PSSM - float shadow = 1.0; -#endif - - - float2 screenCoords = screenCoordsPassthrough.xy / screenCoordsPassthrough.z; - screenCoords.y = (1.0-shSaturate(renderTargetFlipping))+renderTargetFlipping*screenCoords.y; - - float2 nCoord = float2(0.0,0.0); - - nCoord = UV * (WAVE_SCALE * 0.05) + WIND_DIR * waterTimer * (WIND_SPEED*0.04); - float3 normal0 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.015,-waterTimer*0.005)).rgb - 1.0; - nCoord = UV * (WAVE_SCALE * 0.1) + WIND_DIR * waterTimer * (WIND_SPEED*0.08)-(normal0.xy/normal0.zz)*WAVE_CHOPPYNESS; - float3 normal1 = 2.0 * shSample(normalMap, nCoord + float2(+waterTimer*0.020,+waterTimer*0.015)).rgb - 1.0; - - nCoord = UV * (WAVE_SCALE * 0.25) + WIND_DIR * waterTimer * (WIND_SPEED*0.07)-(normal1.xy/normal1.zz)*WAVE_CHOPPYNESS; - float3 normal2 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.04,-waterTimer*0.03)).rgb - 1.0; - nCoord = UV * (WAVE_SCALE * 0.5) + WIND_DIR * waterTimer * (WIND_SPEED*0.09)-(normal2.xy/normal2.z)*WAVE_CHOPPYNESS; - float3 normal3 = 2.0 * shSample(normalMap, nCoord + float2(+waterTimer*0.03,+waterTimer*0.04)).rgb - 1.0; - - nCoord = UV * (WAVE_SCALE* 1.0) + WIND_DIR * waterTimer * (WIND_SPEED*0.4)-(normal3.xy/normal3.zz)*WAVE_CHOPPYNESS; - float3 normal4 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.02,+waterTimer*0.1)).rgb - 1.0; - nCoord = UV * (WAVE_SCALE * 2.0) + WIND_DIR * waterTimer * (WIND_SPEED*0.7)-(normal4.xy/normal4.zz)*WAVE_CHOPPYNESS; - float3 normal5 = 2.0 * shSample(normalMap, nCoord + float2(+waterTimer*0.1,-waterTimer*0.06)).rgb - 1.0; - - - - float3 normal = (normal0 * BIG_WAVES_X + normal1 * BIG_WAVES_Y + - normal2 * MID_WAVES_X + normal3 * MID_WAVES_Y + - normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y); - - normal = normalize(float3(normal.x * BUMP, normal.y * BUMP, normal.z)); - normal = float3(normal.x, normal.y, -normal.z); - - // normal for sunlight scattering - float3 lNormal = (normal0 * BIG_WAVES_X*0.5 + normal1 * BIG_WAVES_Y*0.5 + - normal2 * MID_WAVES_X*0.2 + normal3 * MID_WAVES_Y*0.2 + - normal4 * SMALL_WAVES_X*0.1 + normal5 * SMALL_WAVES_Y*0.1).xyz; - lNormal = normalize(float3(lNormal.x * BUMP, lNormal.y * BUMP, lNormal.z)); - lNormal = float3(lNormal.x, lNormal.y, -lNormal.z); - - - float3 lVec = normalize(sunPosition.xyz); - float3 vVec = normalize(position.xyz - cameraPos.xyz); - - - float isUnderwater = (cameraPos.z > 0.0) ? 0.0 : 1.0; - - // sunlight scattering - float3 pNormal = float3(0,0,1); - float3 lR = reflect(lVec, lNormal); - float3 llR = reflect(lVec, pNormal); - - float s = shSaturate(dot(lR, vVec)*2.0-1.2); - float lightScatter = shadow * shSaturate(dot(-lVec,lNormal)*0.7+0.3) * s * SCATTER_AMOUNT * waterSunFade_sunHeight.x * shSaturate(1.0-exp(-waterSunFade_sunHeight.y)); - float3 scatterColour = shLerp(float3(SCATTER_COLOUR)*float3(1.0,0.4,0.0), SCATTER_COLOUR, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); - - // fresnel - float ior = (cameraPos.z>0.0)?(1.333/1.0):(1.0/1.333); //air to water; water to air - float fresnel = fresnel_dielectric(-vVec, normal, ior); - - fresnel = shSaturate(fresnel); - - // reflection - float3 reflection = shSample(reflectionMap, screenCoords+(normal.xy*REFL_BUMP)).rgb; - - // refraction - float3 R = reflect(vVec, normal); - -#if REFRACTION - float3 refraction = shSample(refractionMap, (screenCoords-(normal.xy*REFR_BUMP))*1.0).rgb; - - // brighten up the refraction underwater - refraction = (cameraPos.z < 0.0) ? shSaturate(refraction * 1.5) : refraction; -#endif - - // specular - float specular = pow(max(dot(R, lVec), 0.0),SPEC_HARDNESS) * shadow; - -#if REFRACTION - shOutputColour(0).xyz = shLerp( shLerp(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpecular.xyz; -#else - shOutputColour(0).xyz = shLerp(reflection, float3(0.090195, 0.115685, 0.12745), (1.0-fresnel)*0.5) + specular * sunSpecular.xyz; -#endif - // fog - float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); - shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); - -#if REFRACTION - shOutputColour(0).w = 1.0; -#else - shOutputColour(0).w = shSaturate(fresnel*2.0 + specular); -#endif - } - -#endif - - -#endif diff --git a/files/materials/water.shaderset b/files/materials/water.shaderset deleted file mode 100644 index 5e070a45a9..0000000000 --- a/files/materials/water.shaderset +++ /dev/null @@ -1,15 +0,0 @@ -shader_set water_vertex -{ - source water.shader - type vertex - profiles_cg vs_2_0 vp40 arbvp1 - profiles_hlsl vs_3_0 vs_2_0 -} - -shader_set water_fragment -{ - source water.shader - type fragment - profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1 - profiles_hlsl ps_3_0 ps_2_0 -} diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 13d1a9e1a4..7494d3ba2d 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -1,11 +1,8 @@ - # Copy resource files into the build directory set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) set(DDIR ${OpenMW_BINARY_DIR}/resources/mygui) set(MYGUI_FILES - black.png - white.png core.skin core.xml core_layouteditor.xml @@ -83,6 +80,7 @@ set(MYGUI_FILES openmw_savegame_dialog.layout openmw_recharge_dialog.layout openmw_screen_fader.layout + openmw_screen_fader_hit.layout openmw_edit_note.layout openmw_debug_window.layout openmw_debug_window.skin.xml diff --git a/files/mygui/black.png b/files/mygui/black.png deleted file mode 100644 index 69db2911a7..0000000000 Binary files a/files/mygui/black.png and /dev/null differ diff --git a/files/mygui/openmw_book.layout b/files/mygui/openmw_book.layout index 2336d5b2ca..7c158af8dc 100644 --- a/files/mygui/openmw_book.layout +++ b/files/mygui/openmw_book.layout @@ -2,7 +2,7 @@ - + diff --git a/files/mygui/openmw_count_window.layout b/files/mygui/openmw_count_window.layout index e9a1cb2c80..27b62ebf03 100644 --- a/files/mygui/openmw_count_window.layout +++ b/files/mygui/openmw_count_window.layout @@ -13,6 +13,8 @@ + + diff --git a/files/mygui/openmw_hud.layout b/files/mygui/openmw_hud.layout index 0cbe0dd971..03c05260f3 100644 --- a/files/mygui/openmw_hud.layout +++ b/files/mygui/openmw_hud.layout @@ -122,55 +122,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/files/mygui/openmw_hud_box.skin.xml b/files/mygui/openmw_hud_box.skin.xml index 4e63497680..33199d6aeb 100644 --- a/files/mygui/openmw_hud_box.skin.xml +++ b/files/mygui/openmw_hud_box.skin.xml @@ -31,5 +31,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_journal.layout b/files/mygui/openmw_journal.layout index 5524f55202..9b530b3796 100644 --- a/files/mygui/openmw_journal.layout +++ b/files/mygui/openmw_journal.layout @@ -2,7 +2,7 @@ - + @@ -26,9 +26,7 @@ - - - + @@ -66,15 +64,11 @@ - - - + - - - + @@ -90,9 +84,7 @@ - - - + diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index 50f83aaa2c..cd8a9f7608 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -1,17 +1,21 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_mainmenu.layout b/files/mygui/openmw_mainmenu.layout index f4c7142ce7..c4d88ff855 100644 --- a/files/mygui/openmw_mainmenu.layout +++ b/files/mygui/openmw_mainmenu.layout @@ -3,10 +3,12 @@ - + + + diff --git a/files/mygui/openmw_progress.skin.xml b/files/mygui/openmw_progress.skin.xml index 761574c15d..71bbfe9f0c 100644 --- a/files/mygui/openmw_progress.skin.xml +++ b/files/mygui/openmw_progress.skin.xml @@ -2,7 +2,7 @@ - + diff --git a/files/mygui/openmw_resources.xml b/files/mygui/openmw_resources.xml index 305cb0c0da..ab68993417 100644 --- a/files/mygui/openmw_resources.xml +++ b/files/mygui/openmw_resources.xml @@ -101,7 +101,7 @@ - + diff --git a/files/mygui/openmw_screen_fader.layout b/files/mygui/openmw_screen_fader.layout index fffd2e66e3..e9009d32a6 100644 --- a/files/mygui/openmw_screen_fader.layout +++ b/files/mygui/openmw_screen_fader.layout @@ -1,7 +1,7 @@ - - + + diff --git a/files/mygui/openmw_screen_fader_hit.layout b/files/mygui/openmw_screen_fader_hit.layout new file mode 100644 index 0000000000..7e60f0ff5a --- /dev/null +++ b/files/mygui/openmw_screen_fader_hit.layout @@ -0,0 +1,7 @@ + + + + + + + diff --git a/files/mygui/openmw_scroll.layout b/files/mygui/openmw_scroll.layout index 194700f36e..b2e62f28d8 100644 --- a/files/mygui/openmw_scroll.layout +++ b/files/mygui/openmw_scroll.layout @@ -2,24 +2,24 @@ - + - + - + - + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 2efd5841ed..6d2424aa5b 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -175,7 +175,7 @@ - + @@ -255,13 +255,9 @@ - - - - - - + + @@ -271,12 +267,12 @@ - - - - - + + + + + @@ -284,7 +280,7 @@ - + @@ -305,7 +301,7 @@ - + @@ -328,7 +324,6 @@ - @@ -348,11 +343,11 @@ - + - + @@ -372,53 +367,38 @@ - + - + - + - - + + - - - - + + + + - - - - - - - - - - - - + + - - - - - - - - - - + + + + diff --git a/files/mygui/openmw_text.skin.xml b/files/mygui/openmw_text.skin.xml index e459f22fab..b7a893580f 100644 --- a/files/mygui/openmw_text.skin.xml +++ b/files/mygui/openmw_text.skin.xml @@ -18,15 +18,6 @@ color_misc=0,205,205 # ???? - - - - - - - - - diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index 2854401c89..682d89ebcc 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -2,7 +2,7 @@ - + @@ -122,12 +122,20 @@ - + + + + + + + + + @@ -151,14 +159,22 @@ - - + + + + + + - - + + + + + + diff --git a/files/mygui/white.png b/files/mygui/white.png deleted file mode 100644 index 9bed5f523c..0000000000 Binary files a/files/mygui/white.png and /dev/null differ diff --git a/files/opencs.ini b/files/opencs.ini deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/components/misc/tests/output/strops_test.out b/files/openmw-cs.cfg similarity index 100% rename from components/misc/tests/output/strops_test.out rename to files/openmw-cs.cfg diff --git a/files/openmw.appdata.xml b/files/openmw.appdata.xml new file mode 100644 index 0000000000..1c16cb9a40 --- /dev/null +++ b/files/openmw.appdata.xml @@ -0,0 +1,39 @@ + + + + openmw.desktop + CC0-1.0 + GPL-3.0 and MIT and zlib + OpenMW + Unofficial open source engine re-implementation of the game Morrowind + +

+ OpenMW is a new engine for 2002's Game of the Year, The Elder Scrolls 3: Morrowind. +

+

+ It aims to be a fully playable (and improved!), open source implementation of the game's engine and functionality (including mods). +

+

+ You will still need the original game data to play OpenMW. +

+
+ + + + http://wiki.openmw.org/images/b/b2/Openmw_0.11.1_launcher_1.png + The OpenMW launcher + + + http://wiki.openmw.org/images/f/f1/Screenshot_mournhold_plaza_0.35.png + The Mournhold's plaza on OpenMW + + + http://wiki.openmw.org/images/5/5b/Screenshot_Vivec_seen_from_Ebonheart_0.35.png + Vivec seen from Ebonheart on OpenMW + + +nobrakal@gmail.com + https://openmw.org + https://bugs.openmw.org/ + https://openmw.org/faq/ +
diff --git a/files/settings-default.cfg b/files/settings-default.cfg index de22e1b56b..49c9c54191 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1,274 +1,368 @@ -# WARNING: Editing this file might have no effect, as these -# settings are overwritten by your user settings file. +# WARNING: If this file is named settings-default.cfg, then editing +# this file might have no effect, as these settings may be overwritten +# by your user settings.cfg file (see documentation for its location). +# +# This file provides minimal documentation for each setting, and +# ranges of recommended values. For detailed explanations of the +# significance of each setting, interaction with other settings, hard +# limits on value ranges and more information in general, please read +# the detailed documentation at the OpenMW Wiki page: +# +# https://wiki.openmw.org/index.php?title=Settings +# -[Video] -resolution x = 800 -resolution y = 600 +[Camera] -fullscreen = false -window border = true -screen = 0 +# Near clipping plane (>0.0, e.g. 0.01 to 18.0). +near clip = 5.0 -# Minimize the window if it loses key focus? -minimize on focus loss = true +# Cull objects smaller than one pixel. +small feature culling = true -# Render system -# blank means default -# Valid values: -# OpenGL Rendering Subsystem -# Direct3D9 Rendering Subsystem -render system = +# Maximum visible distance (e.g. 2000.0 to 6666.0). Caution: this setting +# can dramatically affect performance, see documentation for details. +viewing distance = 6666.0 -# Valid values: -# none -# MSAA 2 -# MSAA 4 -# MSAA 8 -# MSAA 16 -antialiasing = none +# Camera field of view in degrees (e.g. 30.0 to 110.0). +# Does not affect the player's hands in the first person camera. +field of view = 55.0 -vsync = false - -# opengl render to texture mode, valid options: -# PBuffer, FBO, Copy -opengl rtt mode = FBO - -[GUI] -# 1 is fully opaque -menu transparency = 0.84 - -# 0 - instantly, 1 - max. delay -tooltip delay = 0 - -subtitles = false - -hit fader = true -werewolf overlay = true - -stretch menu background = false - -[General] -# Camera field of view -field of view = 55 -gamma = 1.00 -contrast = 1.00 - -# Texture filtering mode. valid values: -# none -# anisotropic -# bilinear -# trilinear -texture filtering = anisotropic - -# Has no effect when texture filtering is not anisotropic -anisotropy = 4 - -# Number of texture mipmaps to generate -num mipmaps = 8 - -shader mode = - -screenshot format = png - -[Shadows] -# Shadows are only supported when object shaders are on! -enabled = false - -# Split the shadow maps, allows for a larger shadow distance -split = false - -# Increasing shadow distance will lower the shadow quality. -# Uses "shadow distance" or "split shadow distance" depending on "split" setting. -shadow distance = 1300 -# This one shouldn't be too low, otherwise you'll see artifacts. Use at least 2x max viewing distance. -split shadow distance = 14000 - -# Size of the shadow textures, higher means higher quality -texture size = 1024 - -# Turn on/off various shadow casters -actor shadows = true -misc shadows = true -statics shadows = true -terrain shadows = true - -# Fraction of the total shadow distance after which the shadow starts to fade out -fade start = 0.8 - -debug = false - -[HUD] -# FPS counter -# 0: not visible -# 1: basic FPS display -# 2: advanced FPS display (batches, triangles) -fps = 0 - -crosshair = true - -[Objects] -shaders = true - -# Max. number of lights that affect objects. Setting to 1 will only reflect sunlight -# Note: has no effect when shaders are turned off -num lights = 8 - -# Use static geometry for static objects. Improves rendering speed. -use static geometry = true - -[Map] -# Adjusts the scale of the global map -global map cell size = 18 - -local map resolution = 256 - -local map widget size = 512 -local map hud widget size = 256 +# Field of view for first person meshes (i.e. the player's hands) +# Best to leave this at the default since vanilla assets are not complete enough to adapt to high FoV's. Too low FoV would clip the hands off screen. +first person field of view = 55.0 [Cells] -exterior grid size = 3 -[Viewing distance] -# Limit the rendering distance of small objects -limit small object distance = false +# Adjacent exterior cells loaded (>0). Caution: this setting can +# dramatically affect performance, see documentation for details. +exterior cell load distance = 1 -# Size below which an object is considered as small -small object size = 250 +[Map] -# Rendering distance for small objects -small object distance = 3500 +# Size of each exterior cell in pixels in the world map. (e.g. 12 to 24). +# Warning: affects explored areas in save files, see documentation. +global map cell size = 18 -# Viewing distance at normal weather conditions -# The maximum distance with no pop-in will be: (see RenderingManager::configureFog) -# viewing distance / minimum weather fog depth (.69) * view frustum factor <= cell size (8192) - loading threshold (1024) -# view frustum factor takes into account that the view frustum end is a plane, so at the edges of the screen you can see further than you should be able to. -# exact factor would depend on FOV -viewing distance = 4600 +# Zoom level in pixels for HUD map widget. 64 is one cell, 128 is 1/4 +# cell, 256 is 1/8 cell. See documentation for details. (e.g. 64 to 256). +local map hud widget size = 256 -# Distance at which fog starts (proportional to viewing distance) -fog start factor = 0.5 +# Resolution of local map in GUI window in pixels. See documentation +# for details which may affect cell load performance. (e.g. 128 to 1024). +local map resolution = 256 -# Distance at which fog ends (proportional to viewing distance) -fog end factor = 1.0 +# Size of local map in GUI window in pixels. (e.g. 256 to 1024). +local map widget size = 512 -[Terrain] -distant land = false +[GUI] -shader = true +# Scales GUI window and widget size. (<1.0 is smaller, >1.0 is larger). +scaling factor = 1.0 -[Water] -shader = false +# Transparency of GUI windows (0.0 to 1.0, transparent to opaque). +menu transparency = 0.84 -refraction = false +# Time until tool tip appears when hovering over an object (0.0 is +# instantly, 1.0 is the maximum delay of about 1.5 seconds). +tooltip delay = 0.0 -rtt size = 512 -reflect terrain = true -reflect statics = false -reflect actors = false +# Stretch menus, load screens, etc. to the window aspect ratio. +stretch menu background = false -[Sound] -# Device name. Blank means default -device = +# Subtitles for NPC spoken dialog and some sound effects. +subtitles = false -# Volumes. master volume affects all other volumes. -master volume = 1.0 -sfx volume = 1.0 -music volume = 0.5 -footsteps volume = 0.2 -voice volume = 0.8 +# Red flash visually showing player damage. +hit fader = true +# Werewolf overlay border around screen or window. +werewolf overlay = true + +# Color for tool tips and crosshair when owned by an NPC (R G B A). +color background owned = 0.15 0.0 0.0 1.0 +color crosshair owned = 1.0 0.15 0.15 1.0 + +[HUD] + +# Displays the crosshair or reticle when not in GUI mode. +crosshair = true + +[Game] + +# Color crosshair and tool tip when object is owned by an NPC. (O is +# no color, 1 is tool tip only, 2 is crosshair only, and 3 is both). +show owned = 0 + +# Always use the best mode of attack: e.g. chop, slash or thrust. +best attack = false + +# Difficulty. Expressed as damage dealt and received. (e.g. -100 to 100). +difficulty = 0 + +# Show duration of magic effect and lights in the spells window. +show effect duration = false + +[General] + +# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). +anisotropy = 4 + +# File format for screenshots. (jpg, png, tga, and possibly more). +screenshot format = png + +# Texture magnification filter type. (nearest or linear). +texture mag filter = linear + +# Texture minification filter type. (nearest or linear). +texture min filter = linear + +# Texture mipmap type. (none, nearest, or linear). +texture mipmap = nearest [Input] +# Capture control of the cursor prevent movement outside the window. grab cursor = true -invert y axis = false - -camera sensitivity = 1.0 - -ui sensitivity = 1.0 - -camera y multiplier = 1.0 - -always run = false - -allow third person zoom = false - +# Key controlling sneak toggles setting instead of being held down. toggle sneak = false -[Game] -# Always use the most powerful attack when striking with a weapon (chop, slash or thrust) -best attack = false +# Player is running by default. +always run = false -difficulty = 0 +# Zoom in and out from player in third person view with mouse wheel. +allow third person zoom = false + +# Camera sensitivity when not in GUI mode. (>0.0, e.g. 0.1 to 5.0). +camera sensitivity = 1.0 + +# Vertical camera sensitivity multiplier when not in GUI mode. +# (>0.0, Because it's a multiplier values should be near 1.0) +camera y multiplier = 1.0 + +# Invert the vertical axis while not in GUI mode. +invert y axis = false [Saves] + +# Name of last character played, and default for loading save files. character = -# Save when resting + +# Automatically save the game whenever the player rests. autosave = true +# Display the time played on each save file in the load menu. +timeplayed = false + +[Sound] + +# Name of audio device file. Blank means use the default device. +device = + +# Volumes are 0.0 for silent and 1.0 for the maximum volume. + +# Master volume. Controls all other volumes. +master volume = 1.0 + +# Footsteps volume. +footsteps volume = 0.2 + +# Music tracks volume. +music volume = 0.5 + +# Sound effects volume. +sfx volume = 1.0 + +# Voice dialog volume. +voice volume = 0.8 + +# Minimum size to use for the sound buffer cache, in MB. When the cache is +# filled, old buffers will be unloaded until it's using no more than this much +# memory. Must be less than or equal to 'buffer cache max'. +buffer cache min = 14 + +# Maximum size to use for the sound buffer cache, in MB. The cache can use up +# to this much memory until old buffers get purged. +buffer cache max = 16 + +# Specifies whether to enable HRTF processing. Valid values are: -1 = auto, +# 0 = off, 1 = on. +hrtf enable = -1 + +# Specifies which HRTF to use when HRTF is used. Blank means use the default. +hrtf = + +[Video] + +# Resolution of the OpenMW window or screen. +resolution x = 800 +resolution y = 600 + +# OpenMW takes complete control of the screen. +fullscreen = false + +# Determines which screen OpenMW is on. (>=0). +screen = 0 + +# Minimize OpenMW if it loses cursor or keyboard focus. +minimize on focus loss = true + +# An operating system border is drawn around the OpenMW window. +window border = true + +# Anti-aliasing reduces jagged polygon edges. (0, 2, 4, 8, 16). +antialiasing = 0 + +# Enable vertical syncing to reduce tearing defects. +vsync = false + +# Maximum frames per second. 0.0 is unlimited, or >0.0 to limit. +framerate limit = 0.0 + +# Game video contrast. (>0.0). No effect in Linux. +contrast = 1.0 + +# Video gamma setting. (>0.0). No effect in Linux. +gamma = 1.0 + +[Water] + +# Enable water shader with reflections and optionally refraction. +shader = false + +# Reflection and refraction texture size in pixels. (512, 1024, 2048). +rtt size = 512 + +# Enable refraction which affects visibility through water plane. +refraction = false + +[Objects] + +# Enable shaders for objects other than water. Unused. +shaders = true + +[Terrain] + +# Use shaders for terrain? Unused. +shader = true + +# Distant land is rendered? Unused. +distant land = false + +[Shadows] + +# Enable shadows. Other shadow settings disabled if false. Unused. +enabled = false + +# Size of the shadow textures in pixels. Unused. (e.g. 256 to 2048). +texture size = 1024 + +# Actors cast shadows. Unused. +actor shadows = true + +# Static objects cast shadows. Unused. +statics shadows = true + +# Terrain cast shadows. Unused. +terrain shadows = true + +# Miscellaneous objects cast shadows. Unused. +misc shadows = true + +# Debugging of shadows. Unused. +debug = false + +# Fraction of distance after which shadow starts to fade out. Unused. +fade start = 0.8 + +# Split shadow maps, allowing for a larger shadow distance. Unused. +split = false + +# Distance for shadows if not split. Smaller is poorer. Unused. +shadow distance = 1300 + +# Distance for shadows if split. Unused. +split shadow distance = 14000 + [Windows] -inventory x = 0 -inventory y = 0.4275 -inventory w = 0.6225 -inventory h = 0.5725 -inventory container x = 0 -inventory container y = 0.4275 -inventory container w = 0.6225 -inventory container h = 0.5725 +# Location and sizes of windows as a fraction of the OpenMW window or +# screen size. (0.0 to 1.0). X & Y, Width & Height. -inventory barter x = 0 -inventory barter y = 0.4275 -inventory barter w = 0.6225 -inventory barter h = 0.5725 - -inventory companion x = 0 -inventory companion y = 0.4275 -inventory companion w = 0.6225 -inventory companion h = 0.5725 - -container x = 0.25 -container y = 0 -container w = 0.75 -container h = 0.375 - -companion x = 0.25 -companion y = 0 -companion w = 0.75 -companion h = 0.375 - -map x = 0.625 -map y = 0 -map w = 0.375 -map h = 0.5725 - -barter x = 0.25 -barter y = 0 -barter w = 0.75 -barter h = 0.375 - -alchemy x = 0.25 -alchemy y = 0.25 -alchemy w = 0.5 -alchemy h = 0.5 - -stats x = 0 -stats y = 0 +# Stats window displaying level, race, class, skills and stats. +stats x = 0.0 +stats y = 0.0 stats w = 0.375 stats h = 0.4275 +# Spells window displaying powers, spells, and magical items. spells x = 0.625 spells y = 0.5725 spells w = 0.375 spells h = 0.4275 -console x = 0 -console y = 0 -console w = 1 -console h = 0.5 +# Local and world map window. +map x = 0.625 +map y = 0.0 +map w = 0.375 +map h = 0.5725 -dialogue h = 0.810 -dialogue w = 0.810 +# Dialog window for talking with NPCs. dialogue x = 0.095 dialogue y = 0.095 +dialogue w = 0.810 +dialogue h = 0.810 + +# Alchemy window for crafting potions. +alchemy x = 0.25 +alchemy y = 0.25 +alchemy w = 0.5 +alchemy h = 0.5 + +# Console command window for debugging commands. +console x = 0.0 +console y = 0.0 +console w = 1.0 +console h = 0.5 + +# Player inventory window when explicitly opened. +inventory x = 0.0 +inventory y = 0.4275 +inventory w = 0.6225 +inventory h = 0.5725 + +# Player inventory window when searching a container. +inventory container x = 0.0 +inventory container y = 0.4275 +inventory container w = 0.6225 +inventory container h = 0.5725 + +# Player inventory window when bartering with a shopkeeper. +inventory barter x = 0.0 +inventory barter y = 0.4275 +inventory barter w = 0.6225 +inventory barter h = 0.5725 + +# Player inventory window when trading with a companion. +inventory companion x = 0.0 +inventory companion y = 0.4275 +inventory companion w = 0.6225 +inventory companion h = 0.5725 + +# Container inventory when searching a container. +container x = 0.25 +container y = 0.0 +container w = 0.75 +container h = 0.375 + +# NPC inventory window when bartering with a shopkeeper. +barter x = 0.25 +barter y = 0.0 +barter w = 0.75 +barter h = 0.375 + +# NPC inventory window when trading with a companion. +companion x = 0.25 +companion y = 0.0 +companion w = 0.75 +companion h = 0.375 diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt new file mode 100644 index 0000000000..fc4706c1f6 --- /dev/null +++ b/files/shaders/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copy resource files into the build directory +set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(DDIR ${OpenMW_BINARY_DIR}/resources/shaders) + +set(SHADER_FILES + water_vertex.glsl + water_fragment.glsl + water_nm.png +) + +copy_all_files(${CMAKE_CURRENT_SOURCE_DIR} ${DDIR} "${SHADER_FILES}") diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl new file mode 100644 index 0000000000..c04233fcf0 --- /dev/null +++ b/files/shaders/water_fragment.glsl @@ -0,0 +1,191 @@ +#version 120 + +#define REFRACTION @refraction_enabled + +// Inspired by Blender GLSL Water by martinsh ( http://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html ) + +// tweakables -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + +const float VISIBILITY = 1200.0; // how far you can look through water + +const float BIG_WAVES_X = 0.1; // strength of big waves +const float BIG_WAVES_Y = 0.1; + +const float MID_WAVES_X = 0.1; // strength of middle sized waves +const float MID_WAVES_Y = 0.1; + +const float SMALL_WAVES_X = 0.1; // strength of small waves +const float SMALL_WAVES_Y = 0.1; + +const float WAVE_CHOPPYNESS = 0.05; // wave choppyness +const float WAVE_SCALE = 75.0; // overall wave scale + +const float BUMP = 0.5; // overall water surface bumpiness +const float REFL_BUMP = 0.10; // reflection distortion amount +const float REFR_BUMP = 0.07; // refraction distortion amount + +const float SCATTER_AMOUNT = 0.3; // amount of sunlight scattering +const vec3 SCATTER_COLOUR = vec3(0.0,1.0,0.95); // colour of sunlight scattering + +const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction + +const float SPEC_HARDNESS = 256.0; // specular highlights hardness + +const vec2 WIND_DIR = vec2(0.5f, -0.8f); +const float WIND_SPEED = 0.2f; + +const vec3 WATER_COLOR = vec3(0.090195, 0.115685, 0.12745); + +// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + +float fresnel_dielectric(vec3 Incoming, vec3 Normal, float eta) +{ + float c = abs(dot(Incoming, Normal)); + float g = eta * eta - 1.0 + c * c; + float result; + + if(g > 0.0) { + g = sqrt(g); + float A =(g - c)/(g + c); + float B =(c *(g + c)- 1.0)/(c *(g - c)+ 1.0); + result = 0.5 * A * A *(1.0 + B * B); + } + else + result = 1.0; /* TIR (no refracted component) */ + + return result; +} + +varying vec3 screenCoordsPassthrough; +varying vec4 position; +varying float depthPassthrough; + +uniform sampler2D normalMap; + +uniform sampler2D reflectionMap; +#if REFRACTION +uniform sampler2D refractionMap; +uniform sampler2D refractionDepthMap; +#endif + +uniform float osg_SimulationTime; + +uniform float near; +uniform float far; +uniform vec3 nodePosition; + +void main(void) +{ + vec3 worldPos = position.xyz + nodePosition.xyz; + vec2 UV = worldPos.xy / (8192.0*5.0) * 3.0; + UV.y *= -1.0; + + float shadow = 1.0; + + vec2 screenCoords = screenCoordsPassthrough.xy / screenCoordsPassthrough.z; + screenCoords.y = (1.0-screenCoords.y); + + vec2 nCoord = vec2(0.0,0.0); + + #define waterTimer osg_SimulationTime + + nCoord = UV * (WAVE_SCALE * 0.05) + WIND_DIR * waterTimer * (WIND_SPEED*0.04); + vec3 normal0 = 2.0 * texture2D(normalMap, nCoord + vec2(-waterTimer*0.015,-waterTimer*0.005)).rgb - 1.0; + nCoord = UV * (WAVE_SCALE * 0.1) + WIND_DIR * waterTimer * (WIND_SPEED*0.08)-(normal0.xy/normal0.zz)*WAVE_CHOPPYNESS; + vec3 normal1 = 2.0 * texture2D(normalMap, nCoord + vec2(+waterTimer*0.020,+waterTimer*0.015)).rgb - 1.0; + + nCoord = UV * (WAVE_SCALE * 0.25) + WIND_DIR * waterTimer * (WIND_SPEED*0.07)-(normal1.xy/normal1.zz)*WAVE_CHOPPYNESS; + vec3 normal2 = 2.0 * texture2D(normalMap, nCoord + vec2(-waterTimer*0.04,-waterTimer*0.03)).rgb - 1.0; + nCoord = UV * (WAVE_SCALE * 0.5) + WIND_DIR * waterTimer * (WIND_SPEED*0.09)-(normal2.xy/normal2.z)*WAVE_CHOPPYNESS; + vec3 normal3 = 2.0 * texture2D(normalMap, nCoord + vec2(+waterTimer*0.03,+waterTimer*0.04)).rgb - 1.0; + + nCoord = UV * (WAVE_SCALE* 1.0) + WIND_DIR * waterTimer * (WIND_SPEED*0.4)-(normal3.xy/normal3.zz)*WAVE_CHOPPYNESS; + vec3 normal4 = 2.0 * texture2D(normalMap, nCoord + vec2(-waterTimer*0.02,+waterTimer*0.1)).rgb - 1.0; + nCoord = UV * (WAVE_SCALE * 2.0) + WIND_DIR * waterTimer * (WIND_SPEED*0.7)-(normal4.xy/normal4.zz)*WAVE_CHOPPYNESS; + vec3 normal5 = 2.0 * texture2D(normalMap, nCoord + vec2(+waterTimer*0.1,-waterTimer*0.06)).rgb - 1.0; + + + + vec3 normal = (normal0 * BIG_WAVES_X + normal1 * BIG_WAVES_Y + + normal2 * MID_WAVES_X + normal3 * MID_WAVES_Y + + normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y); + + normal = normalize(vec3(normal.x * BUMP, normal.y * BUMP, normal.z)); + + normal = vec3(-normal.x, -normal.y, normal.z); + + // normal for sunlight scattering + vec3 lNormal = (normal0 * BIG_WAVES_X*0.5 + normal1 * BIG_WAVES_Y*0.5 + + normal2 * MID_WAVES_X*0.2 + normal3 * MID_WAVES_Y*0.2 + + normal4 * SMALL_WAVES_X*0.1 + normal5 * SMALL_WAVES_Y*0.1).xyz; + lNormal = normalize(vec3(lNormal.x * BUMP, lNormal.y * BUMP, lNormal.z)); + lNormal = vec3(-lNormal.x, -lNormal.y, lNormal.z); + + + vec3 lVec = normalize((gl_ModelViewMatrixInverse * vec4(gl_LightSource[0].position.xyz, 0.0)).xyz); + + vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz; + vec3 vVec = normalize(position.xyz - cameraPos.xyz); + + float isUnderwater = (cameraPos.z > 0.0) ? 0.0 : 1.0; + + // sunlight scattering + vec3 pNormal = vec3(0,0,1); + vec3 lR = reflect(lVec, lNormal); + vec3 llR = reflect(lVec, pNormal); + + float sunHeight = lVec.z; + float sunFade = length(gl_LightModel.ambient.xyz); + + float s = clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0); + float lightScatter = shadow * clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * s * SCATTER_AMOUNT * sunFade * clamp(1.0-exp(-sunHeight), 0.0, 1.0); + vec3 scatterColour = mix(vec3(SCATTER_COLOUR)*vec3(1.0,0.4,0.0), SCATTER_COLOUR, clamp(1.0-exp(-sunHeight*SUN_EXT), 0.0, 1.0)); + + // fresnel + float ior = (cameraPos.z>0.0)?(1.333/1.0):(1.0/1.333); //air to water; water to air + float fresnel = fresnel_dielectric(vVec, normal, ior); + + fresnel = clamp(fresnel, 0.0, 1.0); + + // reflection + vec3 reflection = texture2D(reflectionMap, screenCoords+(normal.xy*REFL_BUMP)).rgb; + + // refraction +#if REFRACTION + vec3 refraction = texture2D(refractionMap, screenCoords-(normal.xy*REFR_BUMP)).rgb; + + // brighten up the refraction underwater + refraction = (cameraPos.z < 0.0) ? clamp(refraction * 1.5, 0.0, 1.0) : refraction; +#endif + + // specular + vec3 R = reflect(vVec, normal); + float specular = pow(max(dot(R, lVec), 0.0),SPEC_HARDNESS) * shadow; + + vec3 waterColor = WATER_COLOR; + waterColor = waterColor * length(gl_LightModel.ambient.xyz); +#if REFRACTION + float refractionDepth = texture2D(refractionDepthMap, screenCoords-(normal.xy*REFR_BUMP)).x; + float z_n = 2.0 * refractionDepth - 1.0; + refractionDepth = 2.0 * near * far / (far + near - z_n * (far - near)); + + float waterDepth = refractionDepth - depthPassthrough; + + if (cameraPos.z > 0.0) + refraction = mix(refraction, waterColor, clamp(waterDepth/VISIBILITY, 0.0, 1.0)); + + gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * gl_LightSource[0].specular.xyz; +#else + gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * gl_LightSource[0].specular.xyz; +#endif + + // fog + float fogValue = clamp((depthPassthrough - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + +#if REFRACTION + gl_FragData[0].w = 1.0; +#else + gl_FragData[0].w = clamp(fresnel*2.0 + specular, 0.0, 1.0); +#endif +} diff --git a/files/water/water_nm.png b/files/shaders/water_nm.png similarity index 100% rename from files/water/water_nm.png rename to files/shaders/water_nm.png diff --git a/files/shaders/water_vertex.glsl b/files/shaders/water_vertex.glsl new file mode 100644 index 0000000000..7d7b7b18aa --- /dev/null +++ b/files/shaders/water_vertex.glsl @@ -0,0 +1,22 @@ +#version 120 + +varying vec3 screenCoordsPassthrough; +varying vec4 position; +varying float depthPassthrough; + +void main(void) +{ + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + + mat4 scalemat = mat4(0.5, 0.0, 0.0, 0.0, + 0.0, -0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0); + + vec4 texcoordProj = ((scalemat) * ( gl_Position)); + screenCoordsPassthrough = vec3(texcoordProj.x, texcoordProj.y, texcoordProj.w); + + position = gl_Vertex; + + depthPassthrough = gl_Position.z; +} diff --git a/files/transparency-overrides.cfg b/files/transparency-overrides.cfg deleted file mode 100644 index 65f9b477af..0000000000 --- a/files/transparency-overrides.cfg +++ /dev/null @@ -1,623 +0,0 @@ -# Bethesda has used wrong transparency settings for many textures -# (who would have guessed) -# This is very unfortunate because objects with real transparency: -# - cannot cast shadows -# - cannot receive advanced framebuffer effects like depth of field or ambient occlusion -# - cannot cover lens flare effects (the lens flare will just shine through) - -# This file lists textures that should be using alpha rejection instead of transparency -# basically these are textures that are not translucent (i.e. at one spot on the texture, either transparent or opaque) - -# Note: all the texture names here have to be lowercase - -# fauna -[textures\tx_wickwheat_01.dds] - alphaRejectValue = 128 - -[textures\tx_wickwheat_03.dds] - alphaRejectValue = 128 - -[textures\tx_red_lichen_01.dds] - alphaRejectValue = 128 - -[textures\tx_stone_flower_01.dds] - alphaRejectValue = 128 - -[textures\tx_ivy_02.dds] - alphaRejectValue = 128 - -[textures\tx_ivy_01.dds] - alphaRejectValue = 128 - -[textures\tx_saltrice_04.dds] - alphaRejectValue = 128 - -[textures\tx_black_lichen_01.dds] - alphaRejectValue = 128 - -[textures\tx_leaves_01.dds] - alphaRejectValue = 128 - -[textures\tx_leaves_02.dds] - alphaRejectValue = 128 - -[textures\tx_leaves_03.dds] - alphaRejectValue = 128 - -[textures\tx_leaves_04.dds] - alphaRejectValue = 128 - -[textures\tx_leaves_06.dds] - alphaRejectValue = 128 - -[textures\tx_leaves_07.dds] - alphaRejectValue = 128 - -[textures\tx_ai_heather_01.dds] - alphaRejectValue = 96 - -[textures\tx_goldkanet_01.dds] - alphaRejectValue = 128 - -[textures\tx_goldkanet_02.dds] - alphaRejectValue = 128 - -[textures\tx_plant_tails00.dds] - alphaRejectValue = 128 - -[textures\tx_vine_01.dds] - alphaRejectValue = 128 - -[textures\tx_comberry_01.dds] - alphaRejectValue = 128 - -[textures\tx_willow_flower_02.dds] - alphaRejectValue = 128 - -[textures\tx_cork_bulb_02.dds] - alphaRejectValue = 128 - -[textures\tx_green_lichen_01.dds] - alphaRejectValue = 128 - -[textures\tx_roobrush_01.dds] - alphaRejectValue = 128 - -[textures\tx_bittergreen_02.dds] - alphaRejectValue = 128 - -[textures\tx_chokeweed_01.dds] - alphaRejectValue = 128 - -[textures\tx_branches_01.dds] - alphaRejectValue = 128 - -[textures\tx_branches_02.dds] - alphaRejectValue = 128 - -[textures\tx_guarskin_hut_03.dds] - alphaRejectValue = 128 - -[textures\tx_hackle-lo_02.dds] - alphaRejectValue = 128 - -[textures\tx_bc_fern_01.dds] - alphaRejectValue = 128 - -[textures\tx_bc_fern_02.dds] - alphaRejectValue = 128 - -[textures\tx_bc_leaves_02.dds] - alphaRejectValue = 128 - -[textures\tx_marshmerrow_03.dds] - alphaRejectValue = 128 - -[textures\tx_bc_moss_01.dds] - alphaRejectValue = 128 - -[textures\tx_bc_moss_02.dds] - alphaRejectValue = 128 - -[textures\tx_bc_lilypad_01.dds] - alphaRejectValue = 128 - -[textures\tx_bc_lilypad_02.dds] - alphaRejectValue = 128 - -[textures\tx_bc_lilypad_03.dds] - alphaRejectValue = 128 - -[textures\tx_fire_fern_01.dds] - alphaRejectValue = 128 - -# banners and flags -[textures\tx_flag_imp_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_arena_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_comfort_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_child_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_count_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_faith_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_walk_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_imp_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_redoran_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_avs_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_serving_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_speak_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_stdeyln_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_stolms_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_thin_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_vivec_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_vivec_02.dds] - alphaRejectValue = 128 - -[textures\tx_ashl_banner_01.dds] - alphaRejectValue = 128 - -[textures\tx_ashl_banner_02.dds] - alphaRejectValue = 128 - -[textures\tx_ashl_banner_04.dds] - alphaRejectValue = 128 - -[textures\tx_ashl_banner_05.dds] - alphaRejectValue = 128 - -[textures\tx_ashl_banner_06.dds] - alphaRejectValue = 128 - -[textures\tx_ashl_banner_07.dds] - alphaRejectValue = 128 - -[textures\tx_ashl_a_banner.dds] - alphaRejectValue = 128 - -[textures\tx_ashl_e_banner.dds] - alphaRejectValue = 128 - -[textures\tx_ashl_u_banner.dds] - alphaRejectValue = 128 - -[textures\tx_ashl_z_banner.dds] - alphaRejectValue = 128 - -[textures\tx_banner_6th.dds] - alphaRejectValue = 128 - -[textures\tx_banner_6th_tall.dds] - alphaRejectValue = 128 - -[textures\tx_banner_gnisis_01.dds] - alphaRejectValue = 128 - -[textures\tx_banner_gnisis_02.dds] - alphaRejectValue = 128 - -[textures\tx_de_tapestry_bhm_01.dds] - alphaRejectValue = 128 - -[textures\tx_de_tapestry_02.dds] - alphaRejectValue = 128 - -[textures\tx_de_tapestry_03.dds] - alphaRejectValue = 128 - -[textures\tx_de_tapestry_04.dds] - alphaRejectValue = 128 - -[textures\tx_de_tapestry_05.dds] - alphaRejectValue = 128 - -[textures\tx_de_tapestry_06.dds] - alphaRejectValue = 128 - -[textures\tx_de_tapestry_07.dds] - alphaRejectValue = 128 - -[textures\tx_de_tapestry_08.dds] - alphaRejectValue = 128 - -[textures\tx_de_tapestry_08.dds] - alphaRejectValue = 128 - -[textures\tx_de_tapestry_09.dds] - alphaRejectValue = 128 - -[textures\tx_de_tapestry_10.dds] - alphaRejectValue = 128 - -[textures\tx_de_tapestry_11.dds] - alphaRejectValue = 128 - -[textures\tx_de_tapestry_12.dds] - alphaRejectValue = 128 - -[textures\tx_de_tapestry_13.dds] - alphaRejectValue = 128 - -[textures\tx_de_lutestrings_01.dds] - alphaRejectValue = 128 - -[textures\tx_fabric_imp_altar_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_akatosh_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_apprentice_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_arkay_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_dibella_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_golem_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_julianos_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_kynareth_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_lady_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_lord_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_lover_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_mara_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_ritual_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_shadow_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_steed_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_stendarr_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_thief_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_tower_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_warrior_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_wizard_01.dds] - alphaRejectValue = 128 - -[textures\tx_c_t_zenithar_01.dds] - alphaRejectValue = 128 - -[textures\tx_banner_dagoth_01.dds] - alphaRejectValue = 128 - -[textures\tx_bannerd_tavern_01.dds] - alphaRejectValue = 128 - -[textures\tx_bannerd_goods_01.dds] - alphaRejectValue = 128 - -[textures\tx_bannerd_danger_01.dds] - alphaRejectValue = 128 - -[textures\tx_bannerd_welcome_01.dds] - alphaRejectValue = 128 - -[textures\tx_bannerd_clothing_01.dds] - alphaRejectValue = 128 - -[textures\tx_bannerd_alchemy_01.dds] - alphaRejectValue = 128 - -[textures\tx_banner_hlaalu_01.dds] - alphaRejectValue = 128 - -[textures\tx_banner_redoran_01.dds] - alphaRejectValue = 128 - -[textures\tx_banner_temple_01.dds] - alphaRejectValue = 128 - -[textures\tx_banner_temple_03.dds] - alphaRejectValue = 128 - -[textures\tx_de_banner_book_01.dds] - alphaRejectValue = 128 - -[textures\tx_de_banner_ald_velothi.dds] - alphaRejectValue = 128 - -[textures\tx_de_banner_gnaar_mok.dds] - alphaRejectValue = 128 - -[textures\tx_de_banner_hla_oad.dds] - alphaRejectValue = 128 - -[textures\tx_de_banner_khull.dds] - alphaRejectValue = 128 - -[textures\tx_de_banner_pawn_01.dds] - alphaRejectValue = 128 - -[textures\tx_de_banner_sadrith_mora.dds] - alphaRejectValue = 128 - -[textures\tx_de_banner_tel_aruhn.dds] - alphaRejectValue = 128 - -[textures\tx_de_banner_tel_branora.dds] - alphaRejectValue = 128 - -[textures\tx_de_banner_tel_fyr.dds] - alphaRejectValue = 128 - -[textures\tx_de_banner_tel_mora.dds] - alphaRejectValue = 128 - -[textures\tx_de_banner_telvani_01.dds] - alphaRejectValue = 128 - -[textures\tx_de_banner_tel_vos.dds] - alphaRejectValue = 128 - -[textures\tx_de_banner_vos.dds] - alphaRejectValue = 128 - -[textures\tx_bannerd_w_a_shop_01.dds] - alphaRejectValue = 128 - -[textures\tx_banner_temple_02.dds] - alphaRejectValue = 128 - -[textures\tx_mural1_00.dds] - alphaRejectValue = 128 - -[textures\tx_mural1_01.dds] - alphaRejectValue = 128 - -[textures\tx_mural4_00.dds] - alphaRejectValue = 128 - -[textures\tx_mural4_01.dds] - alphaRejectValue = 128 - -[textures\tx_mural5_00.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_telvanni_01.dds] - alphaRejectValue = 128 - -[textures\tx_v_b_hlaalu_01.dds] - alphaRejectValue = 128 - -[textures\tx_fabric_tapestry.dds] - alphaRejectValue = 128 - -[textures\tx_fabric_tapestry_01.dds] - alphaRejectValue = 128 - -[textures\tx_fabric_tapestry_02.dds] - alphaRejectValue = 128 - -[textures\tx_fabric_tapestry_03.dds] - alphaRejectValue = 128 - -[textures\tx_fabric_tapestry_04.dds] - alphaRejectValue = 128 - -# characters -[textures\tx_netchgod00.dds] - alphaRejectValue = 128 - -[textures\tx_b_n_argonian_f_hair02.dds] - alphaRejectValue = 128 - -[textures\tx_b_n_argonian_f_hair03.dds] - alphaRejectValue = 128 - -[textures\tx_b_n_argonian_m_hair01.dds] - alphaRejectValue = 128 - -[textures\tx_b_n_argonian_m_hair04.dds] - alphaRejectValue = 128 - -[textures\tx_b_n_argonian_m_hair05.dds] - alphaRejectValue = 128 - -[textures\tx_b_n_khajiit_f_hair01.dds] - alphaRejectValue = 128 - -[textures\tx_b_n_khajiit_f_hair02.dds] - alphaRejectValue = 128 - -[textures\tx_b_n_khajiit_m_hair01.dds] - alphaRejectValue = 128 - -[textures\tx_corprus_stalker12.dds] - alphaRejectValue = 128 - -[textures\tx_a_clavicus02.dds] - alphaRejectValue = 128 - -[textures\tx_b_n_dark elf_m_hair11.dds] - alphaRejectValue = 128 - -[textures\tx_b_n_dark elf_f_hair10.dds] - alphaRejectValue = 128 - -# misc items -[textures\tx_sail.dds] - alphaRejectValue = 128 - -[textures\tx_longboatsail01.dds] - alphaRejectValue = 128 - -[textures\tx_longboatsail01a.dds] - alphaRejectValue = 128 - -[textures\tx_longboatsail01b.dds] - alphaRejectValue = 128 - -[textures\tx_longboatsail02.dds] - alphaRejectValue = 128 - -[textures\tx_quill.dds] - alphaRejectValue = 128 - -[textures\tx_note_01.dds] - alphaRejectValue = 128 - -[textures\tx_note_02.dds] - alphaRejectValue = 128 - -[textures\tx_parchment_02.dds] - alphaRejectValue = 128 - -[textures\tx_parchment_03.dds] - alphaRejectValue = 128 - -[textures\tx_scroll_01.dds] - alphaRejectValue = 128 - -[textures\tx_scroll_02.dds] - alphaRejectValue = 128 - -[textures\tx_scroll_03.dds] - alphaRejectValue = 128 - -[textures\tx_alpha_small_edge.dds] - alphaRejectValue = 128 - -[textures\tx_alpha_shadow_circular.dds] - alphaRejectValue = 128 - -# building materials -[textures\tx_shack_thatch_strip.dds] - alphaRejectValue = 128 - -[textures\tx_rug00.dds] - alphaRejectValue = 128 - -[textures\tx_rug_02.dds] - alphaRejectValue = 128 - -[textures\tx_rug_edge_01.dds] - alphaRejectValue = 128 - -[textures\tx_awning_thatch_02.dds] - alphaRejectValue = 128 - -[textures\tx_awning_woven_01.dds] - alphaRejectValue = 128 - -[textures\tx_bridgeropes.dds] - alphaRejectValue = 128 - -[textures\tx_rope_woven_01.dds] - alphaRejectValue = 128 - -[textures\tx_rope_woven_02.dds] - alphaRejectValue = 128 - -[textures\tx_ashl_tent_06.dds] - alphaRejectValue = 128 - -[textures\tx_guar_tarp.dds] - alphaRejectValue = 128 - -[textures\tx_velothi_glyph00.dds] - alphaRejectValue = 128 - - - -# Bloodmoon - -[textures\tx_bm_holly_01.dds] - alphaRejectValue = 128 - -[textures\tx_bm_holly_snow_01.dds] - alphaRejectValue = 128 - -[textures\tx_bm_pine_04a.dds] - alphaRejectValue = 128 - -[textures\tx_bm_pine_03a.dds] - alphaRejectValue = 128 - -[textures\tx_bm_pine_02a.dds] - alphaRejectValue = 128 - -[textures\tx_bm_pine_01a.dds] - alphaRejectValue = 128 - -[textures\tx_bm_shrub_02.dds] - alphaRejectValue = 128 - -[textures\tx_bm_shrub_01.dds] - alphaRejectValue = 128 - -[textures\tx_bm_snow_pine_01a.dds] - alphaRejectValue = 128 - -[textures\tx_bm_snow_pine_02a.dds] - alphaRejectValue = 128 - -[textures\tx_bm_snow_pine_03a.dds] - alphaRejectValue = 128 - -[textures\tx_bm_snow_pine_04a.dds] - alphaRejectValue = 128 - -[textures\tx_bm_deadpine_01.dds] - alphaRejectValue = 128 - -[textures\tx_bm_shrub_snow_02.dds] - alphaRejectValue = 128 - -[textures\tx_bm_s_deadpine_01.dds] - alphaRejectValue = 128 diff --git a/files/ui/graphicspage.ui b/files/ui/graphicspage.ui index f9ea63efe6..0afda6ac7a 100644 --- a/files/ui/graphicspage.ui +++ b/files/ui/graphicspage.ui @@ -6,30 +6,11 @@ 0 0 - 332 - 297 + 437 + 343
- - - - Render System - - - - - - Rendering Subsystem: - - - - - - - - - @@ -82,7 +63,33 @@ - + + + + 0 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + diff --git a/files/version.in b/files/version.in new file mode 100644 index 0000000000..f894d3b4a5 --- /dev/null +++ b/files/version.in @@ -0,0 +1,3 @@ +@OPENMW_VERSION@ +@OPENMW_VERSION_COMMITHASH@ +@OPENMW_VERSION_TAGHASH@ diff --git a/files/water/circle.png b/files/water/circle.png deleted file mode 100644 index 9a1cf268c0..0000000000 Binary files a/files/water/circle.png and /dev/null differ diff --git a/libs/openengine/.gitignore b/libs/openengine/.gitignore deleted file mode 100644 index 23a5e931b8..0000000000 --- a/libs/openengine/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*~ -*.o -*_test diff --git a/libs/openengine/CMakeLists.txt b/libs/openengine/CMakeLists.txt deleted file mode 100644 index 3542becf64..0000000000 --- a/libs/openengine/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -set(OENGINE_OGRE - ogre/renderer.cpp - ogre/lights.cpp - ogre/selectionbuffer.cpp -) - -set(OENGINE_GUI - gui/loglistener.cpp - gui/manager.cpp - gui/layout.cpp -) - -set(OENGINE_BULLET - bullet/BtOgre.cpp - bullet/BtOgreExtras.h - bullet/BtOgreGP.h - bullet/BtOgrePG.h - bullet/physic.cpp - bullet/physic.hpp - bullet/BulletShapeLoader.cpp - bullet/BulletShapeLoader.h - bullet/trace.cpp - bullet/trace.h -) - -set(OENGINE_MISC - misc/rng.cpp - misc/rng.hpp -) - -set(OENGINE_ALL ${OENGINE_OGRE} ${OENGINE_GUI} ${OENGINE_BULLET} ${OENGINE_MISC}) - -set(OENGINE_LIBRARY "oengine") -set(OENGINE_LIBRARY ${OENGINE_LIBRARY} PARENT_SCOPE) - -source_group(oengine FILES ${OENGINE_ALL}) - -add_library(${OENGINE_LIBRARY} STATIC ${OENGINE_ALL}) diff --git a/libs/openengine/README b/libs/openengine/README deleted file mode 100644 index 621fe8d60c..0000000000 --- a/libs/openengine/README +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 0af173adc2..0000000000 --- a/libs/openengine/bullet/BtOgre.cpp +++ /dev/null @@ -1,1096 +0,0 @@ -/* - * ============================================================================================= - * - * 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" - -#include -#include -#include - -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(std::make_pair(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.5f); - } - 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.z > 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.z > 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.0f / 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.5f*(fY0Max+fY0Min))*box_akAxis[0] + - (0.5f*(fY1Max+fY1Min))*box_akAxis[1] + - (0.5f*(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() - : mVertexBufferCapacity(0) - , mIndexBufferCapacity(0) - { - } - - //------------------------------------------------------------------------------------------------ - 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 deleted file mode 100644 index f8c1fe41d0..0000000000 --- a/libs/openengine/bullet/BtOgreExtras.h +++ /dev/null @@ -1,284 +0,0 @@ -/* - * ===================================================================================== - * - * 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"); - - //mLineDrawer->setVisibilityFlags (1024); - } - - ~DebugDrawer() - { - if (Ogre::MaterialManager::getSingleton().resourceExists("BtOgre/DebugLines")) - Ogre::MaterialManager::getSingleton().remove("BtOgre/DebugLines"); - if (Ogre::ResourceGroupManager::getSingleton().resourceGroupExists("BtOgre")) - 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 deleted file mode 100644 index 7e497b5352..0000000000 --- a/libs/openengine/bullet/BtOgreGP.h +++ /dev/null @@ -1,144 +0,0 @@ -/* - * ===================================================================================== - * - * 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 -#include -#include - -namespace BtOgre { - -typedef std::map BoneIndex; - -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 deleted file mode 100644 index 2e42fe1f91..0000000000 --- a/libs/openengine/bullet/BtOgrePG.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * ===================================================================================== - * - * 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 deleted file mode 100644 index 92d56b42c5..0000000000 --- a/libs/openengine/bullet/BulletShapeLoader.cpp +++ /dev/null @@ -1,159 +0,0 @@ -#include "BulletShapeLoader.h" - -namespace OEngine { -namespace Physic -{ - -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. - */ - mCollisionShape = NULL; - mRaycastingShape = NULL; - mAutogenerated = true; - mCollide = true; - createParamDictionary("BulletShape"); -} - -BulletShape::~BulletShape() -{ - deleteShape(mCollisionShape); - deleteShape(mRaycastingShape); -} - -// farm out to BulletShapeLoader -void BulletShape::loadImpl() -{ - mLoader->loadResource(this); -} - -void BulletShape::deleteShape(btCollisionShape* shape) -{ - if(shape!=NULL) - { - if(shape->isCompound()) - { - btCompoundShape* ms = static_cast(shape); - int a = ms->getNumChildShapes(); - for(int i=0; i getChildShape(i)); - } - } - delete shape; - } -} - -void BulletShape::unloadImpl() -{ - deleteShape(mCollisionShape); - deleteShape(mRaycastingShape); - mCollisionShape = NULL; - mRaycastingShape = NULL; -} - -//TODO:change this? -size_t BulletShape::calculateSize() const -{ - return 1; -} - - - -//============================================================================================================= -BulletShapeManager *BulletShapeManager::sThis = 0; - -BulletShapeManager *BulletShapeManager::getSingletonPtr() -{ - return sThis; -} - -BulletShapeManager &BulletShapeManager::getSingleton() -{ - assert(sThis); - return(*sThis); -} - -BulletShapeManager::BulletShapeManager() -{ - assert(!sThis); - sThis = this; - - 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); - - sThis = 0; -} - -BulletShapePtr BulletShapeManager::getByName(const Ogre::String& name, const Ogre::String& groupName) -{ - return getResourceByName(name, groupName).staticCast(); -} - -BulletShapePtr BulletShapeManager::create (const Ogre::String& name, const Ogre::String& group, - bool isManual, Ogre::ManualResourceLoader* loader, - const Ogre::NameValuePairList* createParams) -{ - return createResource(name,group,isManual,loader,createParams).staticCast(); -} - -Ogre::ResourcePtr BulletShapeManager::load(const Ogre::String &name, const Ogre::String &group, - bool isManual, Ogre::ManualResourceLoader *loader, const Ogre::NameValuePairList *loadParams, - bool backgroundThread) -{ - return this->load(name, group); -} - -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 deleted file mode 100644 index 907ff8bfe9..0000000000 --- a/libs/openengine/bullet/BulletShapeLoader.h +++ /dev/null @@ -1,139 +0,0 @@ -#ifndef OPENMW_BULLET_SHAPE_LOADER_H_ -#define OPENMW_BULLET_SHAPE_LOADER_H_ - -#include -#include -#include -#include - -namespace OEngine { -namespace Physic -{ - -/** -*Define a new resource which describe a Shape usable by bullet.See BulletShapeManager for how to get/use them. -*/ -class BulletShape : public Ogre::Resource -{ -protected: - void loadImpl(); - void unloadImpl(); - size_t calculateSize() const; - - void deleteShape(btCollisionShape* shape); - -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(); - - // Stores animated collision shapes. If any collision nodes in the NIF are animated, then mCollisionShape - // will be a btCompoundShape (which consists of one or more child shapes). - // In this map, for each animated collision shape, - // we store the node's record index mapped to the child index of the shape in the btCompoundShape. - std::map mAnimatedShapes; - - std::map mAnimatedRaycastingShapes; - - btCollisionShape* mCollisionShape; - btCollisionShape* mRaycastingShape; - - // Does this .nif have an autogenerated collision mesh? - bool mAutogenerated; - - Ogre::Vector3 mBoxTranslation; - Ogre::Quaternion mBoxRotation; - //this flag indicate if the shape is used for collision or if it's for raycasting only. - bool mCollide; -}; - -/** -* -*/ - -typedef Ogre::SharedPtr BulletShapePtr; - -/** -*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 -{ -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); - - static BulletShapeManager *sThis; - -private: - /** \brief Explicit private copy constructor. This is a forbidden operation.*/ - BulletShapeManager(const BulletShapeManager &); - - /** \brief Private operator= . This is a forbidden operation. */ - BulletShapeManager& operator=(const BulletShapeManager &); - - // Not intended to be used, declared here to keep the compiler from complaining - // about hidden virtual methods. - virtual Ogre::ResourcePtr load(const Ogre::String &name, const Ogre::String &group, - bool isManual, Ogre::ManualResourceLoader *loader, const Ogre::NameValuePairList *loadParams, - bool backgroundThread); - -public: - - BulletShapeManager(); - virtual ~BulletShapeManager(); - - - /// Get a resource by name - /// @see ResourceManager::getByName - BulletShapePtr getByName(const Ogre::String& name, - const Ogre::String& groupName = Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); - - /// Create a new shape - /// @see ResourceManager::createResource - BulletShapePtr create (const Ogre::String& name, const Ogre::String& group, - bool isManual = false, Ogre::ManualResourceLoader* loader = 0, - const Ogre::NameValuePairList* createParams = 0); - - 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/physic.cpp b/libs/openengine/bullet/physic.cpp deleted file mode 100644 index 15be7665a8..0000000000 --- a/libs/openengine/bullet/physic.cpp +++ /dev/null @@ -1,949 +0,0 @@ -#include "physic.hpp" - -#include -#include -#include - -#include -#include - -#include - -#include -#include - -#include "BtOgrePG.h" -#include "BtOgreGP.h" -#include "BtOgreExtras.h" - -namespace -{ - -// Create a copy of the given collision shape (responsibility of user to delete the returned shape). -btCollisionShape *duplicateCollisionShape(btCollisionShape *shape) -{ - if(shape->isCompound()) - { - btCompoundShape *comp = static_cast(shape); - btCompoundShape *newShape = new btCompoundShape; - - int numShapes = comp->getNumChildShapes(); - for(int i = 0;i < numShapes;i++) - { - btCollisionShape *child = duplicateCollisionShape(comp->getChildShape(i)); - btTransform trans = comp->getChildTransform(i); - newShape->addChildShape(trans, child); - } - - return newShape; - } - - if(btBvhTriangleMeshShape *trishape = dynamic_cast(shape)) - { - btTriangleMesh* oldMesh = static_cast(trishape->getMeshInterface()); - btTriangleMesh* newMesh = new btTriangleMesh(*oldMesh); - NifBullet::TriangleMeshShape *newShape = new NifBullet::TriangleMeshShape(newMesh, true); - - return newShape; - } - - throw std::logic_error(std::string("Unhandled Bullet shape duplication: ")+shape->getName()); -} - -void deleteShape(btCollisionShape* shape) -{ - if(shape!=NULL) - { - if(shape->isCompound()) - { - btCompoundShape* ms = static_cast(shape); - int a = ms->getNumChildShapes(); - for(int i=0; i getChildShape(i)); - } - } - delete shape; - } -} - -} - -namespace OEngine { -namespace Physic -{ - - PhysicActor::PhysicActor(const std::string &name, const std::string &mesh, PhysicEngine *engine, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, float scale) - : mCanWaterWalk(false), mWalkingOnWater(false) - , mBody(0), mScale(scale), mForce(0.0f), mOnGround(false) - , mInternalCollisionMode(true) - , mExternalCollisionMode(true) - , mMesh(mesh), mName(name), mEngine(engine) - { - if (!NifBullet::getBoundingBox(mMesh, mHalfExtents, mMeshTranslation, mMeshOrientation)) - { - mHalfExtents = Ogre::Vector3(0.f); - mMeshTranslation = Ogre::Vector3(0.f); - mMeshOrientation = Ogre::Quaternion::IDENTITY; - } - - // Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it) - if (std::abs(mHalfExtents.x-mHalfExtents.y)= mHalfExtents.x) - { - // Could also be btCapsuleShapeZ, but the movement solver seems to have issues with it (jumping on slopes doesn't work) - mShape.reset(new btCylinderShapeZ(BtOgre::Convert::toBullet(mHalfExtents))); - } - else - mShape.reset(new btBoxShape(BtOgre::Convert::toBullet(mHalfExtents))); - - mShape->setLocalScaling(btVector3(scale,scale,scale)); - - btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo - (0,0, mShape.get()); - mBody = new RigidBody(CI, name); - mBody->mPlaceable = false; - mBody->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); - mBody->setActivationState(DISABLE_DEACTIVATION); - - setPosition(position); - setRotation(rotation); - - updateCollisionMask(); - } - - PhysicActor::~PhysicActor() - { - if(mBody) - { - mEngine->mDynamicsWorld->removeRigidBody(mBody); - delete mBody; - } - } - - void PhysicActor::enableCollisionMode(bool collision) - { - mInternalCollisionMode = collision; - } - - void PhysicActor::enableCollisionBody(bool collision) - { - if (mExternalCollisionMode != collision) - { - mExternalCollisionMode = collision; - updateCollisionMask(); - } - } - - void PhysicActor::updateCollisionMask() - { - mEngine->mDynamicsWorld->removeRigidBody(mBody); - int collisionMask = CollisionType_World | CollisionType_HeightMap; - if (mExternalCollisionMode) - collisionMask |= CollisionType_Actor | CollisionType_Projectile; - if (mCanWaterWalk) - collisionMask |= CollisionType_Water; - mEngine->mDynamicsWorld->addRigidBody(mBody, CollisionType_Actor, collisionMask); - } - - const Ogre::Vector3& PhysicActor::getPosition() const - { - return mPosition; - } - - void PhysicActor::setPosition(const Ogre::Vector3 &position) - { - assert(mBody); - - mPosition = position; - - btTransform tr = mBody->getWorldTransform(); - Ogre::Quaternion meshrot = mMeshOrientation; - Ogre::Vector3 transrot = meshrot * (mMeshTranslation * mScale); - Ogre::Vector3 newPosition = transrot + position; - - tr.setOrigin(BtOgre::Convert::toBullet(newPosition)); - mBody->setWorldTransform(tr); - } - - void PhysicActor::setRotation (const Ogre::Quaternion& rotation) - { - btTransform tr = mBody->getWorldTransform(); - tr.setRotation(BtOgre::Convert::toBullet(mMeshOrientation * rotation)); - mBody->setWorldTransform(tr); - } - - void PhysicActor::setScale(float scale) - { - mScale = scale; - mShape->setLocalScaling(btVector3(scale,scale,scale)); - setPosition(mPosition); - } - - Ogre::Vector3 PhysicActor::getHalfExtents() const - { - return mHalfExtents * mScale; - } - - void PhysicActor::setInertialForce(const Ogre::Vector3 &force) - { - mForce = force; - } - - void PhysicActor::setOnGround(bool grounded) - { - mOnGround = grounded; - } - - bool PhysicActor::isWalkingOnWater() const - { - return mWalkingOnWater; - } - - void PhysicActor::setWalkingOnWater(bool walkingOnWater) - { - mWalkingOnWater = walkingOnWater; - } - - void PhysicActor::setCanWaterWalk(bool waterWalk) - { - if (waterWalk != mCanWaterWalk) - { - mCanWaterWalk = waterWalk; - updateCollisionMask(); - } - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - - RigidBody::RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name) - : btRigidBody(CI) - , mName(name) - , mPlaceable(false) - { - } - - RigidBody::~RigidBody() - { - delete getMotionState(); - } - - - - /////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////// - - - - PhysicEngine::PhysicEngine(BulletShapeLoader* shapeLoader) : - mSceneMgr(NULL) - , mDebugActive(0) - { - // Set up the collision configuration and dispatcher - collisionConfiguration = new btDefaultCollisionConfiguration(); - dispatcher = new btCollisionDispatcher(collisionConfiguration); - - // The actual physics solver - solver = new btSequentialImpulseConstraintSolver; - - broadphase = new btDbvtBroadphase(); - - // The world. - mDynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration); - - // Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this. - // Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb. - mDynamicsWorld->setForceUpdateAllAabbs(false); - - mDynamicsWorld->setGravity(btVector3(0,0,-10)); - - if(BulletShapeManager::getSingletonPtr() == NULL) - { - new BulletShapeManager(); - } - //TODO:singleton? - mShapeLoader = shapeLoader; - - isDebugCreated = false; - mDebugDrawer = NULL; - } - - void PhysicEngine::createDebugRendering() - { - if(!isDebugCreated) - { - Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - mDebugDrawer = new BtOgre::DebugDrawer(node, mDynamicsWorld); - mDynamicsWorld->setDebugDrawer(mDebugDrawer); - isDebugCreated = true; - mDynamicsWorld->debugDrawWorld(); - } - } - - void PhysicEngine::setDebugRenderingMode(bool isDebug) - { - if(!isDebugCreated) - { - createDebugRendering(); - } - mDebugDrawer->setDebugMode(isDebug); - mDebugActive = isDebug; - } - - bool PhysicEngine::toggleDebugRendering() - { - setDebugRenderingMode(!mDebugActive); - return mDebugActive; - } - - void PhysicEngine::setSceneManager(Ogre::SceneManager* sceneMgr) - { - mSceneMgr = sceneMgr; - } - - PhysicEngine::~PhysicEngine() - { - for (std::map::iterator it = mAnimatedShapes.begin(); it != mAnimatedShapes.end(); ++it) - deleteShape(it->second.mCompound); - for (std::map::iterator it = mAnimatedRaycastingShapes.begin(); it != mAnimatedRaycastingShapes.end(); ++it) - deleteShape(it->second.mCompound); - - HeightFieldContainer::iterator hf_it = mHeightFieldMap.begin(); - for (; hf_it != mHeightFieldMap.end(); ++hf_it) - { - mDynamicsWorld->removeRigidBody(hf_it->second.mBody); - delete hf_it->second.mShape; - delete hf_it->second.mBody; - } - - RigidBodyContainer::iterator rb_it = mCollisionObjectMap.begin(); - for (; rb_it != mCollisionObjectMap.end(); ++rb_it) - { - if (rb_it->second != NULL) - { - mDynamicsWorld->removeRigidBody(rb_it->second); - - delete rb_it->second; - rb_it->second = NULL; - } - } - rb_it = mRaycastingObjectMap.begin(); - for (; rb_it != mRaycastingObjectMap.end(); ++rb_it) - { - if (rb_it->second != NULL) - { - mDynamicsWorld->removeRigidBody(rb_it->second); - - delete rb_it->second; - rb_it->second = NULL; - } - } - - PhysicActorContainer::iterator pa_it = mActorMap.begin(); - for (; pa_it != mActorMap.end(); ++pa_it) - { - if (pa_it->second != NULL) - { - delete pa_it->second; - pa_it->second = NULL; - } - } - - delete mDebugDrawer; - - delete mDynamicsWorld; - delete solver; - delete collisionConfiguration; - delete dispatcher; - delete broadphase; - delete mShapeLoader; - - // Moved the cleanup to mwworld/physicssystem - //delete BulletShapeManager::getSingletonPtr(); - } - - 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 = heights[0]; - float maxh = heights[0]; - for (int i=0; imaxh) maxh = h; - if (h(sqrtVerts), static_cast(sqrtVerts), heights, 1, - minh, maxh, 2, - PHY_FLOAT,true); - - hfShape->setUseDiamondSubdivision(true); - - btVector3 scl(triSize, triSize, 1); - hfShape->setLocalScaling(scl); - - btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo(0,0,hfShape); - RigidBody* body = new RigidBody(CI,name); - body->getWorldTransform().setOrigin(btVector3( (x+0.5f)*triSize*(sqrtVerts-1), (y+0.5f)*triSize*(sqrtVerts-1), (maxh+minh)/2.f)); - - HeightField hf; - hf.mBody = body; - hf.mShape = hfShape; - - mHeightFieldMap [name] = hf; - - mDynamicsWorld->addRigidBody(body,CollisionType_HeightMap, - CollisionType_Actor|CollisionType_Raycasting|CollisionType_Projectile); - } - - void PhysicEngine::removeHeightField(int x, int y) - { - const std::string name = "HeightField_" - + boost::lexical_cast(x) + "_" - + boost::lexical_cast(y); - - HeightFieldContainer::iterator it = mHeightFieldMap.find(name); - if (it == mHeightFieldMap.end()) - return; - - const HeightField& hf = it->second; - - mDynamicsWorld->removeRigidBody(hf.mBody); - delete hf.mShape; - delete hf.mBody; - - mHeightFieldMap.erase(it); - } - - void PhysicEngine::adjustRigidBody(RigidBody* body, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, - const Ogre::Vector3 &scaledBoxTranslation, const Ogre::Quaternion &boxRotation) - { - btTransform tr; - Ogre::Quaternion boxrot = rotation * boxRotation; - Ogre::Vector3 transrot = boxrot * scaledBoxTranslation; - Ogre::Vector3 newPosition = transrot + position; - - tr.setOrigin(btVector3(newPosition.x, newPosition.y, newPosition.z)); - tr.setRotation(btQuaternion(boxrot.x,boxrot.y,boxrot.z,boxrot.w)); - body->setWorldTransform(tr); - } - void PhysicEngine::boxAdjustExternal(const std::string &mesh, RigidBody* body, - float scale, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation) - { - std::string sid = (boost::format("%07.3f") % scale).str(); - std::string outputstring = mesh + sid; - - //get the shape from the .nif - mShapeLoader->load(outputstring,"General"); - BulletShapeManager::getSingletonPtr()->load(outputstring,"General"); - BulletShapePtr shape = BulletShapeManager::getSingleton().getByName(outputstring,"General"); - - adjustRigidBody(body, position, rotation, shape->mBoxTranslation * scale, shape->mBoxRotation); - } - - RigidBody* PhysicEngine::createAndAdjustRigidBody(const std::string &mesh, const std::string &name, - float scale, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, - Ogre::Vector3* scaledBoxTranslation, Ogre::Quaternion* boxRotation, bool raycasting, bool placeable) - { - std::string sid = (boost::format("%07.3f") % scale).str(); - std::string outputstring = mesh + sid; - - //get the shape from the .nif - mShapeLoader->load(outputstring,"General"); - BulletShapeManager::getSingletonPtr()->load(outputstring,"General"); - BulletShapePtr shape = BulletShapeManager::getSingleton().getByName(outputstring,"General"); - - // TODO: add option somewhere to enable collision for placeable meshes - - if (placeable && !raycasting && shape->mCollisionShape) - return NULL; - - if (!shape->mCollisionShape && !raycasting) - return NULL; - if (!shape->mRaycastingShape && raycasting) - return NULL; - - btCollisionShape* collisionShape = raycasting ? shape->mRaycastingShape : shape->mCollisionShape; - - // If this is an animated compound shape, we must duplicate it so we can animate - // multiple instances independently. - if (!raycasting && !shape->mAnimatedShapes.empty()) - collisionShape = duplicateCollisionShape(collisionShape); - if (raycasting && !shape->mAnimatedRaycastingShapes.empty()) - collisionShape = duplicateCollisionShape(collisionShape); - - collisionShape->setLocalScaling( btVector3(scale,scale,scale)); - - //create the real body - btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo - (0,0, collisionShape); - RigidBody* body = new RigidBody(CI,name); - body->mPlaceable = placeable; - - if (!raycasting && !shape->mAnimatedShapes.empty()) - { - AnimatedShapeInstance instance; - instance.mAnimatedShapes = shape->mAnimatedShapes; - instance.mCompound = collisionShape; - mAnimatedShapes[body] = instance; - } - if (raycasting && !shape->mAnimatedRaycastingShapes.empty()) - { - AnimatedShapeInstance instance; - instance.mAnimatedShapes = shape->mAnimatedRaycastingShapes; - instance.mCompound = collisionShape; - mAnimatedRaycastingShapes[body] = instance; - } - - if(scaledBoxTranslation != 0) - *scaledBoxTranslation = shape->mBoxTranslation * scale; - if(boxRotation != 0) - *boxRotation = shape->mBoxRotation; - - adjustRigidBody(body, position, rotation, shape->mBoxTranslation * scale, shape->mBoxRotation); - - if (!raycasting) - { - assert (mCollisionObjectMap.find(name) == mCollisionObjectMap.end()); - mCollisionObjectMap[name] = body; - mDynamicsWorld->addRigidBody(body,CollisionType_World,CollisionType_Actor|CollisionType_HeightMap); - } - else - { - assert (mRaycastingObjectMap.find(name) == mRaycastingObjectMap.end()); - mRaycastingObjectMap[name] = body; - mDynamicsWorld->addRigidBody(body,CollisionType_Raycasting,CollisionType_Raycasting|CollisionType_Projectile); - body->setCollisionFlags(body->getCollisionFlags() | btCollisionObject::CF_DISABLE_VISUALIZE_OBJECT); - } - - return body; - } - - void PhysicEngine::removeRigidBody(const std::string &name) - { - RigidBodyContainer::iterator it = mCollisionObjectMap.find(name); - if (it != mCollisionObjectMap.end() ) - { - RigidBody* body = it->second; - if(body != NULL) - { - mDynamicsWorld->removeRigidBody(body); - } - } - it = mRaycastingObjectMap.find(name); - if (it != mRaycastingObjectMap.end() ) - { - RigidBody* body = it->second; - if(body != NULL) - { - mDynamicsWorld->removeRigidBody(body); - } - } - } - - void PhysicEngine::deleteRigidBody(const std::string &name) - { - RigidBodyContainer::iterator it = mCollisionObjectMap.find(name); - if (it != mCollisionObjectMap.end() ) - { - RigidBody* body = it->second; - - if(body != NULL) - { - if (mAnimatedShapes.find(body) != mAnimatedShapes.end()) - deleteShape(mAnimatedShapes[body].mCompound); - mAnimatedShapes.erase(body); - - delete body; - } - mCollisionObjectMap.erase(it); - } - it = mRaycastingObjectMap.find(name); - if (it != mRaycastingObjectMap.end() ) - { - RigidBody* body = it->second; - - if(body != NULL) - { - if (mAnimatedRaycastingShapes.find(body) != mAnimatedRaycastingShapes.end()) - deleteShape(mAnimatedRaycastingShapes[body].mCompound); - mAnimatedRaycastingShapes.erase(body); - - delete body; - } - mRaycastingObjectMap.erase(it); - } - } - - RigidBody* PhysicEngine::getRigidBody(const std::string &name, bool raycasting) - { - RigidBodyContainer* map = raycasting ? &mRaycastingObjectMap : &mCollisionObjectMap; - RigidBodyContainer::iterator it = map->find(name); - if (it != map->end() ) - { - RigidBody* body = (*map)[name]; - return body; - } - else - { - return NULL; - } - } - - class ContactTestResultCallback : public btCollisionWorld::ContactResultCallback - { - public: - std::vector mResult; - - // added in bullet 2.81 - // this is just a quick hack, as there does not seem to be a BULLET_VERSION macro? -#if defined(BT_COLLISION_OBJECT_WRAPPER_H) - virtual btScalar addSingleResult(btManifoldPoint& cp, - const btCollisionObjectWrapper* colObj0Wrap,int partId0,int index0, - const btCollisionObjectWrapper* colObj1Wrap,int partId1,int index1) - { - const RigidBody* body = dynamic_cast(colObj0Wrap->m_collisionObject); - if (body && !(colObj0Wrap->m_collisionObject->getBroadphaseHandle()->m_collisionFilterGroup - & CollisionType_Raycasting)) - mResult.push_back(body->mName); - - return 0.f; - } -#else - virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObject* col0, int partId0, int index0, - const btCollisionObject* col1, int partId1, int index1) - { - const RigidBody* body = dynamic_cast(col0); - if (body && !(col0->getBroadphaseHandle()->m_collisionFilterGroup - & CollisionType_Raycasting)) - mResult.push_back(body->mName); - - return 0.f; - } -#endif - }; - - class DeepestNotMeContactTestResultCallback : public btCollisionWorld::ContactResultCallback - { - const std::string &mFilter; - // Store the real origin, since the shape's origin is its center - btVector3 mOrigin; - - public: - const RigidBody *mObject; - btVector3 mContactPoint; - btScalar mLeastDistSqr; - - DeepestNotMeContactTestResultCallback(const std::string &filter, const btVector3 &origin) - : mFilter(filter), mOrigin(origin), mObject(0), mContactPoint(0,0,0), - mLeastDistSqr(std::numeric_limits::max()) - { } - -#if defined(BT_COLLISION_OBJECT_WRAPPER_H) - virtual btScalar addSingleResult(btManifoldPoint& cp, - const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, - const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) - { - const RigidBody* body = dynamic_cast(col1Wrap->m_collisionObject); - if(body && body->mName != mFilter) - { - btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); - if(!mObject || distsqr < mLeastDistSqr) - { - mObject = body; - mLeastDistSqr = distsqr; - mContactPoint = cp.getPositionWorldOnA(); - } - } - - return 0.f; - } -#else - virtual btScalar addSingleResult(btManifoldPoint& cp, - const btCollisionObject* col0, int partId0, int index0, - const btCollisionObject* col1, int partId1, int index1) - { - const RigidBody* body = dynamic_cast(col1); - if(body && body->mName != mFilter) - { - btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); - if(!mObject || distsqr < mLeastDistSqr) - { - mObject = body; - mLeastDistSqr = distsqr; - mContactPoint = cp.getPositionWorldOnA(); - } - } - - return 0.f; - } -#endif - }; - - - std::vector PhysicEngine::getCollisions(const std::string& name, int collisionGroup, int collisionMask) - { - RigidBody* body = getRigidBody(name); - if (!body) // fall back to raycasting body if there is no collision body - body = getRigidBody(name, true); - ContactTestResultCallback callback; - callback.m_collisionFilterGroup = collisionGroup; - callback.m_collisionFilterMask = collisionMask; - mDynamicsWorld->contactTest(body, callback); - return callback.mResult; - } - - - std::pair PhysicEngine::getFilteredContact(const std::string &filter, - const btVector3 &origin, - btCollisionObject *object) - { - DeepestNotMeContactTestResultCallback callback(filter, origin); - callback.m_collisionFilterGroup = CollisionType_Actor; - callback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor; - mDynamicsWorld->contactTest(object, callback); - return std::make_pair(callback.mObject, callback.mContactPoint); - } - - void PhysicEngine::stepSimulation(double deltaT) - { - // This seems to be needed for character controller objects - mDynamicsWorld->stepSimulation(static_cast(deltaT), 10, 1 / 60.0f); - if(isDebugCreated) - { - mDebugDrawer->step(); - } - } - - void PhysicEngine::addCharacter(const std::string &name, const std::string &mesh, - const Ogre::Vector3 &position, float scale, const Ogre::Quaternion &rotation) - { - // Remove character with given name, so we don't make memory - // leak when character would be added twice - removeCharacter(name); - - PhysicActor* newActor = new PhysicActor(name, mesh, this, position, rotation, scale); - - mActorMap[name] = newActor; - } - - void PhysicEngine::removeCharacter(const std::string &name) - { - PhysicActorContainer::iterator it = mActorMap.find(name); - if (it != mActorMap.end() ) - { - PhysicActor* act = it->second; - if(act != NULL) - { - delete act; - } - mActorMap.erase(it); - } - } - - PhysicActor* PhysicEngine::getCharacter(const std::string &name) - { - PhysicActorContainer::iterator it = mActorMap.find(name); - if (it != mActorMap.end() ) - { - PhysicActor* act = mActorMap[name]; - return act; - } - else - { - return 0; - } - } - - std::pair PhysicEngine::rayTest(const btVector3 &from, const btVector3 &to, bool raycastingObjectOnly, bool ignoreHeightMap, Ogre::Vector3* normal) - { - std::string name = ""; - float d = -1; - - btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); - resultCallback1.m_collisionFilterGroup = 0xff; - if(raycastingObjectOnly) - resultCallback1.m_collisionFilterMask = CollisionType_Raycasting|CollisionType_Actor; - else - resultCallback1.m_collisionFilterMask = CollisionType_World; - - if(!ignoreHeightMap) - resultCallback1.m_collisionFilterMask = resultCallback1.m_collisionFilterMask | CollisionType_HeightMap; - mDynamicsWorld->rayTest(from, to, resultCallback1); - if (resultCallback1.hasHit()) - { - name = static_cast(*resultCallback1.m_collisionObject).mName; - d = resultCallback1.m_closestHitFraction; - if (normal) - *normal = Ogre::Vector3(resultCallback1.m_hitNormalWorld.x(), - resultCallback1.m_hitNormalWorld.y(), - resultCallback1.m_hitNormalWorld.z()); - } - - return std::pair(name,d); - } - - // callback that ignores player in results - struct OurClosestConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback - { - public: - OurClosestConvexResultCallback(const btVector3& convexFromWorld,const btVector3& convexToWorld) - : btCollisionWorld::ClosestConvexResultCallback(convexFromWorld, convexToWorld) {} - - virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) - { - if (const RigidBody* body = dynamic_cast(convexResult.m_hitCollisionObject)) - if (body->mName == "player") - return 0; - return btCollisionWorld::ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace); - } - }; - - std::pair PhysicEngine::sphereCast (float radius, btVector3& from, btVector3& to) - { - OurClosestConvexResultCallback callback(from, to); - callback.m_collisionFilterGroup = 0xff; - callback.m_collisionFilterMask = OEngine::Physic::CollisionType_World|OEngine::Physic::CollisionType_HeightMap; - - btSphereShape shape(radius); - const btQuaternion btrot(0.0f, 0.0f, 0.0f); - - btTransform from_ (btrot, from); - btTransform to_ (btrot, to); - - mDynamicsWorld->convexSweepTest(&shape, from_, to_, callback); - - if (callback.hasHit()) - return std::make_pair(true, callback.m_closestHitFraction); - else - return std::make_pair(false, 1.0f); - } - - std::vector< std::pair > PhysicEngine::rayTest2(const btVector3& from, const btVector3& to, int filterGroup) - { - MyRayResultCallback resultCallback1; - resultCallback1.m_collisionFilterGroup = filterGroup; - resultCallback1.m_collisionFilterMask = CollisionType_Raycasting|CollisionType_Actor|CollisionType_HeightMap; - mDynamicsWorld->rayTest(from, to, resultCallback1); - std::vector< std::pair > results = resultCallback1.results; - - std::vector< std::pair > results2; - - for (std::vector< std::pair >::iterator it=results.begin(); - it != results.end(); ++it) - { - results2.push_back( std::make_pair( (*it).first, static_cast(*(*it).second).mName ) ); - } - - std::sort(results2.begin(), results2.end(), MyRayResultCallback::cmp); - - return results2; - } - - void PhysicEngine::getObjectAABB(const std::string &mesh, float scale, btVector3 &min, btVector3 &max) - { - std::string sid = (boost::format("%07.3f") % scale).str(); - std::string outputstring = mesh + sid; - - mShapeLoader->load(outputstring, "General"); - BulletShapeManager::getSingletonPtr()->load(outputstring, "General"); - BulletShapePtr shape = - BulletShapeManager::getSingleton().getByName(outputstring, "General"); - - btTransform trans; - trans.setIdentity(); - - if (shape->mRaycastingShape) - shape->mRaycastingShape->getAabb(trans, min, max); - else if (shape->mCollisionShape) - shape->mCollisionShape->getAabb(trans, min, max); - else - { - min = btVector3(0,0,0); - max = btVector3(0,0,0); - } - } - - int PhysicEngine::toggleDebugRendering(Ogre::SceneManager *sceneMgr) - { - if(!sceneMgr) - return 0; - - std::map::iterator iter = - mDebugDrawers.find(sceneMgr); - if(iter != mDebugDrawers.end()) // found scene manager - { - if((*iter).second) - { - // set a new drawer each time (maybe with a different scene manager) - mDynamicsWorld->setDebugDrawer(mDebugDrawers[sceneMgr]); - if(!mDebugDrawers[sceneMgr]->getDebugMode()) - mDebugDrawers[sceneMgr]->setDebugMode(1 /*mDebugDrawFlags*/); - else - mDebugDrawers[sceneMgr]->setDebugMode(0); - mDynamicsWorld->debugDrawWorld(); - - return mDebugDrawers[sceneMgr]->getDebugMode(); - } - } - return 0; - } - - void PhysicEngine::stepDebug(Ogre::SceneManager *sceneMgr) - { - if(!sceneMgr) - return; - - std::map::iterator iter = - mDebugDrawers.find(sceneMgr); - if(iter != mDebugDrawers.end()) // found scene manager - { - if((*iter).second) - (*iter).second->step(); - else - return; - } - } - - void PhysicEngine::createDebugDraw(Ogre::SceneManager *sceneMgr) - { - if(mDebugDrawers.find(sceneMgr) == mDebugDrawers.end()) - { - mDebugSceneNodes[sceneMgr] = sceneMgr->getRootSceneNode()->createChildSceneNode(); - mDebugDrawers[sceneMgr] = new BtOgre::DebugDrawer(mDebugSceneNodes[sceneMgr], mDynamicsWorld); - mDebugDrawers[sceneMgr]->setDebugMode(0); - } - } - - void PhysicEngine::removeDebugDraw(Ogre::SceneManager *sceneMgr) - { - std::map::iterator iter = - mDebugDrawers.find(sceneMgr); - if(iter != mDebugDrawers.end()) - { - delete (*iter).second; - mDebugDrawers.erase(iter); - } - - std::map::iterator it = - mDebugSceneNodes.find(sceneMgr); - if(it != mDebugSceneNodes.end()) - { - std::string sceneNodeName = (*it).second->getName(); - if(sceneMgr->hasSceneNode(sceneNodeName)) - sceneMgr->destroySceneNode(sceneNodeName); - } - } - -} -} diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp deleted file mode 100644 index 7784e8941d..0000000000 --- a/libs/openengine/bullet/physic.hpp +++ /dev/null @@ -1,385 +0,0 @@ -#ifndef OENGINE_BULLET_PHYSIC_H -#define OENGINE_BULLET_PHYSIC_H - -#include -#include "BulletCollision/CollisionDispatch/btGhostObject.h" -#include -#include -#include -#include "BulletShapeLoader.h" -#include "BulletCollision/CollisionShapes/btScaledBvhTriangleMeshShape.h" -#include - -class btRigidBody; -class btBroadphaseInterface; -class btDefaultCollisionConfiguration; -class btSequentialImpulseConstraintSolver; -class btCollisionDispatcher; -class btDiscreteDynamicsWorld; -class btHeightfieldTerrainShape; - -namespace BtOgre -{ - class DebugDrawer; -} - -namespace Ogre -{ - class SceneManager; -} - -namespace MWWorld -{ - class World; -} - - -namespace OEngine { -namespace Physic -{ - struct PhysicEvent; - class PhysicEngine; - class RigidBody; - - enum CollisionType { - CollisionType_Nothing = 0, // mShape; - - OEngine::Physic::RigidBody* mBody; - - Ogre::Quaternion mMeshOrientation; - Ogre::Vector3 mMeshTranslation; - Ogre::Vector3 mHalfExtents; - - float mScale; - Ogre::Vector3 mPosition; - - Ogre::Vector3 mForce; - bool mOnGround; - bool mInternalCollisionMode; - bool mExternalCollisionMode; - - std::string mMesh; - std::string mName; - PhysicEngine *mEngine; - - PhysicActor(const PhysicActor&); - PhysicActor& operator=(const PhysicActor&); - }; - - - struct HeightField - { - btHeightfieldTerrainShape* mShape; - RigidBody* mBody; - }; - - struct AnimatedShapeInstance - { - btCollisionShape* mCompound; - - // Maps node record index to child index in the compound shape - std::map mAnimatedShapes; - }; - - /** - * 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(); - - /** - * Creates a RigidBody. It does not add it to the simulation. - * After created, the body is set to the correct rotation, position, and scale - */ - RigidBody* createAndAdjustRigidBody(const std::string &mesh, const std::string &name, - float scale, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, - Ogre::Vector3* scaledBoxTranslation = 0, Ogre::Quaternion* boxRotation = 0, bool raycasting=false, bool placeable=false); - - /** - * Adjusts a rigid body to the right position and rotation - */ - - void adjustRigidBody(RigidBody* body, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, - const Ogre::Vector3 &scaledBoxTranslation = Ogre::Vector3::ZERO, - const Ogre::Quaternion &boxRotation = Ogre::Quaternion::IDENTITY); - /** - Mainly used to (but not limited to) adjust rigid bodies based on box shapes to the right position and rotation. - */ - void boxAdjustExternal(const std::string &mesh, RigidBody* body, float scale, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation); - /** - * 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); - - /** - * Remove a RigidBody from the simulation. It does not delete it, and does not remove it from the RigidBodyMap. - */ - void removeRigidBody(const std::string &name); - - /** - * Delete a RigidBody, and remove it from RigidBodyMap. - */ - void deleteRigidBody(const std::string &name); - - /** - * Return a pointer to a given rigid body. - */ - RigidBody* getRigidBody(const std::string &name, bool raycasting=false); - - /** - * Create and add a character to the scene, and add it to the ActorMap. - */ - void addCharacter(const std::string &name, const std::string &mesh, - const Ogre::Vector3 &position, float scale, const Ogre::Quaternion &rotation); - - /** - * Remove a character from the scene. - */ - void removeCharacter(const std::string &name); - - /** - * Return a pointer to a character - */ - PhysicActor* getCharacter(const std::string &name); - - /** - * This step the simulation of a given time. - */ - void stepSimulation(double deltaT); - - /** - * 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(bool isDebug); - - bool toggleDebugRendering(); - - void getObjectAABB(const std::string &mesh, float scale, btVector3 &min, btVector3 &max); - - void setSceneManager(Ogre::SceneManager* sceneMgr); - - /** - * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). - * If \a normal is non-NULL, the hit normal will be written there (if there is a hit) - */ - std::pair rayTest(const btVector3& from,const btVector3& to,bool raycastingObjectOnly = true, - bool ignoreHeightMap = false, Ogre::Vector3* normal = NULL); - - /** - * Return all objects hit by a ray. - */ - std::vector< std::pair > rayTest2(const btVector3 &from, const btVector3 &to, int filterGroup=0xff); - - std::pair sphereCast (float radius, btVector3& from, btVector3& to); - ///< @return (hit, relative distance) - - std::vector getCollisions(const std::string& name, int collisionGroup, int collisionMask); - - // Get the nearest object that's inside the given object, filtering out objects of the - // provided name - std::pair getFilteredContact(const std::string &filter, - const btVector3 &origin, - btCollisionObject *object); - - //Bullet Stuff - btBroadphaseInterface* broadphase; - btDefaultCollisionConfiguration* collisionConfiguration; - btSequentialImpulseConstraintSolver* solver; - btCollisionDispatcher* dispatcher; - btDiscreteDynamicsWorld* mDynamicsWorld; - - //the NIF file loader. - BulletShapeLoader* mShapeLoader; - - typedef std::map HeightFieldContainer; - HeightFieldContainer mHeightFieldMap; - - typedef std::map RigidBodyContainer; - RigidBodyContainer mCollisionObjectMap; - - // Compound shapes that must be animated each frame based on bone positions - // the index refers to an element in mCollisionObjectMap - std::map mAnimatedShapes; - - RigidBodyContainer mRaycastingObjectMap; - - std::map mAnimatedRaycastingShapes; - - typedef std::map PhysicActorContainer; - PhysicActorContainer mActorMap; - - Ogre::SceneManager* mSceneMgr; - - //debug rendering - BtOgre::DebugDrawer* mDebugDrawer; - bool isDebugCreated; - bool mDebugActive; - - // for OpenCS with multiple engines per document - std::map mDebugDrawers; - std::map mDebugSceneNodes; - - int toggleDebugRendering(Ogre::SceneManager *sceneMgr); - void stepDebug(Ogre::SceneManager *sceneMgr); - void createDebugDraw(Ogre::SceneManager *sceneMgr); - void removeDebugDraw(Ogre::SceneManager *sceneMgr); - - private: - PhysicEngine(const PhysicEngine&); - PhysicEngine& operator=(const PhysicEngine&); - }; - - - struct MyRayResultCallback : public btCollisionWorld::RayResultCallback - { - virtual btScalar addSingleResult( btCollisionWorld::LocalRayResult& rayResult, bool bNormalInWorldSpace) - { - results.push_back( std::make_pair(rayResult.m_hitFraction, rayResult.m_collisionObject) ); - return rayResult.m_hitFraction; - } - - static bool cmp( const std::pair& i, const std::pair& j ) - { - if( i.first > j.first ) return false; - if( j.first > i.first ) return true; - return false; - } - - std::vector < std::pair > results; - }; - -}} - -#endif diff --git a/libs/openengine/bullet/trace.h b/libs/openengine/bullet/trace.h deleted file mode 100644 index f499f4a27c..0000000000 --- a/libs/openengine/bullet/trace.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef OENGINE_BULLET_TRACE_H -#define OENGINE_BULLET_TRACE_H - -#include - - -class btCollisionObject; - - -namespace OEngine -{ -namespace Physic -{ - class PhysicEngine; - class PhysicActor; - - struct ActorTracer - { - Ogre::Vector3 mEndPos; - Ogre::Vector3 mPlaneNormal; - const btCollisionObject* mHitObject; - - float mFraction; - - void doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, - const PhysicEngine *enginePass); - void findGround(const OEngine::Physic::PhysicActor* actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, - const PhysicEngine *enginePass); - }; -} -} - -#endif diff --git a/libs/openengine/gui/loglistener.hpp b/libs/openengine/gui/loglistener.hpp deleted file mode 100644 index 47978ba447..0000000000 --- a/libs/openengine/gui/loglistener.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef OPENENGINE_MYGUI_LOGLISTENER_H -#define OPENENGINE_MYGUI_LOGLISTENER_H - -#include -#include - -#include - -namespace MyGUI -{ - /// \brief Custom MyGUI::ILogListener interface implementation - /// being able to portably handle UTF-8 encoded path. - class CustomLogListener : public ILogListener - { - public: - CustomLogListener(const std::string &name) - : mFileName(name) - {} - - ~CustomLogListener() {} - - virtual void open(); - virtual void close(); - virtual void flush(); - - virtual void log(const std::string& _section, LogLevel _level, const struct tm* _time, const std::string& _message, const char* _file, int _line); - - const std::string& getFileName() const { return mFileName; } - - private: - boost::filesystem::ofstream mStream; - std::string mFileName; - }; - -} - -#endif diff --git a/libs/openengine/gui/manager.cpp b/libs/openengine/gui/manager.cpp deleted file mode 100644 index 5fa284c007..0000000000 --- a/libs/openengine/gui/manager.cpp +++ /dev/null @@ -1,680 +0,0 @@ -#include "manager.hpp" -#include "loglistener.hpp" - -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include - -using namespace OEngine::GUI; - -namespace MyGUI -{ - -/* - * As of MyGUI 3.2.0, MyGUI::OgreDataManager::isDataExist is unnecessarily complex - * this override fixes the resulting performance issue. - */ -// Remove for MyGUI 3.2.2 -class FixedOgreDataManager : public MyGUI::OgreDataManager -{ -public: - bool isDataExist(const std::string& _name) - { - return Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup (_name); - } -}; - - -/* - * As of MyGUI 3.2.0, rendering with shaders is not supported. - * We definitely need this though to run in GL3 core / DX11 at all. - * To make matters worse, subclassing OgreRenderManager doesn't seem to be possible here. :/ - */ -class ShaderBasedRenderManager : public RenderManager, - public IRenderTarget, - public Ogre::WindowEventListener, - public Ogre::RenderQueueListener, - public Ogre::RenderSystem::Listener -{ - // флаг для обновления всех и вся - bool mUpdate; - - IntSize mViewSize; - - Ogre::SceneManager* mSceneManager; - - VertexColourType mVertexFormat; - - // окно, на которое мы подписываемся для изменения размеров - Ogre::RenderWindow* mWindow; - - // вьюпорт, с которым работает система - unsigned short mActiveViewport; - - Ogre::RenderSystem* mRenderSystem; - Ogre::TextureUnitState::UVWAddressingMode mTextureAddressMode; - Ogre::LayerBlendModeEx mColorBlendMode, mAlphaBlendMode; - - RenderTargetInfo mInfo; - - typedef std::map MapTexture; - MapTexture mTextures; - - bool mIsInitialise; - bool mManualRender; - size_t mCountBatch; - - // ADDED - Ogre::GpuProgram* mVertexProgramNoTexture; - Ogre::GpuProgram* mVertexProgramOneTexture; - Ogre::GpuProgram* mFragmentProgramNoTexture; - Ogre::GpuProgram* mFragmentProgramOneTexture; - -public: - ShaderBasedRenderManager& getInstance() - { - return *getInstancePtr(); - } - ShaderBasedRenderManager* getInstancePtr() - { - return static_cast(RenderManager::getInstancePtr()); - } - - ShaderBasedRenderManager() : - mUpdate(false), - mSceneManager(nullptr), - mWindow(nullptr), - mActiveViewport(0), - mRenderSystem(nullptr), - mIsInitialise(false), - mManualRender(false), - mCountBatch(0), - mVertexProgramNoTexture(NULL), - mVertexProgramOneTexture(NULL), - mFragmentProgramNoTexture(NULL), - mFragmentProgramOneTexture(NULL) - { - mTextureAddressMode.u = Ogre::TextureUnitState::TAM_CLAMP; - mTextureAddressMode.v = Ogre::TextureUnitState::TAM_CLAMP; - mTextureAddressMode.w = Ogre::TextureUnitState::TAM_CLAMP; - } - - void initialise(Ogre::RenderWindow* _window, Ogre::SceneManager* _scene) - { - MYGUI_PLATFORM_ASSERT(!mIsInitialise, getClassTypeName() << " initialised twice"); - MYGUI_PLATFORM_LOG(Info, "* Initialise: " << getClassTypeName()); - - mColorBlendMode.blendType = Ogre::LBT_COLOUR; - mColorBlendMode.source1 = Ogre::LBS_TEXTURE; - mColorBlendMode.source2 = Ogre::LBS_DIFFUSE; - mColorBlendMode.operation = Ogre::LBX_MODULATE; - - mAlphaBlendMode.blendType = Ogre::LBT_ALPHA; - mAlphaBlendMode.source1 = Ogre::LBS_TEXTURE; - mAlphaBlendMode.source2 = Ogre::LBS_DIFFUSE; - mAlphaBlendMode.operation = Ogre::LBX_MODULATE; - - mTextureAddressMode.u = Ogre::TextureUnitState::TAM_CLAMP; - mTextureAddressMode.v = Ogre::TextureUnitState::TAM_CLAMP; - mTextureAddressMode.w = Ogre::TextureUnitState::TAM_CLAMP; - - mSceneManager = nullptr; - mWindow = nullptr; - mUpdate = false; - mRenderSystem = nullptr; - mActiveViewport = 0; - - Ogre::Root* root = Ogre::Root::getSingletonPtr(); - if (root != nullptr) - setRenderSystem(root->getRenderSystem()); - setRenderWindow(_window); - setSceneManager(_scene); - - - MYGUI_PLATFORM_LOG(Info, getClassTypeName() << " successfully initialized"); - mIsInitialise = true; - } - - void shutdown() - { - MYGUI_PLATFORM_ASSERT(mIsInitialise, getClassTypeName() << " is not initialised"); - MYGUI_PLATFORM_LOG(Info, "* Shutdown: " << getClassTypeName()); - - destroyAllResources(); - - setSceneManager(nullptr); - setRenderWindow(nullptr); - setRenderSystem(nullptr); - - MYGUI_PLATFORM_LOG(Info, getClassTypeName() << " successfully shutdown"); - mIsInitialise = false; - } - - void setRenderSystem(Ogre::RenderSystem* _render) - { - // отписываемся - if (mRenderSystem != nullptr) - { - mRenderSystem->removeListener(this); - mRenderSystem = nullptr; - } - - mRenderSystem = _render; - - // подписываемся на рендер евент - if (mRenderSystem != nullptr) - { - mRenderSystem->addListener(this); - - // формат цвета в вершинах - Ogre::VertexElementType vertex_type = mRenderSystem->getColourVertexElementType(); - if (vertex_type == Ogre::VET_COLOUR_ARGB) - mVertexFormat = VertexColourType::ColourARGB; - else if (vertex_type == Ogre::VET_COLOUR_ABGR) - mVertexFormat = VertexColourType::ColourABGR; - - updateRenderInfo(); - } - } - - Ogre::RenderSystem* getRenderSystem() - { - return mRenderSystem; - } - - void setRenderWindow(Ogre::RenderWindow* _window) - { - // отписываемся - if (mWindow != nullptr) - { - Ogre::WindowEventUtilities::removeWindowEventListener(mWindow, this); - mWindow = nullptr; - } - - mWindow = _window; - - if (mWindow != nullptr) - { - Ogre::WindowEventUtilities::addWindowEventListener(mWindow, this); - windowResized(mWindow); - } - } - - void setSceneManager(Ogre::SceneManager* _scene) - { - if (nullptr != mSceneManager) - { - mSceneManager->removeRenderQueueListener(this); - mSceneManager = nullptr; - } - - mSceneManager = _scene; - - if (nullptr != mSceneManager) - { - mSceneManager->addRenderQueueListener(this); - } - } - - void setActiveViewport(unsigned short _num) - { - mActiveViewport = _num; - - if (mWindow != nullptr) - { - Ogre::WindowEventUtilities::removeWindowEventListener(mWindow, this); - Ogre::WindowEventUtilities::addWindowEventListener(mWindow, this); - - // рассылка обновлений - windowResized(mWindow); - } - } - - void renderQueueStarted(Ogre::uint8 queueGroupId, const Ogre::String& invocation, bool& skipThisInvocation) - { - Gui* gui = Gui::getInstancePtr(); - if (gui == nullptr) - return; - - if (Ogre::RENDER_QUEUE_OVERLAY != queueGroupId) - return; - - Ogre::Viewport* viewport = mSceneManager->getCurrentViewport(); - if (nullptr == viewport - || !viewport->getOverlaysEnabled()) - return; - - if (mWindow->getNumViewports() <= mActiveViewport - || viewport != mWindow->getViewport(mActiveViewport)) - return; - - mCountBatch = 0; - - static Timer timer; - static unsigned long last_time = timer.getMilliseconds(); - unsigned long now_time = timer.getMilliseconds(); - unsigned long time = now_time - last_time; - - onFrameEvent((float)((double)(time) / (double)1000)); - - last_time = now_time; - - //begin(); - setManualRender(true); - onRenderToTarget(this, mUpdate); - //end(); - - // сбрасываем флаг - mUpdate = false; - } - - void renderQueueEnded(Ogre::uint8 queueGroupId, const Ogre::String& invocation, bool& repeatThisInvocation) - { - } - - void eventOccurred(const Ogre::String& eventName, const Ogre::NameValuePairList* parameters) - { - if (eventName == "DeviceLost") - { - } - else if (eventName == "DeviceRestored") - { - // обновить всех - mUpdate = true; - } - } - - IVertexBuffer* createVertexBuffer() - { - return new OgreVertexBuffer(); - } - - void destroyVertexBuffer(IVertexBuffer* _buffer) - { - delete _buffer; - } - - // для оповещений об изменении окна рендера - void windowResized(Ogre::RenderWindow* _window) - { - if (_window->getNumViewports() > mActiveViewport) - { - Ogre::Viewport* port = _window->getViewport(mActiveViewport); -#if OGRE_VERSION >= MYGUI_DEFINE_VERSION(1, 7, 0) && OGRE_NO_VIEWPORT_ORIENTATIONMODE == 0 - Ogre::OrientationMode orient = port->getOrientationMode(); - if (orient == Ogre::OR_DEGREE_90 || orient == Ogre::OR_DEGREE_270) - mViewSize.set(port->getActualHeight(), port->getActualWidth()); - else - mViewSize.set(port->getActualWidth(), port->getActualHeight()); -#else - mViewSize.set(port->getActualWidth(), port->getActualHeight()); -#endif - - // обновить всех - mUpdate = true; - - updateRenderInfo(); - - onResizeView(mViewSize); - } - } - - void updateRenderInfo() - { - if (mRenderSystem != nullptr) - { - mInfo.maximumDepth = mRenderSystem->getMaximumDepthInputValue(); - mInfo.hOffset = mRenderSystem->getHorizontalTexelOffset() / float(mViewSize.width); - mInfo.vOffset = mRenderSystem->getVerticalTexelOffset() / float(mViewSize.height); - mInfo.aspectCoef = float(mViewSize.height) / float(mViewSize.width); - mInfo.pixScaleX = 1.0f / float(mViewSize.width); - mInfo.pixScaleY = 1.0f / float(mViewSize.height); - } - } - - void initShaders() - { - // ADDED - sh::MaterialInstance* mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/NoTexture"); - sh::Factory::getInstance()._ensureMaterial("MyGUI/NoTexture", "Default"); - mVertexProgramNoTexture = static_cast(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0) - ->getVertexProgram()->_getBindingDelegate(); - - mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/OneTexture"); - sh::Factory::getInstance()._ensureMaterial("MyGUI/OneTexture", "Default"); - mVertexProgramOneTexture = static_cast(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0) - ->getVertexProgram()->_getBindingDelegate(); - - mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/NoTexture"); - sh::Factory::getInstance()._ensureMaterial("MyGUI/NoTexture", "Default"); - mFragmentProgramNoTexture = static_cast(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0) - ->getFragmentProgram()->_getBindingDelegate(); - - mat = sh::Factory::getInstance().getMaterialInstance("MyGUI/OneTexture"); - sh::Factory::getInstance()._ensureMaterial("MyGUI/OneTexture", "Default"); - mFragmentProgramOneTexture = static_cast(mat->getMaterial())->getOgreTechniqueForConfiguration("Default")->getPass(0) - ->getFragmentProgram()->_getBindingDelegate(); - } - - void doRender(IVertexBuffer* _buffer, ITexture* _texture, size_t _count) - { - if (getManualRender()) - { - begin(); - setManualRender(false); - } - - // ADDED - if (!mVertexProgramNoTexture) - initShaders(); - - if (_texture) - { - Ogre::Root::getSingleton().getRenderSystem()->bindGpuProgram(mVertexProgramOneTexture); - Ogre::Root::getSingleton().getRenderSystem()->bindGpuProgram(mFragmentProgramOneTexture); - } - else - { - Ogre::Root::getSingleton().getRenderSystem()->bindGpuProgram(mVertexProgramNoTexture); - Ogre::Root::getSingleton().getRenderSystem()->bindGpuProgram(mFragmentProgramNoTexture); - } - - if (_texture) - { - OgreTexture* texture = static_cast(_texture); - Ogre::TexturePtr texture_ptr = texture->getOgreTexture(); - if (!texture_ptr.isNull()) - { - mRenderSystem->_setTexture(0, true, texture_ptr); - mRenderSystem->_setTextureUnitFiltering(0, Ogre::FO_LINEAR, Ogre::FO_LINEAR, Ogre::FO_NONE); - } - } - - OgreVertexBuffer* buffer = static_cast(_buffer); - Ogre::RenderOperation* operation = buffer->getRenderOperation(); - operation->vertexData->vertexCount = _count; - - mRenderSystem->_render(*operation); - - ++ mCountBatch; - } - - void begin() - { - // set-up matrices - mRenderSystem->_setWorldMatrix(Ogre::Matrix4::IDENTITY); - mRenderSystem->_setViewMatrix(Ogre::Matrix4::IDENTITY); - -#if OGRE_VERSION >= MYGUI_DEFINE_VERSION(1, 7, 0) && OGRE_NO_VIEWPORT_ORIENTATIONMODE == 0 - Ogre::OrientationMode orient = mWindow->getViewport(mActiveViewport)->getOrientationMode(); - mRenderSystem->_setProjectionMatrix(Ogre::Matrix4::IDENTITY * Ogre::Quaternion(Ogre::Degree(orient * 90.f), Ogre::Vector3::UNIT_Z)); -#else - mRenderSystem->_setProjectionMatrix(Ogre::Matrix4::IDENTITY); -#endif - - // initialise render settings - mRenderSystem->setLightingEnabled(false); - mRenderSystem->_setDepthBufferParams(false, false); - mRenderSystem->_setDepthBias(0, 0); - mRenderSystem->_setCullingMode(Ogre::CULL_NONE); - mRenderSystem->_setFog(Ogre::FOG_NONE); - mRenderSystem->_setColourBufferWriteEnabled(true, true, true, true); - mRenderSystem->unbindGpuProgram(Ogre::GPT_FRAGMENT_PROGRAM); - mRenderSystem->unbindGpuProgram(Ogre::GPT_VERTEX_PROGRAM); - mRenderSystem->setShadingType(Ogre::SO_GOURAUD); - - // initialise texture settings - mRenderSystem->_setTextureCoordCalculation(0, Ogre::TEXCALC_NONE); - mRenderSystem->_setTextureCoordSet(0, 0); - mRenderSystem->_setTextureUnitFiltering(0, Ogre::FO_LINEAR, Ogre::FO_LINEAR, Ogre::FO_NONE); - mRenderSystem->_setTextureAddressingMode(0, mTextureAddressMode); - mRenderSystem->_setTextureMatrix(0, Ogre::Matrix4::IDENTITY); -#if OGRE_VERSION < MYGUI_DEFINE_VERSION(1, 6, 0) - mRenderSystem->_setAlphaRejectSettings(Ogre::CMPF_ALWAYS_PASS, 0); -#else - mRenderSystem->_setAlphaRejectSettings(Ogre::CMPF_ALWAYS_PASS, 0, false); -#endif - mRenderSystem->_setTextureBlendMode(0, mColorBlendMode); - mRenderSystem->_setTextureBlendMode(0, mAlphaBlendMode); - mRenderSystem->_disableTextureUnitsFrom(1); - - // enable alpha blending - mRenderSystem->_setSceneBlending(Ogre::SBF_SOURCE_ALPHA, Ogre::SBF_ONE_MINUS_SOURCE_ALPHA); - - // always use wireframe - mRenderSystem->_setPolygonMode(Ogre::PM_SOLID); - } - - void end() - { - } - - ITexture* createTexture(const std::string& _name) - { - MapTexture::const_iterator item = mTextures.find(_name); - MYGUI_PLATFORM_ASSERT(item == mTextures.end(), "Texture '" << _name << "' already exist"); - - OgreTexture* texture = new OgreTexture(_name, OgreDataManager::getInstance().getGroup()); - mTextures[_name] = texture; - return texture; - } - - void destroyTexture(ITexture* _texture) - { - if (_texture == nullptr) return; - - MapTexture::iterator item = mTextures.find(_texture->getName()); - MYGUI_PLATFORM_ASSERT(item != mTextures.end(), "Texture '" << _texture->getName() << "' not found"); - - mTextures.erase(item); - delete _texture; - } - - ITexture* getTexture(const std::string& _name) - { - MapTexture::const_iterator item = mTextures.find(_name); - if (item == mTextures.end()) - { - Ogre::TexturePtr texture = (Ogre::TexturePtr)Ogre::TextureManager::getSingleton().getByName(_name); - if (!texture.isNull()) - { - ITexture* result = createTexture(_name); - static_cast(result)->setOgreTexture(texture); - return result; - } - return nullptr; - } - return item->second; - } - - bool isFormatSupported(PixelFormat _format, TextureUsage _usage) - { - return Ogre::TextureManager::getSingleton().isFormatSupported( - Ogre::TEX_TYPE_2D, - OgreTexture::convertFormat(_format), - OgreTexture::convertUsage(_usage)); - } - - void destroyAllResources() - { - for (MapTexture::const_iterator item = mTextures.begin(); item != mTextures.end(); ++item) - { - delete item->second; - } - mTextures.clear(); - } - -#if MYGUI_DEBUG_MODE == 1 - bool checkTexture(ITexture* _texture) - { - for (MapTexture::const_iterator item = mTextures.begin(); item != mTextures.end(); ++item) - { - if (item->second == _texture) - return true; - } - return false; - } -#endif - - const IntSize& getViewSize() const - { - return mViewSize; - } - - VertexColourType getVertexFormat() - { - return mVertexFormat; - } - - const RenderTargetInfo& getInfo() - { - return mInfo; - } - - size_t getActiveViewport() - { - return mActiveViewport; - } - - Ogre::RenderWindow* getRenderWindow() - { - return mWindow; - } - - bool getManualRender() - { - return mManualRender; - } - - void setManualRender(bool _value) - { - mManualRender = _value; - } - - size_t getBatchCount() const - { - return mCountBatch; - } -}; - -/// \brief Helper class holding data that required during -/// MyGUI log creation -class LogFacility -{ - ConsoleLogListener mConsole; - CustomLogListener mFile; - LevelLogFilter mFilter; - LogSource mSource; - -public: - - LogFacility(const std::string &output, bool console) - : mFile(output) - { - mConsole.setEnabled(console); - mFilter.setLoggingLevel(LogLevel::Info); - - mSource.addLogListener(&mFile); - mSource.addLogListener(&mConsole); - mSource.setLogFilter(&mFilter); - - mSource.open(); - } - - LogSource *getSource() { return &mSource; } -}; - -} - -void MyGUIManager::setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging, const std::string& logDir) -{ - assert(wnd); - assert(mgr); - - mSceneMgr = mgr; - mShaderRenderManager = NULL; - mRenderManager = NULL; - - 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. - - std::string theLogFile = std::string(MYGUI_PLATFORM_LOG_FILENAME); - if(!logDir.empty()) - theLogFile.insert(0, logDir); - - // Set up OGRE platform (bypassing OgrePlatform). We might make this more generic later. - mLogManager = new LogManager(); - if (!Ogre::Root::getSingleton().getRenderSystem()->getCapabilities()->hasCapability(Ogre::RSC_FIXED_FUNCTION)) - mShaderRenderManager = new MyGUI::ShaderBasedRenderManager(); - else - mRenderManager = new MyGUI::OgreRenderManager(); - mDataManager = new MyGUI::FixedOgreDataManager(); - - // Do not use default log since it don't support Unicode path on Windows. - // Instead, manually create log source using LogFacility and pass it. - mLogFacility = new MyGUI::LogFacility(theLogFile, logging); - LogManager::getInstance().addLogSource(mLogFacility->getSource()); - - if (mShaderRenderManager) - mShaderRenderManager->initialise(wnd, mgr); - else - mRenderManager->initialise(wnd, mgr); - mDataManager->initialise("General"); - - // Create GUI - mGui = new Gui(); - mGui->initialise(""); -} - -void MyGUIManager::windowResized() -{ -#ifndef ANDROID - mRenderManager->setActiveViewport(0); -#endif -} - -void MyGUIManager::shutdown() -{ - mGui->shutdown (); - delete mGui; - if(mRenderManager) - { - mRenderManager->shutdown(); - delete mRenderManager; - mRenderManager = NULL; - } - if(mShaderRenderManager) - { - mShaderRenderManager->shutdown(); - delete mShaderRenderManager; - mShaderRenderManager = NULL; - } - if(mDataManager) - { - mDataManager->shutdown(); - delete mDataManager; - mDataManager = NULL; - } - if (mLogManager) - { - delete mLogManager; - mLogManager = NULL; - } - delete mLogFacility; - - mGui = NULL; -} diff --git a/libs/openengine/gui/manager.hpp b/libs/openengine/gui/manager.hpp deleted file mode 100644 index 28eb6419b1..0000000000 --- a/libs/openengine/gui/manager.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef OENGINE_MYGUI_MANAGER_H -#define OENGINE_MYGUI_MANAGER_H - -#include - -namespace MyGUI -{ - class Gui; - class LogManager; - class OgreDataManager; - class OgreRenderManager; - class ShaderBasedRenderManager; - class LogFacility; -} - -namespace Ogre -{ - class RenderWindow; - class SceneManager; -} - -namespace OEngine { -namespace GUI -{ - class MyGUIManager - { - MyGUI::Gui *mGui; - MyGUI::LogManager* mLogManager; - MyGUI::LogFacility* mLogFacility; - MyGUI::OgreDataManager* mDataManager; - MyGUI::OgreRenderManager* mRenderManager; - MyGUI::ShaderBasedRenderManager* mShaderRenderManager; - Ogre::SceneManager* mSceneMgr; - - - public: - MyGUIManager(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false, const std::string& logDir = std::string("")) - { - setup(wnd,mgr,logging, logDir); - } - ~MyGUIManager() - { - shutdown(); - } - - void windowResized(); - - void setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false, const std::string& logDir = std::string("")); - void shutdown(); - }; -} -} -#endif diff --git a/libs/openengine/misc/list.hpp b/libs/openengine/misc/list.hpp deleted file mode 100644 index bda9cb8de7..0000000000 --- a/libs/openengine/misc/list.hpp +++ /dev/null @@ -1,178 +0,0 @@ -#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 deleted file mode 100644 index 3367afdbbf..0000000000 --- a/libs/openengine/ogre/.gitignore +++ /dev/null @@ -1 +0,0 @@ -old diff --git a/libs/openengine/ogre/lights.cpp b/libs/openengine/ogre/lights.cpp deleted file mode 100644 index 97fa938da6..0000000000 --- a/libs/openengine/ogre/lights.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "lights.hpp" - -#include - - -namespace OEngine { -namespace Render { - -Ogre::Real LightFunction::pulseAmplitude(Ogre::Real time) -{ - return std::sin(time); -} - -Ogre::Real LightFunction::flickerAmplitude(Ogre::Real time) -{ - static const float fb = 1.17024f; - static const float f[3] = { 1.5708f, 4.18774f, 5.19934f }; - static const float o[3] = { 0.804248f, 2.11115f, 3.46832f }; - static const float m[3] = { 1.0f, 0.785f, 0.876f }; - static const float s = 0.394f; - - float v = 0.0f; - for(int i = 0;i < 3;++i) - v += std::sin(fb*time*f[i] + o[1])*m[i]; - return v * s; -} - -Ogre::Real LightFunction::flickerFrequency(Ogre::Real phase) -{ - static const float fa = 0.785398f; - static const float tdo = 0.94f; - static const float tdm = 2.48f; - - return tdo + tdm*std::sin(fa * phase); -} - -Ogre::Real LightFunction::calculate(Ogre::Real value) -{ - Ogre::Real brightness = 1.0f; - float cycle_time; - float time_distortion; - - if(mType == LT_Pulse || mType == LT_PulseSlow) - { - cycle_time = 2.0f * Ogre::Math::PI; - time_distortion = 20.0f; - } - else - { - static const float fa = 0.785398f; - static const float phase_wavelength = 120.0f * 3.14159265359f / fa; - - cycle_time = 500.0f; - mPhase = std::fmod(mPhase + value, phase_wavelength); - time_distortion = flickerFrequency(mPhase); - } - - mDeltaCount += mDirection*value*time_distortion; - if(mDirection > 0 && mDeltaCount > +cycle_time) - { - mDirection = -1.0f; - mDeltaCount = 2.0f*cycle_time - mDeltaCount; - } - if(mDirection < 0 && mDeltaCount < -cycle_time) - { - mDirection = +1.0f; - mDeltaCount = -2.0f*cycle_time - mDeltaCount; - } - - static const float fast = 4.0f/1.0f; - static const float slow = 1.0f/1.0f; - - // These formulas are just guesswork, but they work pretty well - if(mType == LT_Normal) - { - // Less than 1/255 light modifier for a constant light: - brightness = 1.0f + flickerAmplitude(mDeltaCount*slow)/255.0f; - } - else if(mType == LT_Flicker) - brightness = 0.75f + flickerAmplitude(mDeltaCount*fast)*0.25f; - else if(mType == LT_FlickerSlow) - brightness = 0.75f + flickerAmplitude(mDeltaCount*slow)*0.25f; - else if(mType == LT_Pulse) - brightness = 1.0f + pulseAmplitude(mDeltaCount*fast)*0.25f; - else if(mType == LT_PulseSlow) - brightness = 1.0f + pulseAmplitude(mDeltaCount*slow)*0.25f; - - return brightness; -} - -Ogre::Real LightValue::getValue() const -{ - return 0.0f; -} - -void LightValue::setValue(Ogre::Real value) -{ - mTarget->setDiffuseColour(mColor * value); -} - -} -} diff --git a/libs/openengine/ogre/lights.hpp b/libs/openengine/ogre/lights.hpp deleted file mode 100644 index 61d09a0e65..0000000000 --- a/libs/openengine/ogre/lights.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef OENGINE_OGRE_LIGHTS_H -#define OENGINE_OGRE_LIGHTS_H - -#include -#include -#include - -/* - * Controller classes to handle pulsing and flicker lights - */ - -namespace OEngine { -namespace Render { - enum LightType { - LT_Normal, - LT_Flicker, - LT_FlickerSlow, - LT_Pulse, - LT_PulseSlow - }; - - class LightFunction : public Ogre::ControllerFunction - { - LightType mType; - Ogre::Real mPhase; - Ogre::Real mDirection; - - static Ogre::Real pulseAmplitude(Ogre::Real time); - - static Ogre::Real flickerAmplitude(Ogre::Real time); - static Ogre::Real flickerFrequency(Ogre::Real phase); - - public: - // MSVC needs the constructor for a class inheriting a template to be defined in header - LightFunction(LightType type) - : ControllerFunction(true) - , mType(type) - , mPhase(Ogre::Math::RangeRandom(-500.0f, +500.0f)) - , mDirection(1.0f) - { - } - virtual Ogre::Real calculate(Ogre::Real value); - }; - - class LightValue : public Ogre::ControllerValue - { - Ogre::Light *mTarget; - Ogre::ColourValue mColor; - - public: - // MSVC needs the constructor for a class inheriting a template to be defined in header - LightValue(Ogre::Light *light, const Ogre::ColourValue &color) - : mTarget(light) - , mColor(color) - { - } - - virtual Ogre::Real getValue() const; - virtual void setValue(Ogre::Real value); - }; -} -} - -#endif diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp deleted file mode 100644 index bdeeeb8c4b..0000000000 --- a/libs/openengine/ogre/renderer.cpp +++ /dev/null @@ -1,241 +0,0 @@ -#include "renderer.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include - -#include -#include - -using namespace Ogre; -using namespace OEngine::Render; - -OgreRenderer::~OgreRenderer() -{ - cleanup(); - restoreWindowGammaRamp(); -} - -void OgreRenderer::cleanup() -{ - if (mWindow) - Ogre::Root::getSingleton().destroyRenderTarget(mWindow); - mWindow = NULL; - - delete mOgreInit; - mOgreInit = NULL; - - // If we don't do this, the desktop resolution is not restored on exit - SDL_SetWindowFullscreen(mSDLWindow, 0); - - SDL_DestroyWindow(mSDLWindow); - mSDLWindow = NULL; -} - -void OgreRenderer::update(float dt) -{ -} - -void OgreRenderer::screenshot(const std::string &file, const std::string& imageFormat) -{ - /* Since Ogre uses narrow character interfaces, it does not support - Unicode paths on Windows. Therefore we had to implement screenshot - saving manually. - */ - namespace bfs = boost::filesystem; - bfs::ofstream out(bfs::path(file), std::ios::binary); - - Ogre::Image image; - - Ogre::PixelFormat pf = mWindow->suggestPixelFormat(); - int w = mWindow->getWidth(); - int h = mWindow->getHeight(); - - image.loadDynamicImage( - OGRE_ALLOC_T(Ogre::uchar, w * h * Ogre::PixelUtil::getNumElemBytes(pf), Ogre::MEMCATEGORY_GENERAL), - w, h, 1, pf, true - ); - mWindow->copyContentsToMemory(image.getPixelBox()); - - Ogre::DataStreamPtr stream = image.encode(imageFormat); - Ogre::MemoryDataStream *mem = dynamic_cast(stream.get()); - if (mem != 0) { // likely - const char *ptr = reinterpret_cast(mem->getCurrentPtr()); - out.write(ptr, mem->size()); - } - else { - char buf[4096]; - size_t size = stream->size(); - while (size > 0) { - size_t chunk = (size > sizeof(buf)) ? sizeof(buf) : size; - stream->read(buf, chunk); - out.write(buf, chunk); - size -= chunk; - } - } -} - -void OgreRenderer::configure(const std::string &logPath, - const std::string& renderSystem, - const std::string& rttMode - ) -{ - mOgreInit = new OgreInit::OgreInit(); - mRoot = mOgreInit->init(logPath + "/ogre.log"); - - RenderSystem* rs = mRoot->getRenderSystemByName(renderSystem); - if (rs == 0) - throw std::runtime_error ("RenderSystem with name " + renderSystem + " not found, make sure the plugins are loaded"); - mRoot->setRenderSystem(rs); - - if (rs->getName().find("OpenGL") != std::string::npos) - rs->setConfigOption ("RTT Preferred Mode", rttMode); -} - -void OgreRenderer::createWindow(const std::string &title, const WindowSettings& settings) -{ - assert(mRoot); - mRoot->initialise(false); - - NameValuePairList params; - params.insert(std::make_pair("title", title)); - params.insert(std::make_pair("FSAA", settings.fsaa)); - params.insert(std::make_pair("vsync", settings.vsync ? "true" : "false")); - - int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(settings.screen), - pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(settings.screen); - - if(settings.fullscreen) - { - pos_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(settings.screen); - pos_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(settings.screen); - } - - - // Create an application window with the following settings: - mSDLWindow = SDL_CreateWindow( - "OpenMW", // window title - pos_x, // initial x position - pos_y, // initial y position - settings.window_x, // width, in pixels - settings.window_y, // height, in pixels - SDL_WINDOW_SHOWN - | SDL_WINDOW_RESIZABLE - | (settings.fullscreen ? SDL_WINDOW_FULLSCREEN : 0) - | (settings.window_border ? 0 : SDL_WINDOW_BORDERLESS) - ); - if (mSDLWindow == 0) - throw std::runtime_error("Failed to create window: " + std::string(SDL_GetError())); - - SFO::SDLWindowHelper helper(mSDLWindow, settings.window_x, settings.window_y, title, settings.fullscreen, params); - if (settings.icon != "") - helper.setWindowIcon(settings.icon); - mWindow = helper.getWindow(); - - SDL_GetWindowGammaRamp(mSDLWindow, mOldSystemGammaRamp, &mOldSystemGammaRamp[256], &mOldSystemGammaRamp[512]); - - // create the semi-transparent black background texture used by the GUI. - // has to be created in code with TU_DYNAMIC_WRITE_ONLY param - // so that it can be modified at runtime. - Ogre::TextureManager::getSingleton().createManual( - "transparent.png", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, - 1, 1, - 0, - Ogre::PF_A8R8G8B8, - Ogre::TU_WRITE_ONLY); - - mScene = mRoot->createSceneManager(ST_GENERIC); - - mCamera = mScene->createCamera("cam"); - - // 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())); -} - -void OgreRenderer::setWindowGammaContrast(float gamma, float contrast) -{ - if (mSDLWindow == NULL) return; - - Uint16 red[256], green[256], blue[256]; - for (int i = 0; i < 256; i++) - { - float k = i/256.0f; - k = (k - 0.5f) * contrast + 0.5f; - k = pow(k, 1.f/gamma); - k *= 256; - float value = k*256; - if (value > 65535) value = 65535; - else if (value < 0) value = 0; - - red[i] = green[i] = blue[i] = static_cast(value); - } - if (SDL_SetWindowGammaRamp(mSDLWindow, red, green, blue) < 0) - std::cout << "Couldn't set gamma: " << SDL_GetError() << std::endl; -} - -void OgreRenderer::restoreWindowGammaRamp() -{ - if (mSDLWindow != NULL) - { - SDL_SetWindowGammaRamp(mSDLWindow, mOldSystemGammaRamp, &mOldSystemGammaRamp[256], &mOldSystemGammaRamp[512]); - } -} - -void OgreRenderer::adjustCamera(float fov, float nearClip) -{ - mCamera->setNearClipDistance(nearClip); - mCamera->setFOVy(Degree(fov)); -} - -void OgreRenderer::adjustViewport() -{ - // Alter the camera aspect ratio to match the viewport - if(mCamera != NULL) - { - mView->setDimensions(0, 0, 1, 1); - mCamera->setAspectRatio(Real(mView->getActualWidth()) / Real(mView->getActualHeight())); - } -} - -void OgreRenderer::setFov(float fov) -{ - mCamera->setFOVy(Degree(fov)); -} - -void OgreRenderer::windowResized(int x, int y) -{ - if (mWindowListener) { - mWindowListener->windowResized(x,y); - } - else { - mWindowWidth = x; - mWindowHeight = y; - mOutstandingResize = true; - } -} - -void OgreRenderer::setWindowListener(WindowSizeListener* listener) -{ - mWindowListener = listener; - if (mOutstandingResize) { - windowResized(mWindowWidth, mWindowHeight); - mOutstandingResize = false; - } -} diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp deleted file mode 100644 index 33a42f8cd8..0000000000 --- a/libs/openengine/ogre/renderer.hpp +++ /dev/null @@ -1,147 +0,0 @@ -#ifndef OENGINE_OGRE_RENDERER_H -#define OENGINE_OGRE_RENDERER_H - -/* - Ogre renderer class - */ - -#include -#include - -#include - - -struct SDL_Window; -struct SDL_Surface; - -namespace Ogre -{ - class Root; - class RenderWindow; - class SceneManager; - class Camera; - class Viewport; - class ParticleEmitterFactory; - class ParticleAffectorFactory; -} - -namespace OgreInit -{ - class OgreInit; -} - -namespace OEngine -{ - namespace Render - { - struct WindowSettings - { - bool vsync; - bool fullscreen; - bool window_border; - int window_x, window_y; - int screen; - std::string fsaa; - std::string icon; - }; - - class WindowSizeListener - { - public: - virtual void windowResized (int x, int y) = 0; - }; - - class OgreRenderer - { - Ogre::Root *mRoot; - Ogre::RenderWindow *mWindow; - SDL_Window *mSDLWindow; - Ogre::SceneManager *mScene; - Ogre::Camera *mCamera; - Ogre::Viewport *mView; - - OgreInit::OgreInit* mOgreInit; - - WindowSizeListener* mWindowListener; - - int mWindowWidth; - int mWindowHeight; - bool mOutstandingResize; - - // Store system gamma ramp on window creation. Restore system gamma ramp on exit - uint16_t mOldSystemGammaRamp[256*3]; - - public: - OgreRenderer() - : mRoot(NULL) - , mWindow(NULL) - , mSDLWindow(NULL) - , mScene(NULL) - , mCamera(NULL) - , mView(NULL) - , mOgreInit(NULL) - , mWindowListener(NULL) - , mWindowWidth(0) - , mWindowHeight(0) - , mOutstandingResize(false) - { - } - - ~OgreRenderer(); - - /** Configure the renderer. This will load configuration files and - set up the Root and logging classes. */ - void configure( - const std::string &logPath, // Path to directory where to store log files - const std::string &renderSystem, - const std::string &rttMode); // Enable or disable logging - - /// Create a window with the given title - void createWindow(const std::string &title, const WindowSettings& settings); - - void setWindowGammaContrast(float gamma, float contrast); - void restoreWindowGammaRamp(); - - /// Set up the scene manager, camera and viewport - void adjustCamera( - float fov=55, // Field of view angle - float nearClip=5 // Near clip distance - ); - - void setFov(float fov); - - /// Kill the renderer. - void cleanup(); - - void update(float dt); - - /// Write a screenshot to file - void screenshot(const std::string &file, const std::string& imageFormat); - - void windowResized(int x, int y); - - /// Get the Root - Ogre::Root *getRoot() { return mRoot; } - - /// Get the rendering window - Ogre::RenderWindow *getWindow() { return mWindow; } - - /// Get the SDL Window - SDL_Window *getSDLWindow() { return mSDLWindow; } - - /// Get the scene manager - Ogre::SceneManager *getScene() { return mScene; } - - /// Camera - Ogre::Camera *getCamera() { return mCamera; } - - /// Viewport - Ogre::Viewport *getViewport() { return mView; } - - void setWindowListener(WindowSizeListener* listener); - - void adjustViewport(); - }; - } -} -#endif diff --git a/libs/openengine/ogre/selectionbuffer.cpp b/libs/openengine/ogre/selectionbuffer.cpp deleted file mode 100644 index 741b672ff1..0000000000 --- a/libs/openengine/ogre/selectionbuffer.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#include "selectionbuffer.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include - -namespace OEngine -{ -namespace Render -{ - - SelectionBuffer::SelectionBuffer(Ogre::Camera *camera, int sizeX, int sizeY, int visibilityFlags) - : mCamera(camera) - , mVisibilityFlags(visibilityFlags) - { - mTexture = Ogre::TextureManager::getSingleton().createManual("SelectionBuffer", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, sizeX, sizeY, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET, this); - - setupRenderTarget(); - - mCurrentColour = Ogre::ColourValue(0.3f, 0.3f, 0.3f); - } - - void SelectionBuffer::setupRenderTarget() - { - mRenderTarget = mTexture->getBuffer()->getRenderTarget(); - mRenderTarget->removeAllViewports(); - Ogre::Viewport* vp = mRenderTarget->addViewport(mCamera); - vp->setOverlaysEnabled(false); - vp->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0)); - vp->setShadowsEnabled(false); - vp->setMaterialScheme("selectionbuffer"); - if (mVisibilityFlags != 0) - vp->setVisibilityMask (mVisibilityFlags); - mRenderTarget->setActive(true); - mRenderTarget->setAutoUpdated (false); - } - - void SelectionBuffer::loadResource(Ogre::Resource *resource) - { - Ogre::Texture* tex = dynamic_cast(resource); - if (!tex) - return; - - tex->createInternalResources(); - - mRenderTarget = NULL; - - // Don't need to re-render texture, because we have a copy in system memory (mBuffer) - } - - SelectionBuffer::~SelectionBuffer() - { - Ogre::TextureManager::getSingleton ().remove("SelectionBuffer"); - } - - void SelectionBuffer::update () - { - Ogre::MaterialManager::getSingleton ().addListener (this); - - mTexture->load(); - if (mRenderTarget == NULL) - setupRenderTarget(); - - mRenderTarget->update(); - - Ogre::MaterialManager::getSingleton ().removeListener (this); - - mTexture->convertToImage(mBuffer); - } - - int SelectionBuffer::getSelected(int xPos, int yPos) - { - Ogre::ColourValue clr = mBuffer.getColourAt (xPos, yPos, 0); - clr.a = 1; - if (mColourMap.find(clr) != mColourMap.end()) - return mColourMap[clr]; - else - return -1; // nothing selected - } - - Ogre::Technique* SelectionBuffer::handleSchemeNotFound ( - unsigned short schemeIndex, const Ogre::String &schemeName, Ogre::Material *originalMaterial, - unsigned short lodIndex, const Ogre::Renderable *rend) - { - if (schemeName == "selectionbuffer") - { - sh::Factory::getInstance ()._ensureMaterial ("SelectionColour", "Default"); - - Ogre::MaterialPtr m = Ogre::MaterialManager::getSingleton ().getByName("SelectionColour"); - - - if(typeid(*rend) == typeid(Ogre::SubEntity)) - { - const Ogre::SubEntity *subEntity = static_cast(rend); - int id = Ogre::any_cast(subEntity->getParent ()->getUserObjectBindings().getUserAny()); - bool found = false; - Ogre::ColourValue colour; - for (std::map::iterator it = mColourMap.begin(); it != mColourMap.end(); ++it) - { - if (it->second == id) - { - found = true; - colour = it->first; - } - } - - - if (!found) - { - getNextColour(); - const_cast(subEntity)->setCustomParameter(1, Ogre::Vector4(mCurrentColour.r, mCurrentColour.g, mCurrentColour.b, 1.0)); - mColourMap[mCurrentColour] = id; - } - else - { - const_cast(subEntity)->setCustomParameter(1, Ogre::Vector4(colour.r, colour.g, colour.b, 1.0)); - } - - assert(m->getTechnique(1)); - return m->getTechnique(1); - } - else - { - m = Ogre::MaterialManager::getSingleton().getByName("NullMaterial"); - if(m.isNull()) - { - m = Ogre::MaterialManager::getSingleton().create("NullMaterial", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - m->getTechnique(0)->getPass(0)->setDepthCheckEnabled(true); - m->getTechnique(0)->getPass(0)->setDepthFunction(Ogre::CMPF_ALWAYS_FAIL); - } - return m->getTechnique(0); - } - } - return NULL; - } - - void SelectionBuffer::getNextColour () - { - Ogre::ARGB color = static_cast(OEngine::Misc::Rng::rollClosedProbability() * std::numeric_limits::max()); - - if (mCurrentColour.getAsARGB () == color) - { - getNextColour(); - return; - } - - mCurrentColour.setAsARGB(color); - mCurrentColour.a = 1; - } - - -} -} diff --git a/libs/openengine/ogre/selectionbuffer.hpp b/libs/openengine/ogre/selectionbuffer.hpp deleted file mode 100644 index 9bdc4c137b..0000000000 --- a/libs/openengine/ogre/selectionbuffer.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef OENGINE_SELECTIONBUFFER_H -#define OENGINE_SELECTIONBUFFER_H - - -#include -#include -#include -#include - -namespace OEngine -{ -namespace Render -{ - - struct cmp_ColourValue - { - bool operator()(const Ogre::ColourValue &a, const Ogre::ColourValue &b) const - { - return a.getAsBGRA() < b.getAsBGRA(); - } - }; - - class SelectionBuffer : public Ogre::MaterialManager::Listener, public Ogre::ManualResourceLoader - { - public: - SelectionBuffer(Ogre::Camera* camera, int sizeX, int sizeY, int visibilityFlags); - virtual ~SelectionBuffer(); - - int getSelected(int xPos, int yPos); - ///< @return ID of the selected object - - void update(); - - virtual void loadResource(Ogre::Resource* resource); - - virtual Ogre::Technique* handleSchemeNotFound ( - unsigned short schemeIndex, const Ogre::String &schemeName, Ogre::Material *originalMaterial, - unsigned short lodIndex, const Ogre::Renderable *rend); - - - private: - Ogre::TexturePtr mTexture; - Ogre::RenderTexture* mRenderTarget; - - Ogre::Image mBuffer; - - std::map mColourMap; - - Ogre::ColourValue mCurrentColour; - - Ogre::Camera* mCamera; - int mVisibilityFlags; - - void getNextColour(); - - void setupRenderTarget(); - }; - -} -} - -#endif diff --git a/libs/platform/strings.h b/libs/platform/strings.h deleted file mode 100644 index c0fbb1a1b2..0000000000 --- a/libs/platform/strings.h +++ /dev/null @@ -1,18 +0,0 @@ -// Wrapper for MSVC/GCC -#ifndef _STRINGS_WRAPPER_H -#define _STRINGS_WRAPPER_H - - -// For GCC, just use strings.h (this applies to mingw too) -#if defined(__GNUC__) -# include -#elif defined(MSVC) || defined(_MSC_VER) -# pragma warning(disable: 4996) -# define strcasecmp stricmp -# define snprintf _snprintf -#else -# warning "Unable to determine your compiler, you should probably take a look here." -# include // Just take a guess -#endif - -#endif /* _STRINGS_WRAPPER_H */