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/ArrayPtr.h"
26 #include "../Core/Context.h"
27 #include "../Graphics/Graphics.h"
28 #include "../Graphics/Texture2D.h"
29 #include "../IO/FileSystem.h"
30 #include "../IO/Log.h"
31 #include "../Math/AreaAllocator.h"
32 #include "../Resource/Image.h"
33 #include "../Resource/ResourceCache.h"
34 #include "../Resource/XMLFile.h"
35 #include "../Urho2D/AnimationSet2D.h"
36 #include "../Urho2D/Sprite2D.h"
37 #include "../Urho2D/SpriterData2D.h"
38 #include "../Urho2D/SpriteSheet2D.h"
39 
40 #include "../DebugNew.h"
41 
42 #ifdef URHO3D_SPINE
43 #include <spine/spine.h>
44 #include <spine/extension.h>
45 #endif
46 
47 #ifdef URHO3D_SPINE
48 // Current animation set
49 static Urho3D::AnimationSet2D* currentAnimationSet = 0;
50 
_spAtlasPage_createTexture(spAtlasPage * self,const char * path)51 void _spAtlasPage_createTexture(spAtlasPage* self, const char* path)
52 {
53     using namespace Urho3D;
54     if (!currentAnimationSet)
55         return;
56 
57     ResourceCache* cache = currentAnimationSet->GetSubsystem<ResourceCache>();
58     Sprite2D* sprite = cache->GetResource<Sprite2D>(path);
59     // Add reference
60     if (sprite)
61         sprite->AddRef();
62 
63     self->width = sprite->GetTexture()->GetWidth();
64     self->height = sprite->GetTexture()->GetHeight();
65 
66     self->rendererObject = sprite;
67 }
68 
_spAtlasPage_disposeTexture(spAtlasPage * self)69 void _spAtlasPage_disposeTexture(spAtlasPage* self)
70 {
71     using namespace Urho3D;
72     Sprite2D* sprite = static_cast<Sprite2D*>(self->rendererObject);
73     if (sprite)
74         sprite->ReleaseRef();
75 
76     self->rendererObject = 0;
77 }
78 
_spUtil_readFile(const char * path,int * length)79 char* _spUtil_readFile(const char* path, int* length)
80 {
81     using namespace Urho3D;
82 
83     if (!currentAnimationSet)
84         return 0;
85 
86     ResourceCache* cache = currentAnimationSet->GetSubsystem<ResourceCache>();
87     SharedPtr<File> file = cache->GetFile(path);
88     if (!file)
89         return 0;
90 
91     unsigned size = file->GetSize();
92 
93     char* data = MALLOC(char, size + 1);
94     file->Read(data, size);
95     data[size] = '\0';
96 
97     file.Reset();
98     *length = size;
99 
100     return data;
101 }
102 #endif
103 
104 namespace Urho3D
105 {
106 
AnimationSet2D(Context * context)107 AnimationSet2D::AnimationSet2D(Context* context) :
108     Resource(context),
109 #ifdef URHO3D_SPINE
110     skeletonData_(0),
111     atlas_(0),
112 #endif
113     hasSpriteSheet_(false)
114 {
115 }
116 
~AnimationSet2D()117 AnimationSet2D::~AnimationSet2D()
118 {
119     Dispose();
120 }
121 
RegisterObject(Context * context)122 void AnimationSet2D::RegisterObject(Context* context)
123 {
124     context->RegisterFactory<AnimationSet2D>();
125 }
126 
BeginLoad(Deserializer & source)127 bool AnimationSet2D::BeginLoad(Deserializer& source)
128 {
129     Dispose();
130 
131     if (GetName().Empty())
132         SetName(source.GetName());
133 
134     String extension = GetExtension(source.GetName());
135 #ifdef URHO3D_SPINE
136     if (extension == ".json")
137         return BeginLoadSpine(source);
138 #endif
139     if (extension == ".scml")
140         return BeginLoadSpriter(source);
141 
142     URHO3D_LOGERROR("Unsupport animation set file: " + source.GetName());
143 
144     return false;
145 }
146 
EndLoad()147 bool AnimationSet2D::EndLoad()
148 {
149 #ifdef URHO3D_SPINE
150     if (jsonData_)
151         return EndLoadSpine();
152 #endif
153     if (spriterData_)
154         return EndLoadSpriter();
155 
156     return false;
157 }
158 
GetNumAnimations() const159 unsigned AnimationSet2D::GetNumAnimations() const
160 {
161 #ifdef URHO3D_SPINE
162     if (skeletonData_)
163         return (unsigned)skeletonData_->animationsCount;
164 #endif
165     if (spriterData_ && !spriterData_->entities_.Empty())
166         return (unsigned)spriterData_->entities_[0]->animations_.Size();
167     return 0;
168 }
169 
GetAnimation(unsigned index) const170 String AnimationSet2D::GetAnimation(unsigned index) const
171 {
172     if (index >= GetNumAnimations())
173         return String::EMPTY;
174 
175 #ifdef URHO3D_SPINE
176     if (skeletonData_)
177         return skeletonData_->animations[index]->name;
178 #endif
179     if (spriterData_ && !spriterData_->entities_.Empty())
180         return spriterData_->entities_[0]->animations_[index]->name_;
181 
182     return String::EMPTY;
183 }
184 
HasAnimation(const String & animationName) const185 bool AnimationSet2D::HasAnimation(const String& animationName) const
186 {
187 #ifdef URHO3D_SPINE
188     if (skeletonData_)
189     {
190         for (int i = 0; i < skeletonData_->animationsCount; ++i)
191         {
192             if (animationName == skeletonData_->animations[i]->name)
193                 return true;
194         }
195     }
196 #endif
197     if (spriterData_ && !spriterData_->entities_.Empty())
198     {
199         const PODVector<Spriter::Animation*>& animations = spriterData_->entities_[0]->animations_;
200         for (unsigned i = 0; i < animations.Size(); ++i)
201         {
202             if (animationName == animations[i]->name_)
203                 return true;
204         }
205     }
206 
207     return false;
208 }
209 
GetSprite() const210 Sprite2D* AnimationSet2D::GetSprite() const
211 {
212     return sprite_;
213 }
214 
GetSpriterFileSprite(int folderId,int fileId) const215 Sprite2D* AnimationSet2D::GetSpriterFileSprite(int folderId, int fileId) const
216 {
217     int key = (folderId << 16) + fileId;
218     HashMap<int, SharedPtr<Sprite2D> >::ConstIterator i = spriterFileSprites_.Find(key);
219     if (i != spriterFileSprites_.End())
220         return i->second_;
221 
222     return 0;
223 }
224 
225 #ifdef URHO3D_SPINE
BeginLoadSpine(Deserializer & source)226 bool AnimationSet2D::BeginLoadSpine(Deserializer& source)
227 {
228     if (GetName().Empty())
229         SetName(source.GetName());
230 
231     unsigned size = source.GetSize();
232     jsonData_ = new char[size + 1];
233     source.Read(jsonData_, size);
234     jsonData_[size] = '\0';
235     SetMemoryUse(size);
236     return true;
237 }
238 
EndLoadSpine()239 bool AnimationSet2D::EndLoadSpine()
240 {
241     currentAnimationSet = this;
242 
243     String atlasFileName = ReplaceExtension(GetName(), ".atlas");
244     atlas_ = spAtlas_createFromFile(atlasFileName.CString(), 0);
245     if (!atlas_)
246     {
247         URHO3D_LOGERROR("Create spine atlas failed");
248         return false;
249     }
250 
251     int numAtlasPages = 0;
252     spAtlasPage* atlasPage = atlas_->pages;
253     while (atlasPage)
254     {
255         ++numAtlasPages;
256         atlasPage = atlasPage->next;
257     }
258 
259     if (numAtlasPages > 1)
260     {
261         URHO3D_LOGERROR("Only one page is supported in Urho3D");
262         return false;
263     }
264 
265     sprite_ = static_cast<Sprite2D*>(atlas_->pages->rendererObject);
266 
267     spSkeletonJson* skeletonJson = spSkeletonJson_create(atlas_);
268     if (!skeletonJson)
269     {
270         URHO3D_LOGERROR("Create skeleton Json failed");
271         return false;
272     }
273 
274     skeletonJson->scale = 0.01f; // PIXEL_SIZE;
275     skeletonData_ = spSkeletonJson_readSkeletonData(skeletonJson, &jsonData_[0]);
276 
277     spSkeletonJson_dispose(skeletonJson);
278     jsonData_.Reset();
279 
280     currentAnimationSet = 0;
281 
282     return true;
283 }
284 #endif
285 
BeginLoadSpriter(Deserializer & source)286 bool AnimationSet2D::BeginLoadSpriter(Deserializer& source)
287 {
288     unsigned dataSize = source.GetSize();
289     if (!dataSize && !source.GetName().Empty())
290     {
291         URHO3D_LOGERROR("Zero sized XML data in " + source.GetName());
292         return false;
293     }
294 
295     SharedArrayPtr<char> buffer(new char[dataSize]);
296     if (source.Read(buffer.Get(), dataSize) != dataSize)
297         return false;
298 
299     spriterData_ = new Spriter::SpriterData();
300     if (!spriterData_->Load(buffer.Get(), dataSize))
301     {
302         URHO3D_LOGERROR("Could not spriter data from " + source.GetName());
303         return false;
304     }
305 
306     // Check has sprite sheet
307     String parentPath = GetParentPath(GetName());
308     ResourceCache* cache = GetSubsystem<ResourceCache>();
309 
310     spriteSheetFilePath_ = parentPath + GetFileName(GetName()) + ".xml";
311     hasSpriteSheet_ = cache->Exists(spriteSheetFilePath_);
312     if (!hasSpriteSheet_)
313     {
314         spriteSheetFilePath_ = parentPath + GetFileName(GetName()) + ".plist";
315         hasSpriteSheet_ = cache->Exists(spriteSheetFilePath_);
316     }
317 
318     if (GetAsyncLoadState() == ASYNC_LOADING)
319     {
320         if (hasSpriteSheet_)
321             cache->BackgroundLoadResource<SpriteSheet2D>(spriteSheetFilePath_, true, this);
322         else
323         {
324             for (unsigned i = 0; i < spriterData_->folders_.Size(); ++i)
325             {
326                 Spriter::Folder* folder = spriterData_->folders_[i];
327                 for (unsigned j = 0; j < folder->files_.Size(); ++j)
328                 {
329                     Spriter::File* file = folder->files_[j];
330                     String imagePath = parentPath + file->name_;
331                     cache->BackgroundLoadResource<Image>(imagePath, true, this);
332                 }
333             }
334         }
335     }
336 
337     // Note: this probably does not reflect internal data structure size accurately
338     SetMemoryUse(dataSize);
339 
340     return true;
341 }
342 
343 struct SpriteInfo
344 {
345     int x;
346     int y;
347     Spriter::File* file_;
348     SharedPtr<Image> image_;
349 };
350 
EndLoadSpriter()351 bool AnimationSet2D::EndLoadSpriter()
352 {
353     if (!spriterData_)
354         return false;
355 
356     ResourceCache* cache = GetSubsystem<ResourceCache>();
357     if (hasSpriteSheet_)
358     {
359         spriteSheet_ = cache->GetResource<SpriteSheet2D>(spriteSheetFilePath_);
360         if (!spriteSheet_)
361             return false;
362 
363         for (unsigned i = 0; i < spriterData_->folders_.Size(); ++i)
364         {
365             Spriter::Folder* folder = spriterData_->folders_[i];
366             for (unsigned j = 0; j < folder->files_.Size(); ++j)
367             {
368                 Spriter::File* file = folder->files_[j];
369                 SharedPtr<Sprite2D> sprite(spriteSheet_->GetSprite(GetFileName(file->name_)));
370                 if (!sprite)
371                 {
372                     URHO3D_LOGERROR("Could not load sprite " + file->name_);
373                     return false;
374                 }
375 
376                 Vector2 hotSpot(file->pivotX_, file->pivotY_);
377 
378                 // If sprite is trimmed, recalculate hot spot
379                 const IntVector2& offset = sprite->GetOffset();
380                 if (offset != IntVector2::ZERO)
381                 {
382                     float pivotX = file->width_ * hotSpot.x_;
383                     float pivotY = file->height_ * (1.0f - hotSpot.y_);
384 
385                     const IntRect& rectangle = sprite->GetRectangle();
386                     hotSpot.x_ = (offset.x_ + pivotX) / rectangle.Width();
387                     hotSpot.y_ = 1.0f - (offset.y_ + pivotY) / rectangle.Height();
388                 }
389 
390                 sprite->SetHotSpot(hotSpot);
391 
392                 if (!sprite_)
393                     sprite_ = sprite;
394 
395                 int key = (folder->id_ << 16) + file->id_;
396                 spriterFileSprites_[key] = sprite;
397             }
398         }
399     }
400     else
401     {
402         Vector<SpriteInfo> spriteInfos;
403         String parentPath = GetParentPath(GetName());
404 
405         for (unsigned i = 0; i < spriterData_->folders_.Size(); ++i)
406         {
407             Spriter::Folder* folder = spriterData_->folders_[i];
408             for (unsigned j = 0; j < folder->files_.Size(); ++j)
409             {
410                 Spriter::File* file = folder->files_[j];
411                 String imagePath = parentPath + file->name_;
412                 SharedPtr<Image> image(cache->GetResource<Image>(imagePath));
413                 if (!image)
414                 {
415                     URHO3D_LOGERROR("Could not load image");
416                     return false;
417                 }
418                 if (image->IsCompressed())
419                 {
420                     URHO3D_LOGERROR("Compressed image is not support");
421                     return false;
422                 }
423                 if (image->GetComponents() != 4)
424                 {
425                     URHO3D_LOGERROR("Only support image with 4 components");
426                     return false;
427                 }
428 
429                 SpriteInfo def;
430                 def.x = 0;
431                 def.y = 0;
432                 def.file_ = file;
433                 def.image_ = image;
434                 spriteInfos.Push(def);
435             }
436         }
437 
438         if (spriteInfos.Empty())
439             return false;
440 
441         if (spriteInfos.Size() > 1)
442         {
443             AreaAllocator allocator(128, 128, 2048, 2048);
444             for (unsigned i = 0; i < spriteInfos.Size(); ++i)
445             {
446                 SpriteInfo& info = spriteInfos[i];
447                 Image* image = info.image_;
448                 if (!allocator.Allocate(image->GetWidth() + 1, image->GetHeight() + 1, info.x, info.y))
449                 {
450                     URHO3D_LOGERROR("Could not allocate area");
451                     return false;
452                 }
453             }
454 
455             SharedPtr<Texture2D> texture(new Texture2D(context_));
456             texture->SetMipsToSkip(QUALITY_LOW, 0);
457             texture->SetNumLevels(1);
458             texture->SetSize(allocator.GetWidth(), allocator.GetHeight(), Graphics::GetRGBAFormat());
459 
460             unsigned textureDataSize = allocator.GetWidth() * allocator.GetHeight() * 4;
461             SharedArrayPtr<unsigned char> textureData(new unsigned char[textureDataSize]);
462             memset(textureData.Get(), 0, textureDataSize);
463 
464             sprite_ = new Sprite2D(context_);
465             sprite_->SetTexture(texture);
466 
467             for (unsigned i = 0; i < spriteInfos.Size(); ++i)
468             {
469                 SpriteInfo& info = spriteInfos[i];
470                 Image* image = info.image_;
471 
472                 for (int y = 0; y < image->GetHeight(); ++y)
473                 {
474                     memcpy(textureData.Get() + ((info.y + y) * allocator.GetWidth() + info.x) * 4,
475                         image->GetData() + y * image->GetWidth() * 4, image->GetWidth() * 4);
476                 }
477 
478                 SharedPtr<Sprite2D> sprite(new Sprite2D(context_));
479                 sprite->SetTexture(texture);
480                 sprite->SetRectangle(IntRect(info.x, info.y, info.x + image->GetWidth(), info.y + image->GetHeight()));
481                 sprite->SetHotSpot(Vector2(info.file_->pivotX_, info.file_->pivotY_));
482 
483                 int key = (info.file_->folder_->id_ << 16) + info.file_->id_;
484                 spriterFileSprites_[key] = sprite;
485             }
486 
487             texture->SetData(0, 0, 0, allocator.GetWidth(), allocator.GetHeight(), textureData.Get());
488         }
489         else
490         {
491             SharedPtr<Texture2D> texture(new Texture2D(context_));
492             texture->SetMipsToSkip(QUALITY_LOW, 0);
493             texture->SetNumLevels(1);
494 
495             SpriteInfo& info = spriteInfos[0];
496             texture->SetData(info.image_, true);
497 
498             sprite_ = new Sprite2D(context_);
499             sprite_->SetTexture(texture);
500             sprite_->SetRectangle(IntRect(info.x, info.y, info.x + info.image_->GetWidth(), info.y + info.image_->GetHeight()));
501             sprite_->SetHotSpot(Vector2(info.file_->pivotX_, info.file_->pivotY_));
502 
503             int key = (info.file_->folder_->id_ << 16) + info.file_->id_;
504             spriterFileSprites_[key] = sprite_;
505         }
506     }
507 
508     return true;
509 }
510 
Dispose()511 void AnimationSet2D::Dispose()
512 {
513 #ifdef URHO3D_SPINE
514     if (skeletonData_)
515     {
516         spSkeletonData_dispose(skeletonData_);
517         skeletonData_ = 0;
518     }
519 
520     if (atlas_)
521     {
522         spAtlas_dispose(atlas_);
523         atlas_ = 0;
524     }
525 #endif
526 
527     spriterData_.Reset();
528 
529     sprite_.Reset();
530     spriteSheet_.Reset();
531     spriterFileSprites_.Clear();
532 }
533 
534 }
535