// 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_optimizer.h" #include "nel/misc/mem_stream.h" #include "nel/misc/vectord.h" #include "nel/3d/track.h" #include "nel/3d/track_keyframer.h" #include "nel/3d/animation.h" #include "nel/3d/track_sampled_quat.h" #include "nel/3d/track_sampled_vector.h" using namespace NLMISC; using namespace std; #ifdef DEBUG_NEW #define new DEBUG_NEW #endif namespace NL3D { // *************************************************************************** CAnimationOptimizer::CAnimationOptimizer() { _SampleFrameRate= 30; _QuaternionThresholdLowPrec= 1.0 - 0.0001; _QuaternionThresholdHighPrec= 1.0 - 0.000001; _VectorThresholdLowPrec= 0.001; _VectorThresholdHighPrec= 0.0001; } // *************************************************************************** void CAnimationOptimizer::setQuaternionThreshold(double lowPrecThre, double highPrecThre) { nlassert(lowPrecThre>=0); nlassert(highPrecThre>=0); _QuaternionThresholdLowPrec= 1.0 - lowPrecThre; _QuaternionThresholdHighPrec= 1.0 - highPrecThre; } // *************************************************************************** void CAnimationOptimizer::setVectorThreshold(double lowPrecThre, double highPrecThre) { nlassert(lowPrecThre>=0); nlassert(highPrecThre>=0); _VectorThresholdLowPrec= lowPrecThre; _VectorThresholdHighPrec= highPrecThre; } // *************************************************************************** void CAnimationOptimizer::setSampleFrameRate(float frameRate) { nlassert(frameRate>0); _SampleFrameRate= frameRate; } // *************************************************************************** void CAnimationOptimizer::optimize(const CAnimation &animIn, CAnimation &animOut) { // reset animOut contReset(animOut); // Parse all tracks of the animation. set setString; animIn.getTrackNames (setString); set::iterator it; for(it=setString.begin();it!=setString.end();it++) { const string &trackName= *it; uint trackId= animIn.getIdTrackByName(trackName); nlassert(trackId!=CAnimation::NotFound); const ITrack *track= animIn.getTrack(trackId); // If the track is optimisable. ITrack *newTrack; if(isTrackOptimisable(track)) { // choose the threshold according to precision wanted if( isLowPrecisionTrack(trackName) ) { _QuaternionThreshold= _QuaternionThresholdLowPrec; _VectorThreshold= _VectorThresholdLowPrec; } else { _QuaternionThreshold= _QuaternionThresholdHighPrec; _VectorThreshold= _VectorThresholdHighPrec; } // optimize it. newTrack= optimizeTrack(track); } else { // just clone it. newTrack= cloneTrack(track); } // Add it to the animation animOut.addTrack(trackName, newTrack); } // Parse all SSS shapes of the animation (important for preload of those shapes) const vector &shapes= animIn.getSSSShapes(); for(uint i=0;i(trackIn); memStream.serialPolyPtr(trackInSerial); // read from the stream. memStream.invert(); ITrack *ret= NULL; memStream.serialPolyPtr(ret); return ret; } // *************************************************************************** bool CAnimationOptimizer::isTrackOptimisable(const ITrack *trackIn) { nlassert(trackIn); // If the track is a Linear, Bezier or a TCB track, suppose we can optimize it. Constant may not be interressant.... if( dynamic_cast(trackIn) || dynamic_cast(trackIn) || dynamic_cast(trackIn) ) return true; // If the track is a Linear, Bezier or a TCB track, suppose we can optimize it. Constant may not be interressant.... if( dynamic_cast(trackIn) || dynamic_cast(trackIn) || dynamic_cast(trackIn) ) return true; return false; } // *************************************************************************** ITrack *CAnimationOptimizer::optimizeTrack(const ITrack *trackIn) { // Get track param. float beginTime= trackIn->getBeginTime(); float endTime= trackIn->getEndTime(); nlassert(endTime>=beginTime); // Get num Sample uint numSamples= (uint)ceil( (endTime-beginTime)*_SampleFrameRate); numSamples= max(1U, numSamples); nlassert(numSamples<65535); // Optimize Quaternion track?? //================ // eval the track only to get its value type!! CAnimatedValueBlock avBlock; const IAnimatedValue &valueType= ((ITrack*)trackIn)->eval(0, avBlock); if( dynamic_cast(&valueType) ) { // sample the animation. Store result in _TimeList/_QuatKeyList sampleQuatTrack(trackIn, beginTime, endTime, numSamples); // check if the sampled track can be reduced to a TrackDefaultQuat. Test _QuatKeyList. if( testConstantQuatTrack() ) { // create a default Track Quat. CTrackDefaultQuat *trackDefault= new CTrackDefaultQuat; // setup the uniform value. trackDefault->setDefaultValue(_QuatKeyList[0]); // return the result. return trackDefault; } // else optimize the sampled animation, and build. else { // optimize. optimizeQuatTrack(); // Create a sampled quaternion track CTrackSampledQuat *trackSQ= new CTrackSampledQuat; // Copy loop from track. trackSQ->setLoopMode(trackIn->getLoopMode()); // Build it. trackSQ->build(_TimeList, _QuatKeyList, beginTime, endTime); // return result. return trackSQ; } } // Optimize Position track?? //================ else if( dynamic_cast(&valueType) ) { // sample the animation. Store result in _TimeList/_VectorKeyList sampleVectorTrack(trackIn, beginTime, endTime, numSamples); // check if the sampled track can be reduced to a TrackDefaultVector. Test _VectorKeyList. if( testConstantVectorTrack() ) { // create a default Track Vector. CTrackDefaultVector *trackDefault= new CTrackDefaultVector; // setup the uniform value. trackDefault->setDefaultValue(_VectorKeyList[0]); // return the result. return trackDefault; } // else optimize the sampled animation, and build. else { // optimize. optimizeVectorTrack(); // Create a sampled Vector track CTrackSampledVector *trackSV= new CTrackSampledVector; // Copy loop from track. trackSV->setLoopMode(trackIn->getLoopMode()); // Build it. trackSV->build(_TimeList, _VectorKeyList, beginTime, endTime); // return result. return trackSV; } } else { // Must be a quaternion track or vector track for now. nlstop; // Avoid warning. return cloneTrack(trackIn); } } // *************************************************************************** // *************************************************************************** // Quaternion optimisation // *************************************************************************** // *************************************************************************** // *************************************************************************** void CAnimationOptimizer::sampleQuatTrack(const ITrack *trackIn, float beginTime, float endTime, uint numSamples) { // resize tmp samples _TimeList.resize(numSamples); _QuatKeyList.resize(numSamples); // Sample the animation. float t= beginTime; float dt= 0; if(numSamples>1) dt= (endTime-beginTime)/(numSamples-1); for(uint i=0;i(trackIn)->interpolate(t, quat); // normalize this quaternion quat.normalize(); // force on same hemisphere according to precedent frame. if(i>0) { quat.makeClosest(_QuatKeyList[i-1]); } // store time and key. _TimeList[i]= i; _QuatKeyList[i]= quat; } } // *************************************************************************** bool CAnimationOptimizer::testConstantQuatTrack() { uint numSamples= (uint)_QuatKeyList.size(); nlassert(numSamples>0); // Get the first sample as the reference quaternion, and test others from this one. CQuat quatRef= _QuatKeyList[0]; for(uint i=0;i0); // <=2 key? => no opt possible.. if(numSamples<=2) return; // prepare dest opt std::vector optTimeList; std::vector optKeyList; optTimeList.reserve(numSamples); optKeyList.reserve(numSamples); // Add the first key. optTimeList.push_back(_TimeList[0]); optKeyList.push_back(_QuatKeyList[0]); double timeRef= _TimeList[0]; CQuatD quatRef= _QuatKeyList[0]; // For all keys, but the first and the last, test if can remove them. for(uint i=1; i255) { mustAdd= true; } // If the next quaternion or the current quaternion are not on same hemisphere than ref, abort. else if( CQuatD::dotProduct(quatCur, quatRef)<0 || CQuatD::dotProduct(quatNext, quatRef)<0 ) { mustAdd= true; } // else, test interpolation else { // If the 3 quats are nearly equals, it is ok (avoid interpolation) if( nearlySameQuaternion(quatRef, quatCur) && nearlySameQuaternion(quatRef, quatNext) ) mustAdd= false; else { // interpolate. CQuatD quatInterpolated; double t= (timeCur-timeRef)/(timeNext/timeRef); quatInterpolated= CQuatD::slerp(quatRef, quatNext, (float)t); // test if cur and interpolate are equal. if( !nearlySameQuaternion(quatCur, quatInterpolated) ) mustAdd= true; } } // If must add the key to the optimized track. if(mustAdd) { optTimeList.push_back(_TimeList[i]); optKeyList.push_back(_QuatKeyList[i]); timeRef= _TimeList[i]; quatRef= _QuatKeyList[i]; } } // Add the last key. optTimeList.push_back(_TimeList[numSamples-1]); optKeyList.push_back(_QuatKeyList[numSamples-1]); // copy the optimized track to the main one. _TimeList= optTimeList; _QuatKeyList= optKeyList; } // *************************************************************************** bool CAnimationOptimizer::nearlySameQuaternion(const CQuatD &quat0, const CQuatD &quat1) { // true if exactly same, or exactly inverse if(quat0==quat1 || quat0==-quat1) return true; // Else compute the rotation to go from qRef to q. Use double for better presion. CQuatD quatDif; quatDif= quat1 * quat0.conjugate(); // inverse the quaternion if necessary. ie make closest to the identity quaternion. if(quatDif.w<0) quatDif= -quatDif; // compare "angle threshold" return (quatDif.w >= _QuaternionThreshold); } // *************************************************************************** // *************************************************************************** // Vector optimisation // *************************************************************************** // *************************************************************************** // *************************************************************************** void CAnimationOptimizer::sampleVectorTrack(const ITrack *trackIn, float beginTime, float endTime, uint numSamples) { // resize tmp samples _TimeList.resize(numSamples); _VectorKeyList.resize(numSamples); // Sample the animation. float t= beginTime; float dt= 0; if(numSamples>1) dt= (endTime-beginTime)/(numSamples-1); for(uint i=0;i(trackIn)->interpolate(t, vector); // store time and key. _TimeList[i]= i; _VectorKeyList[i]= vector; } } // *************************************************************************** bool CAnimationOptimizer::testConstantVectorTrack() { uint numSamples= (uint)_VectorKeyList.size(); nlassert(numSamples>0); // Get the first sample as the reference Vectorer, and test others from this one. CVector vectorRef= _VectorKeyList[0]; for(uint i=0;i0); // <=2 key? => no opt possible.. if(numSamples<=2) return; // prepare dest opt std::vector optTimeList; std::vector optKeyList; optTimeList.reserve(numSamples); optKeyList.reserve(numSamples); // Add the first key. optTimeList.push_back(_TimeList[0]); optKeyList.push_back(_VectorKeyList[0]); double timeRef= _TimeList[0]; CVectorD vectorRef= _VectorKeyList[0]; // For all keys, but the first and the last, test if can remove them. for(uint i=1; i255) { mustAdd= true; } // else, test interpolation else { // If the 3 Vectors are nearly equals, it is ok (avoid interpolation) if( nearlySameVector(vectorRef, vectorCur) && nearlySameVector(vectorRef, vectorNext) ) mustAdd= false; else { // interpolate. CVectorD vectorInterpolated; double t= (timeCur-timeRef)/(timeNext/timeRef); vectorInterpolated= vectorRef*(1-t) + vectorNext*t; // test if cur and interpolate are equal. if( !nearlySameVector(vectorCur, vectorInterpolated) ) mustAdd= true; } } // If must add the key to the optimized track. if(mustAdd) { optTimeList.push_back(_TimeList[i]); optKeyList.push_back(_VectorKeyList[i]); timeRef= _TimeList[i]; vectorRef= _VectorKeyList[i]; } } // Add the last key. optTimeList.push_back(_TimeList[numSamples-1]); optKeyList.push_back(_VectorKeyList[numSamples-1]); // copy the optimized track to the main one. _TimeList= optTimeList; _VectorKeyList= optKeyList; } // *************************************************************************** bool CAnimationOptimizer::nearlySameVector(const CVectorD &v0, const CVectorD &v1) { // true if exactly same if(v0==v1) return true; // Else compute the dif, use double for better precision CVectorD vDif; vDif= v1-v0; // compare norm return (vDif.norm() <= _VectorThreshold); } // *************************************************************************** // *************************************************************************** // LowPrecisionTrack // *************************************************************************** // *************************************************************************** // *************************************************************************** void CAnimationOptimizer::addLowPrecisionTrack(const std::string &name) { _LowPrecTrackKeyName.push_back(name); } // *************************************************************************** void CAnimationOptimizer::clearLowPrecisionTracks() { _LowPrecTrackKeyName.clear(); } // *************************************************************************** bool CAnimationOptimizer::isLowPrecisionTrack(const std::string &trackName) { for(uint i=0; i<_LowPrecTrackKeyName.size(); i++) { // if find a substr of the key, it is a low prec track if( trackName.find(_LowPrecTrackKeyName[i]) != string::npos ) return true; } // no key found return false; } } // NL3D