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