一、基本框架
先看一下OGRE动画的基本框架:
http://blog.csdn.net/leonwei/article/details/5819248
二、动画控制
OGRE的基本动画控制是很简单的,设置一个动画的操作是这样:
// Set idle animation
mAnimationState = ent->getAnimationState( "Idle" );
mAnimationState->setLoop( true );
mAnimationState->setEnabled( true );
(上面这段代码来自Intermediate Tutorial1 – Ogre Wiki)从一个Entity对象中得到AnimationState指针,然后设置一些属性,在每帧需要调用(FrameListener中):
mAnimationState->addTime( evt.timeSinceLastFrame );
传入每帧的时间,这样动画就开始动起来了。
三、动画资源加载
1.Skeleton加载
可以通过SkeletonManager来加载骨骼文件。
SkeletonPtr skel = SkeletonManager::getSingleton().load("jaiqua.skeleton",
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
这个load是SkeletonManager所继承的ResourceManager的虚函数load。
ResourcePtr ResourceManager::load(const String& name,
const String& group, bool isManual, ManualResourceLoader* loader,
const NameValuePairList* loadParams)
{
ResourcePtr r = createOrRetrieve(name,group,isManual,loader,loadParams).first;
// ensure loaded
r->load();
return r;
}
里边创建一个Resource,这边就是Skeleton了,并调用他的load函数。
这个load函数也是继承来的,它是继承于Resource类的,Ogre里的资源都是继承这个类的,如Material,Mesh等。
void Resource::load(bool background)
{
...
if (old==LOADSTATE_UNLOADED)
prepareImpl();
preLoadImpl();
old = LOADSTATE_PREPARED;
if (mGroup == ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME)
{
// Derive resource group
changeGroupOwnership(
ResourceGroupManager::getSingleton()
.findGroupContainingResource(mName));
}
loadImpl();
postLoadImpl();
...
}
这里边的主要加载资源逻辑就是在loadImpl(),这是个纯虚函数,具体由子类来实现,我们现在读取的是skeleton,因此就是使用skeleton类中的loadImpl().
void Skeleton::loadImpl(void)
{
SkeletonSerializer serializer;
LogManager::getSingleton().stream()
<< "Skeleton: Loading " << mName;
DataStreamPtr stream =
ResourceGroupManager::getSingleton().openResource(
mName, mGroup, true, this);
serializer.importSkeleton(stream, this);
// Load any linked skeletons
LinkedSkeletonAnimSourceList::iterator i;
for (i = mLinkedSkeletonAnimSourceList.begin();
i != mLinkedSkeletonAnimSourceList.end(); ++i)
{
i->pSkeleton = SkeletonManager::getSingleton().load(
i->skeletonName, mGroup);
}
}
此函数主要进行了两件事:1、用SkeletonSerializer来解析文件数据流,保存到当前的skeleton实例中;2、载入所有相关连的skeleton(在.skeleton文件的<animationlinks>标签中定义)。
动画信息存储在Skeleton的“AnimationList mAnimationsList”成员变量中,可以根据动画的名字来获取相应的动画信息
SkeletonPtr skel = SkeletonManager::getSingleton().load("Jaiqua.skeleton",
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Animation* anim = skel->getAnimation("Sneak");
这边说一下Skeleton和SkeletonInstance的区别,SkeletonInstance继承了Skeleton,内部保存了一个指向Skeleton的指针。可以打开SkeletonInstance的头文件,可以看到一段简短的英文描述。
/** A SkeletonInstance is a single instance of a Skeleton used by a world object.
@remarks
The difference between a Skeleton and a SkeletonInstance is that the
Skeleton is the 'master' version much like Mesh is a 'master' version of
Entity. Many SkeletonInstance objects can be based on a single Skeleton,
and are copies of it when created. Any changes made to this are not
reflected in the master copy. The exception is animations; these are
shared on the Skeleton itself and may not be modified here.
*/
简单翻译如下:
一个SkeletonInstance类是一个单一的Skeleton的一个实例,Skeleton是一个宿主,就像一个Mesh可以有多个Entity一样。一个Skeleton可以有许多个SkeletonInstance,每个SkeletonInstance拷贝了一份Skeleton信息,对SkeletonInstance里的一些信息的更改不会影响到Skeleton里的信息,但是对动画的修改就会影响到Skeleton里的动画,因为动画信息是共享的。
这边最典型的好处就是可以动态的为骨骼添加一些东西,如武器,装备等。
2.Mesh加载
skeleton单独加载进来了,那Mesh怎么知道哪个skeleton是他的呢?如果用XML转换器将.mesh文件转为XML,打开可以看到在文件末尾有 <skeletonlink name="jaiqua.skeleton" />
所以他会在加载Mesh的时候加载进来。
现在来看Mesh的加载步骤。。这个加载真是能把人绕晕掉。。
先从调用的地方开始看:
Entity* ent = mSceneMgr->createEntity("jaiQua", "jaiqua.mesh");
跳转到:
Entity* SceneManager::createEntity(
const String& entityName,
const String& meshName )
{
// delegate to factory implementation
NameValuePairList params;
params["mesh"] = meshName;
return static_cast<Entity*>(
createMovableObject(entityName, EntityFactory::FACTORY_TYPE_NAME,
¶ms));
}
代理给工产方法:
MovableObject* SceneManager::createMovableObject(const String& name,
const String& typeName, const NameValuePairList* params)
{
...
MovableObjectFactory* factory =
Root::getSingleton().getMovableObjectFactory(typeName);
MovableObject* newObj = factory->createInstance(name, this, params);
...
}
调用工厂方法:
MovableObject* MovableObjectFactory::createInstance(
const String& name, SceneManager* manager,
const NameValuePairList* params)
{
MovableObject* m = createInstanceImpl(name, params);
m->_notifyCreator(this);
m->_notifyManager(manager);
return m;
}
createInstanceImpl()是一个纯虚函数,由不同的子类各自实现,这边就是调用Entity的方法了。
MovableObject* EntityFactory::createInstanceImpl( const String& name,
const NameValuePairList* params)
{
// must have mesh parameter
MeshPtr pMesh;
if (params != 0)
{
NameValuePairList::const_iterator ni = params->find("mesh");
if (ni != params->end())
{
// Get mesh (load if required)
pMesh = MeshManager::getSingleton().load(
ni->second,
// autodetect group location
ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME );
}
}
if (pMesh.isNull())
{
OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
"'mesh' parameter required when constructing an Entity.",
"EntityFactory::createInstance");
}
return OGRE_NEW Entity(name, pMesh);
}
这边也开始调用load函数了,这才开始进入mesh资源的加载。
加载的过程跟skeleton类似,因此只挑重点的说一下:
经过若干次跳转后。。调用到MeshSerializerImpl::importMesh
void MeshSerializerImpl::importMesh(DataStreamPtr& stream, Mesh* pMesh, MeshSerializerListener *listener)
{
// Determine endianness (must be the first thing we do!)
determineEndianness(stream);
// Check header
readFileHeader(stream);
unsigned short streamID;
while(!stream->eof())
{
streamID = readChunk(stream);
switch (streamID)
{
case M_MESH:
readMesh(stream, pMesh, listener);
break;
}
}
}
跟踪下去:
MeshSerializerImpl::readMesh()――case M_MESH_SKELETON_LINK:
=》MeshSerializerImpl::readSkeletonLink();
=》Mesh::setSkeletonName();
=》mSkeleton = SkeletonManager::getSingleton().load(skelName, mGroup);――得到骨骼指针
mSkeleton为Mesh里保存的骨骼的指针。
这样就从Entity中可以得到对应的骨骼了。
SkeletonInstance* skelIns = ent->getSkeleton();
3.动画集合
Animation类的对象就是“An animation sequence”,各种类型的动画序列都由这个类来管理。它管理了三种类型的Track list,分别是:NodeAnimationTrack、NumericAnimationTrack、VertexAnimationTrack。
通俗的讲就是一个动画是由它的各个部分的动画轨迹组成的。比如人的动画是由他的头和四肢的轨迹组成的。
AnimationState是一个Animation的状态管理类,可以在里边设置动画的权重,设置动画是否开启,是否循环等状态。
AnimationStateSet就是所有的AnimationState的集合。
在Entity的构造函数中,如果Mesh含有骨骼动画或者顶点动画,则会new一个AnimationStateSet对象,并调用“mesh->_initAnimationState(mAnimationState);”。
void Entity::_initialise(bool forceReinitialise)
{
...
// Is mesh skeletally animated?
if (mMesh->hasSkeleton() && !mMesh->getSkeleton().isNull())
{
mSkeletonInstance = OGRE_NEW SkeletonInstance(mMesh->getSkeleton());
mSkeletonInstance->load();
}
// Initialise the AnimationState, if Mesh has animation
if (hasSkeleton())
{
mFrameBonesLastUpdated = OGRE_NEW_T(unsigned long, MEMCATEGORY_ANIMATION)(std::numeric_limits<unsigned long>::max());
mNumBoneMatrices = mSkeletonInstance->getNumBones();
mBoneMatrices = static_cast<Matrix4*>(OGRE_MALLOC_SIMD(sizeof(Matrix4) * mNumBoneMatrices, MEMCATEGORY_ANIMATION));
}
if (hasSkeleton() || hasVertexAnimation())
{
mAnimationState = OGRE_NEW AnimationStateSet();
mMesh->_initAnimationState(mAnimationState);
prepareTempBlendBuffers();
}
...
}
可以看到Entity中用的是SkeletonInstance,而不是直接用mesh的Skeleton。
4.动画的更新
主要运算就在“void Entity::updateAnimation(void)”函数中。
=》 Entity::cacheBoneMatrices
=》 Skeleton::setAnimationState
此函数先是调用“Skeleton::reset”,然后针对每个enabled animation state,找到其对应的Animation,然后调用Animation::apply()来计算每个Bone的状态。
几个小技巧:
1.复制动画
Animation* copyAnim = skel->createAnimation("OldSneak", anim->getLength());
*copyAnim = *(anim->clone("OldSneak"));
ent->refreshAvailableAnimationState();
记得要refresh才能用,不然找不到。
2.添加动画:
SkeletonInstance* skelIns = ent->getSkeleton();
skelIns->addLinkedSkeletonAnimationSource("XX.skeleton");