mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-05 08:45:33 +00:00
Allow loading multiple animation sources
Animation sources are treated differently from base objects. When given "path\file.nif", base objects will look for "path\xfile.nif" and use that if it exists (falling back to the original name if not found). Animation sources will instead use "path\xfile.kf", ignoring it if the file doesn't exist.
This commit is contained in:
parent
e85bc8b2cd
commit
8e38dc410f
7 changed files with 218 additions and 51 deletions
|
@ -23,6 +23,8 @@ ActivatorAnimation::ActivatorAnimation(const MWWorld::Ptr &ptr)
|
|||
|
||||
setObjectRoot(mPtr.getRefData().getBaseNode(), name, false);
|
||||
setRenderProperties(mObjectRoot, RV_Misc, RQG_Main, RQG_Alpha);
|
||||
|
||||
addAnimSource(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,7 @@ namespace MWRender
|
|||
{
|
||||
|
||||
Animation::AnimLayer::AnimLayer()
|
||||
: mControllers(NULL)
|
||||
, mTextKeys(NULL)
|
||||
: mSource(NULL)
|
||||
, mTime(0.0f)
|
||||
, mPlaying(false)
|
||||
, mLoopCount(0)
|
||||
|
@ -76,6 +75,8 @@ Animation::~Animation()
|
|||
{
|
||||
if(mInsert)
|
||||
{
|
||||
mAnimSources.clear();
|
||||
|
||||
Ogre::SceneManager *sceneMgr = mInsert->getCreator();
|
||||
destroyObjectList(sceneMgr, mObjectRoot);
|
||||
}
|
||||
|
@ -87,8 +88,22 @@ void Animation::setObjectRoot(Ogre::SceneNode *node, const std::string &model, b
|
|||
OgreAssert(!mInsert, "Object already has a root!");
|
||||
mInsert = node->createChildSceneNode();
|
||||
|
||||
mObjectRoot = (!baseonly ? NifOgre::Loader::createObjects(mInsert, model) :
|
||||
NifOgre::Loader::createObjectBase(mInsert, model));
|
||||
std::string mdlname = Misc::StringUtils::lowerCase(model);
|
||||
std::string::size_type p = mdlname.rfind('\\');
|
||||
if(p == std::string::npos)
|
||||
p = mdlname.rfind('/');
|
||||
if(p != std::string::npos)
|
||||
mdlname.insert(mdlname.begin()+p+1, 'x');
|
||||
else
|
||||
mdlname.insert(mdlname.begin(), 'x');
|
||||
if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(mdlname))
|
||||
{
|
||||
mdlname = model;
|
||||
Misc::StringUtils::toLower(mdlname);
|
||||
}
|
||||
|
||||
mObjectRoot = (!baseonly ? NifOgre::Loader::createObjects(mInsert, mdlname) :
|
||||
NifOgre::Loader::createObjectBase(mInsert, mdlname));
|
||||
if(mObjectRoot.mSkelBase)
|
||||
{
|
||||
mSkelBase = mObjectRoot.mSkelBase;
|
||||
|
@ -109,12 +124,6 @@ void Animation::setObjectRoot(Ogre::SceneNode *node, const std::string &model, b
|
|||
Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator();
|
||||
while(boneiter.hasMoreElements())
|
||||
boneiter.getNext()->setManuallyControlled(true);
|
||||
|
||||
if(mObjectRoot.mTextKeys.size() > 0)
|
||||
{
|
||||
mAccumRoot = mInsert;
|
||||
mNonAccumRoot = skelinst->getBone(mObjectRoot.mTextKeys.begin()->first);
|
||||
}
|
||||
}
|
||||
for(size_t i = 0;i < mObjectRoot.mControllers.size();i++)
|
||||
{
|
||||
|
@ -148,6 +157,75 @@ void Animation::setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::ui
|
|||
}
|
||||
|
||||
|
||||
void Animation::addAnimSource(const std::string &model)
|
||||
{
|
||||
OgreAssert(mInsert, "Object is missing a root!");
|
||||
if(!mSkelBase)
|
||||
return;
|
||||
|
||||
std::string kfname = Misc::StringUtils::lowerCase(model);
|
||||
std::string::size_type p = kfname.rfind('\\');
|
||||
if(p == std::string::npos)
|
||||
p = kfname.rfind('/');
|
||||
if(p != std::string::npos)
|
||||
kfname.insert(kfname.begin()+p+1, 'x');
|
||||
else
|
||||
kfname.insert(kfname.begin(), 'x');
|
||||
|
||||
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;
|
||||
|
||||
mAnimSources.push_back(AnimSource());
|
||||
NifOgre::Loader::createKfControllers(mSkelBase, kfname,
|
||||
mAnimSources.back().mTextKeys,
|
||||
mAnimSources.back().mControllers);
|
||||
if(mAnimSources.back().mTextKeys.size() == 0 || mAnimSources.back().mControllers.size() == 0)
|
||||
{
|
||||
mAnimSources.pop_back();
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Ogre::Controller<Ogre::Real> > &ctrls = mAnimSources.back().mControllers;
|
||||
NifOgre::NodeTargetValue<Ogre::Real> *dstval;
|
||||
|
||||
for(size_t i = 0;i < ctrls.size();i++)
|
||||
{
|
||||
dstval = static_cast<NifOgre::NodeTargetValue<Ogre::Real>*>(ctrls[i].getDestination().getPointer());
|
||||
|
||||
if(i == 0 && !mAccumRoot)
|
||||
{
|
||||
mAccumRoot = mInsert;
|
||||
mNonAccumRoot = dstval->getNode();
|
||||
}
|
||||
|
||||
ctrls[i].setSource(mAnimationValuePtr[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void Animation::clearAnimSources()
|
||||
{
|
||||
for(size_t layer = 0;layer < sMaxLayers;layer++)
|
||||
{
|
||||
mLayer[layer].mGroupName.clear();
|
||||
mLayer[layer].mSource = NULL;
|
||||
mLayer[layer].mTime = 0.0f;
|
||||
mLayer[layer].mLoopCount = 0;
|
||||
mLayer[layer].mPlaying = false;
|
||||
}
|
||||
mNonAccumCtrl = NULL;
|
||||
mAnimVelocity = 0.0f;
|
||||
|
||||
mLastPosition = Ogre::Vector3(0.0f);
|
||||
mAccumRoot = NULL;
|
||||
mNonAccumRoot = NULL;
|
||||
|
||||
mAnimSources.clear();
|
||||
}
|
||||
|
||||
|
||||
Ogre::Node *Animation::getNode(const std::string &name)
|
||||
{
|
||||
if(mSkelBase)
|
||||
|
@ -175,12 +253,10 @@ NifOgre::TextKeyMap::const_iterator Animation::findGroupStart(const NifOgre::Tex
|
|||
|
||||
bool Animation::hasAnimation(const std::string &anim)
|
||||
{
|
||||
if(!mSkelBase)
|
||||
return false;
|
||||
|
||||
if(mObjectRoot.mTextKeys.size() > 0)
|
||||
AnimSourceList::const_iterator iter(mAnimSources.begin());
|
||||
for(;iter != mAnimSources.end();iter++)
|
||||
{
|
||||
const NifOgre::TextKeyMap &keys = mObjectRoot.mTextKeys.begin()->second;
|
||||
const NifOgre::TextKeyMap &keys = iter->mTextKeys;
|
||||
if(findGroupStart(keys, anim) != keys.end())
|
||||
return true;
|
||||
}
|
||||
|
@ -415,8 +491,7 @@ bool Animation::play(const std::string &groupname, const std::string &start, con
|
|||
return false;
|
||||
|
||||
mLayer[layeridx].mGroupName.clear();
|
||||
mLayer[layeridx].mTextKeys = NULL;
|
||||
mLayer[layeridx].mControllers = NULL;
|
||||
mLayer[layeridx].mSource = NULL;
|
||||
mLayer[layeridx].mTime = 0.0f;
|
||||
mLayer[layeridx].mLoopCount = 0;
|
||||
mLayer[layeridx].mPlaying = false;
|
||||
|
@ -428,19 +503,17 @@ bool Animation::play(const std::string &groupname, const std::string &start, con
|
|||
bool foundanim = false;
|
||||
|
||||
/* Look in reverse; last-inserted source has priority. */
|
||||
do {
|
||||
NifOgre::ObjectList &objlist = mObjectRoot;
|
||||
if(objlist.mTextKeys.size() == 0)
|
||||
continue;
|
||||
|
||||
const NifOgre::TextKeyMap &keys = objlist.mTextKeys.begin()->second;
|
||||
AnimSourceList::reverse_iterator iter(mAnimSources.rbegin());
|
||||
for(;iter != mAnimSources.rend();iter++)
|
||||
{
|
||||
const NifOgre::TextKeyMap &keys = iter->mTextKeys;
|
||||
NifOgre::NodeTargetValue<Ogre::Real> *nonaccumctrl = NULL;
|
||||
if(layeridx == 0 && mNonAccumRoot)
|
||||
{
|
||||
for(size_t i = 0;i < objlist.mControllers.size();i++)
|
||||
for(size_t i = 0;i < iter->mControllers.size();i++)
|
||||
{
|
||||
NifOgre::NodeTargetValue<Ogre::Real> *dstval;
|
||||
dstval = dynamic_cast<NifOgre::NodeTargetValue<Ogre::Real>*>(objlist.mControllers[i].getDestination().getPointer());
|
||||
dstval = dynamic_cast<NifOgre::NodeTargetValue<Ogre::Real>*>(iter->mControllers[i].getDestination().getPointer());
|
||||
if(dstval && dstval->getNode() == mNonAccumRoot)
|
||||
{
|
||||
nonaccumctrl = dstval;
|
||||
|
@ -455,8 +528,7 @@ bool Animation::play(const std::string &groupname, const std::string &start, con
|
|||
continue;
|
||||
|
||||
mLayer[layeridx].mGroupName = groupname;
|
||||
mLayer[layeridx].mTextKeys = &keys;
|
||||
mLayer[layeridx].mControllers = &objlist.mControllers;
|
||||
mLayer[layeridx].mSource = &*iter;
|
||||
mLayer[layeridx].mLoopCount = loops;
|
||||
mLayer[layeridx].mPlaying = true;
|
||||
|
||||
|
@ -481,7 +553,7 @@ bool Animation::play(const std::string &groupname, const std::string &start, con
|
|||
movinganim = (nonaccumctrl==mNonAccumCtrl);
|
||||
break;
|
||||
}
|
||||
} while(0);
|
||||
}
|
||||
if(!foundanim)
|
||||
std::cerr<< "Failed to find animation "<<groupname <<std::endl;
|
||||
|
||||
|
@ -494,8 +566,7 @@ void Animation::disable(size_t layeridx)
|
|||
return;
|
||||
|
||||
mLayer[layeridx].mGroupName.clear();
|
||||
mLayer[layeridx].mTextKeys = NULL;
|
||||
mLayer[layeridx].mControllers = NULL;
|
||||
mLayer[layeridx].mSource = NULL;
|
||||
mLayer[layeridx].mTime = 0.0f;
|
||||
mLayer[layeridx].mLoopCount = 0;
|
||||
mLayer[layeridx].mPlaying = false;
|
||||
|
@ -558,6 +629,14 @@ Ogre::Vector3 Animation::runAnimation(float duration)
|
|||
|
||||
for(size_t i = 0;i < mObjectRoot.mControllers.size();i++)
|
||||
mObjectRoot.mControllers[i].update();
|
||||
for(size_t layeridx = 0;layeridx < sMaxLayers;layeridx++)
|
||||
{
|
||||
if(mLayer[layeridx].mGroupName.empty())
|
||||
continue;
|
||||
|
||||
for(size_t i = 0;i < mLayer[layeridx].mSource->mControllers.size();i++)
|
||||
mLayer[layeridx].mSource->mControllers[i].update();
|
||||
}
|
||||
|
||||
if(mSkelBase)
|
||||
{
|
||||
|
|
|
@ -30,10 +30,15 @@ protected:
|
|||
virtual void setValue(Ogre::Real value);
|
||||
};
|
||||
|
||||
struct AnimSource {
|
||||
NifOgre::TextKeyMap mTextKeys;
|
||||
std::vector<Ogre::Controller<Ogre::Real> > mControllers;
|
||||
};
|
||||
typedef std::vector<AnimSource> AnimSourceList;
|
||||
|
||||
struct AnimLayer {
|
||||
std::string mGroupName;
|
||||
std::vector<Ogre::Controller<Ogre::Real> > *mControllers;
|
||||
const NifOgre::TextKeyMap *mTextKeys;
|
||||
AnimSource *mSource;
|
||||
NifOgre::TextKeyMap::const_iterator mStartKey;
|
||||
NifOgre::TextKeyMap::const_iterator mLoopStartKey;
|
||||
NifOgre::TextKeyMap::const_iterator mStopKey;
|
||||
|
@ -52,8 +57,9 @@ protected:
|
|||
Ogre::SceneNode *mInsert;
|
||||
Ogre::Entity *mSkelBase;
|
||||
NifOgre::ObjectList mObjectRoot;
|
||||
AnimSourceList mAnimSources;
|
||||
Ogre::Node *mAccumRoot;
|
||||
Ogre::Bone *mNonAccumRoot;
|
||||
Ogre::Node *mNonAccumRoot;
|
||||
NifOgre::NodeTargetValue<Ogre::Real> *mNonAccumCtrl;
|
||||
Ogre::Vector3 mAccumulate;
|
||||
Ogre::Vector3 mLastPosition;
|
||||
|
@ -95,11 +101,14 @@ protected:
|
|||
bool handleTextKey(size_t layeridx, const NifOgre::TextKeyMap::const_iterator &key);
|
||||
|
||||
void setObjectRoot(Ogre::SceneNode *node, const std::string &model, bool baseonly);
|
||||
void addAnimSource(const std::string &model);
|
||||
|
||||
static void destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects);
|
||||
|
||||
static void setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue);
|
||||
|
||||
void clearAnimSources();
|
||||
|
||||
public:
|
||||
Animation(const MWWorld::Ptr &ptr);
|
||||
virtual ~Animation();
|
||||
|
|
|
@ -23,6 +23,10 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr)
|
|||
|
||||
setObjectRoot(mPtr.getRefData().getBaseNode(), model, false);
|
||||
setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha);
|
||||
|
||||
if((ref->mBase->mFlags&ESM::Creature::Biped))
|
||||
addAnimSource("meshes\\base_anim.nif");
|
||||
addAnimSource(model);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -100,26 +100,26 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, MWWor
|
|||
bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0;
|
||||
std::string smodel = (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif");
|
||||
setObjectRoot(node, smodel, true);
|
||||
#if 0
|
||||
addAnimSource(node, smodel);
|
||||
|
||||
addAnimSource(smodel);
|
||||
if(mBodyPrefix.find("argonian") != std::string::npos)
|
||||
addAnimSource(node, "meshes\\argonian_swimkna.nif");
|
||||
addAnimSource("meshes\\argonian_swimkna.nif");
|
||||
else if(!mNpc->isMale() && !isBeast)
|
||||
addAnimSource(node, "meshes\\base_anim_female.nif");
|
||||
addAnimSource("meshes\\base_anim_female.nif");
|
||||
if(mNpc->mModel.length() > 0)
|
||||
addAnimSource(node, "meshes\\"+mNpc->mModel);
|
||||
addAnimSource("meshes\\"+mNpc->mModel);
|
||||
if(mViewMode == VM_FirstPerson)
|
||||
{
|
||||
/* 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(node, "meshes\\base_anim.1st.nif");
|
||||
addAnimSource("meshes\\base_anim.1st.nif");
|
||||
if(isBeast)
|
||||
addAnimSource(node, "meshes\\base_animkna.1st.nif");
|
||||
addAnimSource("meshes\\base_animkna.1st.nif");
|
||||
if(!mNpc->isMale() && !isBeast)
|
||||
addAnimSource(node, "meshes\\base_anim_female.1st.nif");
|
||||
addAnimSource("meshes\\base_anim_female.1st.nif");
|
||||
}
|
||||
#endif
|
||||
|
||||
forceUpdate();
|
||||
}
|
||||
|
||||
|
@ -134,28 +134,28 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
|
|||
const ESM::Race *race = store.get<ESM::Race>().find(mNpc->mRace);
|
||||
bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0;
|
||||
std::string smodel = (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif");
|
||||
#if 0
|
||||
|
||||
clearAnimSources();
|
||||
addAnimSource(node, smodel);
|
||||
addAnimSource(smodel);
|
||||
if(mBodyPrefix.find("argonian") != std::string::npos)
|
||||
addAnimSource(node, "meshes\\argonian_swimkna.nif");
|
||||
addAnimSource("meshes\\argonian_swimkna.nif");
|
||||
else if(!mNpc->isMale() && !isBeast)
|
||||
addAnimSource(node, "meshes\\base_anim_female.nif");
|
||||
addAnimSource("meshes\\base_anim_female.nif");
|
||||
if(mNpc->mModel.length() > 0)
|
||||
addAnimSource(node, "meshes\\"+mNpc->mModel);
|
||||
addAnimSource("meshes\\"+mNpc->mModel);
|
||||
if(mViewMode == VM_FirstPerson)
|
||||
{
|
||||
/* 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(node, "meshes\\base_anim.1st.nif");
|
||||
addAnimSource("meshes\\base_anim.1st.nif");
|
||||
if(isBeast)
|
||||
addAnimSource(node, "meshes\\base_animkna.1st.nif");
|
||||
addAnimSource("meshes\\base_animkna.1st.nif");
|
||||
if(!mNpc->isMale() && !isBeast)
|
||||
addAnimSource(node, "meshes\\base_anim_female.1st.nif");
|
||||
addAnimSource("meshes\\base_anim_female.1st.nif");
|
||||
}
|
||||
MWBase::Environment::get().getMechanicsManager()->forceStateUpdate(mPtr);
|
||||
#endif
|
||||
|
||||
for(size_t i = 0;i < sPartListSize;i++)
|
||||
removeIndividualPart(i);
|
||||
forceUpdate();
|
||||
|
|
|
@ -818,6 +818,64 @@ public:
|
|||
}
|
||||
createObjects(name, group, sceneMgr, node, objectlist, flags, 0, 0);
|
||||
}
|
||||
|
||||
static void loadKf(Ogre::Skeleton *skel, const std::string &name,
|
||||
TextKeyMap &textKeys, std::vector<Ogre::Controller<Ogre::Real> > &ctrls)
|
||||
{
|
||||
Nif::NIFFile::ptr nif = Nif::NIFFile::create(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::NiSequenceStreamHelper *seq = dynamic_cast<const Nif::NiSequenceStreamHelper*>(r);
|
||||
if(seq == NULL)
|
||||
{
|
||||
nif->warn("First root was not a NiSequenceStreamHelper, but a "+
|
||||
r->recName+".");
|
||||
return;
|
||||
}
|
||||
|
||||
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<const Nif::NiTextKeyExtraData*>(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;
|
||||
}
|
||||
|
||||
const Nif::NiStringExtraData *strdata = static_cast<const Nif::NiStringExtraData*>(extra.getPtr());
|
||||
const Nif::NiKeyframeController *key = static_cast<const Nif::NiKeyframeController*>(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, key->data.getPtr()));
|
||||
Ogre::ControllerFunctionRealPtr func(OGRE_NEW KeyframeController::Function(key, false));
|
||||
|
||||
ctrls.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -912,4 +970,14 @@ ObjectList Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string nam
|
|||
return objectlist;
|
||||
}
|
||||
|
||||
|
||||
void Loader::createKfControllers(Ogre::Entity *skelBase,
|
||||
const std::string &name,
|
||||
TextKeyMap &textKeys,
|
||||
std::vector<Ogre::Controller<Ogre::Real> > &ctrls)
|
||||
{
|
||||
NIFObjectLoader::loadKf(skelBase->getSkeleton(), name, textKeys, ctrls);
|
||||
}
|
||||
|
||||
|
||||
} // namespace NifOgre
|
||||
|
|
|
@ -68,6 +68,11 @@ public:
|
|||
static ObjectList createObjectBase(Ogre::SceneNode *parentNode,
|
||||
std::string name,
|
||||
const std::string &group="General");
|
||||
|
||||
static void createKfControllers(Ogre::Entity *skelBase,
|
||||
const std::string &name,
|
||||
TextKeyMap &textKeys,
|
||||
std::vector<Ogre::Controller<Ogre::Real> > &ctrls);
|
||||
};
|
||||
|
||||
// FIXME: Should be with other general Ogre extensions.
|
||||
|
|
Loading…
Reference in a new issue