Browse Source

Merge from spine-spriter branch.
Main changes:
Make spriter as a lib
Enable spine support in AnimatedSprite2D and Animation2D.

aster2013 10 years ago
parent
commit
0cded698e4

+ 5 - 0
CMake/Modules/Urho3D-CMake-common.cmake

@@ -81,6 +81,7 @@ cmake_dependent_option (URHO3D_NETWORK "Enable networking support" TRUE "NOT EMS
 cmake_dependent_option (URHO3D_DATABASE_ODBC "Enable Database support with ODBC, requires vendor-specific ODBC driver" FALSE "NOT IOS AND NOT ANDROID AND NOT EMSCRIPTEN" FALSE)
 cmake_dependent_option (URHO3D_DATABASE_ODBC "Enable Database support with ODBC, requires vendor-specific ODBC driver" FALSE "NOT IOS AND NOT ANDROID AND NOT EMSCRIPTEN" FALSE)
 option (URHO3D_PHYSICS "Enable physics support" TRUE)
 option (URHO3D_PHYSICS "Enable physics support" TRUE)
 option (URHO3D_URHO2D "Enable 2D graphics and physics support" TRUE)
 option (URHO3D_URHO2D "Enable 2D graphics and physics support" TRUE)
+option (URHO3D_SPINE "Enable spine support" FALSE)
 if (MINGW AND NOT DEFINED URHO3D_SSE)
 if (MINGW AND NOT DEFINED URHO3D_SSE)
     # Certain MinGW versions fail to compile SSE code. This is the initial guess for known "bad" version range, and can be tightened later
     # Certain MinGW versions fail to compile SSE code. This is the initial guess for known "bad" version range, and can be tightened later
     execute_process (COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION ERROR_QUIET)
     execute_process (COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION ERROR_QUIET)
@@ -364,6 +365,10 @@ endif ()
 # Add definition for Urho2D
 # Add definition for Urho2D
 if (URHO3D_URHO2D)
 if (URHO3D_URHO2D)
     add_definitions (-DURHO3D_URHO2D)
     add_definitions (-DURHO3D_URHO2D)
+    # Add definition for Spine
+    if (URHO3D_SPINE)
+        add_definitions (-DURHO3D_SPINE)
+    endif ()
 endif ()
 endif ()
 
 
 # Add definition for Database
 # Add definition for Database

+ 4 - 0
Source/CMakeLists.txt

@@ -85,6 +85,10 @@ endif ()
 
 
 if (URHO3D_URHO2D)
 if (URHO3D_URHO2D)
     add_subdirectory (ThirdParty/Box2D)
     add_subdirectory (ThirdParty/Box2D)
+    if (URHO3D_SPINE)
+    	add_subdirectory (ThirdParty/Spine)
+    endif ()
+    add_subdirectory (ThirdParty/Spriter)
 endif ()
 endif ()
 
 
 if (URHO3D_PHYSICS)
 if (URHO3D_PHYSICS)

+ 10 - 21
Source/Samples/33_Urho2DSpriterAnimation/Urho2DSpriterAnimation.cpp

@@ -39,22 +39,11 @@
 
 
 #include <Urho3D/DebugNew.h>
 #include <Urho3D/DebugNew.h>
 
 
-static const char* animationNames[] =
-{
-    "idle",
-    "run",
-    "attack",
-    "hit",
-    "dead",
-    "dead2",
-    "dead3",
-};
-
 DEFINE_APPLICATION_MAIN(Urho2DSpriterAnimation)
 DEFINE_APPLICATION_MAIN(Urho2DSpriterAnimation)
 
 
 Urho2DSpriterAnimation::Urho2DSpriterAnimation(Context* context) :
 Urho2DSpriterAnimation::Urho2DSpriterAnimation(Context* context) :
     Sample(context),
     Sample(context),
-    animationIndex_(0)
+    spriterAnimationIndex_(0)
 {
 {
 }
 }
 
 
@@ -94,14 +83,13 @@ void Urho2DSpriterAnimation::CreateScene()
     camera->SetZoom(1.5f * Min((float)graphics->GetWidth() / 1280.0f, (float)graphics->GetHeight() / 800.0f)); // Set zoom according to user's resolution to ensure full visibility (initial zoom (1.5) is set for full visibility at 1280x800 resolution)
     camera->SetZoom(1.5f * Min((float)graphics->GetWidth() / 1280.0f, (float)graphics->GetHeight() / 800.0f)); // Set zoom according to user's resolution to ensure full visibility (initial zoom (1.5) is set for full visibility at 1280x800 resolution)
 
 
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     ResourceCache* cache = GetSubsystem<ResourceCache>();
-    AnimationSet2D* animationSet = cache->GetResource<AnimationSet2D>("Urho2D/imp/imp.scml");
-    if (!animationSet)
+    AnimationSet2D* spriterAnimationSet = cache->GetResource<AnimationSet2D>("Urho2D/imp/imp.scml");
+    if (!spriterAnimationSet)
         return;
         return;
 
 
-    spriteNode_ = scene_->CreateChild("SpriterAnimation");
-
-    AnimatedSprite2D* animatedSprite = spriteNode_->CreateComponent<AnimatedSprite2D>();
-    animatedSprite->SetAnimation(animationSet, animationNames[animationIndex_]);
+    spriterNode_ = scene_->CreateChild("SpriterAnimation");
+    AnimatedSprite2D* spriterAnimatedSprite = spriterNode_->CreateComponent<AnimatedSprite2D>();
+    spriterAnimatedSprite->SetAnimation(spriterAnimationSet, spriterAnimationSet->GetAnimation(spriterAnimationIndex_));
 }
 }
 
 
 void Urho2DSpriterAnimation::CreateInstructions()
 void Urho2DSpriterAnimation::CreateInstructions()
@@ -188,7 +176,8 @@ void Urho2DSpriterAnimation::HandleUpdate(StringHash eventType, VariantMap& even
 
 
 void Urho2DSpriterAnimation::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
 void Urho2DSpriterAnimation::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
 {
 {
-    AnimatedSprite2D* animatedSprite = spriteNode_->GetComponent<AnimatedSprite2D>();
-    animationIndex_ = (animationIndex_ + 1) % 7;
-    animatedSprite->SetAnimation(animationNames[animationIndex_], LM_FORCE_LOOPED);
+    AnimatedSprite2D* spriterAnimatedSprite = spriterNode_->GetComponent<AnimatedSprite2D>();
+    AnimationSet2D* spriterAnimationSet = spriterAnimatedSprite->GetAnimationSet();
+    spriterAnimationIndex_ = (spriterAnimationIndex_ + 1) % spriterAnimationSet->GetNumAnimations();
+    spriterAnimatedSprite->SetAnimation(spriterAnimationSet->GetAnimation(spriterAnimationIndex_), LM_FORCE_LOOPED);
 }
 }

+ 2 - 2
Source/Samples/33_Urho2DSpriterAnimation/Urho2DSpriterAnimation.h

@@ -86,7 +86,7 @@ private:
     void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData);
     void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData);
 
 
     /// Sprite nodes.
     /// Sprite nodes.
-    SharedPtr<Node> spriteNode_;
+    SharedPtr<Node> spriterNode_;
     /// Animation index.
     /// Animation index.
-    int animationIndex_;
+    int spriterAnimationIndex_;
 };
 };

+ 36 - 0
Source/ThirdParty/Spriter/CMakeLists.txt

@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2008-2015 the Urho3D project.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+# Define target name
+set (TARGET_NAME Spriter)
+
+# Define source files
+define_source_files (GLOB_CPP_PATTERNS src/*.cpp GLOB_H_PATTERNS src/*.h)
+
+# Define dependency libs
+set (INCLUDE_DIRS src ../PugiXml/src)
+
+# Setup target
+setup_library ()
+
+# Install headers for building the Urho3D library
+install_header_files (DIRECTORY src/ DESTINATION ${DEST_INCLUDE_DIR}/ThirdParty/Spriter FILES_MATCHING PATTERN *.h BUILD_TREE_ONLY)  # Note: the trailing slash is significant

+ 745 - 0
Source/ThirdParty/Spriter/src/SpriterData.cpp

@@ -0,0 +1,745 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "SpriterData.h"
+#include "pugixml.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+
+using namespace pugi;
+
+namespace Spriter
+{
+    SpriterData::SpriterData()
+    {
+    }
+
+    SpriterData::~SpriterData()
+    {
+        Reset();
+    }
+
+    void SpriterData::Reset()
+    {
+        if (!folders_.empty())
+        {
+            for (size_t i = 0; i < folders_.size(); ++i)
+                delete folders_[i];
+            folders_.clear();
+        }
+
+        if (!entities_.empty())
+        {
+            for (size_t i = 0; i < entities_.size(); ++i)
+                delete entities_[i];
+            entities_.clear();
+        }
+    }
+
+    bool SpriterData::Load(const pugi::xml_node& node)
+    {
+        Reset();
+
+        if (strcmp(node.name(), "spriter_data"))
+            return false;
+
+        scmlVersion_ = node.attribute("scml_version").as_int();
+        generator_ = node.attribute("generator").as_string();
+        generatorVersion_ = node.attribute("scml_version").as_string();
+
+        for (xml_node folderNode = node.child("folder"); !folderNode.empty(); folderNode = folderNode.next_sibling("folder"))
+        {
+            folders_.push_back(new  Folder());
+            if (!folders_.back()->Load(folderNode))
+                return false;
+        }
+
+        for (xml_node entityNode = node.child("entity"); !entityNode.empty(); entityNode = entityNode.next_sibling("entity"))
+        {
+            entities_.push_back(new  Entity());
+            if (!entities_.back()->Load(entityNode))
+                return false;
+        }
+
+        return true;
+    }
+
+    bool SpriterData::Load(const void* data, size_t size)
+    {
+        xml_document document;
+        if (!document.load_buffer(data, size))
+            return false;
+
+        return Load(document.child("spriter_data"));
+    }
+
+    Folder::Folder()
+    {
+
+    }
+
+    Folder::~Folder()
+    {
+        Reset();
+    }
+
+    void Folder::Reset()
+    {
+        for (size_t i = 0; i < files_.size(); ++i)
+            delete files_[i];
+        files_.clear();
+    }
+
+    bool Folder::Load(const pugi::xml_node& node)
+    {
+        Reset();
+
+        if (strcmp(node.name(), "folder"))
+            return false;
+
+        id_ = node.attribute("id").as_int();
+        name_ = node.attribute("name").as_string();
+
+        for (xml_node fileNode = node.child("file"); !fileNode.empty(); fileNode = fileNode.next_sibling("file"))
+        {
+            files_.push_back(new  File(this));
+            if (!files_.back()->Load(fileNode))
+                return false;
+        }
+
+        return true;
+    }
+
+    File::File(Folder* folder) : 
+        folder_(folder)
+    {
+    }
+
+    File::~File()
+    {
+    }
+
+    bool File::Load(const pugi::xml_node& node)
+    {
+        if (strcmp(node.name(), "file"))
+            return false;
+
+        id_ = node.attribute("id").as_int();
+        name_ = node.attribute("name").as_string();
+        width_ = node.attribute("width").as_float();
+        height_ = node.attribute("height").as_float();
+        pivotX_ = node.attribute("pivot_x").as_float(0.0f);
+        pivotY_ = node.attribute("pivot_y").as_float(1.0f);
+
+        return true;
+    }
+
+    Entity::Entity()
+    {
+
+    }
+
+    Entity::~Entity()
+    {
+        Reset();
+    }
+
+    void Entity::Reset()
+    {
+        for (size_t i = 0; i < characterMaps_.size(); ++i)
+            delete characterMaps_[i];
+        characterMaps_.clear();
+
+        for (size_t i = 0; i < animations_.size(); ++i)
+            delete animations_[i];
+        animations_.clear();
+    }
+
+    bool Entity::Load(const pugi::xml_node& node)
+    {
+        Reset();
+
+        if (strcmp(node.name(), "entity"))
+            return false;
+
+        id_ = node.attribute("id").as_int();
+        name_ = node.attribute("name").as_string();
+
+        for (xml_node characterMapNode = node.child("character_map"); !characterMapNode.empty(); characterMapNode = characterMapNode.next_sibling("character_map"))
+        {
+            characterMaps_.push_back(new CharacterMap());
+            if (!characterMaps_.back()->Load(characterMapNode))
+                return false;
+        }
+
+        for (xml_node animationNode = node.child("animation"); !animationNode.empty(); animationNode = animationNode.next_sibling("animation"))
+        {
+            animations_.push_back(new  Animation());
+            if (!animations_.back()->Load(animationNode))
+                return false;
+        }
+
+        return true;
+    }
+
+    CharacterMap::CharacterMap()
+    {
+
+    }
+
+    CharacterMap::~CharacterMap()
+    {
+
+    }
+
+    void CharacterMap::Reset()
+    {
+        for (size_t i = 0; i < maps_.size(); ++i)
+            delete maps_[i];
+        maps_.clear();
+    }
+
+    bool CharacterMap::Load(const pugi::xml_node& node)
+    {
+        Reset();
+
+        if (strcmp(node.name(), "character_map"))
+            return false;
+
+        id_ = node.attribute("id").as_int();
+        name_ = node.attribute("name").as_string();
+
+        for (xml_node mapNode = node.child("map"); !mapNode.empty(); mapNode = mapNode.next_sibling("map"))
+        {
+            maps_.push_back(new MapInstruction());
+            if (!maps_.back()->Load(mapNode))
+                return false;
+        }
+
+        return false;
+    }
+
+    MapInstruction::MapInstruction()
+    {
+
+    }
+
+    MapInstruction::~MapInstruction()
+    {
+
+    }
+
+    bool MapInstruction::Load(const pugi::xml_node& node)
+    {
+        if (strcmp(node.name(), "map"))
+            return false;
+
+        folder_ = node.attribute("folder").as_int();
+        file_ = node.attribute("file").as_int();
+        targetFolder_ = node.attribute("target_folder").as_int(-1);
+        targetFile_ = node.attribute("target_file").as_int(-1);
+
+        return true;
+    }
+
+    Animation::Animation()
+    {
+
+    }
+
+    Animation::~Animation()
+    {
+        Reset();
+    }
+
+    void Animation::Reset()
+    {
+        if (!mainlineKeys_.empty())
+        {
+            for (size_t i = 0; i < mainlineKeys_.size(); ++i)
+                delete mainlineKeys_[i];
+            mainlineKeys_.clear();
+        }
+
+        for (size_t i = 0; i < timelines_.size(); ++i)
+            delete timelines_[i];
+        timelines_.clear();
+    }
+
+    bool Animation::Load(const pugi::xml_node& node)
+    {
+        Reset();
+
+        if (strcmp(node.name(), "animation"))
+            return false;
+
+        id_ = node.attribute("id").as_int();
+        name_ = node.attribute("name").as_string();
+        length_ = node.attribute("length").as_float() * 0.001f;
+        looping_ = node.attribute("looping").as_bool(true);
+
+        xml_node mainlineNode = node.child("mainline");
+        for (xml_node keyNode = mainlineNode.child("key"); !keyNode.empty(); keyNode = keyNode.next_sibling("key"))
+        {
+            mainlineKeys_.push_back(new MainlineKey());
+            if (!mainlineKeys_.back()->Load(keyNode))
+                return false;
+        }
+
+        for (xml_node timelineNode = node.child("timeline"); !timelineNode.empty(); timelineNode = timelineNode.next_sibling("timeline"))
+        {
+            timelines_.push_back(new Timeline());
+            if (!timelines_.back()->Load(timelineNode))
+                return false;
+        }
+
+        return true;
+    }
+
+    MainlineKey::MainlineKey()
+    {
+
+    }
+
+    MainlineKey::~MainlineKey()
+    {
+        Reset();
+    }
+
+    void MainlineKey::Reset()
+    {
+        for (size_t i = 0; i < boneRefs_.size(); ++i)
+            delete boneRefs_[i];
+        boneRefs_.clear();
+
+        for (size_t i = 0; i < objectRefs_.size(); ++i)
+            delete objectRefs_[i];
+        objectRefs_.clear();
+    }
+
+    bool MainlineKey::Load(const pugi::xml_node& node)
+    {
+        id_ = node.attribute("id").as_int();
+        time_ = node.attribute("time").as_float() * 0.001f;
+
+        for (xml_node boneRefNode = node.child("bone_ref"); !boneRefNode.empty(); boneRefNode = boneRefNode.next_sibling("bone_ref"))
+        {
+            boneRefs_.push_back(new Ref());
+            if (!boneRefs_.back()->Load(boneRefNode))
+                return false;
+        }
+
+        for (xml_node objectRefNode = node.child("object_ref"); !objectRefNode.empty(); objectRefNode = objectRefNode.next_sibling("object_ref"))
+        {
+            objectRefs_.push_back(new Ref());
+            if (!objectRefs_.back()->Load(objectRefNode))
+                return false;
+        }
+
+        return true;
+    }
+
+    Ref::Ref()
+    {
+
+    }
+
+    Ref::~Ref()
+    {
+    }
+
+    bool Ref::Load(const pugi::xml_node& node)
+    {
+        if (strcmp(node.name(), "bone_ref") && strcmp(node.name(), "object_ref"))
+            return false;
+
+        id_ = node.attribute("id").as_int();
+        parent_ = node.attribute("parent").as_int(-1);
+        timeline_ = node.attribute("timeline").as_int();
+        key_ = node.attribute("key").as_int();
+        zIndex_ = node.attribute("z_index").as_int();
+
+        return true;
+    }
+
+    Timeline::Timeline()
+    {
+
+    }
+
+    Timeline::~Timeline()
+    {
+        Reset();
+    }
+
+    void Timeline::Reset()
+    {
+        for (size_t i = 0; i < keys_.size(); ++i)
+            delete keys_[i];
+        keys_.clear();
+    }
+
+    bool Timeline::Load(const pugi::xml_node& node)
+    {
+        Reset();
+
+        if (strcmp(node.name(), "timeline"))
+            return false;
+
+        id_ = node.attribute("id").as_int();
+        name_ = node.attribute("name").as_string();
+
+        std::string typeString = node.attribute("type").as_string("sprite");
+        if (typeString == "bone")
+        {
+            objectType_ = BONE;
+            for (xml_node keyNode = node.child("key"); !keyNode.empty(); keyNode = keyNode.next_sibling("key"))
+            {
+                keys_.push_back(new BoneTimelineKey(this));
+                if (!keys_.back()->Load(keyNode))
+                    return false;
+            }
+        }
+        else if (typeString == "sprite")
+        {
+            objectType_ = SPRITE;
+            for (xml_node keyNode = node.child("key"); !keyNode.empty(); keyNode = keyNode.next_sibling("key"))
+            {
+                keys_.push_back(new SpriteTimelineKey(this));
+                if (!keys_.back()->Load(keyNode))
+                    return false;
+            }
+        }
+        else
+        {
+            // Unsupported object type now.
+            return false;
+        }
+
+        return true;
+    }
+
+    TimelineKey::TimelineKey(Timeline* timeline)
+    {
+        this->timeline_ = timeline;
+    }
+
+    TimelineKey::~TimelineKey()
+    {
+    }
+
+    bool TimelineKey::Load(const pugi::xml_node& node)
+    {
+        if (strcmp(node.name(), "key"))
+            return false;
+
+        id_ = node.attribute("id").as_int();
+        time_ = node.attribute("time").as_float() * 0.001f;
+
+        /*
+        std::string curveTypeString = node.attribute("curve_type").as_string("linear");
+        if (curveTypeString == "linear")
+            curveType = LINEAR;
+        else if (curveTypeString == "quadratic")
+            curveType = QUADRATIC;
+        else if (curveTypeString == "cubic")
+            curveType = CUBIC;
+        */
+        // Force linear
+        curveType_ = LINEAR;
+
+        c1_ = node.attribute("c1").as_float();
+        c2_ = node.attribute("c2").as_float();
+        c3_ = node.attribute("c2").as_float();
+        c4_ = node.attribute("c2").as_float();
+
+        return true;
+    }
+
+    TimelineKey& TimelineKey::operator=(const TimelineKey& rhs)
+    {
+        id_ = rhs.id_;
+        time_ = rhs.time_;
+        curveType_ = rhs.curveType_;
+        c1_ = rhs.c1_;
+        c2_ = rhs.c2_;
+        c3_ = rhs.c3_;
+        c4_ = rhs.c4_;
+        return *this;
+    }
+
+    void TimelineKey::Interpolate(const TimelineKey& other, float t)
+    {
+
+    }
+
+    float toRadians(float deg)
+    {
+        const float PI = 3.141592653589793f;
+        return deg * PI / 180.0f;
+    }
+
+    float under360(float rotation)
+    {
+        while (rotation > 360.0f)
+        {
+            rotation -= 360.0f;
+        }
+
+        while (rotation < 0.0f)
+        {
+            rotation += 360.0f;
+        }
+
+        return rotation;
+    }
+
+
+    SpatialInfo::SpatialInfo(float x, float y, float angle, float scale_x, float scale_y, float a, int spin)
+    {
+        this->x_ = x; 
+        this->y_ = y; 
+        this->angle_ = angle;
+        this->scaleX_ = scale_x; 
+        this->scaleY_ = scale_y; 
+        this->alpha_ = a;
+        this->spin = spin;
+    }
+
+    SpatialInfo SpatialInfo::UnmapFromParent(const SpatialInfo& parentInfo) const
+    {
+        float unmappedX;
+        float unmappedY;
+        float unmappedAngle = angle_ + parentInfo.angle_;
+        float unmappedScaleX = scaleX_ * parentInfo.scaleX_;
+        float unmappedScaleY = scaleY_ * parentInfo.scaleY_;
+        float unmappedAlpha = alpha_ * parentInfo.alpha_;
+
+        if (x_ != 0.0f || y_ != 0.0f)
+        {
+            float preMultX = x_ * parentInfo.scaleX_;
+            float preMultY = y_ * parentInfo.scaleY_;
+            
+            float parentRad = toRadians(under360(parentInfo.angle_));
+
+            float s = sinf(parentRad);
+            float c = cosf(parentRad);
+
+            unmappedX = (preMultX * c) - (preMultY * s) + parentInfo.x_;
+            unmappedY = (preMultX * s) + (preMultY * c) + parentInfo.y_;
+        }
+        else
+        {
+            unmappedX = parentInfo.x_;
+            unmappedY = parentInfo.y_;
+        }
+
+        return SpatialInfo(unmappedX, unmappedY, unmappedAngle, unmappedScaleX, unmappedScaleY, unmappedAlpha, spin);
+    }
+
+    inline float linear(float a, float b, float t)
+    {
+        return a + (b - a) * t;
+    }
+
+    void SpatialInfo::Interpolate(const SpatialInfo& other, float t)
+    {
+        x_ = linear(x_, other.x_, t);
+        y_ = linear(y_, other.y_, t);
+
+        if (spin > 0.0f && (other.angle_ - angle_ < 0.0f))
+        {
+            angle_ = linear(angle_, other.angle_ + 360.0f, t);
+        }
+        else if (spin < 0.0f && (other.angle_ - angle_ > 0.0f))
+        {
+            angle_ = linear(angle_, other.angle_ - 360.0f, t);
+        }
+        else
+        {
+            angle_ = linear(angle_, other.angle_, t);
+        }
+
+        scaleX_ = linear(scaleX_, other.scaleX_, t);
+        scaleY_ = linear(scaleY_, other.scaleY_, t);
+        alpha_ = linear(alpha_, other.alpha_, t);
+    }
+
+    SpatialTimelineKey::SpatialTimelineKey(Timeline* timeline) : 
+        TimelineKey(timeline)
+    {
+
+    }
+
+    SpatialTimelineKey::~SpatialTimelineKey()
+    {
+
+    }
+
+    bool SpatialTimelineKey::Load(const xml_node& node)
+    {
+        if (!TimelineKey::Load(node))
+            return false;
+
+        xml_node childNode = node.child("bone");
+        if (childNode.empty())
+            childNode = node.child("object");
+
+        info_.x_ = childNode.attribute("x").as_float();
+        info_.y_ = childNode.attribute("y").as_float();
+        info_.angle_ = childNode.attribute("angle").as_float();
+        info_.scaleX_ = childNode.attribute("scale_x").as_float(1.0f);
+        info_.scaleY_ = childNode.attribute("scale_y").as_float(1.0f);
+        info_.alpha_ = childNode.attribute("a").as_float(1.0f);
+
+        info_.spin = node.attribute("spin").as_int(1);
+
+        return true;
+    }
+
+    SpatialTimelineKey& SpatialTimelineKey::operator=(const SpatialTimelineKey& rhs)
+    {
+        TimelineKey::operator=(rhs);
+        info_ = rhs.info_;
+        return *this;
+    }   
+
+    void SpatialTimelineKey::Interpolate(const TimelineKey& other, float t)
+    {
+        const SpatialTimelineKey& o = (const SpatialTimelineKey&)other;
+        info_.Interpolate(o.info_, t);
+    }
+
+    BoneTimelineKey::BoneTimelineKey(Timeline* timeline) : 
+        SpatialTimelineKey(timeline)
+    {
+
+    }
+
+    BoneTimelineKey::~BoneTimelineKey()
+    {
+
+    }
+
+    TimelineKey* BoneTimelineKey::Clone() const
+    {
+        BoneTimelineKey* result = new BoneTimelineKey(timeline_);
+        *result = *this;
+        return result;
+    }
+
+    bool BoneTimelineKey::Load(const xml_node& node)
+    {
+        if (!SpatialTimelineKey::Load(node))
+            return false;
+
+        xml_node boneNode = node.child("bone");
+        length_ = boneNode.attribute("length").as_float(200.0f);
+        width_ = boneNode.attribute("width").as_float(10.0f);
+
+        return true;
+    }
+
+    BoneTimelineKey& BoneTimelineKey::operator=(const BoneTimelineKey& rhs)
+    {
+        SpatialTimelineKey::operator=(rhs);
+        length_ = rhs.length_;
+        width_ = rhs.width_;
+
+        return *this;
+    }
+
+    void BoneTimelineKey::Interpolate(const TimelineKey& other, float t)
+    {
+        SpatialTimelineKey::Interpolate(other, t);
+
+        const BoneTimelineKey& o = (const BoneTimelineKey&)other;
+        length_ = linear(length_, o.length_, t);
+        width_ = linear(width_, o.width_, t);
+    }
+
+    TimelineKey* SpriteTimelineKey::Clone() const
+    {
+        SpriteTimelineKey* result = new SpriteTimelineKey(timeline_);
+        *result = *this;
+        return result;
+    }
+
+    SpriteTimelineKey::SpriteTimelineKey(Timeline* timeline) : 
+        SpatialTimelineKey(timeline)
+    {
+    }
+
+    SpriteTimelineKey::~SpriteTimelineKey()
+    {
+
+    }
+
+    bool SpriteTimelineKey::Load(const pugi::xml_node& node)
+    {
+        if (!SpatialTimelineKey::Load(node))
+            return false;
+
+        xml_node objectNode = node.child("object");
+        folderId_ = objectNode.attribute("folder").as_int(-1);
+        fileId_ = objectNode.attribute("file").as_int(-1);
+
+        xml_attribute pivotXAttr = objectNode.attribute("pivot_x");
+        xml_attribute pivotYAttr = objectNode.attribute("pivot_y");
+        if (pivotXAttr.empty() && pivotYAttr.empty())
+            useDefaultPivot_ = true;
+        else
+        {
+            useDefaultPivot_ = false;
+            pivotX_ = pivotXAttr.as_float(0.0f);
+            pivotY_ = pivotYAttr.as_float(1.0f);
+        }
+
+        return true;
+    }
+
+    void SpriteTimelineKey::Interpolate(const TimelineKey& other, float t)
+    {
+        SpatialTimelineKey::Interpolate(other, t);
+        
+        const SpriteTimelineKey& o = (const SpriteTimelineKey&)other;
+        pivotX_ = linear(pivotX_, o.pivotX_, t);
+        pivotY_ = linear(pivotY_, o.pivotY_, t);
+    }
+
+    SpriteTimelineKey& SpriteTimelineKey::operator=(const SpriteTimelineKey& rhs)
+    {
+        SpatialTimelineKey::operator=(rhs);
+        
+        folderId_ = rhs.folderId_;
+        fileId_ = rhs.fileId_;
+        useDefaultPivot_ = rhs.useDefaultPivot_;
+        pivotX_ = rhs.pivotX_;
+        pivotY_ = rhs.pivotY_;
+
+        return *this;
+    }
+
+}

+ 308 - 0
Source/ThirdParty/Spriter/src/SpriterData.h

@@ -0,0 +1,308 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+namespace pugi
+{
+    class xml_node;
+}
+
+namespace Spriter
+{
+    struct Animation;
+    struct BoneTimelineKey;
+    struct CharacterMap;
+    struct Entity;
+    struct File;
+    struct Folder;
+    struct MainlineKey;
+    struct MapInstruction;
+    struct Ref;
+    struct SpatialInfo;
+    struct SpatialTimelineKey;
+    struct SpriterData;
+    struct SpriteTimelineKey;
+    struct Timeline;
+    struct TimelineKey;
+
+    /// Spriter data.
+    struct SpriterData
+    {
+        SpriterData();
+        ~SpriterData();
+
+        void Reset();
+        bool Load(const pugi::xml_node& node);
+        bool Load(const void* data, size_t size);
+
+        int scmlVersion_;
+        std::string generator_;
+        std::string generatorVersion_;
+        std::vector<Folder*> folders_;
+        std::vector<Entity*> entities_;
+    };
+
+    /// Folder.
+    struct Folder
+    {
+        Folder();
+        ~Folder();
+
+        void Reset();
+        bool Load(const pugi::xml_node& node);
+
+        int id_;
+        std::string name_;
+        std::vector<File*> files_;
+    };
+
+    /// File.
+    struct File
+    {
+        File(Folder* folder);
+        ~File();
+
+        bool Load(const pugi::xml_node& node);
+
+        Folder* folder_;
+        int id_;
+        std::string name_;
+        float width_;
+        float height_;
+        float pivotX_;
+        float pivotY_;
+    };
+
+    /// Entity.
+    struct Entity
+    {
+        Entity();
+        ~Entity();
+
+        void Reset();
+        bool Load(const pugi::xml_node& node);
+
+        int id_;
+        std::string name_;
+        std::vector<CharacterMap*> characterMaps_;
+        std::vector<Animation*> animations_;
+    };
+
+    /// Character map.
+    struct CharacterMap
+    {
+        CharacterMap();
+        ~CharacterMap();
+
+        void Reset();
+        bool Load(const pugi::xml_node& node);
+
+        int id_;
+        std::string name_;
+        std::vector<MapInstruction*> maps_;
+    };
+
+    /// Map instruction.
+    struct MapInstruction
+    {
+        MapInstruction();
+        ~MapInstruction();
+
+        bool Load(const pugi::xml_node& node);
+
+        int folder_;
+        int file_;
+        int targetFolder_;
+        int targetFile_;
+    };
+
+    /// Animation.
+    struct Animation
+    {
+        Animation();
+        ~Animation();
+
+        void Reset();
+        bool Load(const pugi::xml_node& node);
+
+        int id_;
+        std::string name_;
+        float length_;
+        bool looping_;
+        std::vector<MainlineKey*> mainlineKeys_;
+        std::vector<Timeline*> timelines_;
+    };
+
+    /// Mainline key.
+    struct MainlineKey
+    {
+        MainlineKey();
+        ~MainlineKey();
+
+        void Reset();
+        bool Load(const pugi::xml_node& node);
+
+        int id_;
+        float time_;
+        std::vector<Ref*> boneRefs_;
+        std::vector<Ref*> objectRefs_;
+    };
+
+    /// Ref.
+    struct Ref
+    {
+        Ref();
+        ~Ref();
+
+        bool Load(const pugi::xml_node& node);
+
+        int id_;
+        int parent_;
+        int timeline_;
+        int key_;
+        int zIndex_;
+    };
+
+    /// Object type.
+    enum ObjectType
+    {
+        BONE = 0,
+        SPRITE
+    };
+
+    /// Timeline.
+    struct Timeline
+    {
+        Timeline();
+        ~Timeline();
+
+        void Reset();
+        bool Load(const pugi::xml_node& node);
+
+        int id_;
+        std::string name_;
+        ObjectType objectType_;
+        std::vector<SpatialTimelineKey*> keys_;
+    };
+
+    /// Curve type.
+    enum CurveType 
+    {
+        INSTANT = 0,
+        LINEAR,
+        QUADRATIC,
+        CUBIC
+    };
+
+    /// Timeline key.
+    struct TimelineKey
+    {
+        TimelineKey(Timeline* timeline);
+        virtual ~TimelineKey();
+
+        virtual ObjectType GetObjectType() const = 0;
+        virtual TimelineKey* Clone() const = 0;
+        virtual bool Load(const pugi::xml_node& node);
+        virtual void Interpolate(const TimelineKey& other, float t);
+        TimelineKey& operator=(const TimelineKey& rhs);
+
+        Timeline* timeline_;
+        int id_;
+        float time_;
+        CurveType curveType_;
+        float c1_;
+        float c2_;
+        float c3_;
+        float c4_;
+    };
+
+    /// Spatial info.
+    struct SpatialInfo
+    {
+        float x_;
+        float y_;
+        float angle_;
+        float scaleX_;
+        float scaleY_;
+        float alpha_;
+        int spin;
+
+        SpatialInfo(float x = 0.0f, float y = 0.0f, float angle = 0.0f, float scale_x = 1, float scale_y = 1, float a = 1, int spin = 1);
+        SpatialInfo UnmapFromParent(const SpatialInfo& parentInfo) const;
+        void Interpolate(const SpatialInfo& other, float t);
+    };
+
+    /// Spatial timeline key.
+    struct SpatialTimelineKey : TimelineKey
+    {
+        SpatialInfo info_;
+
+        SpatialTimelineKey(Timeline* timeline);
+        virtual ~SpatialTimelineKey();
+
+        virtual bool Load(const pugi::xml_node& node);
+        virtual void Interpolate(const TimelineKey& other, float t);
+        SpatialTimelineKey& operator=(const SpatialTimelineKey& rhs);
+    };
+
+    /// Bone timeline key.
+    struct BoneTimelineKey : SpatialTimelineKey
+    {
+        float length_;
+        float width_;
+
+        BoneTimelineKey(Timeline* timeline);
+        virtual ~BoneTimelineKey();
+
+        virtual ObjectType GetObjectType() const { return BONE; }
+        virtual TimelineKey* Clone() const;
+        virtual bool Load(const pugi::xml_node& node);
+        virtual void Interpolate(const TimelineKey& other, float t);
+        BoneTimelineKey& operator=(const BoneTimelineKey& rhs);
+    };
+
+    /// Sprite timeline key.
+    struct SpriteTimelineKey : SpatialTimelineKey
+    {
+        int folderId_;
+        int fileId_;
+        bool useDefaultPivot_;
+        float pivotX_;
+        float pivotY_;
+
+        // Run time data.
+        int zIndex_;
+
+        SpriteTimelineKey(Timeline* timeline);
+        virtual ~SpriteTimelineKey();
+
+        virtual ObjectType GetObjectType() const { return SPRITE; }
+        virtual TimelineKey* Clone() const;
+        virtual bool Load(const pugi::xml_node& node);
+        virtual void Interpolate(const TimelineKey& other, float t);
+        SpriteTimelineKey& operator=(const SpriteTimelineKey& rhs);
+    };
+}

+ 278 - 0
Source/ThirdParty/Spriter/src/SpriterInstance.cpp

@@ -0,0 +1,278 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "SpriterInstance.h"
+
+#include <cmath>
+
+namespace Spriter
+{
+    SpriterInstance::SpriterInstance(SpriterData* spriteData) : 
+        spriterData_(spriteData),
+        entity_(0),
+        animation_(0)
+    {
+    }
+
+    SpriterInstance::~SpriterInstance()
+    {
+        Clear();
+
+        OnSetAnimation(0);
+        OnSetEntity(0);
+    }
+
+    bool SpriterInstance::SetEntity(int index)
+    {
+        if (!spriterData_)
+            return false;
+
+        if (index < (int)spriterData_->entities_.size())
+        {
+            OnSetEntity(spriterData_->entities_[index]);
+            return true;
+        }
+
+        return false;
+    }
+
+    bool SpriterInstance::SetEntity(const std::string& entityName)
+    {
+        if (!spriterData_)
+            return false;
+
+        for (size_t i = 0; i < spriterData_->entities_.size(); ++i)
+        {
+            if (spriterData_->entities_[i]->name_ == entityName)
+            {
+                OnSetEntity(spriterData_->entities_[i]);
+                return true;
+            }
+        }
+
+        return false;
+    }   
+
+    bool SpriterInstance::SetAnimation(int index, LoopMode loopMode)
+    {
+        if (!entity_)
+            return false;
+
+        if (index < (int)entity_->animations_.size())
+        {
+            OnSetAnimation(entity_->animations_[index], loopMode);
+            return true;
+        }
+
+        return false;
+    }
+
+    bool SpriterInstance::SetAnimation(const std::string& animationName, LoopMode loopMode)
+    {
+        if (!entity_)
+            return false;
+
+        for (size_t i = 0; i < entity_->animations_.size(); ++i)
+        {
+            if (entity_->animations_[i]->name_ == animationName)
+            {
+                OnSetAnimation(entity_->animations_[i], loopMode);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    void SpriterInstance::setSpatialInfo(const SpatialInfo& spatialInfo)
+    {
+        this->spatialInfo_ = spatialInfo;
+    }
+
+    void SpriterInstance::setSpatialInfo(float x, float y, float angle, float scaleX, float scaleY)
+    {
+        spatialInfo_ = SpatialInfo(x, y, angle, scaleX, scaleY);
+    }
+
+    void SpriterInstance::Update(float deltaTime)
+    {
+        if (!animation_)
+            return;
+
+        Clear();
+
+        currentTime_ += deltaTime;
+        if (currentTime_ > animation_->length_)
+        {
+            if (looping_)
+            {
+                currentTime_ = fmod(currentTime_, animation_->length_);
+            }
+            else
+            {
+                currentTime_ = animation_->length_;
+            }
+        }
+
+        UpdateMainlineKey();
+        UpdateTimelineKeys();
+    }
+
+    void SpriterInstance::OnSetEntity(Entity* entity)
+    {
+        if (entity == this->entity_)
+            return;
+
+        OnSetAnimation(0);
+
+        this->entity_ = entity;
+    }
+
+    void SpriterInstance::OnSetAnimation(Animation* animation, LoopMode loopMode)
+    {
+        if (animation == this->animation_)
+            return;
+
+        animation_ = animation;
+        if (animation_)
+        {
+            if (loopMode == Default)
+                looping_ = animation_->looping_;
+            else if (loopMode == ForceLooped)
+                looping_ = true;
+            else
+                looping_ = false;
+        }
+        
+        currentTime_ = 0.0f;
+        Clear();        
+    }
+
+    void SpriterInstance::UpdateTimelineKeys()
+    {
+        for (size_t i = 0; i < mainlineKey_->boneRefs_.size(); ++i)
+        {
+            Ref* ref = mainlineKey_->boneRefs_[i];
+            BoneTimelineKey* timelineKey = (BoneTimelineKey*)GetTimelineKey(ref);
+            if (ref->parent_ >= 0)
+            {
+                timelineKey->info_ = timelineKey->info_.UnmapFromParent(timelineKeys_[ref->parent_]->info_);
+            }
+            else
+            {
+                timelineKey->info_ = timelineKey->info_.UnmapFromParent(spatialInfo_);
+            }            
+            timelineKeys_.push_back(timelineKey);
+        }
+
+        for (size_t i = 0; i < mainlineKey_->objectRefs_.size(); ++i)
+        {
+            Ref* ref = mainlineKey_->objectRefs_[i];
+            SpriteTimelineKey* timelineKey = (SpriteTimelineKey*)GetTimelineKey(ref);
+            
+            if (ref->parent_ >= 0)
+            {
+                timelineKey->info_ = timelineKey->info_.UnmapFromParent(timelineKeys_[ref->parent_]->info_);
+            }
+            else
+            {
+                timelineKey->info_ = timelineKey->info_.UnmapFromParent(spatialInfo_);
+            }
+            
+            timelineKey->zIndex_ = ref->zIndex_;
+
+            timelineKeys_.push_back(timelineKey);
+        }
+    }
+
+    void SpriterInstance::UpdateMainlineKey()
+    {
+        const std::vector<MainlineKey*>& mainlineKeys = animation_->mainlineKeys_;
+        for (size_t i = 0; i < mainlineKeys.size(); ++i)
+        {
+            if (mainlineKeys[i]->time_ <= currentTime_)
+            {
+                mainlineKey_ = mainlineKeys[i];
+            }
+
+            if (mainlineKeys[i]->time_ >= currentTime_)
+            {
+                break;
+            }
+        }
+
+        if (!mainlineKey_)
+        {
+            mainlineKey_ = mainlineKeys[0];
+        }
+    }
+
+    TimelineKey* SpriterInstance::GetTimelineKey(Ref* ref) const
+    {
+        Timeline* timeline = animation_->timelines_[ref->timeline_];
+        TimelineKey* timelineKey = timeline->keys_[ref->key_]->Clone();
+        if (timeline->keys_.size() == 1 || timelineKey->curveType_ == INSTANT)
+        {
+            return timelineKey;
+        }
+
+        size_t nextTimelineKeyIndex = ref->key_ + 1;
+        if (nextTimelineKeyIndex >= timeline->keys_.size())
+        {
+            if (animation_->looping_)
+            {
+                nextTimelineKeyIndex = 0;
+            }
+            else
+            {
+                return timelineKey;
+            }
+        }
+
+        TimelineKey* nextTimelineKey = timeline->keys_[nextTimelineKeyIndex];
+        
+        float nextTimelineKeyTime = nextTimelineKey->time_;
+        if (nextTimelineKey->time_ < timelineKey->time_)
+        {
+            nextTimelineKeyTime += animation_->length_;
+        }
+
+        float t = (currentTime_ - timelineKey->time_) / (nextTimelineKeyTime - timelineKey->time_);
+        timelineKey->Interpolate(*nextTimelineKey, t);
+
+        return timelineKey;
+    }
+
+    void SpriterInstance::Clear()
+    {
+        mainlineKey_ = 0;
+
+        if (!timelineKeys_.empty())
+        {
+            for (size_t i = 0; i < timelineKeys_.size(); ++i)
+            {
+                delete timelineKeys_[i];
+            }
+            timelineKeys_.clear();
+        }
+    }   
+}

+ 101 - 0
Source/ThirdParty/Spriter/src/SpriterInstance.h

@@ -0,0 +1,101 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "SpriterData.h"
+
+namespace Spriter
+{
+    enum LoopMode
+    {
+        Default = 0,
+        ForceLooped,
+        ForceClamped,
+    };
+
+    /// Spriter instance.
+    class SpriterInstance
+    {
+    public:
+        /// Constructor with spriter data.
+        SpriterInstance(SpriterData* spriteData);
+        /// Destructor.
+        ~SpriterInstance();
+
+        /// Set current entity.
+        bool SetEntity(int index);
+        /// Set current entity.
+        bool SetEntity(const std::string& entityName);
+        /// Set current animation.
+        bool SetAnimation(int index, LoopMode loopMode = Default);
+        /// Set current animation.
+        bool SetAnimation(const std::string& animationName, LoopMode loopMode = Default);
+        /// Set root spatial info.
+        void setSpatialInfo(const SpatialInfo& spatialInfo);
+        /// Set root spatial info.
+        void setSpatialInfo(float x, float y, float angle, float scaleX, float scaleY);
+        /// Update animation.
+        void Update(float delta_time);
+
+        /// Return current entity.
+        Entity* GetEntity() const { return entity_; }
+        // Return current animation.
+        Animation* GetAnimation() const { return animation_; }
+        /// Return root spatial info.
+        const SpatialInfo& GetSpatialInfo() const { return spatialInfo_; }
+        /// Return animation result timeline keys.
+        const std::vector<SpatialTimelineKey*>& GetTimelineKeys() const { return timelineKeys_; }
+
+    private:
+        /// Handle set entity.
+        void OnSetEntity(Entity* entity);
+        /// Handle set animation.
+        void OnSetAnimation(Animation* animation, LoopMode loopMode = Default);
+        /// Update mainline key.
+        void UpdateMainlineKey();
+        /// Update timeline keys.
+        void UpdateTimelineKeys();
+        /// Get timeline key by ref.
+        TimelineKey* GetTimelineKey(Ref* ref) const;
+        /// Clear mainline key and timeline keys.
+        void Clear();
+
+        /// Spriter data.
+        SpriterData* spriterData_;
+        /// Current entity.
+        Entity* entity_;
+        /// Current animation.
+        Animation* animation_;
+        /// Looping.
+        bool looping_;
+        /// Root spatial info.
+        SpatialInfo spatialInfo_;
+        /// Current time.
+        float currentTime_;
+        /// Current mainline key.
+        MainlineKey* mainlineKey_;
+        /// Current timeline keys.
+        std::vector<SpatialTimelineKey*> timelineKeys_;
+        
+    };
+}

+ 0 - 12
Source/Urho3D/LuaScript/pkgs/Urho2D/Animation2D.pkg

@@ -1,12 +0,0 @@
-$#include "Urho2D/Animation2D.h"
-
-class Animation2D : RefCounted
-{
-    const String GetName() const;
-    float GetLength() const;
-    bool IsLooped() const;
-
-    tolua_readonly tolua_property__get_set String name;
-    tolua_readonly tolua_property__get_set float length;
-    tolua_readonly tolua_property__is_set bool looped;
-};

+ 2 - 3
Source/Urho3D/LuaScript/pkgs/Urho2D/AnimationSet2D.pkg

@@ -3,8 +3,7 @@ $#include "Urho2D/AnimationSet2D.h"
 class AnimationSet2D : Resource
 class AnimationSet2D : Resource
 {
 {
     unsigned GetNumAnimations() const;
     unsigned GetNumAnimations() const;
-    Animation2D* GetAnimation(const String name) const;
-    Animation2D* GetAnimation(unsigned index) const;
-
+    String GetAnimation(unsigned index) const;
+    
     tolua_readonly tolua_property__get_set unsigned numAnimations;
     tolua_readonly tolua_property__get_set unsigned numAnimations;
 };
 };

+ 0 - 1
Source/Urho3D/LuaScript/pkgs/Urho2DLuaAPI.pkg

@@ -3,7 +3,6 @@ $pfile "Urho2D/SpriteSheet2D.pkg"
 $pfile "Urho2D/Drawable2D.pkg"
 $pfile "Urho2D/Drawable2D.pkg"
 $pfile "Urho2D/StaticSprite2D.pkg"
 $pfile "Urho2D/StaticSprite2D.pkg"
 
 
-$pfile "Urho2D/Animation2D.pkg"
 $pfile "Urho2D/AnimationSet2D.pkg"
 $pfile "Urho2D/AnimationSet2D.pkg"
 $pfile "Urho2D/AnimatedSprite2D.pkg"
 $pfile "Urho2D/AnimatedSprite2D.pkg"
 
 

+ 1 - 11
Source/Urho3D/Script/Urho2DAPI.cpp

@@ -131,20 +131,11 @@ static void RegisterStaticSprite2D(asIScriptEngine* engine)
     RegisterStaticSprite2D<StaticSprite2D>(engine, "StaticSprite2D");
     RegisterStaticSprite2D<StaticSprite2D>(engine, "StaticSprite2D");
 }
 }
 
 
-static void RegisterAnimation2D(asIScriptEngine* engine)
-{
-    RegisterRefCounted<Animation2D>(engine, "Animation2D");
-    engine->RegisterObjectMethod("Animation2D", "const String& get_name() const", asMETHOD(Animation2D, GetName), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Animation2D", "float get_length() const", asMETHOD(Animation2D, GetLength), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Animation2D", "bool get_looped() const", asMETHOD(Animation2D, IsLooped), asCALL_THISCALL);
-}
-
 static void RegisterAnimationSet2D(asIScriptEngine* engine)
 static void RegisterAnimationSet2D(asIScriptEngine* engine)
 {
 {
     RegisterResource<AnimationSet2D>(engine, "AnimationSet2D");
     RegisterResource<AnimationSet2D>(engine, "AnimationSet2D");
     engine->RegisterObjectMethod("AnimationSet2D", "uint get_numAnimations() const", asMETHOD(AnimationSet2D, GetNumAnimations), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimationSet2D", "uint get_numAnimations() const", asMETHOD(AnimationSet2D, GetNumAnimations), asCALL_THISCALL);
-    engine->RegisterObjectMethod("AnimationSet2D", "Animation2D@+ GetAnimation(uint) const", asMETHODPR(AnimationSet2D, GetAnimation, (unsigned) const, Animation2D*), asCALL_THISCALL);
-    engine->RegisterObjectMethod("AnimationSet2D", "Animation2D@+ GetAnimation(const String&) const", asMETHODPR(AnimationSet2D, GetAnimation, (const String&) const, Animation2D*), asCALL_THISCALL);
+    engine->RegisterObjectMethod("AnimationSet2D", "String GetAnimation(uint) const", asMETHOD(AnimationSet2D, GetAnimation), asCALL_THISCALL);
 }
 }
 
 
 static void RegisterAnimatedSprite2D(asIScriptEngine* engine)
 static void RegisterAnimatedSprite2D(asIScriptEngine* engine)
@@ -724,7 +715,6 @@ void RegisterUrho2DAPI(asIScriptEngine* engine)
     RegisterDrawable2D(engine);
     RegisterDrawable2D(engine);
     RegisterStaticSprite2D(engine);
     RegisterStaticSprite2D(engine);
 
 
-    RegisterAnimation2D(engine);
     RegisterAnimationSet2D(engine);
     RegisterAnimationSet2D(engine);
     RegisterAnimatedSprite2D(engine);
     RegisterAnimatedSprite2D(engine);
 
 

+ 287 - 247
Source/Urho3D/Urho2D/AnimatedSprite2D.cpp

@@ -33,6 +33,11 @@
 
 
 #include "../DebugNew.h"
 #include "../DebugNew.h"
 
 
+#ifdef URHO3D_SPINE
+#include <spine/spine.h>
+#endif 
+#include <spriter/SpriterInstance.h>
+
 namespace Urho3D
 namespace Urho3D
 {
 {
 
 
@@ -49,16 +54,20 @@ const char* loopModeNames[] =
 
 
 AnimatedSprite2D::AnimatedSprite2D(Context* context) :
 AnimatedSprite2D::AnimatedSprite2D(Context* context) :
     StaticSprite2D(context),
     StaticSprite2D(context),
+#ifdef URHO3D_SPINE
+    skeleton_(0),
+    animationStateData_(0),
+    animationState_(0),
+#endif
+    spriterInstance_(0),
     speed_(1.0f),
     speed_(1.0f),
-    loopMode_(LM_DEFAULT),
-    looped_(false),
-    currentTime_(0.0f),
-    numTracks_(0)
+    loopMode_(LM_DEFAULT)
 {
 {
 }
 }
 
 
 AnimatedSprite2D::~AnimatedSprite2D()
 AnimatedSprite2D::~AnimatedSprite2D()
 {
 {
+    Dispose();
 }
 }
 
 
 void AnimatedSprite2D::RegisterObject(Context* context)
 void AnimatedSprite2D::RegisterObject(Context* context)
@@ -88,16 +97,6 @@ void AnimatedSprite2D::OnSetEnabled()
         else
         else
             UnsubscribeFromEvent(scene, E_SCENEPOSTUPDATE);
             UnsubscribeFromEvent(scene, E_SCENEPOSTUPDATE);
     }
     }
-
-    for (unsigned i = 0; i < trackNodes_.Size(); ++i)
-    {
-        if (!trackNodes_[i])
-            continue;
-
-        StaticSprite2D* staticSprite = trackNodes_[i]->GetComponent<StaticSprite2D>();
-        if (staticSprite)
-            staticSprite->SetEnabled(enabled);
-    }
 }
 }
 
 
 void AnimatedSprite2D::SetSpeed(float speed)
 void AnimatedSprite2D::SetSpeed(float speed)
@@ -108,50 +107,62 @@ void AnimatedSprite2D::SetSpeed(float speed)
 
 
 void AnimatedSprite2D::SetAnimation(AnimationSet2D* animationSet, const String& name, LoopMode2D loopMode)
 void AnimatedSprite2D::SetAnimation(AnimationSet2D* animationSet, const String& name, LoopMode2D loopMode)
 {
 {
-    animationSet_ = animationSet;
-
+    SetAnimationSet(animationSet);
     SetAnimation(name, loopMode);
     SetAnimation(name, loopMode);
 }
 }
 
 
 void AnimatedSprite2D::SetAnimation(const String& name, LoopMode2D loopMode)
 void AnimatedSprite2D::SetAnimation(const String& name, LoopMode2D loopMode)
 {
 {
     animationName_ = name;
     animationName_ = name;
+    loopMode_ = loopMode;
 
 
-    if (animationSet_)
-        SetAnimation(animationSet_->GetAnimation(animationName_), loopMode);
+    if (!animationSet_ || !animationSet_->HasAnimation(animationName_))
+        return;
+
+#ifdef URHO3D_SPINE
+    if (skeleton_)
+        SetSpineAnimation();    
+#endif
+    if (spriterInstance_)
+        SetSpriterAnimation();
 }
 }
 
 
 void AnimatedSprite2D::SetAnimationSet(AnimationSet2D* animationSet)
 void AnimatedSprite2D::SetAnimationSet(AnimationSet2D* animationSet)
 {
 {
-    if (animationSet == animationSet_)
+    if (animationSet == animationSet_) 
         return;
         return;
 
 
-    animationSet_ = animationSet;
-
-    SetAnimation(animationName_, loopMode_);
-}
-
-void AnimatedSprite2D::SetLoopMode(LoopMode2D loopMode)
-{
-    if (!animation_)
+    Dispose();
+    
+    animationSet_ = animationSet;    
+    if (!animationSet_)
         return;
         return;
 
 
-    loopMode_ = loopMode;
+    SetSprite(animationSet_->GetSprite());
 
 
-    switch (loopMode_)
+#ifdef URHO3D_SPINE
+    if (animationSet_->GetSkeletonData())
     {
     {
-    case LM_FORCE_LOOPED:
-        looped_ = true;
-        break;
+        spSkeletonData* skeletonData = animationSet->GetSkeletonData();
 
 
-    case LM_FORCE_CLAMPED:
-        looped_ = false;
-        break;
-
-    default:
-        looped_ = animation_->IsLooped();
-        break;
+        // Create skeleton
+        skeleton_ = spSkeleton_create(skeletonData);
+        skeleton_->flipX = flipX_;
+        skeleton_->flipY = flipY_;
+        spSkeleton_updateWorldTransform(skeleton_);
     }
     }
+#endif
+    if (animationSet_->GetSpriterData())
+        spriterInstance_ = new Spriter::SpriterInstance(animationSet_->GetSpriterData());
+
+    // Clear animation name
+    animationName_.Clear();
+    loopMode_ = LM_DEFAULT;
+}
+
+void AnimatedSprite2D::SetLoopMode(LoopMode2D loopMode)
+{
+    loopMode_ = loopMode;
 }
 }
 
 
 AnimationSet2D* AnimatedSprite2D::GetAnimationSet() const
 AnimationSet2D* AnimatedSprite2D::GetAnimationSet() const
@@ -161,7 +172,7 @@ AnimationSet2D* AnimatedSprite2D::GetAnimationSet() const
 
 
 Node* AnimatedSprite2D::GetRootNode() const
 Node* AnimatedSprite2D::GetRootNode() const
 {
 {
-    return rootNode_;
+    return 0;
 }
 }
 
 
 void AnimatedSprite2D::SetAnimationSetAttr(const ResourceRef& value)
 void AnimatedSprite2D::SetAnimationSetAttr(const ResourceRef& value)
@@ -193,9 +204,7 @@ void AnimatedSprite2D::OnSceneSet(Scene* scene)
 void AnimatedSprite2D::SetAnimationAttr(const String& name)
 void AnimatedSprite2D::SetAnimationAttr(const String& name)
 {
 {
     animationName_ = name;
     animationName_ = name;
-
-    if (animationSet_)
-        SetAnimation(animationSet_->GetAnimation(animationName_), loopMode_);
+    SetAnimation(animationName_, loopMode_);
 }
 }
 
 
 void AnimatedSprite2D::OnWorldBoundingBoxUpdate()
 void AnimatedSprite2D::OnWorldBoundingBoxUpdate()
@@ -203,281 +212,312 @@ void AnimatedSprite2D::OnWorldBoundingBoxUpdate()
     boundingBox_.Clear();
     boundingBox_.Clear();
     worldBoundingBox_.Clear();
     worldBoundingBox_.Clear();
 
 
-    for (unsigned i = 0; i < numTracks_; ++i)
-    {
-        if (!trackNodes_[i])
-            continue;
-
-        StaticSprite2D* staticSprite = trackNodes_[i]->GetComponent<StaticSprite2D>();
-        if (staticSprite)
-            worldBoundingBox_.Merge(staticSprite->GetWorldBoundingBox());
-    }
+    for (unsigned i = 0; i < sourceBatches_[0].vertices_.Size(); ++i)
+        worldBoundingBox_.Merge(sourceBatches_[0].vertices_[i].position_);
 
 
     boundingBox_ = worldBoundingBox_.Transformed(node_->GetWorldTransform().Inverse());
     boundingBox_ = worldBoundingBox_.Transformed(node_->GetWorldTransform().Inverse());
 }
 }
 
 
-void AnimatedSprite2D::OnDrawOrderChanged()
+void AnimatedSprite2D::UpdateSourceBatches()
 {
 {
-    for (unsigned i = 0; i < numTracks_; ++i)
-    {
-        if (!trackNodes_[i])
-            continue;
+    sourceBatchesDirty_ = false;
 
 
-        StaticSprite2D* staticSprite = trackNodes_[i]->GetComponent<StaticSprite2D>();
-        if (staticSprite)
-            staticSprite->SetLayer(layer_);
-    }
+#ifdef URHO3D_SPINE
+    if (skeleton_ && animationState_)
+        UpdateSourceBatchesSpine();
+#endif
+    if (spriterInstance_ && spriterInstance_->GetAnimation())
+        UpdateSourceBatchesSpriter();
+    
 }
 }
 
 
-void AnimatedSprite2D::OnFlipChanged()
+void AnimatedSprite2D::HandleScenePostUpdate(StringHash eventType, VariantMap& eventData)
 {
 {
-    for (unsigned i = 0; i < numTracks_; ++i)
-    {
-        if (!trackNodes_[i])
-            continue;
-
-        StaticSprite2D* staticSprite = trackNodes_[i]->GetComponent<StaticSprite2D>();
-        if (staticSprite)
-            staticSprite->SetFlip(flipX_, flipY_);
-    }
-
-    // For editor paused mode
-    UpdateAnimation(0.0f);
+    using namespace ScenePostUpdate;
+    float timeStep = eventData[P_TIMESTEP].GetFloat();
+    UpdateAnimation(timeStep);
 }
 }
 
 
-void AnimatedSprite2D::UpdateSourceBatches()
+void AnimatedSprite2D::UpdateAnimation(float timeStep)
 {
 {
-    sourceBatchesDirty_ = false;
+#ifdef URHO3D_SPINE
+    if (skeleton_ && animationState_)
+        UpdateSpineAnimation(timeStep);
+#endif
+    if (spriterInstance_ && spriterInstance_->GetAnimation())
+        UpdateSpriterAnimation(timeStep);
 }
 }
 
 
-void AnimatedSprite2D::SetAnimation(Animation2D* animation, LoopMode2D loopMode)
+#ifdef URHO3D_SPINE
+void AnimatedSprite2D::SetSpineAnimation()
 {
 {
-    if (animation == animation_)
+    if (!animationStateData_)
     {
     {
-        SetLoopMode(loopMode_);
-
-        currentTime_ = 0.0f;
-        UpdateAnimation(0.0f);
-        return;
+        animationStateData_ = spAnimationStateData_create(animationSet_->GetSkeletonData());
+        if (!animationStateData_)
+        {
+            LOGERROR("Create animation state data failed");
+            return;
+        }
     }
     }
 
 
-    for (unsigned i = 0; i < numTracks_; ++i)
+    if (!animationState_)
     {
     {
-        if (trackNodes_[i])
-            trackNodes_[i]->SetEnabled(false);
+        animationState_ = spAnimationState_create(animationStateData_);
+        if (!animationState_)
+        {
+            LOGERROR("Create animation state failed");
+            return;
+        }
     }
     }
 
 
-    numTracks_ = 0;
-    trackNodes_.Clear();
-    trackNodeInfos_.Clear();
+    spAnimationState_setAnimationByName(animationState_, 0, animationName_.CString(), loopMode_ != LM_FORCE_CLAMPED ? true : false);
 
 
-    animation_ = animation;
+    UpdateAnimation(0.0f);
+    MarkNetworkUpdate();
+}
 
 
-    if (!animation_)
-        return;
+void AnimatedSprite2D::UpdateSpineAnimation(float timeStep)
+{
+    timeStep *= speed_;
 
 
-    currentTime_ = 0.0f;
+    skeleton_->flipX = flipX_;
+    skeleton_->flipY = flipY_;
 
 
-    if (!rootNode_)
-    {
-        rootNode_ = GetNode()->CreateChild("_root_", LOCAL);
-        rootNode_->SetTemporary(true);
-    }
+    spSkeleton_update(skeleton_, timeStep);        
+    spAnimationState_update(animationState_, timeStep);
+    spAnimationState_apply(animationState_, skeleton_);
+    spSkeleton_updateWorldTransform(skeleton_);
+
+    sourceBatchesDirty_ = true;
+}
+
+void AnimatedSprite2D::UpdateSourceBatchesSpine()
+{
+    const Matrix3x4& worldTransform = GetNode()->GetWorldTransform();
+
+    SourceBatch2D& sourceBatch = sourceBatches_[0];
+    sourceBatches_[0].vertices_.Clear();
 
 
-    numTracks_ = animation_->GetNumTracks();
-    trackNodes_.Resize(numTracks_);
-    trackNodeInfos_.Resize(numTracks_);
+    static const int SLOT_VERTEX_COUNT_MAX = 1024;
+    static float slotVertices[SLOT_VERTEX_COUNT_MAX];
 
 
-    for (unsigned i = 0; i < numTracks_; ++i)
+    for (int i = 0; i < skeleton_->slotsCount; ++i)
     {
     {
-        const AnimationTrack2D& track = animation->GetTrack(i);
-        SharedPtr<Node> trackNode(rootNode_->GetChild(track.name_));
+        spSlot* slot = skeleton_->drawOrder[i];
+        spAttachment* attachment = slot->attachment;
+        if (!attachment)
+            continue;
 
 
-        StaticSprite2D* staticSprite = 0;
-        if (trackNode)
-        {
-            // Enable track node
-            trackNode->SetEnabled(true);
+        unsigned color = Color(color_.r_ * slot->r, 
+            color_.g_ * slot->g, 
+            color_.b_ * slot->b, 
+            color_.a_ * slot->a).ToUInt();
 
 
-            // Get StaticSprite2D component
-            if (track.hasSprite_)
-                staticSprite = trackNode->GetComponent<StaticSprite2D>();
+        if (attachment->type == SP_ATTACHMENT_REGION)
+        {
+            spRegionAttachment* region = (spRegionAttachment*)attachment;
+            spRegionAttachment_computeWorldVertices(region, slot->bone, slotVertices);
+
+            Vertex2D vertices[4];
+            vertices[0].position_ = worldTransform * Vector3(slotVertices[SP_VERTEX_X1], slotVertices[SP_VERTEX_Y1]);
+            vertices[1].position_ = worldTransform * Vector3(slotVertices[SP_VERTEX_X2], slotVertices[SP_VERTEX_Y2]);
+            vertices[2].position_ = worldTransform * Vector3(slotVertices[SP_VERTEX_X3], slotVertices[SP_VERTEX_Y3]);
+            vertices[3].position_ = worldTransform * Vector3(slotVertices[SP_VERTEX_X4], slotVertices[SP_VERTEX_Y4]);
+
+            vertices[0].color_ = color;
+            vertices[1].color_ = color;
+            vertices[2].color_ = color;
+            vertices[3].color_ = color;
+
+            vertices[0].uv_ = Vector2(region->uvs[SP_VERTEX_X1], region->uvs[SP_VERTEX_Y1]);
+            vertices[1].uv_ = Vector2(region->uvs[SP_VERTEX_X2], region->uvs[SP_VERTEX_Y2]);
+            vertices[2].uv_ = Vector2(region->uvs[SP_VERTEX_X3], region->uvs[SP_VERTEX_Y3]);
+            vertices[3].uv_ = Vector2(region->uvs[SP_VERTEX_X4], region->uvs[SP_VERTEX_Y4]);
+
+            sourceBatches_[0].vertices_.Push(vertices[0]);
+            sourceBatches_[0].vertices_.Push(vertices[1]);
+            sourceBatches_[0].vertices_.Push(vertices[2]);
+            sourceBatches_[0].vertices_.Push(vertices[3]);
         }
         }
-        else
+        else if (attachment->type == SP_ATTACHMENT_MESH)
         {
         {
-            // Create new track node
-            trackNode = rootNode_->CreateChild(track.name_, LOCAL);
-            trackNode->SetTemporary(true);
+            spMeshAttachment* mesh = (spMeshAttachment*)attachment;
+            if (mesh->verticesCount > SLOT_VERTEX_COUNT_MAX)
+                continue;
+
+            spMeshAttachment_computeWorldVertices(mesh, slot, slotVertices);
 
 
-            // Create StaticSprite2D component
-            if (track.hasSprite_)
+            Vertex2D vertex;
+            vertex.color_ = color;
+            for (int j = 0; j < mesh->trianglesCount; ++j)
             {
             {
-                staticSprite = trackNode->CreateComponent<StaticSprite2D>();
-                staticSprite->SetEnabled(IsEnabledEffective());
+                int index = mesh->triangles[j] << 1;
+                vertex.position_ = worldTransform * Vector3(slotVertices[index], slotVertices[index + 1]);
+                vertex.uv_ = Vector2(mesh->uvs[index], mesh->uvs[index + 1]);
+
+                sourceBatches_[0].vertices_.Push(vertex);
+                // Add padding vertex
+                if (i % 3 == 2)
+                    sourceBatches_[0].vertices_.Push(vertex);
             }
             }
         }
         }
-
-        if (staticSprite)
+        else if (attachment->type == SP_ATTACHMENT_SKINNED_MESH)
         {
         {
-            staticSprite->SetLayer(layer_);
-            staticSprite->SetBlendMode(blendMode_);
-            staticSprite->SetFlip(flipX_, flipY_);
-            staticSprite->SetUseHotSpot(true);
+            spSkinnedMeshAttachment* skinnedMesh = (spSkinnedMeshAttachment*)attachment;
+            if (skinnedMesh->uvsCount > SLOT_VERTEX_COUNT_MAX)
+                continue;
+
+            spSkinnedMeshAttachment_computeWorldVertices(skinnedMesh, slot, slotVertices);
+
+            Vertex2D vertex;
+            vertex.color_ = color;
+            for (int j = 0; j < skinnedMesh->trianglesCount; ++j)
+            {
+                int index = skinnedMesh->triangles[j] << 1;
+                vertex.position_ = worldTransform * Vector3(slotVertices[index], slotVertices[index + 1]);
+                vertex.uv_ = Vector2(skinnedMesh->uvs[index], skinnedMesh->uvs[index + 1]);
+
+                sourceBatches_[0].vertices_.Push(vertex);
+                // Add padding vertex
+                if (i % 3 == 2)
+                    sourceBatches_[0].vertices_.Push(vertex);
+            }
         }
         }
+    }
+
+    worldBoundingBoxDirty_ = true;
+}
+#endif
 
 
-        trackNodes_[i] = trackNode;
+void AnimatedSprite2D::SetSpriterAnimation()
+{
+    if (!spriterInstance_)
+        spriterInstance_ = new Spriter::SpriterInstance(animationSet_->GetSpriterData());
 
 
-        trackNodeInfos_[i].hasSprite = track.hasSprite_;
+    // Use first entity
+    if (!spriterInstance_->SetEntity(0))
+    {
+        LOGERROR("Set entity failed");
+        return;
     }
     }
 
 
-    SetLoopMode(loopMode);
-    UpdateAnimation(0.0f);
+    if (!spriterInstance_->SetAnimation(animationName_.CString(), (Spriter::LoopMode)loopMode_))
+    {
+        LOGERROR("Set animation failed");
+        return;
+    }
 
 
+    UpdateAnimation(0.0f);
     MarkNetworkUpdate();
     MarkNetworkUpdate();
 }
 }
 
 
-void AnimatedSprite2D::UpdateAnimation(float timeStep)
+void AnimatedSprite2D::UpdateSpriterAnimation(float timeStep)
 {
 {
-    if (!animation_)
-        return;
+    spriterInstance_->Update(timeStep * speed_);
+    sourceBatchesDirty_ = true;
+}
+
 
 
-    currentTime_ += timeStep * speed_;
+void AnimatedSprite2D::UpdateSourceBatchesSpriter()
+{
+    const Matrix3x4& nodeWorldTransform = GetNode()->GetWorldTransform();
 
 
-    float time;
-    float animationLength = animation_->GetLength();
+    Vector<Vertex2D>& vertices = sourceBatches_[0].vertices_;
+    vertices.Clear();
 
 
-    if (looped_)
-    {
-        time = fmodf(currentTime_, animationLength);
-        if (time < 0.0f)
-            time += animation_->GetLength();
-    }
-    else
-        time = Clamp(currentTime_, 0.0f, animationLength);
+    Rect drawRect;
+    Rect textureRect;
+    unsigned color = color_.ToUInt();
 
 
-    for (unsigned i = 0; i < numTracks_; ++i)
+    Vertex2D vertex0;
+    Vertex2D vertex1;
+    Vertex2D vertex2;
+    Vertex2D vertex3;
+
+    const std::vector<Spriter::SpatialTimelineKey*>& timelineKeys = spriterInstance_->GetTimelineKeys();
+    for (size_t i = 0; i < timelineKeys.size(); ++i)
     {
     {
-        trackNodeInfos_[i].worldSpace = false;
+        if (timelineKeys[i]->GetObjectType() != Spriter::SPRITE)
+            continue;
 
 
-        const AnimationTrack2D& track = animation_->GetTrack(i);
-        const Vector<AnimationKeyFrame2D>& keyFrames = track.keyFrames_;
+        Spriter::SpriteTimelineKey* timelineKey = (Spriter::SpriteTimelineKey*)timelineKeys[i];
 
 
-        // Time out of range
-        if (time < keyFrames[0].time_ || time > keyFrames.Back().time_)
-            trackNodeInfos_[i].value.enabled_ = false;
-        else
-        {
-            unsigned index = keyFrames.Size() - 1;
-            for (unsigned j = 0; j < keyFrames.Size() - 1; ++j)
-            {
-                if (time <= keyFrames[j + 1].time_)
-                {
-                    index = j;
-                    break;
-                }
-            }
+        Spriter::SpatialInfo& info = timelineKey->info_;        
+        Vector3 position(info.x_, info.y_, 0.0f);
+        if (flipX_)
+            position.x_ = -position.x_;
+        if (flipY_)
+            position.y_ = -position.y_;
 
 
-            const AnimationKeyFrame2D& currKey = keyFrames[index];
-            AnimationKeyFrame2D& value = trackNodeInfos_[i].value;
+        float angle = info.angle_;
+        if (flipX_ != flipY_)
+            angle = -angle;
 
 
-            value.enabled_ = currKey.enabled_;
-            value.parent_ = currKey.parent_;
+        Matrix3x4 localTransform(position * PIXEL_SIZE, 
+            Quaternion(angle), 
+            Vector3(info.scaleX_, info.scaleY_, 1.0f));
 
 
-            if (index < keyFrames.Size() - 1)
-            {
-                const AnimationKeyFrame2D& nextKey = keyFrames[index + 1];
-                float t = (time - currKey.time_) / (nextKey.time_ - currKey.time_);
-                value.transform_ = currKey.transform_.Lerp(nextKey.transform_, t, currKey.spin_);
+        Matrix3x4 worldTransform = nodeWorldTransform * localTransform;
+        Sprite2D* sprite = animationSet_->GetSpriterFileSprite(timelineKey->folderId_, timelineKey->fileId_);
+        if (!sprite)
+            return;
 
 
-                if (trackNodeInfos_[i].hasSprite)
-                    value.alpha_ = Urho3D::Lerp(currKey.alpha_, nextKey.alpha_, t);
-            }
-            else
-            {
-                value.transform_ = currKey.transform_;
+        if (timelineKey->useDefaultPivot_)
+            sprite->GetDrawRectangle(drawRect, flipX_, flipY_);
+        else
+            sprite->GetDrawRectangle(drawRect, Vector2(timelineKey->pivotX_, timelineKey->pivotY_), flipX_, flipY_);
 
 
-                if (trackNodeInfos_[i].hasSprite)
-                    value.alpha_ = currKey.alpha_;
-            }
+        if (!sprite->GetTextureRectangle(textureRect, flipX_, flipY_))
+            return;
 
 
-            if (trackNodeInfos_[i].hasSprite)
-            {
-                value.zIndex_ = currKey.zIndex_;
-                value.sprite_ = currKey.sprite_;
-                value.useHotSpot_ = currKey.useHotSpot_;
-                value.hotSpot_ = currKey.hotSpot_;
-            }
-        }
-    }
+        vertex0.position_ = worldTransform * Vector3(drawRect.min_.x_, drawRect.min_.y_, 0.0f);
+        vertex1.position_ = worldTransform * Vector3(drawRect.min_.x_, drawRect.max_.y_, 0.0f);
+        vertex2.position_ = worldTransform * Vector3(drawRect.max_.x_, drawRect.max_.y_, 0.0f);
+        vertex3.position_ = worldTransform * Vector3(drawRect.max_.x_, drawRect.min_.y_, 0.0f);
 
 
-    for (unsigned i = 0; i < numTracks_; ++i)
-    {
-        Node* node = trackNodes_[i];
-        if (!node)
-            continue;
+        vertex0.uv_ = textureRect.min_;
+        vertex1.uv_ = Vector2(textureRect.min_.x_, textureRect.max_.y_);
+        vertex2.uv_ = textureRect.max_;
+        vertex3.uv_ = Vector2(textureRect.max_.x_, textureRect.min_.y_);
 
 
-        TrackNodeInfo& nodeInfo = trackNodeInfos_[i];
+        vertex0.color_ = vertex1.color_ = vertex2.color_ = vertex3.color_ = color;
 
 
-        if (!nodeInfo.value.enabled_)
-            node->SetEnabled(false);
-        else
-        {
-            node->SetEnabled(true);
-
-            // Calculate world transform.
-            CalculateTimelineWorldTransform(i);
-
-            // Update node's transform
-            Vector2 position = nodeInfo.value.transform_.position_ * PIXEL_SIZE;
-            if (flipX_)
-                position.x_ = -position.x_;
-            if (flipY_)
-                position.y_ = -position.y_;
-            node->SetPosition(position);
-
-            float angle = nodeInfo.value.transform_.angle_;
-            if (flipX_ != flipY_)
-                angle = -angle;
-            node->SetRotation(angle);
-            node->SetScale(nodeInfo.value.transform_.scale_);
-
-            if (nodeInfo.hasSprite)
-            {
-                StaticSprite2D* staticSprite = node->GetComponent<StaticSprite2D>();
-                if (staticSprite)
-                {
-                    staticSprite->SetOrderInLayer(orderInLayer_ + nodeInfo.value.zIndex_);
-                    staticSprite->SetSprite(nodeInfo.value.sprite_);
-                    staticSprite->SetAlpha(nodeInfo.value.alpha_);
-                    staticSprite->SetUseHotSpot(nodeInfo.value.useHotSpot_);
-                    staticSprite->SetHotSpot(nodeInfo.value.hotSpot_);
-                }
-            }
-        }
+        vertices.Push(vertex0);
+        vertices.Push(vertex1);
+        vertices.Push(vertex2);
+        vertices.Push(vertex3);
     }
     }
+
+    worldBoundingBoxDirty_ = true;
 }
 }
 
 
-void AnimatedSprite2D::CalculateTimelineWorldTransform(int index)
+void AnimatedSprite2D::Dispose()
 {
 {
-    TrackNodeInfo& info = trackNodeInfos_[index];
-    if (info.worldSpace)
-        return;
-
-    info.worldSpace = true;
+#ifdef URHO3D_SPINE
+    if (animationState_)
+    {
+        spAnimationState_dispose(animationState_);
+        animationState_ = 0;
+    }
 
 
-    int parent = info.value.parent_;
-    if (parent != -1)
+    if (animationStateData_)
     {
     {
-        CalculateTimelineWorldTransform(parent);
-        info.value.transform_ = trackNodeInfos_[parent].value.transform_ * info.value.transform_;
+        spAnimationStateData_dispose(animationStateData_);
+        animationStateData_ = 0;
     }
     }
-}
 
 
-void AnimatedSprite2D::HandleScenePostUpdate(StringHash eventType, VariantMap& eventData)
-{
-    using namespace ScenePostUpdate;
-    float timeStep = eventData[P_TIMESTEP].GetFloat();
-    UpdateAnimation(timeStep);
+    if (skeleton_)
+    {
+        spSkeleton_dispose(skeleton_);
+        skeleton_ = 0;
+    }
+#endif
+    if (spriterInstance_)
+    {
+        delete spriterInstance_;
+        spriterInstance_ = 0;
+    }
 }
 }
 
 
 }
 }

+ 42 - 40
Source/Urho3D/Urho2D/AnimatedSprite2D.h

@@ -22,9 +22,19 @@
 
 
 #pragma once
 #pragma once
 
 
-#include "../Urho2D/Animation2D.h"
 #include "../Urho2D/StaticSprite2D.h"
 #include "../Urho2D/StaticSprite2D.h"
 
 
+#ifdef URHO3D_SPINE
+struct spAnimationState;
+struct spAnimationStateData;
+struct spSkeleton;
+#endif
+
+namespace Spriter
+{
+    class SpriterInstance;
+}
+
 /// Loop mode.
 /// Loop mode.
 enum LoopMode2D
 enum LoopMode2D
 {
 {
@@ -41,7 +51,7 @@ namespace Urho3D
 
 
 class AnimationSet2D;
 class AnimationSet2D;
 
 
-/// Animated sprite component, it uses to play animation created by Spriter (http://www.brashmonkey.com/).
+/// Animated sprite component, it uses to play animation created by Spine £¨£© and Spriter (http://www.brashmonkey.com/).
 class URHO3D_API AnimatedSprite2D : public StaticSprite2D
 class URHO3D_API AnimatedSprite2D : public StaticSprite2D
 {
 {
     OBJECT(AnimatedSprite2D);
     OBJECT(AnimatedSprite2D);
@@ -70,16 +80,12 @@ public:
 
 
     /// Return speed.
     /// Return speed.
     float GetSpeed() const { return speed_; }
     float GetSpeed() const { return speed_; }
-
     /// Return animation name.
     /// Return animation name.
     const String& GetAnimation() const { return animationName_; }
     const String& GetAnimation() const { return animationName_; }
-
     /// Return animation.
     /// Return animation.
     AnimationSet2D* GetAnimationSet() const;
     AnimationSet2D* GetAnimationSet() const;
-
     /// Return loop mode.
     /// Return loop mode.
     LoopMode2D GetLoopMode() const { return loopMode_; }
     LoopMode2D GetLoopMode() const { return loopMode_; }
-
     /// Return root node.
     /// Return root node.
     Node* GetRootNode() const;
     Node* GetRootNode() const;
 
 
@@ -95,20 +101,28 @@ protected:
     virtual void OnSceneSet(Scene* scene);
     virtual void OnSceneSet(Scene* scene);
     /// Recalculate the world-space bounding box.
     /// Recalculate the world-space bounding box.
     virtual void OnWorldBoundingBoxUpdate();
     virtual void OnWorldBoundingBoxUpdate();
-    /// Handle draw order changed.
-    virtual void OnDrawOrderChanged();
     /// Handle update vertices.
     /// Handle update vertices.
     virtual void UpdateSourceBatches();
     virtual void UpdateSourceBatches();
-    /// Handle flip changed.
-    virtual void OnFlipChanged();
-    /// Set animation.
-    void SetAnimation(Animation2D* animation, LoopMode2D loopMode);
-    /// Update animation.
-    void UpdateAnimation(float timeStep);
-    /// Calculate time line world world transform.
-    void CalculateTimelineWorldTransform(int index);
     /// Handle scene post update.
     /// Handle scene post update.
     void HandleScenePostUpdate(StringHash eventType, VariantMap& eventData);
     void HandleScenePostUpdate(StringHash eventType, VariantMap& eventData);
+    /// Update animation.
+    void UpdateAnimation(float timeStep);
+#ifdef URHO3D_SPINE
+    /// Handle set spine animation.
+    void SetSpineAnimation();
+    /// Update spine animation.
+    void UpdateSpineAnimation(float timeStep);
+    /// Update vertices for spine animation;
+    void UpdateSourceBatchesSpine();
+#endif
+    /// Handle set spriter animation.
+    void SetSpriterAnimation();
+    /// Update spriter animation.
+    void UpdateSpriterAnimation(float timeStep);
+    /// Update vertices for spriter animation.
+    void UpdateSourceBatchesSpriter();
+    /// Dispose.
+    void Dispose();
 
 
     /// Speed.
     /// Speed.
     float speed_;
     float speed_;
@@ -116,32 +130,20 @@ protected:
     SharedPtr<AnimationSet2D> animationSet_;
     SharedPtr<AnimationSet2D> animationSet_;
     /// Animation name.
     /// Animation name.
     String animationName_;
     String animationName_;
-    /// Animation.
-    SharedPtr<Animation2D> animation_;
     /// Loop mode.
     /// Loop mode.
     LoopMode2D loopMode_;
     LoopMode2D loopMode_;
-    /// Looped.
-    bool looped_;
-    /// Current time.
-    float currentTime_;
-    /// Root node.
-    SharedPtr<Node> rootNode_;
-    /// Number of tracks.
-    unsigned numTracks_;
-    /// Track nodes.
-    Vector<SharedPtr<Node> > trackNodes_;
-    /// Track node info.
-    struct TrackNodeInfo
-    {
-        /// Has sprite.
-        bool hasSprite;
-        /// World space.
-        bool worldSpace;
-        /// Current value.
-        AnimationKeyFrame2D value;
-    };
-    /// Track node infos.
-    Vector<TrackNodeInfo> trackNodeInfos_;
+
+#ifdef URHO3D_SPINE
+    /// Skeleton.
+    spSkeleton* skeleton_;
+    /// Animation state data.
+    spAnimationStateData* animationStateData_;
+    /// Animation state.
+    spAnimationState* animationState_;
+#endif
+    
+    /// Spriter instance.
+    Spriter::SpriterInstance* spriterInstance_;
 };
 };
 
 
 }
 }

+ 0 - 151
Source/Urho3D/Urho2D/Animation2D.cpp

@@ -1,151 +0,0 @@
-//
-// Copyright (c) 2008-2015 the Urho3D project.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
-
-#include "../Precompiled.h"
-
-#include "../IO/Log.h"
-#include "../Urho2D/Animation2D.h"
-#include "../Urho2D/AnimationSet2D.h"
-#include "../Urho2D/Sprite2D.h"
-
-#include "../DebugNew.h"
-
-namespace Urho3D
-{
-
-Transform2D::Transform2D() :
-    position_(Vector2::ZERO),
-    angle_(0.0f),
-    scale_(1.0f, 1.0f)
-{
-}
-
-Transform2D::Transform2D(const Vector2& position, float angle, const Vector2& scale) :
-    position_(position),
-    angle_(angle),
-    scale_(scale)
-{
-}
-
-Transform2D::Transform2D(const Transform2D& other) :
-    position_(other.position_),
-    angle_(other.angle_),
-    scale_(other.scale_)
-{
-}
-
-Transform2D& Transform2D::operator =(const Transform2D& other)
-{
-    position_ = other.position_;
-    angle_ = other.angle_;
-    scale_ = other.scale_;
-    return *this;
-}
-
-Transform2D Transform2D::operator *(const Transform2D& other) const
-{
-    float x = scale_.x_ * other.position_.x_;
-    float y = scale_.y_ * other.position_.y_;
-    float s = Sin(angle_);
-    float c = Cos(angle_);
-
-    Vector2 position;
-    position.x_ = (x * c) - (y * s);
-    position.y_ = (x * s) + (y * c);
-    position = position_ + position;
-
-    float angle = angle_ + other.angle_;
-    Vector2 scale = scale_ * other.scale_;
-
-    return Transform2D(position, angle, scale);
-}
-
-Transform2D Transform2D::Lerp(const Transform2D& other, float t, int spin) const
-{
-    Transform2D ret;
-    ret.position_ = position_.Lerp(other.position_, t);
-
-    if (spin > 0 && angle_ > other.angle_)
-        ret.angle_ = Urho3D::Lerp(angle_, other.angle_ + 360.0f, t);
-    else if (spin < 0 && angle_ < other.angle_)
-        ret.angle_ = Urho3D::Lerp(angle_, other.angle_ - 360.0f, t);
-    else
-        ret.angle_ = Urho3D::Lerp(angle_, other.angle_, t);
-
-    ret.scale_ = scale_.Lerp(other.scale_, t);
-    return ret;
-}
-
-AnimationKeyFrame2D::AnimationKeyFrame2D() :
-    time_(0.0f),
-    enabled_(false),
-    parent_(-1),
-    spin_(1),
-    zIndex_(0),
-    alpha_(1.0f),
-    useHotSpot_(false)
-{
-}
-
-Animation2D::Animation2D(AnimationSet2D* animationSet) :
-    animationSet_(animationSet),
-    length_(0.0f),
-    looped_(true)
-{
-}
-
-Animation2D::~Animation2D()
-{
-}
-
-void Animation2D::SetName(const String& name)
-{
-    name_ = name;
-}
-
-void Animation2D::SetLength(float length)
-{
-    length_ = Max(0.0f, length);
-}
-
-void Animation2D::SetLooped(bool looped)
-{
-    looped_ = looped;
-}
-
-AnimationSet2D* Animation2D::GetAnimationSet() const
-{
-    return animationSet_;
-}
-
-const AnimationTrack2D& Animation2D::GetTrack(unsigned index) const
-{
-    if (index >= tracks_.Size())
-    {
-        LOGWARNING("Index out of range");
-        index = 0;
-    }
-
-    return tracks_[index];
-}
-
-}

+ 0 - 151
Source/Urho3D/Urho2D/Animation2D.h

@@ -1,151 +0,0 @@
-//
-// Copyright (c) 2008-2015 the Urho3D project.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
-
-#pragma once
-
-#include "../Container/Ptr.h"
-#include "../Container/RefCounted.h"
-#include "../Math/Vector2.h"
-
-namespace Urho3D
-{
-
-class AnimationSet2D;
-class Sprite2D;
-
-/// 2D Transform class for spriter animation.
-struct Transform2D
-{
-    /// Construct.
-    Transform2D();
-    /// Construct from position, angle, scale.
-    Transform2D(const Vector2& position, float angle, const Vector2& scale);
-    /// Copy-construct from another transform.
-    Transform2D(const Transform2D& other);
-
-    /// Assign from another transform.
-    Transform2D& operator =(const Transform2D& other);
-    /// Multiply a transform.
-    Transform2D operator *(const Transform2D& other) const;
-    /// Linear interpolation with another transform.
-    Transform2D Lerp(const Transform2D& other, float t, int spin) const;
-
-    /// Position.
-    Vector2 position_;
-    /// Angle.
-    float angle_;
-    /// Scale.
-    Vector2 scale_;
-};
-
-/// 2D animation key frame.
-struct AnimationKeyFrame2D
-{
-    /// Construct.
-    AnimationKeyFrame2D();
-
-    /// Time.
-    float time_;
-
-    /// Enabled (2D animation may disable node on fly).
-    bool enabled_;
-    /// Parent (2D animation may change parent on fly).
-    int parent_;
-    /// Transform.
-    Transform2D transform_;
-    /// Spin direction.
-    int spin_;
-
-    /// Draw order.
-    int zIndex_;
-    /// Sprite.
-    SharedPtr<Sprite2D> sprite_;
-    /// Alpha.
-    float alpha_;
-    /// Use hot spot.
-    bool useHotSpot_;
-    /// Hot spot.
-    Vector2 hotSpot_;
-};
-
-/// 2D animation track.
-struct AnimationTrack2D
-{
-    /// Name.
-    String name_;
-    /// Is sprite track.
-    bool hasSprite_;
-    /// Animation key frames.
-    Vector<AnimationKeyFrame2D> keyFrames_;
-};
-
-/// 2D Animation.
-class URHO3D_API Animation2D : public RefCounted
-{
-public:
-    /// Construct.
-    Animation2D(AnimationSet2D* animationSet);
-    /// Destruct
-    virtual ~Animation2D();
-
-    /// Set name.
-    void SetName(const String& name);
-    /// Set length.
-    void SetLength(float length);
-    /// Set looped.
-    void SetLooped(bool looped);
-
-    /// Return animation set.
-    AnimationSet2D* GetAnimationSet() const;
-
-    /// Return name.
-    const String& GetName() const { return name_; }
-
-    /// Return length.
-    float GetLength() const { return length_; }
-
-    /// Return looped.
-    bool IsLooped() const { return looped_; }
-
-    /// Return number of animation tracks.
-    unsigned GetNumTracks() const { return tracks_.Size(); }
-
-    /// Return animation track.
-    const AnimationTrack2D& GetTrack(unsigned index) const;
-
-    /// Return all animation tracks (internal use only).
-    Vector<AnimationTrack2D>& GetAllTracks() { return tracks_; }
-
-private:
-    /// Animation set.
-    WeakPtr<AnimationSet2D> animationSet_;
-    /// Name.
-    String name_;
-    /// Length.
-    float length_;
-    /// Looped.
-    bool looped_;
-    /// Animation tracks.
-    Vector<AnimationTrack2D> tracks_;
-};
-
-}

+ 299 - 369
Source/Urho3D/Urho2D/AnimationSet2D.cpp

@@ -23,27 +23,98 @@
 #include "../Precompiled.h"
 #include "../Precompiled.h"
 
 
 #include "../Core/Context.h"
 #include "../Core/Context.h"
+#include "../Graphics/Graphics.h"
+#include "../Graphics/Texture2D.h"
 #include "../IO/FileSystem.h"
 #include "../IO/FileSystem.h"
 #include "../IO/Log.h"
 #include "../IO/Log.h"
+#include "../Math/AreaAllocator.h"
+#include "../Resource/Image.h"
 #include "../Resource/ResourceCache.h"
 #include "../Resource/ResourceCache.h"
-#include "../Urho2D/Animation2D.h"
+#include "../Resource/XMLFile.h"
 #include "../Urho2D/AnimationSet2D.h"
 #include "../Urho2D/AnimationSet2D.h"
 #include "../Urho2D/Sprite2D.h"
 #include "../Urho2D/Sprite2D.h"
-#include "../Urho2D/SpriteSheet2D.h"
-#include "../Resource/XMLFile.h"
 
 
 #include "../DebugNew.h"
 #include "../DebugNew.h"
 
 
+#ifdef URHO3D_SPINE
+#include <spine/spine.h>
+#include <spine/extension.h>
+#endif
+#include <Spriter/SpriterData.h>
+
+#ifdef URHO3D_SPINE
+// Current animation set
+static Urho3D::AnimationSet2D* currentAnimationSet = 0;
+
+void _spAtlasPage_createTexture(spAtlasPage* self, const char* path)
+{
+    using namespace Urho3D;
+    if (!currentAnimationSet)
+        return;
+
+    ResourceCache* cache = currentAnimationSet->GetSubsystem<ResourceCache>();
+    Sprite2D* sprite = cache->GetResource<Sprite2D>(path);
+    // Add reference
+    if (sprite)
+        sprite->AddRef();
+
+    self->width = sprite->GetTexture()->GetWidth();
+    self->height = sprite->GetTexture()->GetHeight();
+
+    self->rendererObject = sprite;
+}
+
+void _spAtlasPage_disposeTexture(spAtlasPage* self)
+{
+    using namespace Urho3D;
+    Sprite2D* sprite = static_cast<Sprite2D*>(self->rendererObject);
+    if (sprite)
+        sprite->ReleaseRef();
+
+    self->rendererObject = 0;
+}
+
+char* _spUtil_readFile(const char* path, int* length)
+{
+    using namespace Urho3D;
+    
+    if (!currentAnimationSet)
+        return 0;
+
+    ResourceCache* cache = currentAnimationSet->GetSubsystem<ResourceCache>();
+    SharedPtr<File> file = cache->GetFile(path);
+    if (!file)
+        return 0;
+
+    unsigned size = file->GetSize();
+
+    char* data = MALLOC(char, size + 1);
+    file->Read(data, size);
+    data[size] = '\0';
+    
+    file.Reset();
+    *length = size;
+
+    return data;
+}
+#endif
+
 namespace Urho3D
 namespace Urho3D
 {
 {
 
 
 AnimationSet2D::AnimationSet2D(Context* context) :
 AnimationSet2D::AnimationSet2D(Context* context) :
-    Resource(context)
+    Resource(context),
+#ifdef URHO3D_SPINE
+    skeletonData_(0),
+    atlas_(0),
+#endif
+    spriterData_(0)
 {
 {
 }
 }
 
 
 AnimationSet2D::~AnimationSet2D()
 AnimationSet2D::~AnimationSet2D()
 {
 {
+    Dispose();    
 }
 }
 
 
 void AnimationSet2D::RegisterObject(Context* context)
 void AnimationSet2D::RegisterObject(Context* context)
@@ -57,8 +128,13 @@ bool AnimationSet2D::BeginLoad(Deserializer& source)
         SetName(source.GetName());
         SetName(source.GetName());
 
 
     String extension = GetExtension(source.GetName());
     String extension = GetExtension(source.GetName());
+#ifdef URHO3D_SPINE
+    if (extension == ".json")
+        return BeginLoadSpine(source);
+#endif
     if (extension == ".scml")
     if (extension == ".scml")
         return BeginLoadSpriter(source);
         return BeginLoadSpriter(source);
+    
 
 
     LOGERROR("Unsupport animation set file: " + source.GetName());
     LOGERROR("Unsupport animation set file: " + source.GetName());
 
 
@@ -67,7 +143,11 @@ bool AnimationSet2D::BeginLoad(Deserializer& source)
 
 
 bool AnimationSet2D::EndLoad()
 bool AnimationSet2D::EndLoad()
 {
 {
-    if (spriterFile_)
+#ifdef URHO3D_SPINE
+    if (jsonData_)
+        return EndLoadSpine();
+#endif
+    if (spriterData_)
         return EndLoadSpriter();
         return EndLoadSpriter();
 
 
     return false;
     return false;
@@ -75,457 +155,307 @@ bool AnimationSet2D::EndLoad()
 
 
 unsigned AnimationSet2D::GetNumAnimations() const
 unsigned AnimationSet2D::GetNumAnimations() const
 {
 {
-    return animations_.Size();
+#ifdef URHO3D_SPINE
+    if (skeletonData_)
+        return (unsigned)skeletonData_->animationsCount;
+#endif
+    if (spriterData_ && !spriterData_->entities_.empty())
+        return (unsigned)spriterData_->entities_[0]->animations_.size();
+    return 0;
 }
 }
 
 
-Animation2D* AnimationSet2D::GetAnimation(unsigned index) const
+String AnimationSet2D::GetAnimation(unsigned index) const
 {
 {
-    if (index < animations_.Size())
-        return animations_[index];
-    return 0;
+    if (index >= GetNumAnimations())
+        return String::EMPTY;
+
+#ifdef URHO3D_SPINE
+    if (skeletonData_)
+        return skeletonData_->animations[index]->name;
+#endif
+    if (spriterData_ && !spriterData_->entities_.empty())
+        return spriterData_->entities_[0]->animations_[index]->name_.c_str();
+
+    return String::EMPTY;
 }
 }
 
 
-Animation2D* AnimationSet2D::GetAnimation(const String& name) const
+bool AnimationSet2D::HasAnimation(const String& animationName) const
 {
 {
-    for (unsigned i = 0; i < animations_.Size(); ++i)
+#ifdef URHO3D_SPINE
+    if (skeletonData_)
     {
     {
-        if (animations_[i]->GetName() == name)
-            return animations_[i];
+        for (int i = 0; i < skeletonData_->animationsCount; ++i)
+        {
+            if (animationName == skeletonData_->animations[i]->name)
+                return true;
+        }
     }
     }
-    return 0;
+#endif    
+    if (spriterData_ && !spriterData_->entities_.empty())
+    {
+        const std::vector<Spriter::Animation*>& animations = spriterData_->entities_[0]->animations_;
+        for (size_t i = 0; i < animations.size(); ++i)
+        {
+            if (animationName == animations[i]->name_.c_str())
+                return true;
+        }
+    }
+
+    return false;
+}
+
+Sprite2D* AnimationSet2D::GetSprite() const
+{
+    return sprite_;
 }
 }
 
 
-Sprite2D* AnimationSet2D::GetSprite(const StringHash& hash) const
+Sprite2D* AnimationSet2D::GetSpriterFileSprite(int folderId, int fileId) const
 {
 {
-    HashMap<StringHash, SharedPtr<Sprite2D> >::ConstIterator i = sprites_.Find(hash);
-    if (i != sprites_.End())
+    int key = (folderId << 16) + fileId;
+    HashMap<int, SharedPtr<Sprite2D> >::ConstIterator i = spriterFileSprites_.Find(key);
+    if (i != spriterFileSprites_.End())
         return i->second_;
         return i->second_;
+
     return 0;
     return 0;
 }
 }
 
 
-bool AnimationSet2D::BeginLoadSpriter(Deserializer& source)
+#ifdef URHO3D_SPINE
+bool AnimationSet2D::BeginLoadSpine(Deserializer& source)
 {
 {
-    spriterFile_ = new XMLFile(context_);
-    if (!spriterFile_->Load(source))
-    {
-        LOGERROR("Load XML failed " + source.GetName());
-        spriterFile_.Reset();
-        return false;
-    }
+    Dispose();
 
 
-    XMLElement rootElem = spriterFile_->GetRoot("spriter_data");
-    if (!rootElem)
-    {
-        LOGERROR("Invalid spriter file " + source.GetName());
-        spriterFile_.Reset();
-        return false;
-    }
+    if (GetName().Empty())
+        SetName(source.GetName());
 
 
-    // When async loading, preprocess folders for spritesheet / sprite files and request them for background loading
-    if (GetAsyncLoadState() == ASYNC_LOADING)
-    {
-        if (!LoadSpriterFolders(rootElem))
-        {
-            spriterFile_.Reset();
-            return false;
-        }
-    }
+    unsigned size = source.GetSize();
+    jsonData_ = new char[size + 1];
+    source.Read(jsonData_, size);
+    jsonData_[size] = '\0';
 
 
     return true;
     return true;
 }
 }
 
 
-bool AnimationSet2D::EndLoadSpriter()
+bool AnimationSet2D::EndLoadSpine()
 {
 {
-    XMLElement rootElem = spriterFile_->GetRoot("spriter_data");
-    if (!LoadSpriterFolders(rootElem))
+    currentAnimationSet = this;
+
+    String atlasFileName = ReplaceExtension(GetName(), ".atlas");
+    atlas_ = spAtlas_createFromFile(atlasFileName.CString(), 0);
+    if (!atlas_)
     {
     {
-        spriterFile_.Reset();
+        LOGERROR("Create spine atlas failed");
         return false;
         return false;
     }
     }
 
 
-    XMLElement entityElem = rootElem.GetChild("entity");
-    if (!entityElem)
+    int numAtlasPages = 0;
+    spAtlasPage* atlasPage = atlas_->pages;
+    while (atlasPage)
     {
     {
-        LOGERROR("Could not find entity");
-        spriterFile_.Reset();
-        return false;
+        ++numAtlasPages;
+        atlasPage = atlasPage->next;
     }
     }
 
 
-    for (XMLElement animationElem = entityElem.GetChild("animation"); animationElem;
-         animationElem = animationElem.GetNext("animation"))
+    if (numAtlasPages > 1)
     {
     {
-        if (!LoadSpriterAnimation(animationElem))
-        {
-            spriterFile_.Reset();
-            return false;
-        }
+        LOGERROR("Only one page is supported in Urho3D");
+        return false;
     }
     }
 
 
-    spriterFile_.Reset();
-    return true;
-}
+    sprite_ = static_cast<Sprite2D*>(atlas_->pages->rendererObject);
 
 
-bool AnimationSet2D::LoadSpriterFolders(const XMLElement& rootElem)
-{
-    ResourceCache* cache = GetSubsystem<ResourceCache>();
-
-    bool async = GetAsyncLoadState() == ASYNC_LOADING;
-
-    String parentPath = GetParentPath(GetName());
-    String spriteSheetFilePathPList = parentPath + GetFileName(GetName()) + ".plist";
-    String spriteSheetFilePathXML = parentPath + GetFileName(GetName()) + ".xml";
-    SpriteSheet2D* spriteSheet = 0;
-    bool hasSpriteSheet = false;
-
-    // When async loading, request the sprite sheet for background loading but do not actually get it
-    if (!async)
-    {
-        spriteSheet = cache->GetResource<SpriteSheet2D>(spriteSheetFilePathPList, false);
-        if (!spriteSheet)
-            spriteSheet = cache->GetResource<SpriteSheet2D>(spriteSheetFilePathXML, false);
-    }
-    else
+    spSkeletonJson* skeletonJson = spSkeletonJson_create(atlas_);
+    if (!skeletonJson)
     {
     {
-        hasSpriteSheet = cache->Exists(spriteSheetFilePathPList);
-        if (hasSpriteSheet)
-            cache->BackgroundLoadResource<SpriteSheet2D>(spriteSheetFilePathPList, false, this);
-        else
-        {
-            hasSpriteSheet = cache->Exists(spriteSheetFilePathXML);
-            if (hasSpriteSheet)
-                cache->BackgroundLoadResource<SpriteSheet2D>(spriteSheetFilePathXML, false, this);
-        }
+        LOGERROR("Create skeleton Json failed");
+        return false;
     }
     }
 
 
-    for (XMLElement folderElem = rootElem.GetChild("folder"); folderElem; folderElem = folderElem.GetNext("folder"))
-    {
-        unsigned folderId = folderElem.GetUInt("id");
+    skeletonJson->scale = 0.01f; // PIXEL_SIZE;
+    skeletonData_ = spSkeletonJson_readSkeletonData(skeletonJson, &jsonData_[0]);
 
 
-        for (XMLElement fileElem = folderElem.GetChild("file"); fileElem; fileElem = fileElem.GetNext("file"))
-        {
-            unsigned fileId = fileElem.GetUInt("id");
-            String fileName = fileElem.GetAttribute("name");
+    spSkeletonJson_dispose(skeletonJson);
+    jsonData_.Reset();
 
 
-            // When async loading, request the sprites for background loading but do not actually get them
-            if (!async)
-            {
-                SharedPtr<Sprite2D> sprite;
-
-                if (spriteSheet)
-                    sprite = spriteSheet->GetSprite(GetFileName(fileName));
-                else
-                    sprite = (cache->GetResource<Sprite2D>(parentPath + fileName));
-
-                if (!sprite)
-                {
-                    LOGERROR("Could not load sprite " + fileName);
-                    return false;
-                }
-
-                Vector2 hotSpot(0.0f, 1.0f);
-                if (fileElem.HasAttribute("pivot_x"))
-                    hotSpot.x_ = fileElem.GetFloat("pivot_x");
-                if (fileElem.HasAttribute("pivot_y"))
-                    hotSpot.y_ = fileElem.GetFloat("pivot_y");
-
-                // If sprite is trimmed, recalculate hot spot
-                const IntVector2& offset = sprite->GetOffset();
-                if (offset != IntVector2::ZERO)
-                {
-                    int width = fileElem.GetInt("width");
-                    int height = fileElem.GetInt("height");
-
-                    float pivotX = width * hotSpot.x_;
-                    float pivotY = height * (1.0f - hotSpot.y_);
-
-                    const IntRect& rectangle = sprite->GetRectangle();
-                    hotSpot.x_ = (offset.x_ + pivotX) / rectangle.Width();
-                    hotSpot.y_ = 1.0f - (offset.y_ + pivotY) / rectangle.Height();
-                }
-
-                sprite->SetHotSpot(hotSpot);
-
-                sprites_[StringHash((folderId << 16) + fileId)] = sprite;
-            }
-            else if (!hasSpriteSheet)
-                cache->BackgroundLoadResource<Sprite2D>(parentPath + fileName, true, this);
-        }
-    }
+    currentAnimationSet = 0;
 
 
     return true;
     return true;
 }
 }
+#endif
 
 
-// Spriter object type.
-enum SpriterObjectType2D
-{
-    SOT_BONE = 0,
-    SOT_SPRITE,
-};
-
-// Spriter timeline key.
-struct SpriterTimelineKey2D
-{
-    SpriterTimelineKey2D() :
-        time_(0.0f),
-        angle_(0.0f),
-        spin_(1),
-        scale_(1.0f, 1.0f),
-        alpha_(1.0f),
-        useHotSpot_(false)
-    {
-    }
-
-    float time_;
-    Vector2 position_;
-    float angle_;
-    int spin_;
-    Vector2 scale_;
-    SharedPtr<Sprite2D> sprite_;
-    float alpha_;
-    bool useHotSpot_;
-    Vector2 hotSpot_;
-};
-
-// Spriter timeline.
-struct SpriterTimeline2D
+bool AnimationSet2D::BeginLoadSpriter(Deserializer& source)
 {
 {
-    SpriterTimeline2D() :
-        parent_(-1),
-        type_(SOT_BONE)
+    unsigned dataSize = source.GetSize();
+    if (!dataSize && !source.GetName().Empty())
     {
     {
+        LOGERROR("Zero sized XML data in " + source.GetName());
+        return false;
     }
     }
 
 
-    String name_;
-    int parent_;
-    SpriterObjectType2D type_;
-    Vector<SpriterTimelineKey2D> timelineKeys_;
-};
+    SharedArrayPtr<char> buffer(new char[dataSize]);
+    if (source.Read(buffer.Get(), dataSize) != dataSize)
+        return false;
 
 
-// Spriter reference.
-struct SpriterReference2D
-{
-    SpriterReference2D() :
-        type_(SOT_BONE),
-        timeline_(-1),
-        key_(-1),
-        zIndex_(0)
+    spriterData_ = new Spriter::SpriterData();
+    if (!spriterData_->Load(buffer.Get(), dataSize))
     {
     {
+        LOGERROR("Could not spriter data from " + source.GetName());
+        return false;
     }
     }
 
 
-    SpriterObjectType2D type_;
-    int timeline_;
-    int key_;
-    int zIndex_;
-};
-
-// Spriter mainline Key.
-struct SpriterMainlineKey2D
-{
-    float time_;
-    PODVector<SpriterReference2D> references_;
-};
-
-bool AnimationSet2D::LoadSpriterAnimation(const XMLElement& animationElem)
-{
-    String name = animationElem.GetAttribute("name");
-    float length = animationElem.GetFloat("length") * 0.001f;
-    bool looped = true;
-    if (animationElem.HasAttribute("looping"))
-        looped = animationElem.GetBool("looping");
-
-    float highestKeyTime = 0.0f;
-
-    // Load timelines
-    Vector<SpriterTimeline2D> timelines;
-    for (XMLElement timelineElem = animationElem.GetChild("timeline"); timelineElem;
-         timelineElem = timelineElem.GetNext("timeline"))
+    if (GetAsyncLoadState() == ASYNC_LOADING)
     {
     {
-        SpriterTimeline2D timeline;
-        timeline.name_ = timelineElem.GetAttribute("name");
-        if (timelineElem.GetAttribute("object_type") == "bone")
-            timeline.type_ = SOT_BONE;
-        else
-            timeline.type_ = SOT_SPRITE;
-
-        for (XMLElement keyElem = timelineElem.GetChild("key"); keyElem; keyElem = keyElem.GetNext("key"))
+        String parentPath = GetParentPath(GetName());
+        ResourceCache* cache = GetSubsystem<ResourceCache>();
+        for (size_t i = 0; i < spriterData_->folders_.size(); ++i)
         {
         {
-            SpriterTimelineKey2D key;
-            key.time_ = keyElem.GetFloat("time") * 0.001f;
-            if (keyElem.HasAttribute("spin"))
-                key.spin_ = keyElem.GetInt("spin");
-
-            XMLElement childElem = keyElem.GetChild();
-
-            key.position_.x_ = childElem.GetFloat("x");
-            key.position_.y_ = childElem.GetFloat("y");
-
-            key.angle_ = childElem.GetFloat("angle");
-
-            if (childElem.HasAttribute("scale_x"))
-                key.scale_.x_ = childElem.GetFloat("scale_x");
-
-            if (childElem.HasAttribute("scale_y"))
-                key.scale_.y_ = childElem.GetFloat("scale_y");
-
-            if (timeline.type_ == SOT_SPRITE)
+            Spriter::Folder* folder = spriterData_->folders_[i];
+            for (size_t j = 0; j < folder->files_.size(); ++j)
             {
             {
-                int folder = childElem.GetUInt("folder");
-                int file = childElem.GetUInt("file");
-                key.sprite_ = GetSprite(StringHash((unsigned)((folder << 16) + file)));
-                if (!key.sprite_)
-                {
-                    LOGERROR("Could not find sprite");
-                    return false;
-                }
-
-                if (childElem.HasAttribute("pivot_x") && childElem.HasAttribute("pivot_y"))
-                {
-                    key.useHotSpot_ = true;
-                    key.hotSpot_.x_ = childElem.GetFloat("pivot_x");
-                    key.hotSpot_.y_ = childElem.GetFloat("pivot_y");
-                }
-
-                if (childElem.HasAttribute("a"))
-                    key.alpha_ = childElem.GetFloat("a");
+                Spriter::File* file = folder->files_[j];
+                String imagePath = parentPath + file->name_.c_str();
+                cache->BackgroundLoadResource<Image>(imagePath, true, this);
             }
             }
-
-            timeline.timelineKeys_.Push(key);
         }
         }
-
-        timelines.Push(timeline);
     }
     }
 
 
-    // Load main line
-    Vector<SpriterMainlineKey2D> mainlineKeys;
-    XMLElement mainlineElem = animationElem.GetChild("mainline");
-    for (XMLElement keyElem = mainlineElem.GetChild("key"); keyElem; keyElem = keyElem.GetNext("key"))
-    {
-        SpriterMainlineKey2D mainlineKey;
-        mainlineKey.time_ = keyElem.GetFloat("time") * 0.001f;
+    // Note: this probably does not reflect internal data structure size accurately
+    SetMemoryUse(dataSize);
 
 
-        for (XMLElement refElem = keyElem.GetChild(); refElem; refElem = refElem.GetNext())
-        {
-            SpriterReference2D ref;
-            if (refElem.GetName() == "bone_ref")
-                ref.type_ = SOT_BONE;
-            else
-                ref.type_ = SOT_SPRITE;
+    return true;
+}
 
 
-            ref.timeline_ = refElem.GetInt("timeline");
-            ref.key_ = refElem.GetInt("key");
+bool AnimationSet2D::EndLoadSpriter()
+{
+    if (!spriterData_)
+        return false;
 
 
-            if (refElem.HasAttribute("parent"))
+    struct SpriteInfo
+    {
+        int x;
+        int y;
+        Spriter::File* file_;
+        SharedPtr<Image> image_;        
+    };
+    Vector<SpriteInfo> spriteInfos;
+    
+    String parentPath = GetParentPath(GetName());
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    for (unsigned i = 0; i < spriterData_->folders_.size(); ++i)
+    {
+        Spriter::Folder* folder = spriterData_->folders_[i];
+        for (unsigned j = 0; j < folder->files_.size(); ++j)
+        {
+            Spriter::File* file = folder->files_[j];
+            String imagePath = parentPath + file->name_.c_str();
+            SharedPtr<Image> image(cache->GetResource<Image>(imagePath));
+            if (!image)
             {
             {
-                int parent = refElem.GetInt("parent");
-                int parentTimeline = mainlineKey.references_[parent].timeline_;
-                timelines[ref.timeline_].parent_ = parentTimeline;
+                LOGERROR("Could not load image");
+                return false;
+            }
+            if (image->IsCompressed())
+            {
+                LOGERROR("Compressed image is not support");
+                return false;
+            }
+            if (image->GetComponents() != 4)
+            {
+                LOGERROR("Only support image with 4 components");
+                return false;
             }
             }
 
 
-            if (refElem.GetName() == "object_ref")
-                ref.zIndex_ = refElem.GetInt("z_index");
-
-            mainlineKey.references_.Push(ref);
+            SpriteInfo def;
+            def.file_ = file;
+            def.image_ = image;
+            spriteInfos.Push(def);
         }
         }
-
-        mainlineKeys.Push(mainlineKey);
     }
     }
 
 
-    unsigned numTimelines = timelines.Size();
-    unsigned numMainlineKeys = mainlineKeys.Size();
-    if (numTimelines == 0 || numMainlineKeys == 0)
-    {
-        LOGERROR("Invalid animation");
+    if (spriteInfos.Empty())
         return false;
         return false;
-    }
-
-    // Create animation
-    SharedPtr<Animation2D> animation(new Animation2D(this));
-    animation->SetName(name);
-    animation->SetLength(length);
-    animation->SetLooped(looped);
-
-    Vector<AnimationTrack2D>& tracks = animation->GetAllTracks();
-    tracks.Resize(numTimelines);
 
 
-    // Setup animation track key frames
-    for (unsigned i = 0; i < numTimelines; ++i)
+    if (spriteInfos.Size() > 1)
     {
     {
-        SpriterTimeline2D& timeline = timelines[i];
-        AnimationTrack2D& track = tracks[i];
-
-        track.name_ = timeline.name_;
-        track.hasSprite_ = timeline.type_ == SOT_SPRITE;
-
-        unsigned numTimelineKeys = timeline.timelineKeys_.Size();
-        tracks[i].keyFrames_.Resize(numTimelineKeys);
-
-        for (unsigned j = 0; j < numTimelineKeys; ++j)
+        AreaAllocator allocator(128, 128, 2048, 2048);
+        for (unsigned i = 0; i < spriteInfos.Size(); ++i)
         {
         {
-            SpriterTimelineKey2D& timelineKey = timeline.timelineKeys_[j];
-            AnimationKeyFrame2D& keyFrame = track.keyFrames_[j];
+            SpriteInfo& info = spriteInfos[i];
+            Image* image = info.image_;
+            if (!allocator.Allocate(image->GetWidth(), image->GetHeight(), info.x, info.y))
+            {
+                LOGERROR("Could not allocate area");
+                return false;
+            }
+        }
 
 
-            keyFrame.time_ = timelineKey.time_;
-            highestKeyTime = Max(highestKeyTime, keyFrame.time_);
+        SharedPtr<Texture2D> texture(new Texture2D(context_));
+        texture->SetMipsToSkip(QUALITY_LOW, 0);
+        texture->SetNumLevels(1);
+        texture->SetSize(allocator.GetWidth(), allocator.GetHeight(), Graphics::GetRGBAFormat());
 
 
-            // Set disabled
-            keyFrame.enabled_ = false;
-            keyFrame.parent_ = timeline.parent_;
-            keyFrame.transform_ = Transform2D(timelineKey.position_, timelineKey.angle_, timelineKey.scale_);
-            keyFrame.spin_ = timelineKey.spin_;
+        sprite_ = new Sprite2D(context_);
+        sprite_->SetTexture(texture);
 
 
-            if (track.hasSprite_)
-            {
-                keyFrame.sprite_ = timelineKey.sprite_;
-                keyFrame.alpha_ = timelineKey.alpha_;
-                keyFrame.useHotSpot_ = timelineKey.useHotSpot_;
-                if (timelineKey.useHotSpot_)
-                    keyFrame.hotSpot_ = timelineKey.hotSpot_;
-            }
+        for (unsigned i = 0; i < spriteInfos.Size(); ++i)
+        {
+            SpriteInfo& info = spriteInfos[i];
+            Image* image = info.image_;
+            
+            texture->SetData(0, info.x, info.y, image->GetWidth(), image->GetHeight(), image->GetData());
+
+            SharedPtr<Sprite2D> sprite(new Sprite2D(context_));
+            sprite->SetTexture(texture);
+            sprite->SetRectangle(IntRect(info.x, info.y, info.x + image->GetWidth(), info.y + image->GetHeight()));
+            sprite->SetHotSpot(Vector2(info.file_->pivotX_, info.file_->pivotY_));
+
+            int key = (info.file_->folder_->id_ << 16) + info.file_->id_;
+            spriterFileSprites_[key] = sprite;
         }
         }
     }
     }
-
-    // Set animation key frame enabled and set draw order
-    for (unsigned i = 0; i < numMainlineKeys; ++i)
+    else
     {
     {
-        SpriterMainlineKey2D& mainlineKey = mainlineKeys[i];
-        PODVector<SpriterReference2D>& references = mainlineKey.references_;
-        for (unsigned j = 0; j < references.Size(); ++j)
-        {
-            SpriterReference2D& ref = references[j];
-            AnimationKeyFrame2D& keyFrame = tracks[ref.timeline_].keyFrames_[ref.key_];
+        SharedPtr<Texture2D> texture(new Texture2D(context_));        
+        texture->SetMipsToSkip(QUALITY_LOW, 0);
+        texture->SetNumLevels(1);
 
 
-            // Set enabled
-            keyFrame.enabled_ = true;
+        SpriteInfo& info = spriteInfos[0];        
+        texture->SetData(info.image_, true);
 
 
-            // Set draw order
-            keyFrame.zIndex_ = ref.zIndex_;
-        }
+        sprite_ = new Sprite2D(context_);
+        sprite_->SetTexture(texture);
+        sprite_->SetRectangle(IntRect(info.x, info.y, info.x + info.image_->GetWidth(), info.y + info.image_->GetHeight()));
+        sprite_->SetHotSpot(Vector2(info.file_->pivotX_, info.file_->pivotY_));
+
+        int key = (info.file_->folder_->id_ << 16) + info.file_->id_;
+        spriterFileSprites_[key] = sprite_;
     }
     }
 
 
-    // Fix looped animation
-    if (looped)
+    return true;
+}
+
+void AnimationSet2D::Dispose()
+{
+#ifdef URHO3D_SPINE
+    if (skeletonData_)
     {
     {
-        for (unsigned i = 0; i < numTimelines; ++i)
-        {
-            Vector<AnimationKeyFrame2D>& keyFrames = tracks[i].keyFrames_;
-            if (keyFrames.Front().time_ < 0.01f && !Equals(keyFrames.Back().time_, length))
-            {
-                AnimationKeyFrame2D keyFrame = keyFrames.Front();
-                keyFrame.time_ = length;
-                keyFrames.Push(keyFrame);
-            }
-        }
+        spSkeletonData_dispose(skeletonData_);
+        skeletonData_ = 0;
     }
     }
-    else
+
+    if (atlas_)
     {
     {
-        // Crop non-looped animation length if longer than the last keyframe
-        if (length > highestKeyTime)
-            animation->SetLength(highestKeyTime);
+        spAtlas_dispose(atlas_);
+        atlas_ = 0;
     }
     }
+#endif
 
 
-    animations_.Push(animation);
+    if (spriterData_)
+    {
+        delete spriterData_;
+        spriterData_ = 0;
+    }
 
 
-    return true;
+    spriterFileSprites_.Clear();
 }
 }
 
 
 }
 }

+ 55 - 19
Source/Urho3D/Urho2D/AnimationSet2D.h

@@ -22,15 +22,24 @@
 
 
 #pragma once
 #pragma once
 
 
+#include "../Container/ArrayPtr.h"
 #include "../Resource/Resource.h"
 #include "../Resource/Resource.h"
 
 
+#ifdef URHO3D_SPINE
+struct spAtlas;
+struct spSkeletonData;
+struct spAnimationStateData;
+#endif
+
+namespace Spriter
+{
+    struct SpriterData;
+}
+
 namespace Urho3D
 namespace Urho3D
 {
 {
 
 
-class Animation2D;
 class Sprite2D;
 class Sprite2D;
-class XMLElement;
-class XMLFile;
 
 
 /// Spriter animation set, it includes one or more animations, for more information please refer to http://www.brashmonkey.com/spriter.htm.
 /// Spriter animation set, it includes one or more animations, for more information please refer to http://www.brashmonkey.com/spriter.htm.
 class URHO3D_API AnimationSet2D : public Resource
 class URHO3D_API AnimationSet2D : public Resource
@@ -52,29 +61,56 @@ public:
 
 
     /// Get number of animations.
     /// Get number of animations.
     unsigned GetNumAnimations() const;
     unsigned GetNumAnimations() const;
-    /// Return animation by index.
-    Animation2D* GetAnimation(unsigned index) const;
-    /// Return animation by name.
-    Animation2D* GetAnimation(const String& name) const;
+    /// Return animation name.
+    String GetAnimation(unsigned index) const;
+    /// Check has animation.
+    bool HasAnimation(const String& animation) const;
+    
+    /// Return sprite.
+    Sprite2D* GetSprite() const;
+
+#ifdef URHO3D_SPINE
+    /// Return spine skeleton data.
+    spSkeletonData* GetSkeletonData() const { return skeletonData_; }
+#endif
+
+    /// Return spriter data.
+    Spriter::SpriterData* GetSpriterData() const { return spriterData_; }
+    /// Return spriter file sprite.
+    Sprite2D* GetSpriterFileSprite(int folderId, int fileId) const;
 
 
 private:
 private:
     /// Return sprite by hash.
     /// Return sprite by hash.
-    Sprite2D* GetSprite(const StringHash& hash) const;
+    Sprite2D* GetSpriterFileSprite(const StringHash& hash) const;
+#ifdef URHO3D_SPINE
+    /// Begin load spine.
+    bool BeginLoadSpine(Deserializer& source);
+    /// Finish load spine.
+    bool EndLoadSpine();
+#endif
     /// Begin load scml.
     /// Begin load scml.
     bool BeginLoadSpriter(Deserializer& source);
     bool BeginLoadSpriter(Deserializer& source);
     /// Finish load scml.
     /// Finish load scml.
     bool EndLoadSpriter();
     bool EndLoadSpriter();
-    /// Load spriter folders.
-    bool LoadSpriterFolders(const XMLElement& rootElem);
-    /// Load spriter animation.
-    bool LoadSpriterAnimation(const XMLElement& animationElem);
-
-    /// Sprites.
-    HashMap<StringHash, SharedPtr<Sprite2D> > sprites_;
-    /// Animations.
-    Vector<SharedPtr<Animation2D> > animations_;
-    /// Spriter file.
-    SharedPtr<XMLFile> spriterFile_;
+    /// Dispose all data.
+    void Dispose();
+    
+    /// Spine sprite.
+    SharedPtr<Sprite2D> sprite_;
+    
+#ifdef URHO3D_SPINE
+    /// Spine json data.
+    SharedArrayPtr<char> jsonData_;
+    /// Spine skeleton data.
+    spSkeletonData* skeletonData_;
+    /// Spine atlas.
+    spAtlas* atlas_;
+#endif
+    
+    /// Spriter data.
+    Spriter::SpriterData* spriterData_;
+    /// Spriter sprites.
+    HashMap<int, SharedPtr<Sprite2D> > spriterFileSprites_;
 };
 };
 
 
 }
 }

+ 10 - 19
bin/Data/LuaScripts/33_Urho2DSpriterAnimation.lua

@@ -6,18 +6,8 @@
 
 
 require "LuaScripts/Utilities/Sample"
 require "LuaScripts/Utilities/Sample"
 
 
-local spriteNode = nil
-local animationIndex = 0
-local animationNames = 
-{
-    "idle",
-    "run",
-    "attack",
-    "hit",
-    "dead",
-    "dead2",
-    "dead3",
-}
+local spriterNode = nil
+local spriterAnimationIndex = 0
 
 
 function Start()
 function Start()
     -- Execute the common startup for samples
     -- Execute the common startup for samples
@@ -55,15 +45,15 @@ function CreateScene()
     camera.orthoSize = graphics.height * PIXEL_SIZE
     camera.orthoSize = graphics.height * PIXEL_SIZE
     camera.zoom = 1.5 * Min(graphics.width / 1280, graphics.height / 800) -- Set zoom according to user's resolution to ensure full visibility (initial zoom (1.5) is set for full visibility at 1280x800 resolution)
     camera.zoom = 1.5 * Min(graphics.width / 1280, graphics.height / 800) -- Set zoom according to user's resolution to ensure full visibility (initial zoom (1.5) is set for full visibility at 1280x800 resolution)
 
 
-    local animationSet = cache:GetResource("AnimationSet2D", "Urho2D/imp/imp.scml")
-    if animationSet == nil then
+    local spriterAnimationSet = cache:GetResource("AnimationSet2D", "Urho2D/imp/imp.scml")
+    if spriterAnimationSet == nil then
         return
         return
     end
     end
 
 
     spriteNode = scene_:CreateChild("SpriterAnimation")
     spriteNode = scene_:CreateChild("SpriterAnimation")
 
 
-    local animatedSprite = spriteNode:CreateComponent("AnimatedSprite2D")
-    animatedSprite:SetAnimation(animationSet, animationNames[animationIndex + 1])
+    local spriterAnimatedSprite = spriteNode:CreateComponent("AnimatedSprite2D")
+    spriterAnimatedSprite:SetAnimation(spriterAnimationSet, spriterAnimationSet:GetAnimation(animationIndex), LM_FORCE_LOOPED)
 end
 end
 
 
 function CreateInstructions()
 function CreateInstructions()
@@ -139,9 +129,10 @@ function HandleUpdate(eventType, eventData)
 end
 end
 
 
 function HandleMouseButtonDown(eventType, eventData)
 function HandleMouseButtonDown(eventType, eventData)
-    local animatedSprite = spriteNode:GetComponent("AnimatedSprite2D")
-    animationIndex = (animationIndex + 1) % 7
-    animatedSprite:SetAnimation(animationNames[animationIndex + 1], LM_FORCE_LOOPED)
+    local spriterAnimatedSprite = spriteNode:GetComponent("AnimatedSprite2D")
+    local spriterAnimationSet = spriterAnimatedSprite.animationSet
+    spriterAnimationIndex = (spriterAnimationIndex + 1) % spriterAnimationSet.numAnimations
+    spriterAnimatedSprite:SetAnimation(spriterAnimationSet:GetAnimation(spriterAnimationIndex), LM_FORCE_LOOPED)
 end
 end
 
 
 -- Create XML patch instructions for screen joystick layout specific to this sample app
 -- Create XML patch instructions for screen joystick layout specific to this sample app

+ 11 - 22
bin/Data/Scripts/33_Urho2DSpriterAnimation.as

@@ -6,19 +6,8 @@
 
 
 #include "Scripts/Utilities/Sample.as"
 #include "Scripts/Utilities/Sample.as"
 
 
-Node@ spriteNode;
-int animationIndex = 0;
-
-Array<String> animationNames = 
-{
-    "idle",
-    "run",
-    "attack",
-    "hit",
-    "dead",
-    "dead2",
-    "dead3",
-};
+Node@ spriterNode;
+int spriterAnimationIndex = 0;
 
 
 void Start()
 void Start()
 {
 {
@@ -59,14 +48,13 @@ void CreateScene()
     camera.orthoSize = graphics.height * PIXEL_SIZE;
     camera.orthoSize = graphics.height * PIXEL_SIZE;
     camera.zoom = 1.5f * Min(graphics.width / 1280.0f, graphics.height / 800.0f); // Set zoom according to user's resolution to ensure full visibility (initial zoom (1.5) is set for full visibility at 1280x800 resolution)
     camera.zoom = 1.5f * Min(graphics.width / 1280.0f, graphics.height / 800.0f); // Set zoom according to user's resolution to ensure full visibility (initial zoom (1.5) is set for full visibility at 1280x800 resolution)
 
 
-    AnimationSet2D@ animationSet = cache.GetResource("AnimationSet2D", "Urho2D/imp/imp.scml");
-    if (animationSet is null)
+    AnimationSet2D@ spriterAnimationSet = cache.GetResource("AnimationSet2D", "Urho2D/imp/imp.scml");
+    if (spriterAnimationSet is null)
         return;
         return;
 
 
-    spriteNode = scene_.CreateChild("SpriterAnimation");
-
-    AnimatedSprite2D@ animatedSprite = spriteNode.CreateComponent("AnimatedSprite2D");
-    animatedSprite.SetAnimation(animationSet, animationNames[animationIndex]);
+    spriterNode = scene_.CreateChild("SpriterAnimation");
+    AnimatedSprite2D@ animatedSprite = spriterNode.CreateComponent("AnimatedSprite2D");
+    animatedSprite.SetAnimation(spriterAnimationSet, spriterAnimationSet.GetAnimation(spriterAnimationIndex), LM_FORCE_LOOPED);
 }
 }
 
 
 void CreateInstructions()
 void CreateInstructions()
@@ -145,9 +133,10 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
 
 
 void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
 void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
 {
 {
-    AnimatedSprite2D@ animatedSprite = spriteNode.GetComponent("AnimatedSprite2D");
-    animationIndex = (animationIndex + 1) % 7;
-    animatedSprite.SetAnimation(animationNames[animationIndex], LM_FORCE_LOOPED);
+    AnimatedSprite2D@ spriterAnimatedSprite = spriterNode.GetComponent("AnimatedSprite2D");
+    AnimationSet2D@ spriterAnimationSet = spriterAnimatedSprite.animationSet;
+    spriterAnimationIndex = (spriterAnimationIndex + 1) % spriterAnimationSet.numAnimations;
+    spriterAnimatedSprite.SetAnimation(spriterAnimationSet.GetAnimation(spriterAnimationIndex), LM_FORCE_LOOPED);
 }
 }
 
 
 // Create XML patch instructions for screen joystick layout specific to this sample app
 // Create XML patch instructions for screen joystick layout specific to this sample app