2
0
aster2013 10 жил өмнө
parent
commit
d1f22dd80a

+ 751 - 0
Source/Urho3D/Urho2D/SpriterData2D.cpp

@@ -0,0 +1,751 @@
+//
+// 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 "../Math/MathDefs.h"
+#include "../Urho2D/SpriterData2D.h"
+
+#include <PugiXml/pugixml.hpp>
+
+#include <cstring>
+
+using namespace pugi;
+
+namespace Urho3D
+{
+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(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(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(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(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(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(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(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(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(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(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();
+
+    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(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(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;
+
+    /*
+    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);
+        float s = Sin(parentInfo.angle_);
+        float c = Cos(parentInfo.angle_);
+
+        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/Urho3D/Urho2D/SpriterData2D.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
+
+namespace pugi
+{
+class xml_node;
+}
+
+namespace Urho3D
+{
+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_;
+    String generator_;
+    String generatorVersion_;
+    PODVector<Folder*> folders_;
+    PODVector<Entity*> entities_;
+};
+
+/// Folder.
+struct Folder
+{
+    Folder();
+    ~Folder();
+
+    void Reset();
+    bool Load(const pugi::xml_node& node);
+
+    int id_;
+    String name_;
+    PODVector<File*> files_;
+};
+
+/// File.
+struct File
+{
+    File(Folder* folder);
+    ~File();
+
+    bool Load(const pugi::xml_node& node);
+
+    Folder* folder_;
+    int id_;
+    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_;
+    String name_;
+    PODVector<CharacterMap*> characterMaps_;
+    PODVector<Animation*> animations_;
+};
+
+/// Character map.
+struct CharacterMap
+{
+    CharacterMap();
+    ~CharacterMap();
+
+    void Reset();
+    bool Load(const pugi::xml_node& node);
+
+    int id_;
+    String name_;
+    PODVector<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_;
+    String name_;
+    float length_;
+    bool looping_;
+    PODVector<MainlineKey*> mainlineKeys_;
+    PODVector<Timeline*> timelines_;
+};
+
+/// Mainline key.
+struct MainlineKey
+{
+    MainlineKey();
+    ~MainlineKey();
+
+    void Reset();
+    bool Load(const pugi::xml_node& node);
+
+    int id_;
+    float time_;
+    PODVector<Ref*> boneRefs_;
+    PODVector<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_;
+    String name_;
+    ObjectType objectType_;
+    PODVector<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);
+};
+}
+}

+ 283 - 0
Source/Urho3D/Urho2D/SpriterInstance2D.cpp

@@ -0,0 +1,283 @@
+//
+// 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 "../Urho2D/SpriterInstance2D.h"
+
+#include <cmath>
+
+namespace Urho3D
+{
+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 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 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(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(timelineKey);
+    }
+}
+
+void SpriterInstance::UpdateMainlineKey()
+{
+    const PODVector<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();
+    }
+}   
+}
+}

+ 104 - 0
Source/Urho3D/Urho2D/SpriterInstance2D.h

@@ -0,0 +1,104 @@
+//
+// 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 "../Urho2D/SpriterData2D.h"
+
+namespace Urho3D
+{
+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 String& entityName);
+    /// Set current animation.
+    bool SetAnimation(int index, LoopMode loopMode = Default);
+    /// Set current animation.
+    bool SetAnimation(const 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 PODVector<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.
+    PODVector<SpatialTimelineKey*> timelineKeys_;
+        
+};
+}
+}