diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 87cc31954..35fc597fe 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -41,6 +41,14 @@ set(GAMEGUI ) source_group(apps\\openmw\\mwgui FILES ${GAMEGUI_HEADER} ${GAMEGUI}) +set(GAMEDIALOGUE_HEADER + mwdialogue/dialoguemanager.hpp +) +set(GAMEDIALOGUE + mwdialogue/dialoguemanager.cpp +) +source_group(apps\\openmw\\mwdialogue FILES ${GAMEDIALOGUE_HEADER} ${GAMEDIALOGUE}) + set(GAMESCRIPT mwscript/scriptmanager.cpp mwscript/compilercontext.cpp @@ -97,6 +105,7 @@ set(GAMEWORLD_HEADER mwworld/action.hpp mwworld/nullaction.hpp mwworld/actionteleport.hpp + mwworld/containerstore.hpp mwworld/actiontalk.hpp mwworld/actiontake.hpp mwworld/containerstore.hpp @@ -164,11 +173,11 @@ set(GAMEMECHANICS_HEADER source_group(apps\\openmw\\mwmechanics FILES ${GAMEMECHANICS} ${GAMEMECHANICS_HEADER}) set(OPENMW_CPP ${GAME} ${GAMEREND} ${GAMEINPUT} ${GAMESCRIPT} ${GAMESOUND} ${GAMEGUI} ${GAMEWORLD} - ${GAMECLASS} ${GAMEMECHANICS} + ${GAMECLASS} ${GAMEMECHANICS} ${GAMEDIALOGUE} ) set(OPENMW_HEADER ${GAME_HEADER} ${GAMEREND_HEADER} ${GAMEINPUT_HEADER} ${GAMESCRIPT_HEADER} ${GAMESOUND_HEADER} ${GAMEGUI_HEADER} ${GAMEWORLD_HEADER} ${GAMECLASS_HEADER} - ${GAMEMECHANICS_HEADER} + ${GAMEMECHANICS_HEADER} ${GAMEDIALOG_HEADERUE} ) # Main executable diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index a77ee925e..8c5ee33f7 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -28,6 +28,8 @@ #include "mwclass/classes.hpp" +#include "mwdialogue/dialoguemanager.hpp" + #include "mwmechanics/mechanicsmanager.hpp" #include @@ -117,6 +119,7 @@ OMW::Engine::~Engine() delete mEnvironment.mSoundManager; delete mEnvironment.mGlobalScripts; delete mEnvironment.mMechanicsManager; + delete mEnvironment.mDialogueManager; delete mScriptManager; delete mScriptContext; } @@ -253,6 +256,9 @@ void OMW::Engine::go() mEnvironment.mMechanicsManager = new MWMechanics::MechanicsManager ( mEnvironment.mWorld->getStore(), *mEnvironment.mWindowManager); + // Create dialog system + mEnvironment.mDialogueManager = new MWDialogue::DialogueManager (mEnvironment); + // load cell ESM::Position pos; pos.pos[0] = pos.pos[1] = pos.pos[2] = 0; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 8757c93fd..fc9515fff 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -15,6 +15,14 @@ namespace MWClass { + std::string Creature::getId (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->mId; + } + void Creature::insertObj (const MWWorld::Ptr& ptr, MWRender::CellRenderImp& cellRender, MWWorld::Environment& environment) const { @@ -96,7 +104,7 @@ namespace MWClass } return *ptr.getRefData().getContainerStore(); - } + } std::string Creature::getScript (const MWWorld::Ptr& ptr) const { diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index ec354a015..9f6708f40 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -9,6 +9,9 @@ namespace MWClass { public: + virtual std::string getId (const MWWorld::Ptr& ptr) const; + ///< Return ID of \a ptr + virtual void insertObj (const MWWorld::Ptr& ptr, MWRender::CellRenderImp& cellRender, MWWorld::Environment& environment) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 1aae8928d..3bc005b06 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -16,6 +16,14 @@ namespace MWClass { + std::string Npc::getId (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->mId; + } + void Npc::insertObj (const MWWorld::Ptr& ptr, MWRender::CellRenderImp& cellRender, MWWorld::Environment& environment) const { diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 1bbf427de..dd54806bd 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -9,6 +9,9 @@ namespace MWClass { public: + virtual std::string getId (const MWWorld::Ptr& ptr) const; + ///< Return ID of \a ptr + virtual void insertObj (const MWWorld::Ptr& ptr, MWRender::CellRenderImp& cellRender, MWWorld::Environment& environment) const; ///< Add reference into a cell for rendering @@ -26,14 +29,14 @@ namespace MWClass virtual MWMechanics::CreatureStats& getCreatureStats (const MWWorld::Ptr& ptr) const; ///< Return creature stats - virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, - const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const; - ///< Generate action for activation - virtual MWWorld::ContainerStore& getContainerStore ( const MWWorld::Ptr& ptr) const; ///< Return container store + virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, + const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const; + ///< Generate action for activation + virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr diff --git a/apps/openmw/mwdialogue/dialoguemanager.cpp b/apps/openmw/mwdialogue/dialoguemanager.cpp new file mode 100644 index 000000000..85a955992 --- /dev/null +++ b/apps/openmw/mwdialogue/dialoguemanager.cpp @@ -0,0 +1,280 @@ + +#include "dialoguemanager.hpp" + +#include +#include +#include + +#include + +#include + +#include "../mwworld/class.hpp" +#include "../mwworld/environment.hpp" +#include "../mwworld/world.hpp" +#include "../mwworld/refdata.hpp" + +#include "../mwgui/window_manager.hpp" + +#include + +namespace +{ + std::string toLower (const std::string& name) + { + std::string lowerCase; + + std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), + (int(*)(int)) std::tolower); + + return lowerCase; + } + + template + bool selectCompare (char comp, T1 value1, T2 value2) + { + switch (comp) + { + case '0': return value1==value2; + case '1': return value1!=value2; + case '2': return value1>value2; + case '3': return value1>=value2; + case '4': return value1 + bool checkLocal (char comp, const std::string& name, T value, const MWWorld::Ptr& actor, + const ESMS::ESMStore& store) + { + std::string scriptName = MWWorld::Class::get (actor).getScript (actor); + + if (scriptName.empty()) + return false; // no script + + const ESM::Script *script = store.scripts.find (scriptName); + + int i = 0; + + for (; i (script->varNames.size()); ++i) + if (script->varNames[i]==name) + break; + + if (i>=static_cast (script->varNames.size())) + return false; // script does not have a variable of this name + + const MWScript::Locals& locals = actor.getRefData().getLocals(); + + if (idata.numShorts) + return selectCompare (comp, locals.mShorts[i], value); + else + i -= script->data.numShorts; + + if (idata.numLongs) + return selectCompare (comp, locals.mLongs[i], value); + else + i -= script->data.numShorts; + + return selectCompare (comp, locals.mFloats.at (i), value); + } + + template + bool checkGlobal (char comp, const std::string& name, T value, MWWorld::World& world) + { + switch (world.getGlobalVariableType (name)) + { + case 's': + + return selectCompare (comp, value, world.getGlobalVariable (name).mShort); + + case 'l': + + return selectCompare (comp, value, world.getGlobalVariable (name).mLong); + + case 'f': + + return selectCompare (comp, value, world.getGlobalVariable (name).mFloat); + + case ' ': + + world.getGlobalVariable (name); // trigger exception + break; + + default: + + throw std::runtime_error ("unsupported gobal variable type"); + } + + return false; + } +} + +namespace MWDialogue +{ + bool DialogueManager::isMatching (const MWWorld::Ptr& actor, + const ESM::DialInfo::SelectStruct& select) const + { + char type = select.selectRule[1]; + + if (type!='0') + { + char comp = select.selectRule[4]; + std::string name = select.selectRule.substr (5); + + // TODO types 4, 5, 6, 7, 8, 9, A, B, C + + switch (type) + { + case '1': // function + + return false; // TODO implement functions + + case '2': // global + + if (select.type==ESM::VT_Short || select.type==ESM::VT_Int || + select.type==ESM::VT_Long) + { + if (!checkGlobal (comp, toLower (name), select.i, *mEnvironment.mWorld)) + return false; + } + else if (select.type==ESM::VT_Float) + { + if (!checkGlobal (comp, toLower (name), select.f, *mEnvironment.mWorld)) + return false; + } + else + throw std::runtime_error ( + "unsupported variable type in dialogue info select"); + + return true; + + case '3': // local + + if (select.type==ESM::VT_Short || select.type==ESM::VT_Int || + select.type==ESM::VT_Long) + { + if (!checkLocal (comp, toLower (name), select.i, actor, + mEnvironment.mWorld->getStore())) + return false; + } + else if (select.type==ESM::VT_Float) + { + if (!checkLocal (comp, toLower (name), select.f, actor, + mEnvironment.mWorld->getStore())) + return false; + } + else + throw std::runtime_error ( + "unsupported variable type in dialogue info select"); + + return true; + + default: + + std::cout << "unchecked select: " << type << " " << comp << " " << name << std::endl; + } + } + + return true; + } + + bool DialogueManager::isMatching (const MWWorld::Ptr& actor, const ESM::DialInfo& info) const + { + // actor id + if (!info.actor.empty()) + if (toLower (info.actor)!=MWWorld::Class::get (actor).getId (actor)) + return false; + + if (!info.race.empty()) + { + ESMS::LiveCellRef *cellRef = actor.get(); + + if (!cellRef) + return false; + + if (toLower (info.race)!=toLower (cellRef->base->race)) + return false; + } + + if (!info.clas.empty()) + { + ESMS::LiveCellRef *cellRef = actor.get(); + + if (!cellRef) + return false; + + if (toLower (info.clas)!=toLower (cellRef->base->cls)) + return false; + } + + if (!info.npcFaction.empty()) + { + ESMS::LiveCellRef *cellRef = actor.get(); + + if (!cellRef) + return false; + + if (toLower (info.npcFaction)!=toLower (cellRef->base->faction)) + return false; + } + + // TODO check player faction + + // check cell + if (!info.cell.empty()) + if (mEnvironment.mWorld->getPlayerPos().getPlayer().getCell()->cell->name != info.cell) + return false; + + // TODO check DATAstruct + + for (std::vector::const_iterator iter (info.selects.begin()); + iter != info.selects.end(); ++iter) + if (!isMatching (actor, *iter)) + return false; + + std::cout + << "unchecked entries:" << std::endl + << " player faction: " << info.pcFaction << std::endl + << " DATAstruct" << std::endl; + + return true; + } + + DialogueManager::DialogueManager (MWWorld::Environment& environment) : mEnvironment (environment) {} + + void DialogueManager::startDialogue (const MWWorld::Ptr& actor) + { + std::cout << "talking with " << MWWorld::Class::get (actor).getName (actor) << std::endl; + + const ESM::Dialogue *dialogue = mEnvironment.mWorld->getStore().dialogs.find ("hello"); + + for (std::vector::const_iterator iter (dialogue->mInfo.begin()); + iter!=dialogue->mInfo.end(); ++iter) + { + if (isMatching (actor, *iter)) + { + // start dialogue + std::cout << "found matching info record" << std::endl; + + std::cout << "response: " << iter->response << std::endl; + + if (!iter->sound.empty()) + { + // TODO play sound + } + + if (!iter->resultScript.empty()) + { + std::cout << "script: " << iter->resultScript << std::endl; + // TODO execute script + } + + mEnvironment.mWindowManager->setMode (MWGui::GM_Dialogue); + } + } + } + +} diff --git a/apps/openmw/mwdialogue/dialoguemanager.hpp b/apps/openmw/mwdialogue/dialoguemanager.hpp new file mode 100644 index 000000000..5b6b26240 --- /dev/null +++ b/apps/openmw/mwdialogue/dialoguemanager.hpp @@ -0,0 +1,32 @@ +#ifndef GAME_MMDIALOG_DIALOGUEMANAGER_H +#define GAME_MWDIALOG_DIALOGUEMANAGER_H + +#include + +#include "../mwworld/ptr.hpp" + +namespace MWWorld +{ + class Environment; +} + +namespace MWDialogue +{ + class DialogueManager + { + MWWorld::Environment& mEnvironment; + + bool isMatching (const MWWorld::Ptr& actor, const ESM::DialInfo::SelectStruct& select) const; + + bool isMatching (const MWWorld::Ptr& actor, const ESM::DialInfo& info) const; + + public: + + DialogueManager (MWWorld::Environment& environment); + + void startDialogue (const MWWorld::Ptr& actor); + + }; +} + +#endif diff --git a/apps/openmw/mwworld/actiontalk.cpp b/apps/openmw/mwworld/actiontalk.cpp index 2b4aba735..7d38c22fd 100644 --- a/apps/openmw/mwworld/actiontalk.cpp +++ b/apps/openmw/mwworld/actiontalk.cpp @@ -3,7 +3,7 @@ #include "environment.hpp" -#include "../mwgui/window_manager.hpp" +#include "../mwdialogue/dialoguemanager.hpp" namespace MWWorld { @@ -11,6 +11,6 @@ namespace MWWorld void ActionTalk::execute (Environment& environment) { - environment.mWindowManager->setMode (MWGui::GM_Dialogue); + environment.mDialogueManager->startDialogue (mActor); } } diff --git a/apps/openmw/mwworld/actiontalk.hpp b/apps/openmw/mwworld/actiontalk.hpp index 034d6131c..a60a61660 100644 --- a/apps/openmw/mwworld/actiontalk.hpp +++ b/apps/openmw/mwworld/actiontalk.hpp @@ -13,6 +13,7 @@ namespace MWWorld public: ActionTalk (const Ptr& actor); + ///< \param actor The actor the player is talking to virtual void execute (Environment& environment); }; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 386ba650e..bc36944e6 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -14,6 +14,11 @@ namespace MWWorld Class::~Class() {} + std::string Class::getId (const Ptr& ptr) const + { + throw std::runtime_error ("class does not support ID retrieval"); + } + void Class::insertObj (const Ptr& ptr, MWRender::CellRenderImp& cellRender, MWWorld::Environment& environment) const { diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index d230605da..4b6644e96 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -42,6 +42,10 @@ namespace MWWorld virtual ~Class(); + 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) + virtual void insertObj (const Ptr& ptr, MWRender::CellRenderImp& cellRender, MWWorld::Environment& environment) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). diff --git a/apps/openmw/mwworld/environment.hpp b/apps/openmw/mwworld/environment.hpp index 0c6476d52..f9e2e8a42 100644 --- a/apps/openmw/mwworld/environment.hpp +++ b/apps/openmw/mwworld/environment.hpp @@ -21,17 +21,22 @@ namespace MWMechanics class MechanicsManager; } +namespace MWDialogue +{ + class DialogueManager; +} + namespace MWWorld { class World; ///< Collection of script-accessable sub-systems class Environment - { + { public: Environment() : mWorld (0), mSoundManager (0), mGlobalScripts (0), mWindowManager (0), - mMechanicsManager (0), mFrameDuration (0) + mMechanicsManager (0), mDialogueManager (0), mFrameDuration (0) {} World *mWorld; @@ -39,9 +44,9 @@ namespace MWWorld MWScript::GlobalScripts *mGlobalScripts; MWGui::WindowManager *mWindowManager; MWMechanics::MechanicsManager *mMechanicsManager; + MWDialogue::DialogueManager *mDialogueManager; float mFrameDuration; }; } #endif - diff --git a/cmake/FindIconv.cmake b/cmake/FindIconv.cmake index 000e09b84..571a959af 100644 --- a/cmake/FindIconv.cmake +++ b/cmake/FindIconv.cmake @@ -14,6 +14,11 @@ IF (ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) SET(ICONV_FIND_QUIETLY TRUE) ENDIF (ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) +IF(WIN32) + SET(ICONV_INCLUDE_DIR $ENV{ICONV_INCLUDE_DIR}) + SET(ICONV_LIBRARIES $ENV{ICONV_LIBRARIES}) +ENDIF(WIN32) + FIND_PATH(ICONV_INCLUDE_DIR iconv.h) FIND_LIBRARY(ICONV_LIBRARIES NAMES iconv libiconv c) diff --git a/components/esm/esm_reader.hpp b/components/esm/esm_reader.hpp index 70a81e6f4..32f5570ad 100644 --- a/components/esm/esm_reader.hpp +++ b/components/esm/esm_reader.hpp @@ -9,7 +9,10 @@ #include #include #include -#include + +#ifndef __WIN32__ + #include +#endif #include #include @@ -618,89 +621,93 @@ public: return convertToUTF8(res); } - // Convert a string from the encoding used by Morrowind to UTF-8 - std::string convertToUTF8(std::string input) - { - std::string output = ""; - - //create convert description - iconv_t cd = iconv_open("UTF-8", "WINDOWS-1252"); - - if (cd == (iconv_t)-1) //error handling - { - std::string errMsg = "Creating description for UTF-8 converting failed: "; - - switch (errno) //detailed error messages (maybe it contains too much detail :) - { - case EMFILE: - errMsg += "{OPEN_MAX} files descriptors are currently open in the calling process."; - case ENFILE: - errMsg += "Too many files are currently open in the system."; - case ENOMEM: - errMsg +="Insufficient storage space is available."; - case EINVAL: - errMsg += "The conversion specified by fromcode and tocode is not supported by the implementation."; - - default: - errMsg += "Unknown Error\n"; - } - - fail(errMsg); - - } - else + // Convert a string from the encoding used by Morrowind to UTF-8 + std::string convertToUTF8 (std::string input) { - const size_t inputSize = input.size(); +#ifdef __WIN32__ + return input; +#else + std::string output = ""; - if (inputSize) //input is not empty - { - //convert function doesn't accept const char *, therefore copy content into an char * - std::vector inputBuffer(input.begin(), input.end()); - char *inputBufferBegin = &inputBuffer[0]; + //create convert description + iconv_t cd = iconv_open ("UTF-8", "WINDOWS-1252"); - size_t inputBytesLeft = inputSize; //bytes to convert + if (cd == (iconv_t)-1) //error handling + { + std::string errMsg = "Creating description for UTF-8 converting failed: "; - static const size_t outputSize = 1000; - size_t outputBytesLeft; + switch (errno) //detailed error messages (maybe it contains too much detail :) + { + case EMFILE: + errMsg += "{OPEN_MAX} files descriptors are currently open in the calling process."; + case ENFILE: + errMsg += "Too many files are currently open in the system."; + case ENOMEM: + errMsg +="Insufficient storage space is available."; + case EINVAL: + errMsg += "The conversion specified by fromcode and tocode is not supported by the implementation."; + + default: + errMsg += "Unknown Error\n"; + } - char outputBuffer[outputSize]; - char *outputBufferBegin; + fail (errMsg); - while (inputBytesLeft > 0 ) + } + else { - outputBytesLeft = outputSize; - outputBufferBegin = outputBuffer; + const size_t inputSize = input.size(); - if (iconv(cd, &inputBufferBegin, &inputBytesLeft, &outputBufferBegin, &outputBytesLeft) == (size_t)-1) - { - switch (errno) + if (inputSize) //input is not empty { - case E2BIG: //outputBuffer is full - output += std::string(outputBuffer, outputSize); - break; - case EILSEQ: - fail("Iconv: Invalid multibyte sequence.\n"); - break; - case EINVAL: - fail("Iconv: Incomplete multibyte sequence.\n"); - break; - default: - fail("Iconv: Unknown Error\n"); - } + //convert function doesn't accept const char *, therefore copy content into an char * + std::vector inputBuffer (input.begin(), input.end()); + char *inputBufferBegin = &inputBuffer[0]; + + size_t inputBytesLeft = inputSize; //bytes to convert + + static const size_t outputSize = 1000; + size_t outputBytesLeft; + + char outputBuffer[outputSize]; + char *outputBufferBegin; + + while (inputBytesLeft > 0) + { + outputBytesLeft = outputSize; + outputBufferBegin = outputBuffer; + + if (iconv (cd, &inputBufferBegin, &inputBytesLeft, &outputBufferBegin, &outputBytesLeft) == (size_t)-1) + { + switch (errno) + { + case E2BIG: //outputBuffer is full + output += std::string (outputBuffer, outputSize); + break; + case EILSEQ: + fail ("Iconv: Invalid multibyte sequence.\n"); + break; + case EINVAL: + fail ("Iconv: Incomplete multibyte sequence.\n"); + break; + default: + fail ("Iconv: Unknown Error\n"); + } + + } + } + + //read only relevant bytes from outputBuffer + output += std::string (outputBuffer, outputSize - outputBytesLeft); - } + } } - //read only relevant bytes from outputBuffer - output += std::string(outputBuffer, outputSize - outputBytesLeft); + iconv_close (cd); - } + return output; } - - iconv_close (cd); - - return output; - } +#endif void skip(int bytes) { esm->seek(esm->tell()+bytes); } uint64_t getOffset() { return esm->tell(); } diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index 4c80fc783..c435eb276 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -1,7 +1,10 @@ #ifndef _ESM_DIAL_H #define _ESM_DIAL_H +#include + #include "esm_reader.hpp" +#include "loadinfo.hpp" namespace ESM { @@ -23,6 +26,7 @@ struct Dialogue }; char type; + std::vector mInfo; void load(ESMReader &esm) { diff --git a/components/esm/records.hpp b/components/esm/records.hpp index 4ef8ce564..fb5733c63 100644 --- a/components/esm/records.hpp +++ b/components/esm/records.hpp @@ -14,13 +14,13 @@ #include "loadcont.hpp" #include "loadcrea.hpp" #include "loadcrec.hpp" +#include "loadinfo.hpp" #include "loaddial.hpp" #include "loaddoor.hpp" #include "loadench.hpp" #include "loadfact.hpp" #include "loadglob.hpp" #include "loadgmst.hpp" -#include "loadinfo.hpp" #include "loadingr.hpp" #include "loadland.hpp" #include "loadlevlist.hpp" diff --git a/components/esm_store/store.cpp b/components/esm_store/store.cpp index 2016272e0..c43855873 100644 --- a/components/esm_store/store.cpp +++ b/components/esm_store/store.cpp @@ -18,32 +18,70 @@ static string toStr(int i) void ESMStore::load(ESMReader &esm) { - set missing; + set missing; - // Loop through all records - while(esm.hasMoreRecs()) + ESM::Dialogue *dialogue = 0; + + // Loop through all records + while(esm.hasMoreRecs()) { - NAME n = esm.getRecName(); - esm.getRecHeader(); + NAME n = esm.getRecName(); + esm.getRecHeader(); - // Look up the record type. - RecListList::iterator it = recLists.find(n.val); + // Look up the record type. + RecListList::iterator it = recLists.find(n.val); - if(it == recLists.end()) + if(it == recLists.end()) { - // Not found (this would be an error later) - esm.skipRecord(); - missing.insert(n.toString()); - continue; + if (n.val==ESM::REC_INFO) + { + if (dialogue) + { + ESM::DialInfo info; + info.load (esm); + + dialogue->mInfo.push_back (info); + } + else + { + std::cerr << "error: info record without dialog" << std::endl; + esm.skipRecord(); + continue; + } + } + else + { + // Not found (this would be an error later) + esm.skipRecord(); + missing.insert(n.toString()); + continue; + } } + else + { + // Load it + std::string id = esm.getHNOString("NAME"); + it->second->load(esm, id); + + if (n.val==ESM::REC_DIAL) + { + RecListT& recList = static_cast& > (*it->second); - // Load it - std::string id = esm.getHNOString("NAME"); - it->second->load(esm, id); + id = recList.toLower (id); - // Insert the reference into the global lookup - if(!id.empty()) - all[id] = n.val; + RecListT::MapType::iterator iter = recList.list.find (id); + + assert (iter!=recList.list.end()); + + dialogue = &iter->second; + } + else + dialogue = 0; + + // Insert the reference into the global lookup + if(!id.empty()) + all[id] = n.val; + } } /* This information isn't needed on screen. But keep the code around diff --git a/components/esm_store/store.hpp b/components/esm_store/store.hpp index 6138014ef..1b9184aa6 100644 --- a/components/esm_store/store.hpp +++ b/components/esm_store/store.hpp @@ -69,7 +69,6 @@ namespace ESMS // Lists that need special rules CellList cells; RecIDListT gameSettings; - //RecListT dialInfos; //RecListT lands; //RecListT landTexts; //RecListT magicEffects; @@ -92,7 +91,6 @@ namespace ESMS ESMStore() { - recLists[REC_ACTI] = &activators; recLists[REC_ACTI] = &activators; recLists[REC_ALCH] = &potions; recLists[REC_APPA] = &appas; @@ -113,7 +111,6 @@ namespace ESMS recLists[REC_FACT] = &factions; recLists[REC_GLOB] = &globals; recLists[REC_GMST] = &gameSettings; - //recLists[REC_INFO] = &dialInfos; recLists[REC_INGR] = &ingreds; //recLists[REC_LAND] = &lands; recLists[REC_LEVC] = &creatureLists; diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 670fba830..b935f49f8 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -26,12 +26,13 @@ #include #include -#include "components/nif/nif_file.hpp" -#include "components/nif/node.hpp" -#include "components/nif/data.hpp" -#include "components/nif/property.hpp" -#include "libs/platform/strings.h" +#include "../nif/nif_file.hpp" +#include "../nif/node.hpp" +#include "../nif/data.hpp" +#include "../nif/property.hpp" +#include +#include // For warning messages #include @@ -45,93 +46,127 @@ using namespace Ogre; using namespace Nif; using namespace Mangle::VFS; -// This is the interface to the Ogre resource system. It allows us to -// load NIFs from BSAs, in the file system and in any other place we -// tell Ogre to look (eg. in zip or rar files.) It's also used to -// check for the existence of texture files, so we can exchange the -// extension from .tga to .dds if the texture is missing. -static OgreVFS *vfs; +NIFLoader& NIFLoader::getSingleton() +{ + static NIFLoader instance; + + return instance; +} + +NIFLoader* NIFLoader::getSingletonPtr() +{ + return &getSingleton(); +} + +void NIFLoader::warn(string msg) +{ + std::cerr << "NIFLoader: Warn:" << msg << "\n"; +} -// Singleton instance used by load() -static NIFLoader g_sing; +void NIFLoader::fail(string msg) +{ + std::cerr << "NIFLoader: Fail: "<< msg << std::endl; + assert(1); +} + +Vector3 NIFLoader::convertVector3(const Nif::Vector& vec) +{ + return Ogre::Vector3(vec.array); +} -// Makeshift error reporting system -static string errName; -static void warn(const string &msg) +Quaternion NIFLoader::convertRotation(const Nif::Matrix& rot) { - cout << "WARNING (NIF:" << errName << "): " << msg << endl; + Real matrix[3][3]; + + for (int i=0; i<3; i++) + for (int j=0; j<3; j++) + matrix[i][j] = rot.v[i].array[j]; + + return Quaternion(Matrix3(matrix)); } // Helper class that computes the bounding box and of a mesh class BoundsFinder { - struct MaxMinFinder - { - float max, min; + struct MaxMinFinder + { + float max, min; + + MaxMinFinder() + { + min = numeric_limits::infinity(); + max = -min; + } + + void add(float f) + { + if (f > max) max = f; + if (f < min) min = f; + } - MaxMinFinder() + // 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) { - min = numeric_limits::infinity(); - max = -min; + for (int i=0;i max) max = f; - if(f < min) min = f; + return + minX() <= maxX() && + minY() <= maxY() && + minZ() <= maxZ(); } - // Return Max(max**2, min**2) - float getMaxSquared() + // Compute radius + float getRadius() { - float m1 = max*max; - float m2 = min*min; - if(m1 >= m2) return m1; - return m2; - } - }; + assert(isValid()); - MaxMinFinder X, Y, Z; + // The radius is computed from the origin, not from the geometric + // center of the mesh. + return sqrt(X.getMaxSquared() + Y.getMaxSquared() + Z.getMaxSquared()); + } -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;i OGRE. @@ -177,7 +212,7 @@ static CompareFunction getTestMode(int mode) } */ -static void createMaterial(const String &name, +void NIFLoader::createMaterial(const String &name, const Vector &ambient, const Vector &diffuse, const Vector &specular, @@ -186,207 +221,195 @@ static void createMaterial(const String &name, float alphaFlags, float alphaTest, const String &texName) { - MaterialPtr material = MaterialManager::getSingleton().create(name, "General"); - - // This assigns the texture to this material. If the texture name is - // a file name, and this file exists (in a resource directory), it - // will automatically be loaded when needed. If not (such as for - // internal NIF textures that we might support later), we should - // already have inserted a manual loader for the texture. - if(!texName.empty()) + MaterialPtr material = MaterialManager::getSingleton().create(name, resourceGroup); + + // This assigns the texture to this material. If the texture name is + // a file name, and this file exists (in a resource directory), it + // will automatically be loaded when needed. If not (such as for + // internal NIF textures that we might support later), we should + // already have inserted a manual loader for the texture. + if (!texName.empty()) { - Pass *pass = material->getTechnique(0)->getPass(0); - /*TextureUnitState *txt =*/ pass->createTextureUnitState(texName); - - /* As of yet UNTESTED code from Chris: - pass->setTextureFiltering(Ogre::TFO_ANISOTROPIC); - pass->setDepthFunction(Ogre::CMPF_LESS_EQUAL); - pass->setDepthCheckEnabled(true); - - // Add transparency if NiAlphaProperty was present - if(alphaFlags != -1) + Pass *pass = material->getTechnique(0)->getPass(0); + /*TextureUnitState *txt =*/ + pass->createTextureUnitState(texName); + + /* As of yet UNTESTED code from Chris: + pass->setTextureFiltering(Ogre::TFO_ANISOTROPIC); + pass->setDepthFunction(Ogre::CMPF_LESS_EQUAL); + pass->setDepthCheckEnabled(true); + + // Add transparency if NiAlphaProperty was present + if(alphaFlags != -1) + { + if((alphaFlags&1)) + { + pass->setDepthWriteEnabled(false); + pass->setSceneBlending(getBlendFactor((alphaFlags>>1)&0xf), + getBlendFactor((alphaFlags>>5)&0xf)); + } + else + pass->setDepthWriteEnabled(true); + + if((alphaFlags>>9)&1) + pass->setAlphaRejectSettings(getTestMode((alphaFlags>>10)&0x7), + alphaTest); + + pass->setTransparentSortingEnabled(!((alphaFlags>>13)&1)); + } + else + pass->setDepthWriteEnabled(true); + */ + + // Add transparency if NiAlphaProperty was present + if (alphaFlags != -1) { - if((alphaFlags&1)) + // The 237 alpha flags are by far the most common. Check + // NiAlphaProperty in nif/property.h if you need to decode + // other values. 237 basically means normal transparencly. + if (alphaFlags == 237) { - pass->setDepthWriteEnabled(false); - pass->setSceneBlending(getBlendFactor((alphaFlags>>1)&0xf), - getBlendFactor((alphaFlags>>5)&0xf)); - } - else - pass->setDepthWriteEnabled(true); - - if((alphaFlags>>9)&1) - pass->setAlphaRejectSettings(getTestMode((alphaFlags>>10)&0x7), - alphaTest); + // Enable transparency + pass->setSceneBlending(SBT_TRANSPARENT_ALPHA); - pass->setTransparentSortingEnabled(!((alphaFlags>>13)&1)); - } - else - pass->setDepthWriteEnabled(true); - */ - - // Add transparency if NiAlphaProperty was present - if(alphaFlags != -1) - { - // The 237 alpha flags are by far the most common. Check - // NiAlphaProperty in nif/property.h if you need to decode - // other values. 237 basically means normal transparencly. - if(alphaFlags == 237) - { - // Enable transparency - pass->setSceneBlending(SBT_TRANSPARENT_ALPHA); - - //pass->setDepthCheckEnabled(false); - pass->setDepthWriteEnabled(false); + //pass->setDepthCheckEnabled(false); + pass->setDepthWriteEnabled(false); } - else - warn("Unhandled alpha setting for texture " + texName); + else + warn("Unhandled alpha setting for texture " + texName); } } - // Add material bells and whistles - material->setAmbient(ambient.array[0], ambient.array[1], ambient.array[2]); - material->setDiffuse(diffuse.array[0], diffuse.array[1], diffuse.array[2], alpha); - material->setSpecular(specular.array[0], specular.array[1], specular.array[2], alpha); - material->setSelfIllumination(emissive.array[0], emissive.array[1], emissive.array[2]); - material->setShininess(glossiness); + // Add material bells and whistles + material->setAmbient(ambient.array[0], ambient.array[1], ambient.array[2]); + material->setDiffuse(diffuse.array[0], diffuse.array[1], diffuse.array[2], alpha); + material->setSpecular(specular.array[0], specular.array[1], specular.array[2], alpha); + material->setSelfIllumination(emissive.array[0], emissive.array[1], emissive.array[2]); + material->setShininess(glossiness); } // Takes a name and adds a unique part to it. This is just used to // make sure that all materials are given unique names. -static String getUniqueName(const String &input) +String NIFLoader::getUniqueName(const String &input) { - static int addon = 0; - static char buf[8]; - snprintf(buf, 8, "_%d", addon++); + static int addon = 0; + static char buf[8]; + snprintf(buf, 8, "_%d", addon++); - // Don't overflow the buffer - if(addon > 999999) addon = 0; + // Don't overflow the buffer + if (addon > 999999) addon = 0; - return input + buf; + return input + buf; } // Check if the given texture name exists in the real world. If it // does not, change the string IN PLACE to say .dds instead and try // that. The texture may still not exist, but no information of value // is lost in that case. -static void findRealTexture(String &texName) +void NIFLoader::findRealTexture(String &texName) { - assert(vfs); - if(vfs->isFile(texName)) return; + assert(vfs); + if (vfs->isFile(texName)) return; - int len = texName.size(); - if(len < 4) return; + int len = texName.size(); + if (len < 4) return; - // Change texture extension to .dds - texName[len-3] = 'd'; - texName[len-2] = 'd'; - texName[len-1] = 's'; + // Change texture extension to .dds + texName[len-3] = 'd'; + texName[len-2] = 'd'; + texName[len-1] = 's'; } // Convert Nif::NiTriShape to Ogre::SubMesh, attached to the given // mesh. -static void createOgreMesh(Mesh *mesh, NiTriShape *shape, const String &material) +void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material) { - NiTriShapeData *data = shape->data.getPtr(); - SubMesh *sub = mesh->createSubMesh(shape->name.toString()); - - int nextBuf = 0; - - // This function is just one long stream of Ogre-barf, but it works - // great. - - // Add vertices - int numVerts = data->vertices.length / 3; - sub->vertexData = new VertexData(); - sub->vertexData->vertexCount = numVerts; - sub->useSharedVertices = false; - VertexDeclaration *decl = sub->vertexData->vertexDeclaration; - decl->addElement(nextBuf, 0, VET_FLOAT3, VES_POSITION); - HardwareVertexBufferSharedPtr vbuf = - HardwareBufferManager::getSingleton().createVertexBuffer( - VertexElement::getTypeSize(VET_FLOAT3), - numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY); - vbuf->writeData(0, vbuf->getSizeInBytes(), data->vertices.ptr, true); - VertexBufferBinding* bind = sub->vertexData->vertexBufferBinding; - bind->setBinding(nextBuf++, vbuf); - - // Vertex normals - if(data->normals.length) + NiTriShapeData *data = shape->data.getPtr(); + SubMesh *sub = mesh->createSubMesh(shape->name.toString()); + + int nextBuf = 0; + + // This function is just one long stream of Ogre-barf, but it works + // great. + + // Add vertices + int numVerts = data->vertices.length / 3; + sub->vertexData = new VertexData(); + sub->vertexData->vertexCount = numVerts; + sub->useSharedVertices = false; + + VertexDeclaration *decl = sub->vertexData->vertexDeclaration; + decl->addElement(nextBuf, 0, VET_FLOAT3, VES_POSITION); + + HardwareVertexBufferSharedPtr vbuf = + HardwareBufferManager::getSingleton().createVertexBuffer( + VertexElement::getTypeSize(VET_FLOAT3), + numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY); + vbuf->writeData(0, vbuf->getSizeInBytes(), data->vertices.ptr, true); + + VertexBufferBinding* bind = sub->vertexData->vertexBufferBinding; + bind->setBinding(nextBuf++, vbuf); + + // Vertex normals + if (data->normals.length) { - decl->addElement(nextBuf, 0, VET_FLOAT3, VES_NORMAL); - vbuf = HardwareBufferManager::getSingleton().createVertexBuffer( - VertexElement::getTypeSize(VET_FLOAT3), - numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY); - vbuf->writeData(0, vbuf->getSizeInBytes(), data->normals.ptr, true); - bind->setBinding(nextBuf++, vbuf); + decl->addElement(nextBuf, 0, VET_FLOAT3, VES_NORMAL); + vbuf = HardwareBufferManager::getSingleton().createVertexBuffer( + VertexElement::getTypeSize(VET_FLOAT3), + numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY); + vbuf->writeData(0, vbuf->getSizeInBytes(), data->normals.ptr, true); + bind->setBinding(nextBuf++, vbuf); } - // Vertex colors - if(data->colors.length) + // Vertex colors + if (data->colors.length) { - const float *colors = data->colors.ptr; - RenderSystem* rs = Root::getSingleton().getRenderSystem(); - std::vector colorsRGB(numVerts); - RGBA *pColour = &colorsRGB.front(); - for(int i=0; iconvertColourValue(ColourValue(colors[0],colors[1],colors[2], - colors[3]),pColour++); - colors += 4; - } - decl->addElement(nextBuf, 0, VET_COLOUR, VES_DIFFUSE); - vbuf = HardwareBufferManager::getSingleton().createVertexBuffer( - VertexElement::getTypeSize(VET_COLOUR), - numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY); - vbuf->writeData(0, vbuf->getSizeInBytes(), &colorsRGB.front(), true); - bind->setBinding(nextBuf++, vbuf); + const float *colors = data->colors.ptr; + RenderSystem* rs = Root::getSingleton().getRenderSystem(); + std::vector colorsRGB(numVerts); + RGBA *pColour = &colorsRGB.front(); + for (int i=0; iconvertColourValue(ColourValue(colors[0],colors[1],colors[2], + colors[3]),pColour++); + colors += 4; + } + decl->addElement(nextBuf, 0, VET_COLOUR, VES_DIFFUSE); + vbuf = HardwareBufferManager::getSingleton().createVertexBuffer( + VertexElement::getTypeSize(VET_COLOUR), + numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY); + vbuf->writeData(0, vbuf->getSizeInBytes(), &colorsRGB.front(), true); + bind->setBinding(nextBuf++, vbuf); } - // Texture UV coordinates - if(data->uvlist.length) + // Texture UV coordinates + if (data->uvlist.length) { - decl->addElement(nextBuf, 0, VET_FLOAT2, VES_TEXTURE_COORDINATES); - vbuf = HardwareBufferManager::getSingleton().createVertexBuffer( - VertexElement::getTypeSize(VET_FLOAT2), - numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY); + decl->addElement(nextBuf, 0, VET_FLOAT2, VES_TEXTURE_COORDINATES); + vbuf = HardwareBufferManager::getSingleton().createVertexBuffer( + VertexElement::getTypeSize(VET_FLOAT2), + numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY); - vbuf->writeData(0, vbuf->getSizeInBytes(), data->uvlist.ptr, true); - bind->setBinding(nextBuf++, vbuf); + vbuf->writeData(0, vbuf->getSizeInBytes(), data->uvlist.ptr, true); + bind->setBinding(nextBuf++, vbuf); } - // Triangle faces - int numFaces = data->triangles.length; - if(numFaces) + // Triangle faces + int numFaces = data->triangles.length; + if (numFaces) { - HardwareIndexBufferSharedPtr ibuf = HardwareBufferManager::getSingleton(). - createIndexBuffer(HardwareIndexBuffer::IT_16BIT, - numFaces, - HardwareBuffer::HBU_STATIC_WRITE_ONLY); - ibuf->writeData(0, ibuf->getSizeInBytes(), data->triangles.ptr, true); - sub->indexData->indexBuffer = ibuf; - sub->indexData->indexCount = numFaces; - sub->indexData->indexStart = 0; + HardwareIndexBufferSharedPtr ibuf = HardwareBufferManager::getSingleton(). + createIndexBuffer(HardwareIndexBuffer::IT_16BIT, + numFaces, + HardwareBuffer::HBU_STATIC_WRITE_ONLY); + ibuf->writeData(0, ibuf->getSizeInBytes(), data->triangles.ptr, true); + sub->indexData->indexBuffer = ibuf; + sub->indexData->indexCount = numFaces; + sub->indexData->indexStart = 0; } - // Set material if one was given - if(!material.empty()) sub->setMaterialName(material); - - /* Old commented D code. Might be useful when reimplementing - animation. - // Assign this submesh to the given bone - VertexBoneAssignment v; - v.boneIndex = ((Bone*)bone)->getHandle(); - v.weight = 1.0; - - std::cerr << "+ Assigning bone index " << v.boneIndex << "\n"; - - for(int i=0; i < numVerts; i++) - { - v.vertexIndex = i; - sub->addBoneAssignment(v); - } - */ + // Set material if one was given + if (!material.empty()) sub->setMaterialName(material); } // Helper math functions. Reinventing linear algebra for the win! @@ -394,351 +417,459 @@ static void createOgreMesh(Mesh *mesh, NiTriShape *shape, const String &material // Computes B = AxB (matrix*matrix) static void matrixMul(const Matrix &A, Matrix &B) { - for(int i=0;i<3;i++) + for (int i=0;i<3;i++) { - float a = B.v[0].array[i]; - float b = B.v[1].array[i]; - float c = B.v[2].array[i]; + float a = B.v[0].array[i]; + float b = B.v[1].array[i]; + float c = B.v[2].array[i]; - B.v[0].array[i] = a*A.v[0].array[0] + b*A.v[0].array[1] + c*A.v[0].array[2]; - B.v[1].array[i] = a*A.v[1].array[0] + b*A.v[1].array[1] + c*A.v[1].array[2]; - B.v[2].array[i] = a*A.v[2].array[0] + b*A.v[2].array[1] + c*A.v[2].array[2]; + B.v[0].array[i] = a*A.v[0].array[0] + b*A.v[0].array[1] + c*A.v[0].array[2]; + B.v[1].array[i] = a*A.v[1].array[0] + b*A.v[1].array[1] + c*A.v[1].array[2]; + B.v[2].array[i] = a*A.v[2].array[0] + b*A.v[2].array[1] + c*A.v[2].array[2]; } } // Computes C = B + AxC*scale static void vectorMulAdd(const Matrix &A, const Vector &B, float *C, float scale) { - // Keep the original values - float a = C[0]; - float b = C[1]; - float c = C[2]; - - // Perform matrix multiplication, scaling and addition - for(int i=0;i<3;i++) - C[i] = B.array[i] + (a*A.v[i].array[0] + b*A.v[i].array[1] + c*A.v[i].array[2])*scale; + // Keep the original values + float a = C[0]; + float b = C[1]; + float c = C[2]; + + // Perform matrix multiplication, scaling and addition + for (int i=0;i<3;i++) + C[i] = B.array[i] + (a*A.v[i].array[0] + b*A.v[i].array[1] + c*A.v[i].array[2])*scale; } // Computes B = AxB (matrix*vector) static void vectorMul(const Matrix &A, float *C) { - // Keep the original values - float a = C[0]; - float b = C[1]; - float c = C[2]; - - // Perform matrix multiplication, scaling and addition - for(int i=0;i<3;i++) - C[i] = a*A.v[i].array[0] + b*A.v[i].array[1] + c*A.v[i].array[2]; + // Keep the original values + float a = C[0]; + float b = C[1]; + float c = C[2]; + + // Perform matrix multiplication, scaling and addition + for (int i=0;i<3;i++) + C[i] = a*A.v[i].array[0] + b*A.v[i].array[1] + c*A.v[i].array[2]; } -static void handleNiTriShape(Mesh *mesh, NiTriShape *shape, int flags, BoundsFinder &bounds) +void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bounds) { - assert(shape != NULL); + assert(shape != NULL); - // Interpret flags - bool hidden = (flags & 0x01) != 0; // Not displayed - bool collide = (flags & 0x02) != 0; // Use mesh for collision - bool bbcollide = (flags & 0x04) != 0; // Use bounding box for collision + // Interpret flags + bool hidden = (flags & 0x01) != 0; // Not displayed + bool collide = (flags & 0x02) != 0; // Use mesh for collision + bool bbcollide = (flags & 0x04) != 0; // Use bounding box for collision - // Bounding box collision isn't implemented, always use mesh for now. - if(bbcollide) + // Bounding box collision isn't implemented, always use mesh for now. + if (bbcollide) { - collide = true; - bbcollide = false; + collide = true; + bbcollide = false; } - // If the object was marked "NCO" earlier, it shouldn't collide with - // anything. - if(flags & 0x800) - { collide = false; bbcollide = false; } + // If the object was marked "NCO" earlier, it shouldn't collide with + // anything. + if (flags & 0x800) + { + collide = false; + bbcollide = false; + } - if(!collide && !bbcollide && hidden) - // This mesh apparently isn't being used for anything, so don't - // bother setting it up. - return; + if (!collide && !bbcollide && hidden) + // This mesh apparently isn't being used for anything, so don't + // bother setting it up. + return; - // Material name for this submesh, if any - String material; + // Material name for this submesh, if any + String material; - // Skip the entire material phase for hidden nodes - if(!hidden) + // Skip the entire material phase for hidden nodes + if (!hidden) { - // These are set below if present - NiTexturingProperty *t = NULL; - NiMaterialProperty *m = NULL; - NiAlphaProperty *a = NULL; - - // Scan the property list for material information - PropertyList &list = shape->props; - int n = list.length(); - for(int i=0; iprops; + int n = list.length(); + for (int i=0; irecType == RC_NiTexturingProperty) - t = (NiTexturingProperty*)pr; - else if(pr->recType == RC_NiMaterialProperty) - m = (NiMaterialProperty*)pr; - else if(pr->recType == RC_NiAlphaProperty) - a = (NiAlphaProperty*)pr; + if (pr->recType == RC_NiTexturingProperty) + t = (NiTexturingProperty*)pr; + else if (pr->recType == RC_NiMaterialProperty) + m = (NiMaterialProperty*)pr; + else if (pr->recType == RC_NiAlphaProperty) + a = (NiAlphaProperty*)pr; } - // Texture - String texName; - if(t && t->textures[0].inUse) + // Texture + String texName; + if (t && t->textures[0].inUse) { - NiSourceTexture *st = t->textures[0].texture.getPtr(); - if(st->external) + NiSourceTexture *st = t->textures[0].texture.getPtr(); + if (st->external) { - SString tname = st->filename; - - /* findRealTexture checks if the file actually - exists. If it doesn't, and the name ends in .tga, it - will try replacing the extension with .dds instead - and search for that. Bethesda at some at some point - converted all their BSA textures from tga to dds for - increased load speed, but all texture file name - references were kept as .tga. - - The function replaces the name in place (that's why - we cast away the const modifier), but this is no - problem since all the nif data is stored in a local - throwaway buffer. - */ - texName = "textures\\" + tname.toString(); - findRealTexture(texName); + SString tname = st->filename; + + /* findRealTexture checks if the file actually + exists. If it doesn't, and the name ends in .tga, it + will try replacing the extension with .dds instead + and search for that. Bethesda at some at some point + converted all their BSA textures from tga to dds for + increased load speed, but all texture file name + references were kept as .tga. + + The function replaces the name in place (that's why + we cast away the const modifier), but this is no + problem since all the nif data is stored in a local + throwaway buffer. + */ + texName = "textures\\" + tname.toString(); + findRealTexture(texName); } - else warn("Found internal texture, ignoring."); + else warn("Found internal texture, ignoring."); } - // Alpha modifiers - int alphaFlags = -1; - ubyte alphaTest = 0; - if(a) + // Alpha modifiers + int alphaFlags = -1; + ubyte alphaTest = 0; + if (a) { - alphaFlags = a->flags; - alphaTest = a->data->threshold; + alphaFlags = a->flags; + alphaTest = a->data->threshold; } - // Material - if(m || !texName.empty()) + // Material + if (m || !texName.empty()) { - // If we're here, then this mesh has a material. Thus we - // need to calculate a snappy material name. It should - // contain the mesh name (mesh->getName()) but also has to - // be unique. One mesh may use many materials. - material = getUniqueName(mesh->getName()); + // If we're here, then this mesh has a material. Thus we + // need to calculate a snappy material name. It should + // contain the mesh name (mesh->getName()) but also has to + // be unique. One mesh may use many materials. + material = getUniqueName(mesh->getName()); - if(m) + if (m) { - // Use NiMaterialProperty data to create the data - const S_MaterialProperty *d = m->data; - createMaterial(material, d->ambient, d->diffuse, d->specular, d->emissive, - d->glossiness, d->alpha, alphaFlags, alphaTest, texName); + // Use NiMaterialProperty data to create the data + const S_MaterialProperty *d = m->data; + createMaterial(material, d->ambient, d->diffuse, d->specular, d->emissive, + d->glossiness, d->alpha, alphaFlags, alphaTest, texName); } - else + else { - // We only have a texture name. Create a default - // material for it. - Vector zero, one; - for(int i=0; i<3;i++) + // We only have a texture name. Create a default + // material for it. + Vector zero, one; + for (int i=0; i<3;i++) { - zero.array[i] = 0.0; - one.array[i] = 1.0; + zero.array[i] = 0.0; + one.array[i] = 1.0; } - createMaterial(material, one, one, zero, zero, 0.0, 1.0, - alphaFlags, alphaTest, texName); + createMaterial(material, one, one, zero, zero, 0.0, 1.0, + alphaFlags, alphaTest, texName); } } } // End of material block, if(!hidden) ... - /* Do in-place transformation of all the vertices and normals. This - is pretty messy stuff, but we need it to make the sub-meshes - appear in the correct place. Neither Ogre nor Bullet support - nested levels of sub-meshes with transformations applied to each - level. - */ - NiTriShapeData *data = shape->data.getPtr(); - int numVerts = data->vertices.length / 3; - - float *ptr = (float*)data->vertices.ptr; - float *optr = ptr; - - // Rotate, scale and translate all the vertices - const Matrix &rot = shape->trafo->rotation; - const Vector &pos = shape->trafo->pos; - float scale = shape->trafo->scale; - for(int i=0; idata.getPtr(); + int numVerts = data->vertices.length / 3; + + float *ptr = (float*)data->vertices.ptr; + float *optr = ptr; + + //use niskindata for the position of vertices. + if (!shape->skin.empty()) { - vectorMulAdd(rot, pos, ptr, scale); - ptr += 3; - } + // vector that stores if the position if a vertex is absolute + std::vector vertexPosAbsolut(numVerts,false); + + + float *ptrNormals = (float*)data->normals.ptr; + //the bone from skin->bones[boneIndex] is linked to skin->data->bones[boneIndex] + //the first one contains a link to the bone, the second vertex transformation + //relative to the bone + int boneIndex = 0; + Bone *bonePtr; + Vector3 vecPos; + Quaternion vecRot; + + std::vector boneList = shape->skin->data->bones; + + /* + Iterate through the boneList which contains what vertices are linked to + the bone (it->weights array) and at what position (it->trafo) + That position is added to every vertex. + */ + for (std::vector::iterator it = boneList.begin(); + it != boneList.end(); it++) + { + //get the bone from bones array of skindata + bonePtr = skel->getBone(shape->skin->bones[boneIndex].name.toString()); + + // final_vector = old_vector + old_rotation*new_vector*old_scale + vecPos = bonePtr->_getDerivedPosition() + + bonePtr->_getDerivedOrientation() * convertVector3(it->trafo->trans); + + vecRot = bonePtr->_getDerivedOrientation() * convertRotation(it->trafo->rotation); + + for (unsigned int i=0; iweights.length; i++) + { + unsigned int verIndex = (it->weights.ptr + i)->vertex; - // Remember to rotate all the vertex normals as well - if(data->normals.length) + //Check if the vertex is relativ, FIXME: Is there a better solution? + if (vertexPosAbsolut[verIndex] == false) + { + //apply transformation to the vertices + Vector3 absVertPos = vecPos + vecRot * Vector3(ptr + verIndex *3); + + //convert it back to float * + for (int j=0; j<3; j++) + (ptr + verIndex*3)[j] = absVertPos[j]; + + //apply rotation to the normals (not every vertex has a normal) + //FIXME: I guessed that vertex[i] = normal[i], is that true? + if (verIndex < data->normals.length) + { + Vector3 absNormalsPos = vecRot * Vector3(ptrNormals + verIndex *3); + + for (int j=0; j<3; j++) + (ptrNormals + verIndex*3)[j] = absNormalsPos[j]; + } + + + //TODO: create vbas, and give them to createOgreSubMesh + + vertexPosAbsolut[verIndex] = true; + } + } + + boneIndex++; + //it->trafo (pos, rot, scale) of the vertex + //it->weights array containt the vertices linked to the bone and the weight + } + } + else { - ptr = (float*)data->normals.ptr; - for(int i=0; itrafo->rotation; + const Vector &pos = shape->trafo->pos; + float scale = shape->trafo->scale; + for (int i=0; inormals.length) + { + ptr = (float*)data->normals.ptr; + for (int i=0; iflags; + // Accumulate the flags from all the child nodes. This works for all + // the flags we currently use, at least. + flags |= node->flags; - // Check for extra data - Extra *e = node; - while(!e->extra.empty()) + // Check for extra data + Extra *e = node; + while (!e->extra.empty()) { - // Get the next extra data in the list - e = e->extra.getPtr(); - assert(e != NULL); + // Get the next extra data in the list + e = e->extra.getPtr(); + assert(e != NULL); - if(e->recType == RC_NiStringExtraData) + if (e->recType == RC_NiStringExtraData) { - // String markers may contain important information - // affecting the entire subtree of this node - NiStringExtraData *sd = (NiStringExtraData*)e; - - if(sd->string == "NCO") - // No collision. Use an internal flag setting to mark this. - flags |= 0x800; - else if(sd->string == "MRK") - // Marker objects. These are only visible in the - // editor. Until and unless we add an editor component to - // the engine, just skip this entire node. - return; + // String markers may contain important information + // affecting the entire subtree of this node + NiStringExtraData *sd = (NiStringExtraData*)e; + + if (sd->string == "NCO") + // No collision. Use an internal flag setting to mark this. + flags |= 0x800; + else if (sd->string == "MRK") + // Marker objects. These are only visible in the + // editor. Until and unless we add an editor component to + // the engine, just skip this entire node. + return; } } - // Apply the parent transformation to this node. We overwrite the - // existing data with the final transformation. - if(trafo) + Bone *bone = 0; + + // create skeleton or add bones + if (node->recType == RC_NiNode) { - // Get a non-const reference to the node's data, since we're - // overwriting it. TODO: Is this necessary? - Transformation &final = *((Transformation*)node->trafo); + if (node->name == "Bip01") //root node, create a skeleton + { + skel = SkeletonManager::getSingleton().create(getSkeletonName(), resourceGroup, true); + } - // For both position and rotation we have that: - // final_vector = old_vector + old_rotation*new_vector*old_scale - vectorMulAdd(trafo->rotation, trafo->pos, final.pos.array, trafo->scale); - vectorMulAdd(trafo->rotation, trafo->velocity, final.velocity.array, trafo->scale); + if (!skel.isNull()) //if there is a skeleton + { + bone = skel->createBone(node->name.toString()); - // Merge the rotations together - matrixMul(trafo->rotation, final.rotation); + if (parentBone) + parentBone->addChild(bone); - // Scalar values are so nice to deal with. Why can't everything - // just be scalar? - final.scale *= trafo->scale; + bone->setInheritOrientation(true); + bone->setPosition(convertVector3(node->trafo->pos)); + bone->setOrientation(convertRotation(node->trafo->rotation)); + } } - // For NiNodes, loop through children - if(node->recType == RC_NiNode) + // Apply the parent transformation to this node. We overwrite the + // existing data with the final transformation. + if (trafo) { - NodeList &list = ((NiNode*)node)->children; - int n = list.length(); - for(int i=0; itrafo); + + // For both position and rotation we have that: + // final_vector = old_vector + old_rotation*new_vector*old_scale + vectorMulAdd(trafo->rotation, trafo->pos, final.pos.array, trafo->scale); + vectorMulAdd(trafo->rotation, trafo->velocity, final.velocity.array, trafo->scale); + + // Merge the rotations together + matrixMul(trafo->rotation, final.rotation); + + // Scalar values are so nice to deal with. Why can't everything + // just be scalar? + final.scale *= trafo->scale; + } + + // For NiNodes, loop through children + if (node->recType == RC_NiNode) + { + NodeList &list = ((NiNode*)node)->children; + int n = list.length(); + for (int i=0; itrafo, bounds); + if (list.has(i)) + handleNode(&list[i], flags, node->trafo, bounds, bone); } } - else if(node->recType == RC_NiTriShape) + else if (node->recType == RC_NiTriShape) // For shapes - handleNiTriShape(mesh, dynamic_cast(node), flags, bounds); + handleNiTriShape(dynamic_cast(node), flags, bounds); } void NIFLoader::loadResource(Resource *resource) { - // Set up the VFS if it hasn't been done already - if(!vfs) vfs = new OgreVFS("General"); + resourceName = ""; + mesh = 0; + skel.setNull(); + + // Set up the VFS if it hasn't been done already + if (!vfs) vfs = new OgreVFS(resourceGroup); + + // Get the mesh + mesh = dynamic_cast(resource); + assert(mesh); - // Get the mesh - Mesh *mesh = dynamic_cast(resource); - assert(mesh); + // Look it up + resourceName = mesh->getName(); - // Look it up - const String &name = mesh->getName(); - errName = name; // Set name for error messages - if(!vfs->isFile(name)) + if (!vfs->isFile(resourceName)) { - warn("File not found."); - return; + warn("File not found."); + return; } - // Helper that computes bounding boxes for us. - BoundsFinder bounds; + // Helper that computes bounding boxes for us. + BoundsFinder bounds; - // Load the NIF. TODO: Wrap this in a try-catch block once we're out - // of the early stages of development. Right now we WANT to catch - // every error as early and intrusively as possible, as it's most - // likely a sign of incomplete code rather than faulty input. - NIFFile nif(vfs->open(name), name); + // Load the NIF. TODO: Wrap this in a try-catch block once we're out + // of the early stages of development. Right now we WANT to catch + // every error as early and intrusively as possible, as it's most + // likely a sign of incomplete code rather than faulty input. + NIFFile nif(vfs->open(resourceName), resourceName); - if(nif.numRecords() < 1) + if (nif.numRecords() < 1) { - warn("Found no records in NIF."); - return; + warn("Found no records in NIF."); + return; } - // The first record is assumed to be the root node - Record *r = nif.getRecord(0); - assert(r != NULL); + // The first record is assumed to be the root node + Record *r = nif.getRecord(0); + assert(r != NULL); - Nif::Node *node = dynamic_cast(r); + Nif::Node *node = dynamic_cast(r); - if(node == NULL) + if (node == NULL) { - warn("First record in file was not a node, but a " + - r->recName.toString() + ". Skipping file."); - return; + warn("First record in file was not a node, but a " + + r->recName.toString() + ". Skipping file."); + return; } - // Handle the node - handleNode(mesh, node, 0, NULL, bounds); + // Handle the node + handleNode(node, 0, NULL, bounds, 0); - // Finally, set the bounding value. - if(bounds.isValid()) + //set skeleton +// if (!skel.isNull()) +// mesh->setSkeletonName(getSkeletonName()); + + // Finally, set the bounding value. + if (bounds.isValid()) { - mesh->_setBounds(AxisAlignedBox(bounds.minX(), bounds.minY(), bounds.minZ(), - bounds.maxX(), bounds.maxY(), bounds.maxZ())); - mesh->_setBoundingSphereRadius(bounds.getRadius()); + mesh->_setBounds(AxisAlignedBox(bounds.minX(), bounds.minY(), bounds.minZ(), + bounds.maxX(), bounds.maxY(), bounds.maxZ())); + mesh->_setBoundingSphereRadius(bounds.getRadius()); } } MeshPtr NIFLoader::load(const std::string &name, const std::string &group) { - MeshManager *m = MeshManager::getSingletonPtr(); + MeshManager *m = MeshManager::getSingletonPtr(); - // Check if the resource already exists - ResourcePtr ptr = m->getByName(name, group); - if(!ptr.isNull()) - return MeshPtr(ptr); + // Check if the resource already exists + ResourcePtr ptr = m->getByName(name, group); + if (!ptr.isNull()) + return MeshPtr(ptr); - // Nope, create a new one. - return MeshManager::getSingleton().createManual(name, group, &g_sing); + // Nope, create a new one. + return MeshManager::getSingleton().createManual(name, group, NIFLoader::getSingletonPtr()); } /* More code currently not in use, from the old D source. This was @@ -779,70 +910,5 @@ extern "C" void ogre_insertTexture(char* name, uint32_t width, uint32_t height, pixelBuffer->unlock(); } -// We need this later for animated meshes. -extern "C" void* ogre_setupSkeleton(char* name) -{ - SkeletonPtr skel = SkeletonManager::getSingleton().create( - name, "Closet", true); - - skel->load(); - - // Create all bones at the origin and unrotated. This is necessary - // since our submeshes each have their own model space. We must - // move the bones after creating an entity, then copy this entity. - return (void*)skel->createBone(); -} - -extern "C" void *ogre_insertBone(char* name, void* rootBone, int32_t index) -{ - return (void*) ( ((Bone*)rootBone)->createChild(index) ); -} -*/ -/* This was the D part: - - // Create a skeleton and get the root bone (index 0) - BonePtr bone = ogre_setupSkeleton(name); - // Reset the bone index. The next bone to be created has index 1. - boneIndex = 1; - // Create a mesh and assign the skeleton to it - MeshPtr mesh = ogre_setupMesh(name); - - // Loop through the nodes, creating submeshes, materials and - // skeleton bones in the process. - handleNode(node, bone, mesh); - - // Create the "template" entity - EntityPtr entity = ogre_createEntity(name); - - // Loop through once again, this time to set the right - // transformations on the entity's SkeletonInstance. The order of - // children will be the same, allowing us to reference bones using - // their boneIndex. - int lastBone = boneIndex; - boneIndex = 1; - transformBones(node, entity); - if(lastBone != boneIndex) writefln("WARNING: Bone number doesn't match"); - - if(!hasBBox) - ogre_setMeshBoundingBox(mesh, minX, minY, minZ, maxX, maxY, maxZ); - - return entity; -} -void handleNode(Node node, BonePtr root, MeshPtr mesh) -{ - // Insert a new bone for this node - BonePtr bone = ogre_insertBone(node.name, root, boneIndex++); - -} - -void transformBones(Node node, EntityPtr entity) -{ - ogre_transformBone(entity, &node.trafo, boneIndex++); - - NiNode n = cast(NiNode)node; - if(n !is null) - foreach(Node nd; n.children) - transformBones(nd, entity); -} */ diff --git a/components/nifogre/ogre_nif_loader.hpp b/components/nifogre/ogre_nif_loader.hpp index 96b746e16..aea8d3a8d 100644 --- a/components/nifogre/ogre_nif_loader.hpp +++ b/components/nifogre/ogre_nif_loader.hpp @@ -27,6 +27,27 @@ #include #include #include +#include + + +class BoundsFinder; + +namespace Nif +{ + class Node; + class Transformation; + class NiTriShape; + class Vector; + class Matrix; +} + +namespace Mangle +{ + namespace VFS + { + class OgreVFS; + } +} /** Manual resource loader for NIF meshes. This is the main class responsible for translating the internal NIF mesh structure into @@ -43,12 +64,66 @@ very resource intensive, and can safely be done for a large number of meshes at load time. */ -struct NIFLoader : Ogre::ManualResourceLoader +class NIFLoader : Ogre::ManualResourceLoader { - void loadResource(Ogre::Resource *resource); + public: + static NIFLoader& getSingleton(); + static NIFLoader* getSingletonPtr(); + + virtual void loadResource(Ogre::Resource *resource); + + static Ogre::MeshPtr load(const std::string &name, + const std::string &group="General"); + + Ogre::Vector3 convertVector3(const Nif::Vector& vec); + Ogre::Quaternion convertRotation(const Nif::Matrix& rot); + + private: + NIFLoader() : resourceGroup("General") {} + NIFLoader(NIFLoader& n) {} + + void warn(std::string msg); + void fail(std::string msg); + + void handleNode( Nif::Node *node, int flags, + const Nif::Transformation *trafo, BoundsFinder &bounds, Ogre::Bone *parentBone); + + void handleNiTriShape(Nif::NiTriShape *shape, int flags, BoundsFinder &bounds); + + void createOgreSubMesh(Nif::NiTriShape *shape, const Ogre::String &material); + + void createMaterial(const Ogre::String &name, + const Nif::Vector &ambient, + const Nif::Vector &diffuse, + const Nif::Vector &specular, + const Nif::Vector &emissive, + float glossiness, float alpha, + float alphaFlags, float alphaTest, + const Ogre::String &texName); + + void findRealTexture(Ogre::String &texName); + + Ogre::String getUniqueName(const Ogre::String &input); + + //returns the skeleton name of this mesh + std::string getSkeletonName() + { + return resourceName + ".skel"; + } + + // This is the interface to the Ogre resource system. It allows us to + // load NIFs from BSAs, in the file system and in any other place we + // tell Ogre to look (eg. in zip or rar files.) It's also used to + // check for the existence of texture files, so we can exchange the + // extension from .tga to .dds if the texture is missing. + Mangle::VFS::OgreVFS *vfs; + + std::string resourceName; + std::string resourceGroup; - static Ogre::MeshPtr load(const std::string &name, - const std::string &group="General"); + // pointer to the ogre mesh which is currently build + Ogre::Mesh *mesh; + Ogre::SkeletonPtr skel; }; #endif