1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22 
23 #include "../Precompiled.h"
24 
25 #include "../Container/Sort.h"
26 #include "../Core/Context.h"
27 #include "../Core/Profiler.h"
28 #include "../Graphics/Animation.h"
29 #include "../IO/Deserializer.h"
30 #include "../IO/FileSystem.h"
31 #include "../IO/Log.h"
32 #include "../IO/Serializer.h"
33 #include "../Resource/ResourceCache.h"
34 #include "../Resource/XMLFile.h"
35 #include "../Resource/JSONFile.h"
36 
37 #include "../DebugNew.h"
38 
39 namespace Urho3D
40 {
41 
CompareTriggers(AnimationTriggerPoint & lhs,AnimationTriggerPoint & rhs)42 inline bool CompareTriggers(AnimationTriggerPoint& lhs, AnimationTriggerPoint& rhs)
43 {
44     return lhs.time_ < rhs.time_;
45 }
46 
CompareKeyFrames(AnimationKeyFrame & lhs,AnimationKeyFrame & rhs)47 inline bool CompareKeyFrames(AnimationKeyFrame& lhs, AnimationKeyFrame& rhs)
48 {
49     return lhs.time_ < rhs.time_;
50 }
51 
SetKeyFrame(unsigned index,const AnimationKeyFrame & keyFrame)52 void AnimationTrack::SetKeyFrame(unsigned index, const AnimationKeyFrame& keyFrame)
53 {
54     if (index < keyFrames_.Size())
55     {
56         keyFrames_[index] = keyFrame;
57         Urho3D::Sort(keyFrames_.Begin(), keyFrames_.End(), CompareKeyFrames);
58     }
59     else if (index == keyFrames_.Size())
60         AddKeyFrame(keyFrame);
61 }
62 
AddKeyFrame(const AnimationKeyFrame & keyFrame)63 void AnimationTrack::AddKeyFrame(const AnimationKeyFrame& keyFrame)
64 {
65     bool needSort = keyFrames_.Size() ? keyFrames_.Back().time_ > keyFrame.time_ : false;
66     keyFrames_.Push(keyFrame);
67     if (needSort)
68         Urho3D::Sort(keyFrames_.Begin(), keyFrames_.End(), CompareKeyFrames);
69 }
70 
InsertKeyFrame(unsigned index,const AnimationKeyFrame & keyFrame)71 void AnimationTrack::InsertKeyFrame(unsigned index, const AnimationKeyFrame& keyFrame)
72 {
73     keyFrames_.Insert(index, keyFrame);
74     Urho3D::Sort(keyFrames_.Begin(), keyFrames_.End(), CompareKeyFrames);
75 }
76 
RemoveKeyFrame(unsigned index)77 void AnimationTrack::RemoveKeyFrame(unsigned index)
78 {
79     keyFrames_.Erase(index);
80 }
81 
RemoveAllKeyFrames()82 void AnimationTrack::RemoveAllKeyFrames()
83 {
84     keyFrames_.Clear();
85 }
86 
GetKeyFrame(unsigned index)87 AnimationKeyFrame* AnimationTrack::GetKeyFrame(unsigned index)
88 {
89     return index < keyFrames_.Size() ? &keyFrames_[index] : (AnimationKeyFrame*)0;
90 }
91 
GetKeyFrameIndex(float time,unsigned & index) const92 void AnimationTrack::GetKeyFrameIndex(float time, unsigned& index) const
93 {
94     if (time < 0.0f)
95         time = 0.0f;
96 
97     if (index >= keyFrames_.Size())
98         index = keyFrames_.Size() - 1;
99 
100     // Check for being too far ahead
101     while (index && time < keyFrames_[index].time_)
102         --index;
103 
104     // Check for being too far behind
105     while (index < keyFrames_.Size() - 1 && time >= keyFrames_[index + 1].time_)
106         ++index;
107 }
108 
Animation(Context * context)109 Animation::Animation(Context* context) :
110     ResourceWithMetadata(context),
111     length_(0.f)
112 {
113 }
114 
~Animation()115 Animation::~Animation()
116 {
117 }
118 
RegisterObject(Context * context)119 void Animation::RegisterObject(Context* context)
120 {
121     context->RegisterFactory<Animation>();
122 }
123 
BeginLoad(Deserializer & source)124 bool Animation::BeginLoad(Deserializer& source)
125 {
126     unsigned memoryUse = sizeof(Animation);
127 
128     // Check ID
129     if (source.ReadFileID() != "UANI")
130     {
131         URHO3D_LOGERROR(source.GetName() + " is not a valid animation file");
132         return false;
133     }
134 
135     // Read name and length
136     animationName_ = source.ReadString();
137     animationNameHash_ = animationName_;
138     length_ = source.ReadFloat();
139     tracks_.Clear();
140 
141     unsigned tracks = source.ReadUInt();
142     memoryUse += tracks * sizeof(AnimationTrack);
143 
144     // Read tracks
145     for (unsigned i = 0; i < tracks; ++i)
146     {
147         AnimationTrack* newTrack = CreateTrack(source.ReadString());
148         newTrack->channelMask_ = source.ReadUByte();
149 
150         unsigned keyFrames = source.ReadUInt();
151         newTrack->keyFrames_.Resize(keyFrames);
152         memoryUse += keyFrames * sizeof(AnimationKeyFrame);
153 
154         // Read keyframes of the track
155         for (unsigned j = 0; j < keyFrames; ++j)
156         {
157             AnimationKeyFrame& newKeyFrame = newTrack->keyFrames_[j];
158             newKeyFrame.time_ = source.ReadFloat();
159             if (newTrack->channelMask_ & CHANNEL_POSITION)
160                 newKeyFrame.position_ = source.ReadVector3();
161             if (newTrack->channelMask_ & CHANNEL_ROTATION)
162                 newKeyFrame.rotation_ = source.ReadQuaternion();
163             if (newTrack->channelMask_ & CHANNEL_SCALE)
164                 newKeyFrame.scale_ = source.ReadVector3();
165         }
166     }
167 
168     // Optionally read triggers from an XML file
169     ResourceCache* cache = GetSubsystem<ResourceCache>();
170     String xmlName = ReplaceExtension(GetName(), ".xml");
171 
172     SharedPtr<XMLFile> file(cache->GetTempResource<XMLFile>(xmlName, false));
173     if (file)
174     {
175         XMLElement rootElem = file->GetRoot();
176         for (XMLElement triggerElem = rootElem.GetChild("trigger"); triggerElem; triggerElem = triggerElem.GetNext("trigger"))
177         {
178             if (triggerElem.HasAttribute("normalizedtime"))
179                 AddTrigger(triggerElem.GetFloat("normalizedtime"), true, triggerElem.GetVariant());
180             else if (triggerElem.HasAttribute("time"))
181                 AddTrigger(triggerElem.GetFloat("time"), false, triggerElem.GetVariant());
182         }
183 
184         LoadMetadataFromXML(rootElem);
185 
186         memoryUse += triggers_.Size() * sizeof(AnimationTriggerPoint);
187         SetMemoryUse(memoryUse);
188         return true;
189     }
190 
191     // Optionally read triggers from a JSON file
192     String jsonName = ReplaceExtension(GetName(), ".json");
193 
194     SharedPtr<JSONFile> jsonFile(cache->GetTempResource<JSONFile>(jsonName, false));
195     if (jsonFile)
196     {
197         const JSONValue& rootVal = jsonFile->GetRoot();
198         const JSONArray& triggerArray = rootVal.Get("triggers").GetArray();
199 
200         for (unsigned i = 0; i < triggerArray.Size(); i++)
201         {
202             const JSONValue& triggerValue = triggerArray.At(i);
203             JSONValue normalizedTimeValue = triggerValue.Get("normalizedTime");
204             if (!normalizedTimeValue.IsNull())
205                 AddTrigger(normalizedTimeValue.GetFloat(), true, triggerValue.GetVariant());
206             else
207             {
208                 JSONValue timeVal = triggerValue.Get("time");
209                 if (!timeVal.IsNull())
210                     AddTrigger(timeVal.GetFloat(), false, triggerValue.GetVariant());
211             }
212         }
213 
214         const JSONArray& metadataArray = rootVal.Get("metadata").GetArray();
215         LoadMetadataFromJSON(metadataArray);
216 
217         memoryUse += triggers_.Size() * sizeof(AnimationTriggerPoint);
218         SetMemoryUse(memoryUse);
219         return true;
220     }
221 
222     SetMemoryUse(memoryUse);
223     return true;
224 }
225 
Save(Serializer & dest) const226 bool Animation::Save(Serializer& dest) const
227 {
228     // Write ID, name and length
229     dest.WriteFileID("UANI");
230     dest.WriteString(animationName_);
231     dest.WriteFloat(length_);
232 
233     // Write tracks
234     dest.WriteUInt(tracks_.Size());
235     for (HashMap<StringHash, AnimationTrack>::ConstIterator i = tracks_.Begin(); i != tracks_.End(); ++i)
236     {
237         const AnimationTrack& track = i->second_;
238         dest.WriteString(track.name_);
239         dest.WriteUByte(track.channelMask_);
240         dest.WriteUInt(track.keyFrames_.Size());
241 
242         // Write keyframes of the track
243         for (unsigned j = 0; j < track.keyFrames_.Size(); ++j)
244         {
245             const AnimationKeyFrame& keyFrame = track.keyFrames_[j];
246             dest.WriteFloat(keyFrame.time_);
247             if (track.channelMask_ & CHANNEL_POSITION)
248                 dest.WriteVector3(keyFrame.position_);
249             if (track.channelMask_ & CHANNEL_ROTATION)
250                 dest.WriteQuaternion(keyFrame.rotation_);
251             if (track.channelMask_ & CHANNEL_SCALE)
252                 dest.WriteVector3(keyFrame.scale_);
253         }
254     }
255 
256     // If triggers have been defined, write an XML file for them
257     if (!triggers_.Empty() || HasMetadata())
258     {
259         File* destFile = dynamic_cast<File*>(&dest);
260         if (destFile)
261         {
262             String xmlName = ReplaceExtension(destFile->GetName(), ".xml");
263 
264             SharedPtr<XMLFile> xml(new XMLFile(context_));
265             XMLElement rootElem = xml->CreateRoot("animation");
266 
267             for (unsigned i = 0; i < triggers_.Size(); ++i)
268             {
269                 XMLElement triggerElem = rootElem.CreateChild("trigger");
270                 triggerElem.SetFloat("time", triggers_[i].time_);
271                 triggerElem.SetVariant(triggers_[i].data_);
272             }
273 
274             SaveMetadataToXML(rootElem);
275 
276             File xmlFile(context_, xmlName, FILE_WRITE);
277             xml->Save(xmlFile);
278         }
279         else
280             URHO3D_LOGWARNING("Can not save animation trigger data when not saving into a file");
281     }
282 
283     return true;
284 }
285 
SetAnimationName(const String & name)286 void Animation::SetAnimationName(const String& name)
287 {
288     animationName_ = name;
289     animationNameHash_ = StringHash(name);
290 }
291 
SetLength(float length)292 void Animation::SetLength(float length)
293 {
294     length_ = Max(length, 0.0f);
295 }
296 
CreateTrack(const String & name)297 AnimationTrack* Animation::CreateTrack(const String& name)
298 {
299     /// \todo When tracks / keyframes are created dynamically, memory use is not updated
300     StringHash nameHash(name);
301     AnimationTrack* oldTrack = GetTrack(nameHash);
302     if (oldTrack)
303         return oldTrack;
304 
305     AnimationTrack& newTrack = tracks_[nameHash];
306     newTrack.name_ = name;
307     newTrack.nameHash_ = nameHash;
308     return &newTrack;
309 }
310 
RemoveTrack(const String & name)311 bool Animation::RemoveTrack(const String& name)
312 {
313     HashMap<StringHash, AnimationTrack>::Iterator i = tracks_.Find(StringHash(name));
314     if (i != tracks_.End())
315     {
316         tracks_.Erase(i);
317         return true;
318     }
319     else
320         return false;
321 }
322 
RemoveAllTracks()323 void Animation::RemoveAllTracks()
324 {
325     tracks_.Clear();
326 }
327 
SetTrigger(unsigned index,const AnimationTriggerPoint & trigger)328 void Animation::SetTrigger(unsigned index, const AnimationTriggerPoint& trigger)
329 {
330     if (index == triggers_.Size())
331         AddTrigger(trigger);
332     else if (index < triggers_.Size())
333     {
334         triggers_[index] = trigger;
335         Sort(triggers_.Begin(), triggers_.End(), CompareTriggers);
336     }
337 }
338 
AddTrigger(const AnimationTriggerPoint & trigger)339 void Animation::AddTrigger(const AnimationTriggerPoint& trigger)
340 {
341     triggers_.Push(trigger);
342     Sort(triggers_.Begin(), triggers_.End(), CompareTriggers);
343 }
344 
AddTrigger(float time,bool timeIsNormalized,const Variant & data)345 void Animation::AddTrigger(float time, bool timeIsNormalized, const Variant& data)
346 {
347     AnimationTriggerPoint newTrigger;
348     newTrigger.time_ = timeIsNormalized ? time * length_ : time;
349     newTrigger.data_ = data;
350     triggers_.Push(newTrigger);
351 
352     Sort(triggers_.Begin(), triggers_.End(), CompareTriggers);
353 }
354 
RemoveTrigger(unsigned index)355 void Animation::RemoveTrigger(unsigned index)
356 {
357     if (index < triggers_.Size())
358         triggers_.Erase(index);
359 }
360 
RemoveAllTriggers()361 void Animation::RemoveAllTriggers()
362 {
363     triggers_.Clear();
364 }
365 
SetNumTriggers(unsigned num)366 void Animation::SetNumTriggers(unsigned num)
367 {
368     triggers_.Resize(num);
369 }
370 
Clone(const String & cloneName) const371 SharedPtr<Animation> Animation::Clone(const String& cloneName) const
372 {
373     SharedPtr<Animation> ret(new Animation(context_));
374 
375     ret->SetName(cloneName);
376     ret->SetAnimationName(animationName_);
377     ret->length_ = length_;
378     ret->tracks_ = tracks_;
379     ret->triggers_ = triggers_;
380     ret->CopyMetadata(*this);
381     ret->SetMemoryUse(GetMemoryUse());
382 
383     return ret;
384 }
385 
GetTrack(unsigned index)386 AnimationTrack* Animation::GetTrack(unsigned index)
387 {
388     if (index >= GetNumTracks())
389         return (AnimationTrack*) 0;
390 
391     int j = 0;
392     for(HashMap<StringHash, AnimationTrack>::Iterator i = tracks_.Begin(); i != tracks_.End(); ++i)
393     {
394         if (j == index)
395             return &i->second_;
396 
397         ++j;
398     }
399 
400     return (AnimationTrack*) 0;
401 }
402 
GetTrack(const String & name)403 AnimationTrack* Animation::GetTrack(const String& name)
404 {
405     HashMap<StringHash, AnimationTrack>::Iterator i = tracks_.Find(StringHash(name));
406     return i != tracks_.End() ? &i->second_ : (AnimationTrack*)0;
407 }
408 
GetTrack(StringHash nameHash)409 AnimationTrack* Animation::GetTrack(StringHash nameHash)
410 {
411     HashMap<StringHash, AnimationTrack>::Iterator i = tracks_.Find(nameHash);
412     return i != tracks_.End() ? &i->second_ : (AnimationTrack*)0;
413 }
414 
GetTrigger(unsigned index)415 AnimationTriggerPoint* Animation::GetTrigger(unsigned index)
416 {
417     return index < triggers_.Size() ? &triggers_[index] : (AnimationTriggerPoint*)0;
418 }
419 
420 }
421