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/misc/quat.h"
20 #include "nel/misc/common.h"
21 #include "nel/3d/track_sampled_quat.h"
22 #include "nel/3d/track_sampled_quat_small_header.h"
24 using namespace NLMISC
;
34 // ***************************************************************************
35 // ***************************************************************************
36 // Quaternion compression
37 // ***************************************************************************
38 // ***************************************************************************
40 const double NL3D_OO32767
= 1.0f
/32767;
41 const double NL3D_OO65535
= 1.0f
/65535;
43 #ifdef NL3D_TSQ_ALLOW_QUAT_COMPRESS
44 // ***************************************************************************
45 void CQuatPack::pack(const CQuat
&quat
)
48 This is the most precise/faster compression we can have. Some other tries have been made.
50 - deducing w from x,y,z is possible with w= 1-sqrt(x^2+y^2+z^2) (with tradeoff of the W sign)
52 - Transform the quaternion to an AxisAngle is possible, but slower (some cos/sin or LUT).
53 Axis is encoded with sint16, and angle is encoded with uint16.
54 - The same than above, but encode the axis as X/Y only, and deduce Z from
55 them, is possible but precision problems arise.
57 You can see that the operation "deduce a 3/4 member from unit length rule" is definetly not precise.
59 Hence this simpler but workable way.
62 // normalize the quaterion.
66 sint ax
= (sint
)floor(nquat
.x
* 32767 + 0.5);
67 sint ay
= (sint
)floor(nquat
.y
* 32767 + 0.5);
68 sint az
= (sint
)floor(nquat
.z
* 32767 + 0.5);
69 sint aw
= (sint
)floor(nquat
.w
* 32767 + 0.5);
70 clamp(ax
, -32767, 32767);
71 clamp(ay
, -32767, 32767);
72 clamp(az
, -32767, 32767);
73 clamp(aw
, -32767, 32767);
80 // ***************************************************************************
81 void CQuatPack::unpack(CQuat
&quat
)
85 quatD
.x
= x
* NL3D_OO32767
;
86 quatD
.y
= y
* NL3D_OO32767
;
87 quatD
.z
= z
* NL3D_OO32767
;
88 quatD
.w
= w
* NL3D_OO32767
;
96 // ***************************************************************************
97 // ***************************************************************************
99 // ***************************************************************************
100 // ***************************************************************************
103 // ***************************************************************************
104 CTrackSampledQuat::CTrackSampledQuat()
108 // ***************************************************************************
109 CTrackSampledQuat::~CTrackSampledQuat()
113 // ***************************************************************************
114 void CTrackSampledQuat::serial(NLMISC::IStream
&f
)
118 - split class with base CTrackSampledCommon (must add a version in it).
122 sint ver
= f
.serialVersion(1);
126 // serial Time infos, directly in CTrackSampledCommon
128 f
.serial(_BeginTime
);
130 f
.serial(_TotalRange
);
131 f
.serial(_OOTotalRange
);
132 f
.serial(_DeltaTime
);
133 f
.serial(_OODeltaTime
);
134 f
.serial(_TimeBlocks
);
138 // serial Time infos.
139 CTrackSampledCommon::serialCommon(f
);
146 // ***************************************************************************
147 void CTrackSampledQuat::build(const std::vector
<uint16
> &timeList
, const std::vector
<CQuat
> &keyList
,
148 float beginTime
, float endTime
)
150 nlassert( endTime
>beginTime
|| (beginTime
==endTime
&& keyList
.size()<=1) );
151 nlassert( keyList
.size()==timeList
.size() );
155 uint numKeys
= (uint
)keyList
.size();
159 // Build Common time information
160 CTrackSampledCommon::buildCommon(timeList
, beginTime
, endTime
);
163 // Compute All Key values.
164 //===================
165 _Keys
.resize(numKeys
);
166 for(i
=0; i
<numKeys
;i
++)
168 _Keys
[i
].pack(keyList
[i
]);
173 // ***************************************************************************
174 const IAnimatedValue
&CTrackSampledQuat::eval (const TAnimationTime
& date
, CAnimatedValueBlock
&avBlock
)
176 /* IF YOU CHANGE THIS CODE, CHANGE too CTrackSampledQuatSmallHeader
179 // Eval time, and get key interpolation info
183 TEvalType evalType
= evalTime(date
, _Keys
.size(), keyId0
, keyId1
, interpValue
);
186 if( evalType
==EvalDiscard
)
187 return avBlock
.ValQuat
;
188 // One Key? easy, and quit.
189 else if( evalType
==EvalKey0
)
191 _Keys
[keyId0
].unpack(avBlock
.ValQuat
.Value
);
194 else if( evalType
==EvalInterpolate
)
196 CQuatPack valueKey0
= _Keys
[keyId0
];
197 CQuatPack valueKey1
= _Keys
[keyId1
];
199 // If the 2 keys have same value, just unpack.
200 if(valueKey0
== valueKey1
)
202 valueKey0
.unpack(avBlock
.ValQuat
.Value
);
209 valueKey0
.unpack(quat0
);
210 valueKey1
.unpack(quat1
);
213 avBlock
.ValQuat
.Value
= CQuat::slerp(quat0
, quat1
, interpValue
);
221 return avBlock
.ValQuat
;
224 // ***************************************************************************
225 void CTrackSampledQuat::applySampleDivisor(uint sampleDivisor
)
230 // **** build the key indices to keep, and rebuild the timeBlocks
231 static std::vector
<uint32
> keepKeys
;
232 applySampleDivisorCommon(sampleDivisor
, keepKeys
);
234 // **** rebuild the keys
235 NLMISC::CObjectVector
<CQuatPack
, false> newKeys
;
236 newKeys
.resize((uint32
)keepKeys
.size());
237 for(uint i
=0;i
<newKeys
.size();i
++)
239 newKeys
[i
]= _Keys
[keepKeys
[i
]];
245 /*nlinfo("ANIMQUAT:\t%d\t%d\t%d\t%d", sizeof(*this), _TimeBlocks.size(),
246 _TimeBlocks.size()?_TimeBlocks[0].Times.size():0,
247 _Keys.size() * sizeof(CQuatPack));*/
251 // ***************************************************************************
252 bool CTrackSampledQuat::applyTrackQuatHeaderCompressionPass0(class CTrackSampleCounter
&quatCounter
)
254 // if there is more than 1 timeBlock, fails
255 if(_TimeBlocks
.size()>1)
258 // Support only 255 keys and not 256!!! cause _NumKeys is encoded in 8 bits!
259 if(_Keys
.size()>=256)
262 // if the number of keys ovveride the uint16 limit, abort
263 if(_Keys
.size()+quatCounter
.NumKeys
> 65536)
266 // Search if the Track header is the same as one of the quatCounter.
267 // NB: O(N*N) but quatCounter.TrackHeaders should be very small
269 for(headerIndex
=0;headerIndex
<quatCounter
.TrackHeaders
.size();headerIndex
++)
271 CTrackSampleHeader
&tsh
= quatCounter
.TrackHeaders
[headerIndex
];
272 if( tsh
.LoopMode
== _LoopMode
&&
273 tsh
.BeginTime
== _BeginTime
&&
274 tsh
.EndTime
== _EndTime
&&
275 tsh
.TotalRange
== _TotalRange
&&
276 tsh
.OOTotalRange
== _OOTotalRange
&&
277 tsh
.DeltaTime
== _DeltaTime
&&
278 tsh
.OODeltaTime
== _OODeltaTime
)
281 if(headerIndex
==quatCounter
.TrackHeaders
.size())
283 // then must increment the TrackHeaders. must not ovverride the uint8 limit
284 if(quatCounter
.TrackHeaders
.size()==256)
288 nlassert(quatCounter
.TrackHeaders
.size()<256);
289 CTrackSampleHeader tsh
;
290 tsh
.LoopMode
= _LoopMode
;
291 tsh
.BeginTime
= _BeginTime
;
292 tsh
.EndTime
= _EndTime
;
293 tsh
.TotalRange
= _TotalRange
;
294 tsh
.OOTotalRange
= _OOTotalRange
;
295 tsh
.DeltaTime
= _DeltaTime
;
296 tsh
.OODeltaTime
= _OODeltaTime
;
297 quatCounter
.TrackHeaders
.push_back(tsh
);
300 // else ok, one Header match
302 // increment the number of keys in the packed data
303 quatCounter
.NumKeys
+= _Keys
.size();
305 // at least this track can be compressed
309 // ***************************************************************************
310 ITrack
*CTrackSampledQuat::applyTrackQuatHeaderCompressionPass1(uint
&globalKeyOffset
, class CTrackSamplePack
&quatPacker
)
312 // if there is more than 1 timeBlock, fails
313 if(_TimeBlocks
.size()>1)
316 // Support only 255 keys and not 256!!! cause _NumKeys is encoded in 8 bits!
317 if(_Keys
.size()>=256)
320 // if the number of keys ovveride the uint16 limit, abort
321 if(_Keys
.size()+globalKeyOffset
> 65536)
324 // Search if the Track header is the same as one of the quatPacker.
325 // NB: O(N*N) but quatPacker.TrackHeaders should be very small
327 for(headerIndex
=0;headerIndex
<quatPacker
.TrackHeaders
.size();headerIndex
++)
329 CTrackSampleHeader
&tsh
= quatPacker
.TrackHeaders
[headerIndex
];
330 if( tsh
.LoopMode
== _LoopMode
&&
331 tsh
.BeginTime
== _BeginTime
&&
332 tsh
.EndTime
== _EndTime
&&
333 tsh
.TotalRange
== _TotalRange
&&
334 tsh
.OOTotalRange
== _OOTotalRange
&&
335 tsh
.DeltaTime
== _DeltaTime
&&
336 tsh
.OODeltaTime
== _OODeltaTime
)
339 if(headerIndex
==quatPacker
.TrackHeaders
.size())
344 // OK! this track can be converted to a CTrackSampledQuatSmallHeader
345 uint keyIndex
= globalKeyOffset
;
347 // increment the number of keys in the packed data
348 globalKeyOffset
+= _Keys
.size();
350 // **** fill the packer struct
352 for(i
=0;i
<_Keys
.size();i
++)
354 quatPacker
.Times
[keyIndex
+i
]= _TimeBlocks
[0].Times
[i
];
355 quatPacker
.Keys
[keyIndex
+i
]= _Keys
[i
];
358 // **** Build the compressed quat, and return it
359 return new CTrackSampledQuatSmallHeader(&quatPacker
, (uint8
)headerIndex
, (uint8
)_Keys
.size(), keyIndex
);