// NeL - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "std3d.h"
#include "nel/3d/animation.h"
#include "nel/3d/animation_set.h"
#include "nel/3d/track.h"
#include "nel/3d/track_sampled_quat_small_header.h"
#include "nel/misc/file.h"
#include "nel/misc/path.h"
#include "nel/misc/hierarchical_timer.h"
#include "nel/misc/algo.h"
using namespace std;
using namespace NLMISC;
namespace NL3D
{
H_AUTO_DECL( NL3D_UI_Animation )
#define NL3D_HAUTO_UI_ANIMATION H_AUTO_USE( NL3D_UI_Animation )
// ***************************************************************************
CAnimation::CAnimation() : _BeginTimeTouched(true), _EndTimeTouched(true), _AnimLoopTouched(true)
{
_MinEndTime = -FLT_MAX;
_TrackSamplePack= NULL;
_AnimationSetOwner= NULL;
}
// ***************************************************************************
CAnimation::~CAnimation ()
{
// Delete all the pointers in the array
for (uint i=0; i<_TrackVector.size(); i++)
// Delete
delete _TrackVector[i];
// if created, release the _TrackSamplePack
if(_TrackSamplePack)
delete _TrackSamplePack;
_TrackSamplePack= NULL;
}
// ***************************************************************************
void CAnimation::addTrack (const std::string& name, ITrack* pChannel)
{
// must not already be HeaderOptimized
nlassert(_IdByChannelId.empty());
// Add an entry in the map
_IdByName.insert (TMapStringUInt::value_type (name, (uint32)_TrackVector.size()));
// Add an entry in the array
_TrackVector.push_back (pChannel);
//
_BeginTimeTouched = _EndTimeTouched = _AnimLoopTouched= true;
}
// ***************************************************************************
void CAnimation::serial (NLMISC::IStream& f)
{
// cannot save if anim header compressed
nlassert(_IdByChannelId.empty());
// Serial a header
f.serialCheck ((uint32)'_LEN');
f.serialCheck ((uint32)'MINA');
// Serial a version
sint version=f.serialVersion (2);
// Serial the name
f.serial (_Name);
// Serial the name/id map
f.serialCont(_IdByName);
// Serial the vector
f.serialContPolyPtr (_TrackVector);
// Serial the min end time
if (version>=1)
{
f.serial (_MinEndTime);
}
// Serial the SSS shapes
if (version>=2)
{
f.serialCont (_SSSShapes);
}
// TestYoyo
//nlinfo("ANIMYOYO: Anim NumTracks: %d", _TrackVector.size());
}
// ***************************************************************************
uint CAnimation::getIdTrackByName (const std::string& name) const
{
// if not be HeaderOptimized
if (_IdByChannelId.empty())
{
// Find an entry in the name/id map
TMapStringUInt::const_iterator ite=_IdByName.find (name);
// Not found ?
if (ite==_IdByName.end ())
// yes, error
return NotFound;
else
// no, return track ID
return (uint)ite->second;
}
else
{
nlassert(_AnimationSetOwner);
// get the channel id from name
uint channelId= _AnimationSetOwner->getChannelIdByName(name);
if(channelId==CAnimationSet::NotFound)
return CAnimation::NotFound;
else
return getIdTrackByChannelId(channelId);
}
}
// ***************************************************************************
void CAnimation::getTrackNames (std::set& setString) const
{
// if not be HeaderOptimized
if (_IdByChannelId.empty())
{
// For each track name
TMapStringUInt::const_iterator ite=_IdByName.begin();
while (ite!=_IdByName.end())
{
// Add the name in the map
setString.insert (ite->first);
// Next track
ite++;
}
}
else
{
nlassert(_AnimationSetOwner);
// For each track channel Id,
for(uint i=0;i<_IdByChannelId.size();i++)
{
// Add in the map the channel name => same as track name
setString.insert ( _AnimationSetOwner->getChannelName(_IdByChannelId[i]) );
}
}
}
// ***************************************************************************
TAnimationTime CAnimation::getBeginTime () const
{
NL3D_HAUTO_UI_ANIMATION;
if (_BeginTimeTouched)
{
// Track count
uint trackCount=_TrackVector.size();
// Track count empty ?
if (trackCount==0)
return 0.f;
// Look for the lowest
_BeginTime=_TrackVector[0]->getBeginTime ();
// Scan all keys
for (uint t=1; tgetBeginTime ()<_BeginTime)
_BeginTime=_TrackVector[t]->getBeginTime ();
}
_BeginTimeTouched = false;
}
return _BeginTime;
}
// ***************************************************************************
TAnimationTime CAnimation::getEndTime () const
{
NL3D_HAUTO_UI_ANIMATION;
if (_EndTimeTouched)
{
// Track count
uint trackCount=_TrackVector.size();
// Track count empty ?
if (trackCount==0)
return 0.f;
// Look for the highest
_EndTime=_TrackVector[0]->getEndTime ();
// Scan tracks keys
for (uint t=1; tgetEndTime ()>_EndTime)
_EndTime=_TrackVector[t]->getEndTime ();
}
// Check min end time
if (_EndTime < _MinEndTime)
_EndTime = _MinEndTime;
_EndTimeTouched = false;
}
return _EndTime;
}
// ***************************************************************************
bool CAnimation::allTrackLoop() const
{
NL3D_HAUTO_UI_ANIMATION;
if(_AnimLoopTouched)
{
// Track count
uint trackCount=_TrackVector.size();
// Default is true
_AnimLoop= true;
// Scan tracks keys
for (uint t=0; tgetLoopMode())
{
_AnimLoop= false;
break;
}
}
_AnimLoopTouched = false;
}
return _AnimLoop;
}
// ***************************************************************************
UTrack* CAnimation::getTrackByName (const char* name)
{
NL3D_HAUTO_UI_ANIMATION;
// Get track id
uint id=getIdTrackByName (name);
// Not found ?
if (id==CAnimation::NotFound)
// Error, return NULL
return NULL;
else
// No error, return the track
return getTrack (id);
}
// ***************************************************************************
void CAnimation::releaseTrack (UTrack* /* track */)
{
NL3D_HAUTO_UI_ANIMATION;
// Nothing to do
}
// ***************************************************************************
void CAnimation::setMinEndTime (TAnimationTime minEndTime)
{
_MinEndTime = minEndTime;
}
// ***************************************************************************
UAnimation* UAnimation::createAnimation (const char* sPath)
{
NL3D_HAUTO_UI_ANIMATION;
// Allocate an animation
std::auto_ptr anim (new CAnimation);
// Read it
NLMISC::CIFile file;
if (file.open ( NLMISC::CPath::lookup( sPath ) ) )
{
// Serial the animation
file.serial (*anim);
// Return pointer
CAnimation *ret=anim.release ();
// Return the animation interface
return ret;
}
else
return NULL;
}
// ***************************************************************************
void UAnimation::releaseAnimation (UAnimation* animation)
{
NL3D_HAUTO_UI_ANIMATION;
// Cast the pointer
CAnimation* release=(CAnimation*)animation;
// Delete it
delete release;
}
// ***************************************************************************
void CAnimation::applySampleDivisor(uint sampleDivisor)
{
NL3D_HAUTO_UI_ANIMATION;
for(uint i=0;i<_TrackVector.size();i++)
{
ITrack *track= _TrackVector[i];
if(track)
track->applySampleDivisor(sampleDivisor);
}
}
// ***************************************************************************
void CAnimation::applyTrackQuatHeaderCompression()
{
NL3D_HAUTO_UI_ANIMATION;
// if the header compression has already been donne, no op
if(_TrackSamplePack)
return;
// **** First pass: count the number of keys to allocate
CTrackSampleCounter sampleCounter;
bool someTrackOK= false;
for(uint i=0;i<_TrackVector.size();i++)
{
ITrack *track= _TrackVector[i];
if(track)
{
// return true only for CTrackSampledQuat
if(track->applyTrackQuatHeaderCompressionPass0(sampleCounter))
someTrackOK= true;
}
}
// **** second pass: fill, onlmy if some track matchs (fails for instance for big animations)
if(someTrackOK)
{
uint i;
// must create the Sample packer
_TrackSamplePack= new CTrackSamplePack;
// just copy the built track headers
_TrackSamplePack->TrackHeaders.resize(sampleCounter.TrackHeaders.size());
for(i=0;i<_TrackSamplePack->TrackHeaders.size();i++)
{
_TrackSamplePack->TrackHeaders[i]= sampleCounter.TrackHeaders[i];
}
// and allocate keys
_TrackSamplePack->Times.resize(sampleCounter.NumKeys);
_TrackSamplePack->Keys.resize(sampleCounter.NumKeys);
// start the counter for Pass1 to work
uint globalKeyOffset= 0;
// fill it for each track
for(i=0;i<_TrackVector.size();i++)
{
ITrack *track= _TrackVector[i];
if(track)
{
ITrack *newTrack= track->applyTrackQuatHeaderCompressionPass1(globalKeyOffset, *_TrackSamplePack);
// if compressed
if(newTrack)
{
// delete the old track, and replace with compressed one
delete _TrackVector[i];
_TrackVector[i]= newTrack;
}
}
}
nlassert(globalKeyOffset == _TrackSamplePack->Keys.size());
}
}
// ***************************************************************************
struct CTempTrackInfo
{
uint16 ChannelId;
ITrack *Track;
bool operator<(const CTempTrackInfo &o) const
{
return ChannelId < o.ChannelId;
}
};
void CAnimation::applyAnimHeaderCompression(CAnimationSet *animationSetOwner, const std::map &channelMap)
{
uint i;
nlassert(animationSetOwner);
// must not be already done
nlassert(_IdByChannelId.empty());
// fill the track info, with Track
std::vector tempTrackInfo;
tempTrackInfo.resize(_TrackVector.size());
for(i=0;i::const_iterator itChan= channelMap.find(it->first);
nlassert(itChan!=channelMap.end());
// store the channelId in the associated track
tempTrackInfo[it->second].ChannelId= (uint16)itChan->second;
}
// sort by channelId
sort(tempTrackInfo.begin(), tempTrackInfo.end());
// refill the TrackVector (sorted)
_IdByChannelId.resize( tempTrackInfo.size() );
for(i=0;i