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