1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "nel/3d/animation_optimizer.h"
20 #include "nel/misc/mem_stream.h"
21 #include "nel/misc/vectord.h"
22 #include "nel/3d/track.h"
23 #include "nel/3d/track_keyframer.h"
24 #include "nel/3d/animation.h"
25 #include "nel/3d/track_sampled_quat.h"
26 #include "nel/3d/track_sampled_vector.h"
29 using namespace NLMISC
;
40 // ***************************************************************************
41 CAnimationOptimizer::CAnimationOptimizer()
44 _QuaternionThresholdLowPrec
= 1.0 - 0.0001;
45 _QuaternionThresholdHighPrec
= 1.0 - 0.000001;
46 _VectorThresholdLowPrec
= 0.001;
47 _VectorThresholdHighPrec
= 0.0001;
51 // ***************************************************************************
52 void CAnimationOptimizer::setQuaternionThreshold(double lowPrecThre
, double highPrecThre
)
54 nlassert(lowPrecThre
>=0);
55 nlassert(highPrecThre
>=0);
56 _QuaternionThresholdLowPrec
= 1.0 - lowPrecThre
;
57 _QuaternionThresholdHighPrec
= 1.0 - highPrecThre
;
61 // ***************************************************************************
62 void CAnimationOptimizer::setVectorThreshold(double lowPrecThre
, double highPrecThre
)
64 nlassert(lowPrecThre
>=0);
65 nlassert(highPrecThre
>=0);
66 _VectorThresholdLowPrec
= lowPrecThre
;
67 _VectorThresholdHighPrec
= highPrecThre
;
71 // ***************************************************************************
72 void CAnimationOptimizer::setSampleFrameRate(float frameRate
)
74 nlassert(frameRate
>0);
75 _SampleFrameRate
= frameRate
;
79 // ***************************************************************************
80 void CAnimationOptimizer::optimize(const CAnimation
&animIn
, CAnimation
&animOut
)
85 // Parse all tracks of the animation.
86 set
<string
> setString
;
87 animIn
.getTrackNames (setString
);
88 set
<string
>::iterator it
;
90 for(it
=setString
.begin();it
!=setString
.end();it
++)
92 const string
&trackName
= *it
;
93 uint trackId
= animIn
.getIdTrackByName(trackName
);
94 nlassert(trackId
!=CAnimation::NotFound
);
95 const ITrack
*track
= animIn
.getTrack(trackId
);
97 // If the track is optimisable.
99 if(isTrackOptimisable(track
))
101 // choose the threshold according to precision wanted
102 if( isLowPrecisionTrack(trackName
) )
104 _QuaternionThreshold
= _QuaternionThresholdLowPrec
;
105 _VectorThreshold
= _VectorThresholdLowPrec
;
109 _QuaternionThreshold
= _QuaternionThresholdHighPrec
;
110 _VectorThreshold
= _VectorThresholdHighPrec
;
114 newTrack
= optimizeTrack(track
);
119 newTrack
= cloneTrack(track
);
122 // Add it to the animation
123 animOut
.addTrack(trackName
, newTrack
);
126 // Parse all SSS shapes of the animation (important for preload of those shapes)
127 const vector
<string
> &shapes
= animIn
.getSSSShapes();
128 for(uint i
=0;i
<shapes
.size();i
++)
129 animOut
.addSSSShape(shapes
[i
]);
131 // Set min animation length
132 animOut
.setMinEndTime (animIn
.getEndTime ());
133 nlassert (animOut
.getEndTime() == animIn
.getEndTime());
136 // ***************************************************************************
137 ITrack
*CAnimationOptimizer::cloneTrack(const ITrack
*trackIn
)
139 CMemStream memStream
;
141 // write to the stream.
142 ITrack
*trackInSerial
= const_cast<ITrack
*>(trackIn
);
143 memStream
.serialPolyPtr(trackInSerial
);
145 // read from the stream.
148 memStream
.serialPolyPtr(ret
);
154 // ***************************************************************************
155 bool CAnimationOptimizer::isTrackOptimisable(const ITrack
*trackIn
)
159 // If the track is a Linear, Bezier or a TCB track, suppose we can optimize it. Constant may not be interressant....
160 if( dynamic_cast<const CTrackKeyFramerTCBQuat
*>(trackIn
) ||
161 dynamic_cast<const CTrackKeyFramerBezierQuat
*>(trackIn
) ||
162 dynamic_cast<const CTrackKeyFramerLinearQuat
*>(trackIn
) )
165 // If the track is a Linear, Bezier or a TCB track, suppose we can optimize it. Constant may not be interressant....
166 if( dynamic_cast<const CTrackKeyFramerTCBVector
*>(trackIn
) ||
167 dynamic_cast<const CTrackKeyFramerBezierVector
*>(trackIn
) ||
168 dynamic_cast<const CTrackKeyFramerLinearVector
*>(trackIn
) )
175 // ***************************************************************************
176 ITrack
*CAnimationOptimizer::optimizeTrack(const ITrack
*trackIn
)
179 float beginTime
= trackIn
->getBeginTime();
180 float endTime
= trackIn
->getEndTime();
181 nlassert(endTime
>=beginTime
);
184 uint numSamples
= (uint
)ceil( (endTime
-beginTime
)*_SampleFrameRate
);
185 numSamples
= max(1U, numSamples
);
186 nlassert(numSamples
<65535);
189 // Optimize Quaternion track??
191 // eval the track only to get its value type!!
192 CAnimatedValueBlock avBlock
;
193 const IAnimatedValue
&valueType
= ((ITrack
*)trackIn
)->eval(0, avBlock
);
194 if( dynamic_cast<const CAnimatedValueQuat
*>(&valueType
) )
196 // sample the animation. Store result in _TimeList/_QuatKeyList
197 sampleQuatTrack(trackIn
, beginTime
, endTime
, numSamples
);
199 // check if the sampled track can be reduced to a TrackDefaultQuat. Test _QuatKeyList.
200 if( testConstantQuatTrack() )
202 // create a default Track Quat.
203 CTrackDefaultQuat
*trackDefault
= new CTrackDefaultQuat
;
204 // setup the uniform value.
205 trackDefault
->setDefaultValue(_QuatKeyList
[0]);
207 // return the result.
210 // else optimize the sampled animation, and build.
216 // Create a sampled quaternion track
217 CTrackSampledQuat
*trackSQ
= new CTrackSampledQuat
;
219 // Copy loop from track.
220 trackSQ
->setLoopMode(trackIn
->getLoopMode());
223 trackSQ
->build(_TimeList
, _QuatKeyList
, beginTime
, endTime
);
229 // Optimize Position track??
231 else if( dynamic_cast<const CAnimatedValueVector
*>(&valueType
) )
233 // sample the animation. Store result in _TimeList/_VectorKeyList
234 sampleVectorTrack(trackIn
, beginTime
, endTime
, numSamples
);
236 // check if the sampled track can be reduced to a TrackDefaultVector. Test _VectorKeyList.
237 if( testConstantVectorTrack() )
239 // create a default Track Vector.
240 CTrackDefaultVector
*trackDefault
= new CTrackDefaultVector
;
241 // setup the uniform value.
242 trackDefault
->setDefaultValue(_VectorKeyList
[0]);
244 // return the result.
247 // else optimize the sampled animation, and build.
251 optimizeVectorTrack();
253 // Create a sampled Vector track
254 CTrackSampledVector
*trackSV
= new CTrackSampledVector
;
256 // Copy loop from track.
257 trackSV
->setLoopMode(trackIn
->getLoopMode());
260 trackSV
->build(_TimeList
, _VectorKeyList
, beginTime
, endTime
);
268 // Must be a quaternion track or vector track for now.
271 return cloneTrack(trackIn
);
276 // ***************************************************************************
277 // ***************************************************************************
278 // Quaternion optimisation
279 // ***************************************************************************
280 // ***************************************************************************
283 // ***************************************************************************
284 void CAnimationOptimizer::sampleQuatTrack(const ITrack
*trackIn
, float beginTime
, float endTime
, uint numSamples
)
286 // resize tmp samples
287 _TimeList
.resize(numSamples
);
288 _QuatKeyList
.resize(numSamples
);
290 // Sample the animation.
294 dt
= (endTime
-beginTime
)/(numSamples
-1);
295 for(uint i
=0;i
<numSamples
; i
++, t
+=dt
)
299 // make exact endTime match (avoid precision problem)
303 // evaluate the track
304 const_cast<ITrack
*>(trackIn
)->interpolate(t
, quat
);
306 // normalize this quaternion
309 // force on same hemisphere according to precedent frame.
312 quat
.makeClosest(_QuatKeyList
[i
-1]);
315 // store time and key.
317 _QuatKeyList
[i
]= quat
;
322 // ***************************************************************************
323 bool CAnimationOptimizer::testConstantQuatTrack()
325 uint numSamples
= (uint
)_QuatKeyList
.size();
326 nlassert(numSamples
>0);
328 // Get the first sample as the reference quaternion, and test others from this one.
329 CQuat quatRef
= _QuatKeyList
[0];
330 for(uint i
=0;i
<numSamples
;i
++)
332 // All values must be nearly equal to the reference quaternion.
333 if(!nearlySameQuaternion(quatRef
, _QuatKeyList
[i
]))
342 // ***************************************************************************
343 void CAnimationOptimizer::optimizeQuatTrack()
345 uint numSamples
= (uint
)_QuatKeyList
.size();
346 nlassert(numSamples
>0);
348 // <=2 key? => no opt possible..
353 std::vector
<uint16
> optTimeList
;
354 std::vector
<CQuat
> optKeyList
;
355 optTimeList
.reserve(numSamples
);
356 optKeyList
.reserve(numSamples
);
358 // Add the first key.
359 optTimeList
.push_back(_TimeList
[0]);
360 optKeyList
.push_back(_QuatKeyList
[0]);
361 double timeRef
= _TimeList
[0];
362 CQuatD quatRef
= _QuatKeyList
[0];
364 // For all keys, but the first and the last, test if can remove them.
365 for(uint i
=1; i
<numSamples
-1; i
++)
367 CQuatD quatCur
= _QuatKeyList
[i
];
368 CQuatD quatNext
= _QuatKeyList
[i
+1];
369 double timeCur
= _TimeList
[i
];
370 double timeNext
= _TimeList
[i
+1];
375 // If the Delta time are too big, abort (CTrackSampledQuat limitation)
376 if(timeNext
-timeRef
>255)
380 // If the next quaternion or the current quaternion are not on same hemisphere than ref, abort.
381 else if( CQuatD::dotProduct(quatCur
, quatRef
)<0 || CQuatD::dotProduct(quatNext
, quatRef
)<0 )
385 // else, test interpolation
388 // If the 3 quats are nearly equals, it is ok (avoid interpolation)
389 if( nearlySameQuaternion(quatRef
, quatCur
) && nearlySameQuaternion(quatRef
, quatNext
) )
394 CQuatD quatInterpolated
;
395 double t
= (timeCur
-timeRef
)/(timeNext
/timeRef
);
396 quatInterpolated
= CQuatD::slerp(quatRef
, quatNext
, (float)t
);
398 // test if cur and interpolate are equal.
399 if( !nearlySameQuaternion(quatCur
, quatInterpolated
) )
404 // If must add the key to the optimized track.
407 optTimeList
.push_back(_TimeList
[i
]);
408 optKeyList
.push_back(_QuatKeyList
[i
]);
409 timeRef
= _TimeList
[i
];
410 quatRef
= _QuatKeyList
[i
];
415 optTimeList
.push_back(_TimeList
[numSamples
-1]);
416 optKeyList
.push_back(_QuatKeyList
[numSamples
-1]);
418 // copy the optimized track to the main one.
419 _TimeList
= optTimeList
;
420 _QuatKeyList
= optKeyList
;
424 // ***************************************************************************
425 bool CAnimationOptimizer::nearlySameQuaternion(const CQuatD
&quat0
, const CQuatD
&quat1
)
427 // true if exactly same, or exactly inverse
428 if(quat0
==quat1
|| quat0
==-quat1
)
431 // Else compute the rotation to go from qRef to q. Use double for better presion.
433 quatDif
= quat1
* quat0
.conjugate();
434 // inverse the quaternion if necessary. ie make closest to the identity quaternion.
438 // compare "angle threshold"
439 return (quatDif
.w
>= _QuaternionThreshold
);
443 // ***************************************************************************
444 // ***************************************************************************
445 // Vector optimisation
446 // ***************************************************************************
447 // ***************************************************************************
450 // ***************************************************************************
451 void CAnimationOptimizer::sampleVectorTrack(const ITrack
*trackIn
, float beginTime
, float endTime
, uint numSamples
)
453 // resize tmp samples
454 _TimeList
.resize(numSamples
);
455 _VectorKeyList
.resize(numSamples
);
457 // Sample the animation.
461 dt
= (endTime
-beginTime
)/(numSamples
-1);
462 for(uint i
=0;i
<numSamples
; i
++, t
+=dt
)
466 // make exact endTime match (avoid precision problem)
470 // evaluate the track
471 const_cast<ITrack
*>(trackIn
)->interpolate(t
, vector
);
473 // store time and key.
475 _VectorKeyList
[i
]= vector
;
480 // ***************************************************************************
481 bool CAnimationOptimizer::testConstantVectorTrack()
483 uint numSamples
= (uint
)_VectorKeyList
.size();
484 nlassert(numSamples
>0);
486 // Get the first sample as the reference Vectorer, and test others from this one.
487 CVector vectorRef
= _VectorKeyList
[0];
488 for(uint i
=0;i
<numSamples
;i
++)
490 // All values must be nearly equal to the reference vector.
491 if(!nearlySameVector(vectorRef
, _VectorKeyList
[i
]))
500 // ***************************************************************************
501 void CAnimationOptimizer::optimizeVectorTrack()
503 uint numSamples
= (uint
)_VectorKeyList
.size();
504 nlassert(numSamples
>0);
506 // <=2 key? => no opt possible..
511 std::vector
<uint16
> optTimeList
;
512 std::vector
<CVector
> optKeyList
;
513 optTimeList
.reserve(numSamples
);
514 optKeyList
.reserve(numSamples
);
516 // Add the first key.
517 optTimeList
.push_back(_TimeList
[0]);
518 optKeyList
.push_back(_VectorKeyList
[0]);
519 double timeRef
= _TimeList
[0];
520 CVectorD vectorRef
= _VectorKeyList
[0];
522 // For all keys, but the first and the last, test if can remove them.
523 for(uint i
=1; i
<numSamples
-1; i
++)
525 CVectorD vectorCur
= _VectorKeyList
[i
];
526 CVectorD vectorNext
= _VectorKeyList
[i
+1];
527 double timeCur
= _TimeList
[i
];
528 double timeNext
= _TimeList
[i
+1];
533 // If the Delta time are too big, abort (CTrackSampledVector limitation)
534 if(timeNext
-timeRef
>255)
538 // else, test interpolation
541 // If the 3 Vectors are nearly equals, it is ok (avoid interpolation)
542 if( nearlySameVector(vectorRef
, vectorCur
) && nearlySameVector(vectorRef
, vectorNext
) )
547 CVectorD vectorInterpolated
;
548 double t
= (timeCur
-timeRef
)/(timeNext
/timeRef
);
549 vectorInterpolated
= vectorRef
*(1-t
) + vectorNext
*t
;
551 // test if cur and interpolate are equal.
552 if( !nearlySameVector(vectorCur
, vectorInterpolated
) )
557 // If must add the key to the optimized track.
560 optTimeList
.push_back(_TimeList
[i
]);
561 optKeyList
.push_back(_VectorKeyList
[i
]);
562 timeRef
= _TimeList
[i
];
563 vectorRef
= _VectorKeyList
[i
];
568 optTimeList
.push_back(_TimeList
[numSamples
-1]);
569 optKeyList
.push_back(_VectorKeyList
[numSamples
-1]);
571 // copy the optimized track to the main one.
572 _TimeList
= optTimeList
;
573 _VectorKeyList
= optKeyList
;
577 // ***************************************************************************
578 bool CAnimationOptimizer::nearlySameVector(const CVectorD
&v0
, const CVectorD
&v1
)
580 // true if exactly same
584 // Else compute the dif, use double for better precision
589 return (vDif
.norm() <= _VectorThreshold
);
593 // ***************************************************************************
594 // ***************************************************************************
596 // ***************************************************************************
597 // ***************************************************************************
600 // ***************************************************************************
601 void CAnimationOptimizer::addLowPrecisionTrack(const std::string
&name
)
603 _LowPrecTrackKeyName
.push_back(name
);
606 // ***************************************************************************
607 void CAnimationOptimizer::clearLowPrecisionTracks()
609 _LowPrecTrackKeyName
.clear();
612 // ***************************************************************************
613 bool CAnimationOptimizer::isLowPrecisionTrack(const std::string
&trackName
)
615 for(uint i
=0; i
<_LowPrecTrackKeyName
.size(); i
++)
617 // if find a substr of the key, it is a low prec track
618 if( trackName
.find(_LowPrecTrackKeyName
[i
]) != string::npos
)