Browse Source

Merge remote-tracking branch 'hjmediastudios/master'

Lasse Öörni 10 years ago
parent
commit
2d7ab2080f

+ 7 - 6
Docs/Reference.dox

@@ -313,7 +313,7 @@ Unless you have extremely serious reasons for doing so, you should not subclass
 
 \section SceneModel_LoadSave Loading and saving scenes
 
-Scenes can be loaded and saved in either binary or XML format; see the functions \ref Scene::Load "Load()", \ref Scene::LoadXML "LoadXML()", \ref Scene::Save "Save()" and \ref Scene::SaveXML "SaveXML()". See \ref Serialization
+Scenes can be loaded and saved in either binary, JSON, or XML formats; see the functions \ref Scene::Load "Load()", \ref Scene::LoadXML "LoadXML()", \ref Scene::LoadJSON "LoadJSON", \ref Scene::Save "Save()" and \ref Scene::SaveXML "SaveXML()", and \ref Scene::SaveJSON "SaveJSON()". See \ref Serialization
 "Serialization" for the technical details on how this works. When a scene is loaded, all existing content in it (child nodes and components) is removed first.
 
 Nodes and components that are marked temporary will not be saved. See \ref Serializable::SetTemporary "SetTemporary()".
@@ -322,13 +322,13 @@ To be able to track the progress of loading a (large) scene without having the p
 
 \section SceneModel_Instantiation Object prefabs
 
-Just loading or saving whole scenes is not flexible enough for eg. games where new objects need to be dynamically created. On the other hand, creating complex objects and setting their properties in code will also be tedious. For this reason, it is also possible to save a scene node (and its child nodes, components and attributes) to either binary or XML to be able to instantiate it later into a scene. Such a saved object is often referred to as a prefab. There are three ways to do this:
+Just loading or saving whole scenes is not flexible enough for eg. games where new objects need to be dynamically created. On the other hand, creating complex objects and setting their properties in code will also be tedious. For this reason, it is also possible to save a scene node (and its child nodes, components and attributes) to either binary, JSON, or XML to be able to instantiate it later into a scene. Such a saved object is often referred to as a prefab. There are three ways to do this:
 
-- In code by calling \ref Node::Save "Save()" or \ref Node::SaveXML "SaveXML()" on the Node in question.
+- In code by calling \ref Node::Save "Save()", \ref Node::SaveJSON "SaveJSON()", or \ref Node::SaveXML "SaveXML()" on the Node in question.
 - In the editor, by selecting the node in the hierarchy window and choosing "Save node as" from the "File" menu.
 - Using the "node" command in AssetImporter, which will save the scene node hierarchy and any models contained in the input asset (eg. a Collada file)
 
-To instantiate the saved node into a scene, call \ref Scene::Instantiate "Instantiate()" or \ref Scene::InstantiateXML "InstantiateXML()" depending on the format. The node will be created as a child of the Scene but can be freely reparented after that. Position and rotation for placing the node need to be specified. The NinjaSnowWar example uses XML format for its object prefabs; these exist in the bin/Data/Objects directory.
+To instantiate the saved node into a scene, call \ref Scene::Instantiate "Instantiate()", \ref Scene::InstantiateJSON() or \ref Scene::InstantiateXML "InstantiateXML()" depending on the format. The node will be created as a child of the Scene but can be freely reparented after that. Position and rotation for placing the node need to be specified. The NinjaSnowWar example uses XML format for its object prefabs; these exist in the bin/Data/Objects directory.
 
 \section SceneModel_FurtherInformation Further information
 
@@ -351,6 +351,7 @@ Resources include most things in Urho3D that are loaded from mass storage during
 - Texture3D
 - TextureCube
 - XMLFile
+- JSONFile
 
 They are managed and loaded by the ResourceCache subsystem. Like with all other \ref ObjectTypes "typed objects", resource types are identified by 32-bit type name hashes (C++) or type names (script). An object factory must be registered for each resource type.
 
@@ -381,7 +382,7 @@ parse data, upload to GPU if necessary) and can therefore result in framerate dr
 
 If you know in advance what resources you need, you can request them to be loaded in a background thread by calling \ref ResourceCache::BackgroundLoadResource "BackgroundLoadResource()". The event E_RESOURCEBACKGROUNDLOADED will be sent after the loading is complete; it will tell if the loading actually was a success or a failure. Depending on the resource, only a part of the loading process may be moved to a background thread, for example the finishing GPU upload step always needs to happen in the main thread. Note that if you call GetResource() for a resource that is queued for background loading, the main thread will stall until its loading is complete.
 
-The asynchronous scene loading functionality \ref Scene::LoadAsync "LoadAsync()" and \ref Scene::LoadAsyncXML "LoadAsyncXML()" has the option to background load the resources first before proceeding to load the scene content. It can also be used to only load the resources without modifying the scene, by specifying the LOAD_RESOURCES_ONLY mode. This allows to prepare a scene or object prefab file for fast instantiation.
+The asynchronous scene loading functionality \ref Scene::LoadAsync "LoadAsync()", \ref Scene::LoadAsyncJSON "LoadAsyncJSON()" and \ref Scene::LoadAsyncXML "LoadAsyncXML()" have the option to background load the resources first before proceeding to load the scene content. It can also be used to only load the resources without modifying the scene, by specifying the LOAD_RESOURCES_ONLY mode. This allows to prepare a scene or object prefab file for fast instantiation.
 
 Finally the maximum time (in milliseconds) spent each frame on finishing background loaded resources can be configured, see \ref ResourceCache::SetFinishBackgroundResourcesMs "SetFinishBackgroundResourcesMs()".
 
@@ -1031,7 +1032,7 @@ OpenGL ES 2.0 has further limitations:
 
 \page Materials Materials
 
-Material and Technique resources define how to render 3D scene geometry. On the disk, they are XML data. Default and example materials exist in the bin/CoreData/Materials & bin/Data/Materials subdirectories, and techniques exist in the bin/CoreData/Techniques subdirectory.
+Material and Technique resources define how to render 3D scene geometry. On the disk, they are XML or JSON data. Default and example materials exist in the bin/CoreData/Materials & bin/Data/Materials subdirectories, and techniques exist in the bin/CoreData/Techniques subdirectory.
 
 A material defines the textures, shader parameters and culling & fill mode to use, and refers to one or several techniques. A technique defines the actual rendering passes, the shaders to use in each, and all other rendering states such as depth test, depth write, and blending.
 

+ 2 - 0
Source/Urho3D/AngelScript/APITemplates.h

@@ -437,6 +437,8 @@ template <class T> void RegisterSerializable(asIScriptEngine* engine, const char
     engine->RegisterObjectMethod(className, "bool Save(VectorBuffer&) const", asFUNCTION(SerializableSaveVectorBuffer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod(className, "bool LoadXML(const XMLElement&, bool setInstanceDefault = false)", asMETHODPR(T, LoadXML, (const XMLElement&, bool), bool), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool SaveXML(XMLElement&) const", asMETHODPR(T, SaveXML, (XMLElement&) const, bool), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "bool LoadJSON(const JSONValue&, bool setInstanceDefault = false)", asMETHODPR(T, LoadJSON, (const JSONValue&, bool), bool), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "bool SaveJSON(JSONValue&) const", asMETHODPR(T, SaveJSON, (JSONValue&) const, bool), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void MarkNetworkUpdate() const", asMETHODPR(T, MarkNetworkUpdate, (), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void ApplyAttributes()", asMETHODPR(T, ApplyAttributes, (), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool SetAttribute(const String&in, const Variant&in)", asMETHODPR(T, SetAttribute, (const String&, const Variant&), bool), asCALL_THISCALL);

+ 58 - 0
Source/Urho3D/AngelScript/SceneAPI.cpp

@@ -100,11 +100,21 @@ static bool NodeSaveXML(File* file, const String& indentation, Node* ptr)
     return file && ptr->SaveXML(*file, indentation);
 }
 
+static bool NodeSaveJSON(File* file, Node* ptr)
+{
+    return file && ptr->SaveJSON(*file);
+}
+
 static bool NodeSaveXMLVectorBuffer(VectorBuffer& buffer, const String& indentation, Node* ptr)
 {
     return ptr->SaveXML(buffer, indentation);
 }
 
+static bool NodeSaveJSONVectorBuffer(VectorBuffer& buffer, Node* ptr)
+{
+    return ptr->SaveJSON(buffer);
+}
+
 static void RegisterNode(asIScriptEngine* engine)
 {
     engine->RegisterEnum("CreateMode");
@@ -129,6 +139,8 @@ static void RegisterNode(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Node", "bool get_enabledSelf() const", asMETHOD(Node, IsEnabledSelf), asCALL_THISCALL);
     engine->RegisterObjectMethod("Node", "bool SaveXML(File@+, const String&in indentation = \"\t\")", asFUNCTION(NodeSaveXML), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Node", "bool SaveXML(VectorBuffer&, const String&in indentation = \"\t\")", asFUNCTION(NodeSaveXMLVectorBuffer), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Node", "bool SaveJSON(File@+)", asFUNCTION(NodeSaveJSON), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Node", "bool SaveJSON(VectorBuffer&)", asFUNCTION(NodeSaveJSONVectorBuffer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Node", "Node@+ Clone(CreateMode mode = REPLICATED)", asMETHOD(Node, Clone), asCALL_THISCALL);
     RegisterObjectConstructor<Node>(engine, "Node");
     RegisterNamedObjectConstructor<Node>(engine, "Node");
@@ -153,16 +165,36 @@ static bool SceneLoadXMLVectorBuffer(VectorBuffer& buffer, Scene* ptr)
     return ptr->LoadXML(buffer);
 }
 
+static bool SceneLoadJSONVectorBuffer(VectorBuffer& buffer, Scene* ptr)
+{
+    return ptr->LoadJSON(buffer);
+}
+
+static bool SceneLoadJSON(File* file, Scene* ptr)
+{
+    return file && ptr->LoadJSON(*file);
+}
+
 static bool SceneSaveXML(File* file, const String& indentation, Scene* ptr)
 {
     return file && ptr->SaveXML(*file, indentation);
 }
 
+static bool SceneSaveJSON(File* file, const String& indentation, Scene* ptr)
+{
+    return file && ptr->SaveJSON(*file, indentation);
+}
+
 static bool SceneSaveXMLVectorBuffer(VectorBuffer& buffer, const String& indentation, Scene* ptr)
 {
     return ptr->SaveXML(buffer, indentation);
 }
 
+static bool SceneSaveJSONVectorBuffer(VectorBuffer& buffer, const String& indentation, Scene* ptr)
+{
+    return ptr->SaveJSON(buffer, indentation);
+}
+
 static Node* SceneInstantiate(File* file, const Vector3& position, const Quaternion& rotation, CreateMode mode, Scene* ptr)
 {
     return file ? ptr->Instantiate(*file, position, rotation, mode) : 0;
@@ -178,16 +210,31 @@ static Node* SceneInstantiateXML(File* file, const Vector3& position, const Quat
     return file ? ptr->InstantiateXML(*file, position, rotation, mode) : 0;
 }
 
+static Node* SceneInstantiateJSON(File* file, const Vector3& position, const Quaternion& rotation, CreateMode mode, Scene* ptr)
+{
+    return file ? ptr->InstantiateJSON(*file, position, rotation, mode) : 0;
+}
+
 static Node* SceneInstantiateXMLVectorBuffer(VectorBuffer& buffer, const Vector3& position, const Quaternion& rotation, CreateMode mode, Scene* ptr)
 {
     return ptr->InstantiateXML(buffer, position, rotation, mode);
 }
 
+static Node* SceneInstantiateJSONVectorBuffer(VectorBuffer& buffer, const Vector3& position, const Quaternion& rotation, CreateMode mode, Scene* ptr)
+{
+    return ptr->InstantiateJSON(buffer, position, rotation, mode);
+}
+
 static Node* SceneInstantiateXMLFile(XMLFile* xml, const Vector3& position, const Quaternion& rotation, CreateMode mode, Scene* ptr)
 {
     return xml ? ptr->InstantiateXML(xml->GetRoot(), position, rotation, mode) : 0;
 }
 
+static Node* SceneInstantiateJSONFile(JSONFile* json, const Vector3& position, const Quaternion& rotation, CreateMode mode, Scene* ptr)
+{
+    return json ? ptr->InstantiateJSON(json->GetRoot(), position, rotation, mode) : 0;
+}
+
 static CScriptArray* SceneGetRequiredPackageFiles(Scene* ptr)
 {
     return VectorToHandleArray<PackageFile>(ptr->GetRequiredPackageFiles(), "Array<PackageFile@>");
@@ -279,15 +326,26 @@ static void RegisterScene(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Scene", "bool LoadXML(VectorBuffer&)", asFUNCTION(SceneLoadXMLVectorBuffer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "bool SaveXML(File@+, const String&in indentation = \"\t\")", asFUNCTION(SceneSaveXML), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "bool SaveXML(VectorBuffer&, const String&in indentation = \"\t\")", asFUNCTION(SceneSaveXMLVectorBuffer), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "bool LoadJSON(File@+)", asFUNCTION(SceneLoadJSON), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "bool LoadJSON(VectorBuffer&)", asFUNCTION(SceneLoadJSONVectorBuffer), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "bool SaveJSON(File@+, const String&in indentation = \"\t\")", asFUNCTION(SceneSaveJSON), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "bool SaveJSON(VectorBuffer&, const String&in indentation = \"\t\")", asFUNCTION(SceneSaveJSONVectorBuffer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "bool LoadAsync(File@+, LoadMode mode = LOAD_SCENE_AND_RESOURCES)", asMETHOD(Scene, LoadAsync), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool LoadAsyncXML(File@+, LoadMode mode = LOAD_SCENE_AND_RESOURCES)", asMETHOD(Scene, LoadAsyncXML), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void StopAsyncLoading()", asMETHOD(Scene, StopAsyncLoading), asCALL_THISCALL);
+
     engine->RegisterObjectMethod("Scene", "Node@+ Instantiate(File@+, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiate), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "Node@+ Instantiate(VectorBuffer&, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiateVectorBuffer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "Node@+ InstantiateXML(File@+, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiateXML), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "Node@+ InstantiateXML(VectorBuffer&, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiateXMLVectorBuffer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "Node@+ InstantiateXML(XMLFile@+, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiateXMLFile), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "Node@+ InstantiateXML(const XMLElement&in, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asMETHODPR(Scene, InstantiateXML, (const XMLElement&, const Vector3&, const Quaternion&, CreateMode), Node*), asCALL_THISCALL);
+
+    engine->RegisterObjectMethod("Scene", "Node@+ InstantiateJSON(File@+, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiateJSON), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "Node@+ InstantiateJSON(VectorBuffer&, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiateJSONVectorBuffer), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "Node@+ InstantiateJSON(JSONFile@+, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiateJSONFile), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "Node@+ InstantiateJSON(const JSONValue&in, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asMETHODPR(Scene, InstantiateJSON, (const JSONValue&, const Vector3&, const Quaternion&, CreateMode), Node*), asCALL_THISCALL);
+
     engine->RegisterObjectMethod("Scene", "void Clear(bool clearReplicated = true, bool clearLocal = true)", asMETHOD(Scene, Clear), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void AddRequiredPackageFile(PackageFile@+)", asMETHOD(Scene, AddRequiredPackageFile), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void ClearRequiredPackageFiles()", asMETHOD(Scene, ClearRequiredPackageFiles), asCALL_THISCALL);

+ 9 - 0
Source/Urho3D/Graphics/AnimatedModel.cpp

@@ -131,6 +131,15 @@ bool AnimatedModel::LoadXML(const XMLElement& source, bool setInstanceDefault)
     return success;
 }
 
+bool AnimatedModel::LoadJSON(const JSONValue& source, bool setInstanceDefault)
+{
+    loading_ = true;
+    bool success = Component::LoadJSON(source, setInstanceDefault);
+    loading_ = false;
+
+    return success;
+}
+
 void AnimatedModel::ApplyAttributes()
 {
     if (assignBonesPending_)

+ 2 - 0
Source/Urho3D/Graphics/AnimatedModel.h

@@ -51,6 +51,8 @@ public:
     virtual bool Load(Deserializer& source, bool setInstanceDefault = false);
     /// Load from XML data. Return true if successful.
     virtual bool LoadXML(const XMLElement& source, bool setInstanceDefault = false);
+    /// Load from JSON data. Return true if successful.
+    virtual bool LoadJSON(const JSONValue& source, bool setInstanceDefault = false);
     /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
     virtual void ApplyAttributes();
     /// Process octree raycast. May be called from a worker thread.

+ 31 - 0
Source/Urho3D/Graphics/Animation.cpp

@@ -32,6 +32,7 @@
 #include "../IO/Serializer.h"
 #include "../Resource/ResourceCache.h"
 #include "../Resource/XMLFile.h"
+#include "../Resource/JSONFile.h"
 
 #include "../DebugNew.h"
 
@@ -184,6 +185,36 @@ bool Animation::BeginLoad(Deserializer& source)
         }
 
         memoryUse += triggers_.Size() * sizeof(AnimationTriggerPoint);
+        SetMemoryUse(memoryUse);
+        return true;
+    }
+
+    // Optionally read triggers from a JSON file
+    String jsonName = ReplaceExtension(GetName(), ".json");
+
+    SharedPtr<JSONFile> jsonFile(cache->GetTempResource<JSONFile>(jsonName, false));
+    if (jsonFile)
+    {
+        const JSONValue& rootVal = jsonFile->GetRoot();
+        JSONArray triggerArray = rootVal.Get("triggers").GetArray();
+
+        for (unsigned i = 0; i < triggerArray.Size(); i++)
+        {
+            const JSONValue& triggerValue = triggerArray.At(i);
+            JSONValue normalizedTimeValue = triggerValue.Get("normalizedTime");
+            if (!normalizedTimeValue.IsNull())
+                AddTrigger(normalizedTimeValue.GetFloat(), true, triggerValue.GetVariant());
+            else
+            {
+                JSONValue timeVal = triggerValue.Get("time");
+                if (!timeVal.IsNull())
+                    AddTrigger(timeVal.GetFloat(), false, triggerValue.GetVariant());
+            }
+        }
+
+        memoryUse += triggers_.Size() * sizeof(AnimationTriggerPoint);
+        SetMemoryUse(memoryUse);
+        return true;
     }
 
     SetMemoryUse(memoryUse);

+ 317 - 19
Source/Urho3D/Graphics/Material.cpp

@@ -36,6 +36,7 @@
 #include "../IO/VectorBuffer.h"
 #include "../Resource/ResourceCache.h"
 #include "../Resource/XMLFile.h"
+#include "../Resource/JSONFile.h"
 #include "../Scene/Scene.h"
 #include "../Scene/SceneEvents.h"
 #include "../Scene/ValueAnimation.h"
@@ -197,6 +198,63 @@ bool Material::BeginLoad(Deserializer& source)
     if (!graphics)
         return true;
 
+    String extension = GetExtension(source.GetName());
+
+    bool success = false;
+    if (extension == ".xml")
+    {
+        success = BeginLoadXML(source);
+        if (!success)
+            success = BeginLoadJSON(source);
+
+        if (success)
+            return true;
+    }
+    else // Load JSON file
+    {
+        success = BeginLoadJSON(source);
+        if (!success)
+            success = BeginLoadXML(source);
+
+        if (success)
+            return true;
+    }
+
+    // All loading failed
+    ResetToDefaults();
+    loadJSONFile_.Reset();
+    return false;
+}
+
+bool Material::EndLoad()
+{
+    // In headless mode, do not actually load the material, just return success
+    Graphics* graphics = GetSubsystem<Graphics>();
+    if (!graphics)
+        return true;
+
+    bool success = false;
+    if (loadXMLFile_)
+    {
+        // If async loading, get the techniques / textures which should be ready now
+        XMLElement rootElem = loadXMLFile_->GetRoot();
+        success = Load(rootElem);
+    }
+
+    if (loadJSONFile_)
+    {
+        JSONValue rootVal = loadJSONFile_->GetRoot();
+        success = Load(rootVal);
+    }
+
+    loadXMLFile_.Reset();
+    loadJSONFile_.Reset();
+    return success;
+}
+
+bool Material::BeginLoadXML(Deserializer& source)
+{
+    ResetToDefaults();
     loadXMLFile_ = new XMLFile(context_);
     if (loadXMLFile_->Load(source))
     {
@@ -239,34 +297,65 @@ bool Material::BeginLoad(Deserializer& source)
 
         return true;
     }
-    else
-    {
-        ResetToDefaults();
-        loadXMLFile_.Reset();
-        return false;
-    }
+
+    return false;
 }
 
-bool Material::EndLoad()
+bool Material::BeginLoadJSON(Deserializer& source)
 {
-    // In headless mode, do not actually load the material, just return success
-    Graphics* graphics = GetSubsystem<Graphics>();
-    if (!graphics)
-        return true;
+    // Attempt to load a JSON file
+    ResetToDefaults();
+    loadXMLFile_.Reset();
 
-    bool success = false;
-    if (loadXMLFile_)
+    // Attempt to load from JSON file instead
+    loadJSONFile_ = new JSONFile(context_);
+    if (loadJSONFile_->Load(source))
     {
-        // If async loading, get the techniques / textures which should be ready now
-        XMLElement rootElem = loadXMLFile_->GetRoot();
-        success = Load(rootElem);
+        // If async loading, scan the XML content beforehand for technique & texture resources
+        // and request them to also be loaded. Can not do anything else at this point
+        if (GetAsyncLoadState() == ASYNC_LOADING)
+        {
+            ResourceCache* cache = GetSubsystem<ResourceCache>();
+            const JSONValue& rootVal = loadJSONFile_->GetRoot();
+
+            JSONArray techniqueArray = rootVal.Get("techniques").GetArray();
+            for (unsigned i = 0; i < techniqueArray.Size(); i++)
+            {
+                const JSONValue& techVal = techniqueArray[i];
+                cache->BackgroundLoadResource<Technique>(techVal.Get("name").GetString(), true, this);
+            }
+
+            JSONObject textureObject = rootVal.Get("textures").GetObject();
+            for (JSONObject::ConstIterator it = textureObject.Begin(); it != textureObject.End(); it++)
+            {
+                String unitString = it->first_;
+                String name = it->second_.GetString();
+                // Detect cube maps by file extension: they are defined by an XML file
+                /// \todo Differentiate with 3D textures by actually reading the XML content
+                if (GetExtension(name) == ".xml")
+                {
+    #ifdef DESKTOP_GRAPHICS
+                    TextureUnit unit = TU_DIFFUSE;
+                    unit = ParseTextureUnitName(unitString);
+
+                    if (unit == TU_VOLUMEMAP)
+                        cache->BackgroundLoadResource<Texture3D>(name, true, this);
+                    else
+    #endif
+                        cache->BackgroundLoadResource<TextureCube>(name, true, this);
+                }
+                else
+                    cache->BackgroundLoadResource<Texture2D>(name, true, this);
+            }
+        }
+
+        // JSON material was successfully loaded
+        return true;
     }
 
-    loadXMLFile_.Reset();
-    return success;
+    return false;
 }
 
-
 bool Material::Save(Serializer& dest) const
 {
     SharedPtr<XMLFile> xml(new XMLFile(context_));
@@ -400,6 +489,137 @@ bool Material::Load(const XMLElement& source)
     return true;
 }
 
+bool Material::Load(const JSONValue& source)
+{
+    ResetToDefaults();
+
+    if (source.IsNull())
+    {
+        URHO3D_LOGERROR("Can not load material from null JSON element");
+        return false;
+    }
+
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    // Load techniques
+    JSONArray techniquesArray = source.Get("techniques").GetArray();
+    techniques_.Clear();
+    techniques_.Reserve(techniquesArray.Size());
+
+    for (unsigned i = 0; i < techniquesArray.Size(); i++)
+    {
+        const JSONValue& techVal = techniquesArray[i];
+        Technique* tech = cache->GetResource<Technique>(techVal.Get("name").GetString());
+        if (tech)
+        {
+            TechniqueEntry newTechnique;
+            newTechnique.technique_ = tech;
+            JSONValue qualityVal = techVal.Get("quality");
+            if (!qualityVal.IsNull())
+                newTechnique.qualityLevel_ = qualityVal.GetInt();
+            JSONValue lodDistanceVal = techVal.Get("loddistance");
+            if (!lodDistanceVal.IsNull())
+                newTechnique.lodDistance_ = lodDistanceVal.GetFloat();
+            techniques_.Push(newTechnique);
+        }
+    }
+
+    SortTechniques();
+
+    // Load textures
+    JSONObject textureObject = source.Get("textures").GetObject();
+    for (JSONObject::ConstIterator it = textureObject.Begin(); it != textureObject.End(); it++)
+    {
+        String textureUnit = it->first_;
+        String textureName = it->second_.GetString();
+
+        TextureUnit unit = TU_DIFFUSE;
+        unit = ParseTextureUnitName(textureUnit);
+
+        if (unit < MAX_TEXTURE_UNITS)
+        {
+            // Detect cube maps by file extension: they are defined by an XML file
+            /// \todo Differentiate with 3D textures by actually reading the XML content
+            if (GetExtension(textureName) == ".xml")
+            {
+#ifdef DESKTOP_GRAPHICS
+                if (unit == TU_VOLUMEMAP)
+                    SetTexture(unit, cache->GetResource<Texture3D>(textureName));
+                else
+#endif
+                    SetTexture(unit, cache->GetResource<TextureCube>(textureName));
+            }
+            else
+                SetTexture(unit, cache->GetResource<Texture2D>(textureName));
+        }
+    }
+
+    // Get shader parameters
+    batchedParameterUpdate_ = true;
+    JSONObject parameterObject = source.Get("shaderParameters").GetObject();
+
+    for (JSONObject::ConstIterator it = parameterObject.Begin(); it != parameterObject.End(); it++)
+    {
+        String name = it->first_;
+        SetShaderParameter(name, ParseShaderParameterValue(it->second_.GetString()));
+    }
+    batchedParameterUpdate_ = false;
+
+    // Load shader parameter animationss
+    JSONObject paramAnimationsObject = source.Get("shaderParameterAnimations").GetObject();
+    for (JSONObject::ConstIterator it = paramAnimationsObject.Begin(); it != paramAnimationsObject.End(); it++)
+    {
+        String name = it->first_;
+        JSONValue paramAnimVal = it->second_;
+
+        SharedPtr<ValueAnimation> animation(new ValueAnimation(context_));
+        if (!animation->LoadJSON(paramAnimVal))
+        {
+            URHO3D_LOGERROR("Could not load parameter animation");
+            return false;
+        }
+
+        String wrapModeString = paramAnimVal.Get("wrapmode").GetString();
+        WrapMode wrapMode = WM_LOOP;
+        for (int i = 0; i <= WM_CLAMP; ++i)
+        {
+            if (wrapModeString == wrapModeNames[i])
+            {
+                wrapMode = (WrapMode)i;
+                break;
+            }
+        }
+
+        float speed = paramAnimVal.Get("speed").GetFloat();
+        SetShaderParameterAnimation(name, animation, wrapMode, speed);
+    }
+
+    JSONValue cullVal = source.Get("cull");
+    if (!cullVal.IsNull())
+        SetCullMode((CullMode)GetStringListIndex(cullVal.GetString().CString(), cullModeNames, CULL_CCW));
+
+    JSONValue shadowCullVal = source.Get("shadowcull");
+    if (!shadowCullVal.IsNull())
+        SetShadowCullMode((CullMode)GetStringListIndex(shadowCullVal.GetString().CString(), cullModeNames, CULL_CCW));
+
+    JSONValue fillVal = source.Get("fill");
+    if (!fillVal.IsNull())
+        SetFillMode((FillMode)GetStringListIndex(fillVal.GetString().CString(), fillModeNames, FILL_SOLID));
+
+    JSONValue depthBiasVal = source.Get("depthbias");
+    if (!depthBiasVal.IsNull())
+        SetDepthBias(BiasParameters(depthBiasVal.Get("constant").GetFloat(), depthBiasVal.Get("slopescaled").GetFloat()));
+
+    JSONValue renderOrderVal = source.Get("renderorder");
+    if (!renderOrderVal.IsNull())
+        SetRenderOrder((unsigned char)renderOrderVal.Get("value").GetUInt());
+
+    RefreshShaderParameterHash();
+    RefreshMemoryUse();
+    CheckOcclusion();
+    return true;
+}
+
 bool Material::Save(XMLElement& dest) const
 {
     if (dest.IsNull())
@@ -479,6 +699,84 @@ bool Material::Save(XMLElement& dest) const
     return true;
 }
 
+bool Material::Save(JSONValue& dest) const
+{
+    if (dest.IsNull())
+    {
+        URHO3D_LOGERROR("Can not save material to null JSON value");
+        return false;
+    }
+
+    // Write techniques
+    JSONArray techniquesArray;
+    techniquesArray.Reserve(techniques_.Size());
+    for (unsigned i = 0; i < techniques_.Size(); ++i)
+    {
+        const TechniqueEntry& entry = techniques_[i];
+        if (!entry.technique_)
+            continue;
+
+        JSONValue techniqueVal;
+        techniqueVal.Set("name", entry.technique_->GetName());
+        techniqueVal.Set("quality", (int) entry.qualityLevel_);
+        techniqueVal.Set("loddistance", entry.lodDistance_);
+        techniquesArray.Push(techniqueVal);
+    }
+    dest.Set("techniques", techniquesArray);
+
+    // Write texture units
+    JSONValue texturesValue;
+    for (unsigned j = 0; j < MAX_TEXTURE_UNITS; ++j)
+    {
+        Texture* texture = GetTexture((TextureUnit)j);
+        if (texture)
+            texturesValue.Set(textureUnitNames[j], texture->GetName());
+    }
+    dest.Set("textures", texturesValue);
+
+    // Write shader parameters
+    JSONValue shaderParamsVal;
+    for (HashMap<StringHash, MaterialShaderParameter>::ConstIterator j = shaderParameters_.Begin();
+         j != shaderParameters_.End(); ++j)
+    {
+        shaderParamsVal.Set(j->second_.name_, j->second_.value_.ToString());
+    }
+    dest.Set("shaderParameters", shaderParamsVal);
+
+    // Write shader parameter animations
+    JSONValue shaderParamAnimationsVal;
+    for (HashMap<StringHash, SharedPtr<ShaderParameterAnimationInfo> >::ConstIterator j = shaderParameterAnimationInfos_.Begin();
+         j != shaderParameterAnimationInfos_.End(); ++j)
+    {
+        ShaderParameterAnimationInfo* info = j->second_;
+        JSONValue paramAnimationVal;
+        if (!info->GetAnimation()->SaveJSON(paramAnimationVal))
+            return false;
+
+        paramAnimationVal.Set("wrapmode", wrapModeNames[info->GetWrapMode()]);
+        paramAnimationVal.Set("speed", info->GetSpeed());
+        shaderParamAnimationsVal.Set(info->GetName(), paramAnimationVal);
+    }
+    dest.Set("shaderParameterAnimations", shaderParamAnimationsVal);
+
+    // Write culling modes
+    dest.Set("cull", cullModeNames[cullMode_]);
+    dest.Set("shadowcull", cullModeNames[shadowCullMode_]);
+
+    // Write fill mode
+    dest.Set("fill", fillModeNames[fillMode_]);
+
+    // Write depth bias
+    JSONValue depthBiasValue;
+    depthBiasValue.Set("constant", depthBias_.constantBias_);
+    depthBiasValue.Set("slopescaled", depthBias_.slopeScaledBias_);
+
+    // Write render order
+    dest.Set("renderorder", (unsigned) renderOrder_);
+
+    return true;
+}
+
 void Material::SetNumTechniques(unsigned num)
 {
     if (!num)

+ 14 - 0
Source/Urho3D/Graphics/Material.h

@@ -39,6 +39,7 @@ class Texture;
 class Texture2D;
 class TextureCube;
 class ValueAnimationInfo;
+class JSONFile;
 
 static const unsigned char DEFAULT_RENDER_ORDER = 128;
 
@@ -123,6 +124,12 @@ public:
     bool Load(const XMLElement& source);
     /// Save to an XML element. Return true if successful.
     bool Save(XMLElement& dest) const;
+
+    /// Load from a JSON value. Return true if successful.
+    bool Load(const JSONValue& source);
+    /// Save to a JSON value. Return true if successful.
+    bool Save(JSONValue& dest) const;
+
     /// Set number of techniques.
     void SetNumTechniques(unsigned num);
     /// Set technique.
@@ -231,6 +238,11 @@ public:
     static Variant ParseShaderParameterValue(const String& value);
 
 private:
+    /// Helper function for loading JSON files
+    bool BeginLoadJSON(Deserializer& source);
+    /// Helper function for loading XML files
+    bool BeginLoadXML(Deserializer& source);
+
     /// Re-evaluate occlusion rendering.
     void CheckOcclusion();
     /// Reset to defaults.
@@ -278,6 +290,8 @@ private:
     bool batchedParameterUpdate_;
     /// XML file used while loading.
     SharedPtr<XMLFile> loadXMLFile_;
+    /// JSON file used while loading.
+    SharedPtr<JSONFile> loadJSONFile_;
     /// Associated scene for shader parameter animation updates.
     WeakPtr<Scene> scene_;
 };

+ 7 - 0
Source/Urho3D/LuaScript/pkgs/Scene/Node.pkg

@@ -23,6 +23,7 @@ class Node : public Animatable
     virtual ~Node();
 
     tolua_outside bool NodeSaveXML @ SaveXML(File* dest, const String indentation = "\t") const;
+    tolua_outside bool NodeSaveJSON @ SaveJSON(File* dest, const String indentation = "\t") const;
     void SetName(const String name);
 
     void SetPosition(const Vector3& position);
@@ -186,6 +187,7 @@ class Node : public Animatable
 
     bool Load(Deserializer& source, SceneResolver& resolver, bool loadChildren = true, bool rewriteIDs = false, CreateMode mode = REPLICATED);
     bool LoadXML(const XMLElement& source, SceneResolver& resolver, bool loadChildren = true, bool rewriteIDs = false, CreateMode mode = REPLICATED);
+    bool LoadJSON(const JSONValue& source, SceneResolver& resolver, bool loadChildren = true, bool rewriteIDs = false, CreateMode mode = REPLICATED);
 
     Node* CreateChild(unsigned id, CreateMode mode);
     void AddComponent(Component* component, unsigned id, CreateMode mode);
@@ -241,6 +243,11 @@ static bool NodeSaveXML(const Node* node, File* file, const String& indentation)
     return file ? node->SaveXML(*file, indentation) : false;
 }
 
+static bool NodeSaveJSON(const Node* node, File* file, const String& indentation)
+{
+    return file ? node->SaveJSON(*file, indentation) : false;
+}
+
 #define TOLUA_DISABLE_tolua_SceneLuaAPI_Node_CreateScriptObject00
 
 static int tolua_SceneLuaAPI_Node_CreateScriptObject00(lua_State* tolua_S)

+ 28 - 0
Source/Urho3D/LuaScript/pkgs/Scene/Scene.pkg

@@ -25,6 +25,10 @@ class Scene : public Node
     tolua_outside bool SceneSaveXML @ SaveXML(File* dest, const String indentation = "\t") const;
     tolua_outside bool SceneLoadXML @ LoadXML(const String fileName);
     tolua_outside bool SceneSaveXML @ SaveXML(const String fileName, const String indentation = "\t") const;
+    tolua_outside bool SceneLoadJSON @ LoadJSON(File* source);
+    tolua_outside bool SceneSaveJSON @ SaveJSON(File* dest, const String indentation = "\t") const;
+    tolua_outside bool SceneLoadJSON @ LoadJSON(const String fileName);
+    tolua_outside bool SceneSaveJSON @ SaveJSON(const String fileName, const String indentation = "\t") const;
     tolua_outside Node* SceneInstantiate @ Instantiate(File* source, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED);
     tolua_outside Node* SceneInstantiate @ Instantiate(const String fileName, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED);
     tolua_outside Node* SceneInstantiateXML @ InstantiateXML(File* source, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED);
@@ -152,6 +156,30 @@ static bool SceneSaveXML(const Scene* scene, const String& fileName, const Strin
     return scene->SaveXML(file, indentation);
 }
 
+static bool SceneLoadJSON(Scene* scene, File* file)
+{
+    return file ? scene->LoadJSON(*file) : false;
+}
+
+static bool SceneSaveJSON(const Scene* scene, File* file, const String& indentation)
+{
+    return file ? scene->SaveJSON(*file, indentation) : false;
+}
+
+static bool SceneLoadJSON(Scene* scene, const String& fileName)
+{
+    File file(scene->GetContext(), fileName, FILE_READ);
+    return file.IsOpen() && scene->LoadJSON(file);
+}
+
+static bool SceneSaveJSON(const Scene* scene, const String& fileName, const String& indentation)
+{
+    File file(scene->GetContext(), fileName, FILE_WRITE);
+    if (!file.IsOpen())
+        return false;
+    return scene->SaveJSON(file, indentation);
+}
+
 static bool SceneLoadAsync(Scene* scene, const String& fileName, LoadMode mode)
 {
     SharedPtr<File> file(new File(scene->GetContext(), fileName, FILE_READ));

+ 98 - 0
Source/Urho3D/Scene/Animatable.cpp

@@ -25,6 +25,7 @@
 #include "../Core/Context.h"
 #include "../IO/Log.h"
 #include "../Resource/ResourceCache.h"
+#include "../Resource/JSONValue.h"
 #include "../Resource/XMLElement.h"
 #include "../Scene/Animatable.h"
 #include "../Scene/ObjectAnimation.h"
@@ -127,6 +128,64 @@ bool Animatable::LoadXML(const XMLElement& source, bool setInstanceDefault)
     return true;
 }
 
+bool Animatable::LoadJSON(const JSONValue& source, bool setInstanceDefault)
+{
+    if (!Serializable::LoadJSON(source, setInstanceDefault))
+        return false;
+
+    SetObjectAnimation(0);
+    attributeAnimationInfos_.Clear();
+
+    JSONValue value = source.Get("objectanimation");
+    if (!value.IsNull())
+    {
+        SharedPtr<ObjectAnimation> objectAnimation(new ObjectAnimation(context_));
+        if (!objectAnimation->LoadJSON(value))
+            return false;
+
+        SetObjectAnimation(objectAnimation);
+    }
+
+    JSONValue attributeAnimationValue = source.Get("attributeanimation");
+
+    if (attributeAnimationValue.IsNull())
+        return true;
+
+    if (!attributeAnimationValue.IsObject())
+    {
+        URHO3D_LOGWARNING("'attributeanimation' value is present in JSON data, but is not a JSON object; skipping it");
+        return true;
+    }
+
+    const JSONObject& attributeAnimationObject = attributeAnimationValue.GetObject();
+    for (JSONObject::ConstIterator it = attributeAnimationObject.Begin(); it != attributeAnimationObject.End(); it++)
+    {
+        String name = it->first_;
+        JSONValue value = it->second_;
+        SharedPtr<ValueAnimation> attributeAnimation(new ValueAnimation(context_));
+        if (!attributeAnimation->LoadJSON(it->second_))
+            return false;
+
+        String wrapModeString = source.Get("wrapmode").GetString();
+        WrapMode wrapMode = WM_LOOP;
+        for (int i = 0; i <= WM_CLAMP; ++i)
+        {
+            if (wrapModeString == wrapModeNames[i])
+            {
+                wrapMode = (WrapMode)i;
+                break;
+            }
+        }
+
+        float speed = value.Get("speed").GetFloat();
+        SetAttributeAnimation(name, attributeAnimation, wrapMode, speed);
+
+        it++;
+    }
+
+    return true;
+}
+
 bool Animatable::SaveXML(XMLElement& dest) const
 {
     if (!Serializable::SaveXML(dest))
@@ -140,6 +199,7 @@ bool Animatable::SaveXML(XMLElement& dest) const
             return false;
     }
 
+
     for (HashMap<String, SharedPtr<AttributeAnimationInfo> >::ConstIterator i = attributeAnimationInfos_.Begin();
          i != attributeAnimationInfos_.End(); ++i)
     {
@@ -160,6 +220,44 @@ bool Animatable::SaveXML(XMLElement& dest) const
     return true;
 }
 
+bool Animatable::SaveJSON(JSONValue& dest) const
+{
+    if (!Serializable::SaveJSON(dest))
+        return false;
+
+    // Object animation without name
+    if (objectAnimation_ && objectAnimation_->GetName().Empty())
+    {
+        JSONValue objectAnimationValue;
+        if (!objectAnimation_->SaveJSON(objectAnimationValue))
+            return false;
+        dest.Set("objectanimation", objectAnimationValue);
+    }
+
+    JSONValue attributeAnimationValue;
+
+    for (HashMap<String, SharedPtr<AttributeAnimationInfo> >::ConstIterator i = attributeAnimationInfos_.Begin();
+         i != attributeAnimationInfos_.End(); ++i)
+    {
+        ValueAnimation* attributeAnimation = i->second_->GetAnimation();
+        if (attributeAnimation->GetOwner())
+            continue;
+
+        const AttributeInfo& attr = i->second_->GetAttributeInfo();
+        JSONValue attributeValue;
+        attributeValue.Set("name", attr.name_);
+        if (!attributeAnimation->SaveJSON(attributeValue))
+            return false;
+
+        attributeValue.Set("wrapmode", wrapModeNames[i->second_->GetWrapMode()]);
+        attributeValue.Set("speed", (float) i->second_->GetSpeed());
+
+        attributeAnimationValue.Set(attr.name_, attributeValue);
+    }
+
+    return true;
+}
+
 void Animatable::SetAnimationEnabled(bool enable)
 {
     if (objectAnimation_)

+ 4 - 0
Source/Urho3D/Scene/Animatable.h

@@ -76,6 +76,10 @@ public:
     virtual bool LoadXML(const XMLElement& source, bool setInstanceDefault = false);
     /// Save as XML data. Return true if successful.
     virtual bool SaveXML(XMLElement& dest) const;
+    /// Load from JSON data. When setInstanceDefault is set to true, after setting the attribute value, store the value as instance's default value. Return true if successful.
+    virtual bool LoadJSON(const JSONValue& source, bool setInstanceDefault = false);
+    /// Save as JSON data. Return true if successful.
+    virtual bool SaveJSON(JSONValue& dest) const;
 
     /// Set automatic update of animation, default true.
     void SetAnimationEnabled(bool enable);

+ 11 - 0
Source/Urho3D/Scene/Component.cpp

@@ -29,6 +29,7 @@
 #include "../Scene/SceneEvents.h"
 
 #include "../DebugNew.h"
+#include "./Resource/JSONValue.h"
 
 #ifdef _MSC_VER
 #pragma warning(disable:6293)
@@ -74,6 +75,16 @@ bool Component::SaveXML(XMLElement& dest) const
     return Animatable::SaveXML(dest);
 }
 
+bool Component::SaveJSON(JSONValue& dest) const
+{
+    // Write type and ID
+    dest.Set("type", GetTypeName());
+    dest.Set("id", (int) id_);
+
+    // Write attributes
+    return Animatable::SaveJSON(dest);
+}
+
 void Component::MarkNetworkUpdate()
 {
     if (!networkUpdate_ && id_ < FIRST_LOCAL_ID)

+ 2 - 0
Source/Urho3D/Scene/Component.h

@@ -54,6 +54,8 @@ public:
     virtual bool Save(Serializer& dest) const;
     /// Save as XML data. Return true if successful.
     virtual bool SaveXML(XMLElement& dest) const;
+    /// Save as JSON data. Return true if successful.
+    virtual bool SaveJSON(JSONValue& dest) const;
     /// Mark for attribute check on the next network update.
     virtual void MarkNetworkUpdate();
     /// Return the depended on nodes to order network updates.

+ 119 - 1
Source/Urho3D/Scene/Node.cpp

@@ -27,6 +27,7 @@
 #include "../IO/Log.h"
 #include "../IO/MemoryBuffer.h"
 #include "../Resource/XMLFile.h"
+#include "../Resource/JSONFile.h"
 #include "../Scene/Component.h"
 #include "../Scene/ObjectAnimation.h"
 #include "../Scene/ReplicationState.h"
@@ -169,6 +170,25 @@ bool Node::LoadXML(const XMLElement& source, bool setInstanceDefault)
     return success;
 }
 
+bool Node::LoadJSON(const JSONValue& source, bool setInstanceDefault)
+{
+    SceneResolver resolver;
+
+    // Read own ID. Will not be applied, only stored for resolving possible references
+    unsigned nodeID = source.Get("id").GetUInt();
+    resolver.AddNode(nodeID, this);
+
+    // Read attributes, components and child nodes
+    bool success = LoadJSON(source, resolver);
+    if (success)
+    {
+        resolver.Resolve();
+        ApplyAttributes();
+    }
+
+    return success;
+}
+
 bool Node::SaveXML(XMLElement& dest) const
 {
     // Write node ID
@@ -206,6 +226,49 @@ bool Node::SaveXML(XMLElement& dest) const
     return true;
 }
 
+bool Node::SaveJSON(JSONValue& dest) const
+{
+    // Write node ID
+    dest.Set("id", (unsigned) id_);
+
+    // Write attributes
+    if (!Animatable::SaveJSON(dest))
+        return false;
+
+    // Write components
+    JSONArray componentsArray;
+    componentsArray.Reserve(components_.Size());
+    for (unsigned i = 0; i < components_.Size(); ++i)
+    {
+        Component* component = components_[i];
+        if (component->IsTemporary())
+            continue;
+
+        JSONValue compVal;
+        if (!component->SaveJSON(compVal))
+            return false;
+        componentsArray.Push(compVal);
+    }
+    dest.Set("components", componentsArray);
+
+    // Write child nodes
+    JSONArray childrenArray;
+    childrenArray.Reserve(children_.Size());
+    for (unsigned i = 0; i < children_.Size(); ++i)
+    {
+        Node* node = children_[i];
+        if (node->IsTemporary())
+            continue;
+
+        JSONValue childVal;
+        if (!node->SaveJSON(childVal))
+            return false;
+        childrenArray.Push(childVal);
+    }
+
+    return true;
+}
+
 void Node::ApplyAttributes()
 {
     for (unsigned i = 0; i < components_.Size(); ++i)
@@ -242,6 +305,17 @@ bool Node::SaveXML(Serializer& dest, const String& indentation) const
     return xml->Save(dest, indentation);
 }
 
+bool Node::SaveJSON(Serializer& dest, const String& indentation) const
+{
+    SharedPtr<JSONFile> json(new JSONFile(context_));
+    JSONValue& rootElem = json->GetRoot();
+
+    if (!SaveJSON(rootElem))
+        return false;
+
+    return json->Save(dest, indentation);
+}
+
 void Node::SetName(const String& name)
 {
     if (name != name_)
@@ -1353,6 +1427,50 @@ bool Node::LoadXML(const XMLElement& source, SceneResolver& resolver, bool readC
     return true;
 }
 
+bool Node::LoadJSON(const JSONValue& source, SceneResolver& resolver, bool readChildren, bool rewriteIDs, CreateMode mode)
+{
+    // Remove all children and components first in case this is not a fresh load
+    RemoveAllChildren();
+    RemoveAllComponents();
+
+    if (!Animatable::LoadJSON(source))
+        return false;
+
+    const JSONArray& componentsArray = source.Get("components").GetArray();
+
+    for (unsigned i = 0; i < componentsArray.Size(); i++)
+    {
+        const JSONValue& compVal = componentsArray.At(i);
+        String typeName = compVal.Get("type").GetString();
+        unsigned compID = compVal.Get("id").GetUInt();
+        Component* newComponent = SafeCreateComponent(typeName, StringHash(typeName),
+            (mode == REPLICATED && compID < FIRST_LOCAL_ID) ? REPLICATED : LOCAL, rewriteIDs ? 0 : compID);
+        if (newComponent)
+        {
+            resolver.AddComponent(compID, newComponent);
+            if (!newComponent->LoadJSON(compVal))
+                return false;
+        }
+    }
+
+    if (!readChildren)
+        return true;
+
+    const JSONArray& childrenArray = source.Get("children").GetArray();
+    for (unsigned i = 0; i < childrenArray.Size(); i++)
+    {
+        const JSONValue& childVal = childrenArray.At(i);
+
+        unsigned nodeID = childVal.Get("id").GetUInt();
+        Node* newNode = CreateChild(rewriteIDs ? 0 : nodeID, (mode == REPLICATED && nodeID < FIRST_LOCAL_ID) ? REPLICATED :
+            LOCAL);
+        resolver.AddNode(nodeID, newNode);
+        if (!newNode->LoadJSON(childVal, resolver, readChildren, rewriteIDs, mode))
+            return false;
+    }
+
+    return true;
+}
 
 void Node::PrepareNetworkUpdate()
 {
@@ -1909,4 +2027,4 @@ void Node::HandleAttributeAnimationUpdate(StringHash eventType, VariantMap& even
     UpdateAttributeAnimations(eventData[P_TIMESTEP].GetFloat());
 }
 
-}
+}

+ 9 - 1
Source/Urho3D/Scene/Node.h

@@ -70,10 +70,14 @@ public:
     virtual bool Load(Deserializer& source, bool setInstanceDefault = false);
     /// Load from XML data. Return true if successful.
     virtual bool LoadXML(const XMLElement& source, bool setInstanceDefault = false);
+    /// Load from JSON data. Return true if successful.
+    virtual bool LoadJSON(const JSONValue& source, bool setInstanceDefault = false);
     /// Save as binary data. Return true if successful.
     virtual bool Save(Serializer& dest) const;
     /// Save as XML data. Return true if successful.
     virtual bool SaveXML(XMLElement& dest) const;
+    /// Save as JSON data. Return true if successful.
+    virtual bool SaveJSON(JSONValue& dest) const;
     /// Apply attribute changes that can not be applied immediately recursively to child nodes and components.
     virtual void ApplyAttributes();
 
@@ -87,6 +91,8 @@ public:
 
     /// Save to an XML file. Return true if successful.
     bool SaveXML(Serializer& dest, const String& indentation = "\t") const;
+    /// Save to a JSON file. Return true if successful.
+    bool SaveJSON(Serializer& dest, const String& indentation = "\t") const;
     /// Set name of the scene node. Names are not required to be unique.
     void SetName(const String& name);
     /// Set position in parent space. If the scene node is on the root level (is child of the scene itself), this is same as world space.
@@ -530,7 +536,9 @@ public:
     /// Load components from XML data and optionally load child nodes.
     bool LoadXML(const XMLElement& source, SceneResolver& resolver, bool loadChildren = true, bool rewriteIDs = false,
         CreateMode mode = REPLICATED);
-
+    /// Load components from XML data and optionally load child nodes.
+    bool LoadJSON(const JSONValue& source, SceneResolver& resolver, bool loadChildren = true, bool rewriteIDs = false,
+        CreateMode mode = REPLICATED);
     /// Return the depended on nodes to order network updates.
     const PODVector<Node*>& GetDependencyNodes() const { return dependencyNodes_; }
 

+ 63 - 0
Source/Urho3D/Scene/ObjectAnimation.cpp

@@ -24,6 +24,7 @@
 
 #include "../Core/Context.h"
 #include "../Resource/XMLFile.h"
+#include "../Resource/JSONFile.h"
 #include "../Scene/ObjectAnimation.h"
 #include "../Scene/SceneEvents.h"
 #include "../Scene/ValueAnimation.h"
@@ -129,6 +130,68 @@ bool ObjectAnimation::SaveXML(XMLElement& dest) const
     return true;
 }
 
+bool ObjectAnimation::LoadJSON(const JSONValue& source)
+{
+    attributeAnimationInfos_.Clear();
+
+    JSONValue attributeAnimationsValue = source.Get("attributeanimations");
+    if (attributeAnimationsValue.IsNull())
+        return true;
+    if (!attributeAnimationsValue.IsObject())
+        return true;
+
+    const JSONObject& attributeAnimationsObject = attributeAnimationsValue.GetObject();
+
+    for (JSONObject::ConstIterator it = attributeAnimationsObject.Begin(); it != attributeAnimationsObject.End(); it++)
+    {
+        String name = it->first_;
+        JSONValue value = it->second_;
+        SharedPtr<ValueAnimation> animation(new ValueAnimation(context_));
+        if (!animation->LoadJSON(value))
+            return false;
+
+        String wrapModeString = value.Get("wrapmode").GetString();
+        WrapMode wrapMode = WM_LOOP;
+        for (int i = 0; i <= WM_CLAMP; ++i)
+        {
+            if (wrapModeString == wrapModeNames[i])
+            {
+                wrapMode = (WrapMode)i;
+                break;
+            }
+        }
+
+        float speed = value.Get("speed").GetFloat();
+        AddAttributeAnimation(name, animation, wrapMode, speed);
+    }
+
+    return true;
+}
+
+bool ObjectAnimation::SaveJSON(JSONValue& dest) const
+{
+    JSONValue attributeAnimationsValue;
+
+    for (HashMap<String, SharedPtr<ValueAnimationInfo> >::ConstIterator i = attributeAnimationInfos_.Begin();
+         i != attributeAnimationInfos_.End(); ++i)
+    {
+        JSONValue animValue;
+        animValue.Set("name", i->first_);
+
+        const ValueAnimationInfo* info = i->second_;
+        if (!info->GetAnimation()->SaveJSON(animValue))
+            return false;
+
+        animValue.Set("wrapmode", wrapModeNames[info->GetWrapMode()]);
+        animValue.Set("speed", (float) info->GetSpeed());
+
+        attributeAnimationsValue.Set(i->first_, animValue);
+    }
+
+    dest.Set("attributeanimations", attributeAnimationsValue);
+    return true;
+}
+
 void ObjectAnimation::AddAttributeAnimation(const String& name, ValueAnimation* attributeAnimation, WrapMode wrapMode, float speed)
 {
     if (!attributeAnimation)

+ 5 - 0
Source/Urho3D/Scene/ObjectAnimation.h

@@ -31,6 +31,7 @@ namespace Urho3D
 class ValueAnimation;
 class ValueAnimationInfo;
 class XMLElement;
+class JSONValue;
 
 /// Object animation class, an object animation include one or more attribute animations and theirs wrap mode and speed for an Animatable object.
 class URHO3D_API ObjectAnimation : public Resource
@@ -53,6 +54,10 @@ public:
     bool LoadXML(const XMLElement& source);
     /// Save as XML data. Return true if successful.
     bool SaveXML(XMLElement& dest) const;
+    /// Load from JSON data. Return true if successful.
+    bool LoadJSON(const JSONValue& source);
+    /// Save as JSON data. Return true if successful.
+    bool SaveJSON(JSONValue& dest) const;
 
     /// Add attribute animation, attribute name can in following format: "attribute" or "#0/#1/attribute" or ""#0/#1/@component#1/attribute.
     void AddAttributeAnimation

+ 266 - 8
Source/Urho3D/Scene/Scene.cpp

@@ -32,6 +32,7 @@
 #include "../Resource/ResourceCache.h"
 #include "../Resource/ResourceEvents.h"
 #include "../Resource/XMLFile.h"
+#include "../Resource/JSONFile.h"
 #include "../Scene/Component.h"
 #include "../Scene/ObjectAnimation.h"
 #include "../Scene/ReplicationState.h"
@@ -178,6 +179,23 @@ bool Scene::LoadXML(const XMLElement& source, bool setInstanceDefault)
         return false;
 }
 
+bool Scene::LoadJSON(const JSONValue& source, bool setInstanceDefault)
+{
+    URHO3D_PROFILE(LoadSceneJSON);
+
+    StopAsyncLoading();
+
+    // Load the whole scene, then perform post-load if successfully loaded
+    // Note: the scene filename and checksum can not be set, as we only used an XML element
+    if (Node::LoadJSON(source, setInstanceDefault))
+    {
+        FinishLoading(0);
+        return true;
+    }
+    else
+        return false;
+}
+
 void Scene::MarkNetworkUpdate()
 {
     if (!networkUpdate_)
@@ -219,6 +237,29 @@ bool Scene::LoadXML(Deserializer& source)
         return false;
 }
 
+bool Scene::LoadJSON(Deserializer& source)
+{
+    URHO3D_PROFILE(LoadSceneJSON);
+
+    StopAsyncLoading();
+
+    SharedPtr<JSONFile> json(new JSONFile(context_));
+    if (!json->Load(source))
+        return false;
+
+    URHO3D_LOGINFO("Loading scene from " + source.GetName());
+
+    Clear();
+
+    if (Node::LoadJSON(json->GetRoot()))
+    {
+        FinishLoading(&source);
+        return true;
+    }
+    else
+        return false;
+}
+
 bool Scene::SaveXML(Serializer& dest, const String& indentation) const
 {
     URHO3D_PROFILE(SaveSceneXML);
@@ -241,6 +282,30 @@ bool Scene::SaveXML(Serializer& dest, const String& indentation) const
         return false;
 }
 
+bool Scene::SaveJSON(Serializer& dest, const String& indentation) const
+{
+    URHO3D_PROFILE(SaveSceneJSON);
+
+    SharedPtr<JSONFile> json(new JSONFile(context_));
+    JSONValue rootVal;
+    if (!SaveJSON(rootVal))
+        return false;
+
+    Deserializer* ptr = dynamic_cast<Deserializer*>(&dest);
+    if (ptr)
+        URHO3D_LOGINFO("Saving scene to " + ptr->GetName());
+
+    json->GetRoot() = rootVal;
+
+    if (json->Save(dest, indentation))
+    {
+        FinishSaving(&dest);
+        return true;
+    }
+    else
+        return false;
+}
+
 bool Scene::LoadAsync(File* file, LoadMode mode)
 {
     if (!file)
@@ -383,12 +448,79 @@ bool Scene::LoadAsyncXML(File* file, LoadMode mode)
     return true;
 }
 
+bool Scene::LoadAsyncJSON(File* file, LoadMode mode)
+{
+    if (!file)
+    {
+        URHO3D_LOGERROR("Null file for async loading");
+        return false;
+    }
+
+    StopAsyncLoading();
+
+    SharedPtr<JSONFile> json(new JSONFile(context_));
+    if (!json->Load(*file))
+        return false;
+
+    if (mode > LOAD_RESOURCES_ONLY)
+    {
+        URHO3D_LOGINFO("Loading scene from " + file->GetName());
+        Clear();
+    }
+
+    asyncLoading_ = true;
+    asyncProgress_.jsonFile_ = json;
+    asyncProgress_.file_ = file;
+    asyncProgress_.mode_ = mode;
+    asyncProgress_.loadedNodes_ = asyncProgress_.totalNodes_ = asyncProgress_.loadedResources_ = asyncProgress_.totalResources_ = 0;
+    asyncProgress_.resources_.Clear();
+
+    if (mode > LOAD_RESOURCES_ONLY)
+    {
+        JSONValue rootVal = json->GetRoot();
+
+        // Preload resources if appropriate
+        if (mode != LOAD_SCENE)
+        {
+            URHO3D_PROFILE(FindResourcesToPreload);
+
+            PreloadResourcesJSON(rootVal);
+        }
+
+        // Store own old ID for resolving possible root node references
+        unsigned nodeID = rootVal.Get("id").GetUInt();
+        resolver_.AddNode(nodeID, this);
+
+        // Load the root level components first
+        if (!Node::LoadJSON(rootVal, resolver_, false))
+            return false;
+
+        // Then prepare for loading all root level child nodes in the async update
+        JSONArray childrenArray = rootVal.Get("children").GetArray();
+        asyncProgress_.jsonIndex_ = 0;
+
+        // Count the amount of child nodes
+        asyncProgress_.totalNodes_ = childrenArray.Size();
+    }
+    else
+    {
+        URHO3D_PROFILE(FindResourcesToPreload);
+
+        URHO3D_LOGINFO("Preloading resources from " + file->GetName());
+        PreloadResourcesJSON(json->GetRoot());
+    }
+
+    return true;
+}
+
 void Scene::StopAsyncLoading()
 {
     asyncLoading_ = false;
     asyncProgress_.file_.Reset();
     asyncProgress_.xmlFile_.Reset();
+    asyncProgress_.jsonFile_.Reset();
     asyncProgress_.xmlElement_ = XMLElement::EMPTY;
+    asyncProgress_.jsonIndex_ = 0;
     asyncProgress_.resources_.Clear();
     resolver_.Reset();
 }
@@ -439,6 +571,29 @@ Node* Scene::InstantiateXML(const XMLElement& source, const Vector3& position, c
     }
 }
 
+Node* Scene::InstantiateJSON(const JSONValue& source, const Vector3& position, const Quaternion& rotation, CreateMode mode)
+{
+    URHO3D_PROFILE(InstantiateJSON);
+
+    SceneResolver resolver;
+    unsigned nodeID = source.Get("id").GetUInt();
+    // Rewrite IDs when instantiating
+    Node* node = CreateChild(0, mode);
+    resolver.AddNode(nodeID, node);
+    if (node->LoadJSON(source, resolver, true, true, mode))
+    {
+        resolver.Resolve();
+        node->ApplyAttributes();
+        node->SetTransform(position, rotation);
+        return node;
+    }
+    else
+    {
+        node->Remove();
+        return 0;
+    }
+}
+
 Node* Scene::InstantiateXML(Deserializer& source, const Vector3& position, const Quaternion& rotation, CreateMode mode)
 {
     SharedPtr<XMLFile> xml(new XMLFile(context_));
@@ -448,6 +603,15 @@ Node* Scene::InstantiateXML(Deserializer& source, const Vector3& position, const
     return InstantiateXML(xml->GetRoot(), position, rotation, mode);
 }
 
+Node* Scene::InstantiateJSON(Deserializer& source, const Vector3& position, const Quaternion& rotation, CreateMode mode)
+{
+    SharedPtr<JSONFile> json(new JSONFile(context_));
+    if (!json->Load(source))
+        return 0;
+
+    return InstantiateJSON(json->GetRoot(), position, rotation, mode);
+}
+
 void Scene::Clear(bool clearReplicated, bool clearLocal)
 {
     StopAsyncLoading();
@@ -1001,22 +1165,33 @@ void Scene::UpdateAsyncLoading()
             return;
         }
 
-        // Read one child node with its full sub-hierarchy either from binary or XML
+
+        // Read one child node with its full sub-hierarchy either from binary, JSON, or XML
         /// \todo Works poorly in scenes where one root-level child node contains all content
-        if (!asyncProgress_.xmlFile_)
+        if (asyncProgress_.xmlFile_)
         {
-            unsigned nodeID = asyncProgress_.file_->ReadUInt();
+            unsigned nodeID = asyncProgress_.xmlElement_.GetUInt("id");
             Node* newNode = CreateChild(nodeID, nodeID < FIRST_LOCAL_ID ? REPLICATED : LOCAL);
             resolver_.AddNode(nodeID, newNode);
-            newNode->Load(*asyncProgress_.file_, resolver_);
+            newNode->LoadXML(asyncProgress_.xmlElement_, resolver_);
+            asyncProgress_.xmlElement_ = asyncProgress_.xmlElement_.GetNext("node");
         }
-        else
+        else if (asyncProgress_.jsonFile_) // Load from JSON
         {
-            unsigned nodeID = asyncProgress_.xmlElement_.GetUInt("id");
+            const JSONValue& childValue = asyncProgress_.jsonFile_->GetRoot().Get("children").GetArray().At(asyncProgress_.jsonIndex_);
+
+            unsigned nodeID =childValue.Get("id").GetUInt();
             Node* newNode = CreateChild(nodeID, nodeID < FIRST_LOCAL_ID ? REPLICATED : LOCAL);
             resolver_.AddNode(nodeID, newNode);
-            newNode->LoadXML(asyncProgress_.xmlElement_, resolver_);
-            asyncProgress_.xmlElement_ = asyncProgress_.xmlElement_.GetNext("node");
+            newNode->LoadJSON(childValue, resolver_);
+            ++asyncProgress_.jsonIndex_;
+        }
+        else // Load from binary
+        {
+            unsigned nodeID = asyncProgress_.file_->ReadUInt();
+            Node* newNode = CreateChild(nodeID, nodeID < FIRST_LOCAL_ID ? REPLICATED : LOCAL);
+            resolver_.AddNode(nodeID, newNode);
+            newNode->Load(*asyncProgress_.file_, resolver_);
         }
 
         ++asyncProgress_.loadedNodes_;
@@ -1231,6 +1406,89 @@ void Scene::PreloadResourcesXML(const XMLElement& element)
 #endif
 }
 
+void Scene::PreloadResourcesJSON(const JSONValue& value)
+{
+    // If not threaded, can not background load resources, so rather load synchronously later when needed
+#ifdef URHO3D_THREADING
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    // Node or Scene attributes do not include any resources; therefore skip to the components
+    JSONArray componentArray = value.Get("components").GetArray();
+
+    for (unsigned i = 0; i < componentArray.Size(); i++)
+    {
+        const JSONValue& compValue = componentArray.At(i);
+        String typeName = compValue.Get("type").GetString();
+
+        const Vector<AttributeInfo>* attributes = context_->GetAttributes(StringHash(typeName));
+        if (attributes)
+        {
+            JSONArray attributesArray = compValue.Get("attributes").GetArray();
+
+            unsigned startIndex = 0;
+
+            for (unsigned j = 0; j < attributesArray.Size(); j++)
+            {
+                const JSONValue& attrVal = attributesArray.At(j);
+                String name = attrVal.Get("name").GetString();
+                unsigned i = startIndex;
+                unsigned attempts = attributes->Size();
+
+                while (attempts)
+                {
+                    const AttributeInfo& attr = attributes->At(i);
+                    if ((attr.mode_ & AM_FILE) && !attr.name_.Compare(name, true))
+                    {
+                        if (attr.type_ == VAR_RESOURCEREF)
+                        {
+                            ResourceRef ref = attrVal.Get("value").GetVariantValue(attr.type_).GetResourceRef();
+                            String name = cache->SanitateResourceName(ref.name_);
+                            bool success = cache->BackgroundLoadResource(ref.type_, name);
+                            if (success)
+                            {
+                                ++asyncProgress_.totalResources_;
+                                asyncProgress_.resources_.Insert(StringHash(name));
+                            }
+                        }
+                        else if (attr.type_ == VAR_RESOURCEREFLIST)
+                        {
+                            ResourceRefList refList = attrVal.Get("value").GetVariantValue(attr.type_).GetResourceRefList();
+                            for (unsigned k = 0; k < refList.names_.Size(); ++k)
+                            {
+                                String name = cache->SanitateResourceName(refList.names_[k]);
+                                bool success = cache->BackgroundLoadResource(refList.type_, name);
+                                if (success)
+                                {
+                                    ++asyncProgress_.totalResources_;
+                                    asyncProgress_.resources_.Insert(StringHash(name));
+                                }
+                            }
+                        }
+
+                        startIndex = (i + 1) % attributes->Size();
+                        break;
+                    }
+                    else
+                    {
+                        i = (i + 1) % attributes->Size();
+                        --attempts;
+                    }
+                }
+
+            }
+        }
+
+    }
+
+    JSONArray childrenArray = value.Get("children").GetArray();
+    for (unsigned i = 0; i < childrenArray.Size(); i++)
+    {
+        const JSONValue& childVal = childrenArray.At(i);
+        PreloadResourcesJSON(childVal);
+    }
+#endif
+}
+
 void RegisterSceneLibrary(Context* context)
 {
     ValueAnimation::RegisterObject(context);

+ 26 - 0
Source/Urho3D/Scene/Scene.h

@@ -25,6 +25,7 @@
 #include "../Container/HashSet.h"
 #include "../Core/Mutex.h"
 #include "../Resource/XMLElement.h"
+#include "../Resource/JSONFile.h"
 #include "../Scene/Node.h"
 #include "../Scene/SceneResolver.h"
 
@@ -57,8 +58,15 @@ struct AsyncProgress
     SharedPtr<File> file_;
     /// XML file for XML mode.
     SharedPtr<XMLFile> xmlFile_;
+    /// JSON file for JSON mode
+    SharedPtr<JSONFile> jsonFile_;
+
     /// Current XML element for XML mode.
     XMLElement xmlElement_;
+
+    /// Current JSON child array and for JSON mode
+    unsigned jsonIndex_;
+
     /// Current load mode.
     LoadMode mode_;
     /// Resource name hashes left to load.
@@ -80,6 +88,7 @@ class URHO3D_API Scene : public Node
 
     using Node::GetComponent;
     using Node::SaveXML;
+    using Node::SaveJSON;
 
 public:
     /// Construct.
@@ -95,6 +104,8 @@ public:
     virtual bool Save(Serializer& dest) const;
     /// Load from XML data. Removes all existing child nodes and components first. Return true if successful.
     virtual bool LoadXML(const XMLElement& source, bool setInstanceDefault = false);
+    /// Load from JSON data. Removes all existing child nodes and components first. Return true if successful.
+    virtual bool LoadJSON(const JSONValue& source, bool setInstanceDefault = false);
     /// Mark for attribute check on the next network update.
     virtual void MarkNetworkUpdate();
     /// Add a replication state that is tracking this scene.
@@ -102,12 +113,18 @@ public:
 
     /// Load from an XML file. Return true if successful.
     bool LoadXML(Deserializer& source);
+    /// Load from a JSON file. Return true if successful.
+    bool LoadJSON(Deserializer& source);
     /// Save to an XML file. Return true if successful.
     bool SaveXML(Serializer& dest, const String& indentation = "\t") const;
+    /// Save to a JSON file. Return true if successful.
+    bool SaveJSON(Serializer& dest, const String& indentation = "\t") const;
     /// Load from a binary file asynchronously. Return true if started successfully. The LOAD_RESOURCES_ONLY mode can also be used to preload resources from object prefab files.
     bool LoadAsync(File* file, LoadMode mode = LOAD_SCENE_AND_RESOURCES);
     /// Load from an XML file asynchronously. Return true if started successfully. The LOAD_RESOURCES_ONLY mode can also be used to preload resources from object prefab files.
     bool LoadAsyncXML(File* file, LoadMode mode = LOAD_SCENE_AND_RESOURCES);
+    /// Load from a JSON file asynchronously. Return true if started successfully. The LOAD_RESOURCES_ONLY mode can also be used to preload resources from object prefab files.
+    bool LoadAsyncJSON(File* file, LoadMode mode = LOAD_SCENE_AND_RESOURCES);
     /// Stop asynchronous loading.
     void StopAsyncLoading();
     /// Instantiate scene content from binary data. Return root node if successful.
@@ -117,6 +134,13 @@ public:
         (const XMLElement& source, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED);
     /// Instantiate scene content from XML data. Return root node if successful.
     Node* InstantiateXML(Deserializer& source, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED);
+    /// Instantiate scene content from JSON data. Return root node if successful.
+    Node* InstantiateJSON
+        (const JSONValue& source, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED);
+    /// Instantiate scene content from XML data. Return root node if successful.
+    Node* InstantiateJSON(Deserializer& source, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED);
+
+
     /// Clear scene completely of either replicated, local or all nodes and components.
     void Clear(bool clearReplicated = true, bool clearLocal = true);
     /// Enable or disable scene update.
@@ -242,6 +266,8 @@ private:
     void PreloadResources(File* file, bool isSceneFile);
     /// Preload resources from an XML scene or object prefab file.
     void PreloadResourcesXML(const XMLElement& element);
+    /// Preload resources from a JSON scene or object prefab file.
+    void PreloadResourcesJSON(const JSONValue& value);
 
     /// Replicated scene nodes by ID.
     HashMap<unsigned, Node*> replicatedNodes_;

+ 139 - 0
Source/Urho3D/Scene/Serializable.cpp

@@ -27,6 +27,7 @@
 #include "../IO/Log.h"
 #include "../IO/Serializer.h"
 #include "../Resource/XMLElement.h"
+#include "../Resource/JSONValue.h"
 #include "../Scene/ReplicationState.h"
 #include "../Scene/SceneEvents.h"
 #include "../Scene/Serializable.h"
@@ -410,6 +411,99 @@ bool Serializable::LoadXML(const XMLElement& source, bool setInstanceDefault)
     return true;
 }
 
+bool Serializable::LoadJSON(const JSONValue& source, bool setInstanceDefault)
+{
+    if (source.IsNull())
+    {
+        URHO3D_LOGERROR("Could not load " + GetTypeName() + ", null JSON source element");
+        return false;
+    }
+
+    const Vector<AttributeInfo>* attributes = GetAttributes();
+    if (!attributes)
+        return true;
+
+    // Get attributes value
+    JSONValue attributesValue = source.Get("attributes");
+    if (attributesValue.IsNull())
+        return true;
+    // Warn if the attributes value isn't an object
+    if (!attributesValue.IsObject())
+    {
+        URHO3D_LOGWARNING("'attributes' object is present in " + GetTypeName() + " but is not a JSON object; skipping load");
+        return true;
+    }
+
+    const JSONObject& attributesObject = attributesValue.GetObject();
+
+    unsigned startIndex = 0;
+
+    for (JSONObject::ConstIterator it = attributesObject.Begin(); it != attributesObject.End();)
+    {
+        String name = it->first_;
+        const JSONValue& value = it->second_;
+        unsigned i = startIndex;
+        unsigned attempts = attributesObject.Size();
+
+        while (attempts)
+        {
+            const AttributeInfo& attr = attributes->At(i);
+            if ((attr.mode_ & AM_FILE) && !attr.name_.Compare(name, true))
+            {
+                Variant varValue;
+
+                // If enums specified, do enum lookup ad int assignment. Otherwise assign variant directly
+                if (attr.enumNames_)
+                {
+                    String valueStr = value.GetString();
+                    bool enumFound = false;
+                    int enumValue = 0;
+                    const char** enumPtr = attr.enumNames_;
+                    while (*enumPtr)
+                    {
+                        if (!valueStr.Compare(*enumPtr, false))
+                        {
+                            enumFound = true;
+                            break;
+                        }
+                        ++enumPtr;
+                        ++enumValue;
+                    }
+                    if (enumFound)
+                        varValue = enumValue;
+                    else
+                        URHO3D_LOGWARNING("Unknown enum value " + valueStr + " in attribute " + attr.name_);
+                }
+                else
+                    varValue = value.GetVariantValue(attr.type_);
+
+                if (!varValue.IsEmpty())
+                {
+                    OnSetAttribute(attr, varValue);
+
+                    if (setInstanceDefault)
+                        SetInstanceDefault(attr.name_, varValue);
+                }
+
+                startIndex = (i + 1) % attributes->Size();
+                break;
+            }
+            else
+            {
+                i = (i + 1) % attributes->Size();
+                --attempts;
+            }
+        }
+
+        if (!attempts)
+            URHO3D_LOGWARNING("Unknown attribute " + name + " in JSON data");
+
+        it++;
+    }
+
+    return true;
+}
+
 bool Serializable::SaveXML(XMLElement& dest) const
 {
     if (dest.IsNull())
@@ -452,6 +546,51 @@ bool Serializable::SaveXML(XMLElement& dest) const
     return true;
 }
 
+bool Serializable::SaveJSON(JSONValue& dest) const
+{
+    if (dest.IsNull())
+    {
+        URHO3D_LOGERROR("Could not save " + GetTypeName() + ", null destination JSON value");
+        return false;
+    }
+
+    const Vector<AttributeInfo>* attributes = GetAttributes();
+    if (!attributes)
+        return true;
+
+    Variant value;
+    JSONValue attributesValue;
+
+    for (unsigned i = 0; i < attributes->Size(); ++i)
+    {
+        const AttributeInfo& attr = attributes->At(i);
+        if (!(attr.mode_ & AM_FILE))
+            continue;
+
+        OnGetAttribute(attr, value);
+        Variant defaultValue(GetAttributeDefault(i));
+
+        // In JSON serialization default values can be skipped. This will make the file easier to read or edit manually
+        if (value == defaultValue && !SaveDefaultAttributes())
+            continue;
+
+        JSONValue attrVal;
+        // If enums specified, set as an enum string. Otherwise set directly as a Variant
+        if (attr.enumNames_)
+        {
+            int enumValue = value.GetInt();
+            attrVal = attr.enumNames_[enumValue];
+        }
+        else
+            attrVal.SetVariantValue(value);
+
+        attributesValue.Set(attr.name_, attrVal);
+    }
+    dest.Set("attributes", attributesValue);
+
+    return true;
+}
+
 bool Serializable::SetAttribute(unsigned index, const Variant& value)
 {
     const Vector<AttributeInfo>* attributes = GetAttributes();

+ 5 - 0
Source/Urho3D/Scene/Serializable.h

@@ -34,6 +34,7 @@ class Connection;
 class Deserializer;
 class Serializer;
 class XMLElement;
+class JSONValue;
 
 struct DirtyBits;
 struct NetworkState;
@@ -66,6 +67,10 @@ public:
     virtual bool LoadXML(const XMLElement& source, bool setInstanceDefault = false);
     /// Save as XML data. Return true if successful.
     virtual bool SaveXML(XMLElement& dest) const;
+    /// Load from JSON data. When setInstanceDefault is set to true, after setting the attribute value, store the value as instance's default value. Return true if successful.
+    virtual bool LoadJSON(const JSONValue& source, bool setInstanceDefault = false);
+    /// Save as JSON data. Return true if successful.
+    virtual bool SaveJSON(JSONValue& dest) const;
 
     /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
     virtual void ApplyAttributes() { }

+ 66 - 1
Source/Urho3D/Scene/UnknownComponent.cpp

@@ -27,6 +27,7 @@
 #include "../IO/Log.h"
 #include "../IO/Serializer.h"
 #include "../Resource/XMLElement.h"
+#include "../Resource/JSONValue.h"
 #include "../Scene/UnknownComponent.h"
 
 #include "../DebugNew.h"
@@ -136,6 +137,41 @@ bool UnknownComponent::LoadXML(const XMLElement& source, bool setInstanceDefault
     return true;
 }
 
+
+bool UnknownComponent::LoadJSON(const JSONValue& source, bool setInstanceDefault)
+{
+    useXML_ = true;
+    xmlAttributes_.Clear();
+    xmlAttributeInfos_.Clear();
+    binaryAttributes_.Clear();
+
+    JSONArray attributesArray = source.Get("attributes").GetArray();
+    for (unsigned i = 0; i < attributesArray.Size(); i++)
+    {
+        const JSONValue& attrVal = attributesArray.At(i);
+
+        AttributeInfo attr;
+        attr.mode_ = AM_FILE;
+        attr.name_ = attrVal.Get("name").GetString();
+        attr.type_ = VAR_STRING;
+
+        if (!attr.name_.Empty())
+        {
+            String attrValue = attrVal.Get("value").GetString();
+            attr.defaultValue_ = String::EMPTY;
+            xmlAttributeInfos_.Push(attr);
+            xmlAttributes_.Push(attrValue);
+        }
+    }
+
+    // Fix up pointers to the attributes after all have been read
+    for (unsigned i = 0; i < xmlAttributeInfos_.Size(); ++i)
+        xmlAttributeInfos_[i].ptr_ = &xmlAttributes_[i];
+
+    return true;
+}
+
+
 bool UnknownComponent::Save(Serializer& dest) const
 {
     if (useXML_)
@@ -162,7 +198,7 @@ bool UnknownComponent::SaveXML(XMLElement& dest) const
     }
 
     if (!useXML_)
-        URHO3D_LOGWARNING("UnknownComponent loaded in binary mode, attributes will be empty for XML save");
+        URHO3D_LOGWARNING("UnknownComponent loaded in binary or JSON mode, attributes will be empty for XML save");
 
     // Write type and ID
     if (!dest.SetString("type", GetTypeName()))
@@ -180,6 +216,35 @@ bool UnknownComponent::SaveXML(XMLElement& dest) const
     return true;
 }
 
+bool UnknownComponent::SaveJSON(JSONValue& dest) const
+{
+    if (dest.IsNull())
+    {
+        URHO3D_LOGERROR("Could not save " + GetTypeName() + ", null destination element");
+        return false;
+    }
+
+    if (!useXML_)
+        URHO3D_LOGWARNING("UnknownComponent loaded in binary mode, attributes will be empty for JSON save");
+
+    // Write type and ID
+    dest.Set("type", GetTypeName());
+    dest.Set("id", (int) id_);
+
+    JSONArray attributesArray;
+    attributesArray.Reserve(xmlAttributeInfos_.Size());
+    for (unsigned i = 0; i < xmlAttributeInfos_.Size(); ++i)
+    {
+        JSONValue attrVal;
+        attrVal.Set("name", xmlAttributeInfos_[i].name_);
+        attrVal.Set("value", xmlAttributes_[i]);
+        attributesArray.Push(attrVal);
+    }
+    dest.Set("attributes", attributesArray);
+
+    return true;
+}
+
 void UnknownComponent::SetTypeName(const String& typeName)
 {
     typeName_ = typeName;

+ 6 - 1
Source/Urho3D/Scene/UnknownComponent.h

@@ -50,10 +50,14 @@ public:
     virtual bool Load(Deserializer& source, bool setInstanceDefault = false);
     /// Load from XML data. Return true if successful.
     virtual bool LoadXML(const XMLElement& source, bool setInstanceDefault = false);
+    /// Load from JSON data. Return true if successful.
+    virtual bool LoadJSON(const JSONValue& source, bool setInstanceDefault = false);
     /// Save as binary data. Return true if successful.
     virtual bool Save(Serializer& dest) const;
     /// Save as XML data. Return true if successful.
     virtual bool SaveXML(XMLElement& dest) const;
+    /// Save as JSON data. Return true if successful.
+    virtual bool SaveJSON(JSONValue& dest) const;
 
     /// Initialize the type name. Called by Node when loading.
     void SetTypeName(const String& typeName);
@@ -93,8 +97,9 @@ private:
     Vector<String> xmlAttributes_;
     /// Binary attributes.
     PODVector<unsigned char> binaryAttributes_;
-    /// Flag of whether was loaded using XML data.
+    /// Flag of whether was loaded using XML/JSON data.
     bool useXML_;
+
 };
 
 }

+ 84 - 0
Source/Urho3D/Scene/ValueAnimation.cpp

@@ -27,6 +27,7 @@
 #include "../IO/Log.h"
 #include "../IO/Serializer.h"
 #include "../Resource/XMLFile.h"
+#include "../Resource/JSONFile.h"
 #include "../Scene/Animatable.h"
 #include "../Scene/ObjectAnimation.h"
 #include "../Scene/ValueAnimation.h"
@@ -155,6 +156,89 @@ bool ValueAnimation::SaveXML(XMLElement& dest) const
     return true;
 }
 
+bool ValueAnimation::LoadJSON(const JSONValue& source)
+{
+    valueType_ = VAR_NONE;
+    eventFrames_.Clear();
+
+    String interpMethodString = source.Get("interpolationmethod").GetString();
+    InterpMethod method = IM_LINEAR;
+    for (int i = 0; i <= IM_SPLINE; ++i)
+    {
+        if (interpMethodString == interpMethodNames[i])
+        {
+            method = (InterpMethod)i;
+            break;
+        }
+    }
+
+    SetInterpolationMethod(method);
+    if (interpolationMethod_ == IM_SPLINE)
+        splineTension_ = source.Get("splinetension").GetFloat();
+
+    // Load keyframes
+    JSONArray keyFramesArray = source.Get("keyframes").GetArray();
+    for (unsigned i = 0; i < keyFramesArray.Size(); i++)
+    {
+        const JSONValue& val = keyFramesArray[i];
+        float time = val.Get("time").GetFloat();
+        Variant value = val.Get("value").GetVariant();
+        SetKeyFrame(time, value);
+    }
+
+    // Load event frames
+    JSONArray eventFramesArray = source.Get("eventframes").GetArray();
+    for (unsigned i = 0; i < eventFramesArray.Size(); i++)
+    {
+        const JSONValue& eventFrameVal = eventFramesArray[i];
+        float time = eventFrameVal.Get("time").GetFloat();
+        unsigned eventType = eventFrameVal.Get("eventtype").GetUInt();
+        VariantMap eventData = eventFrameVal.Get("eventdata").GetVariantMap();
+        SetEventFrame(time, StringHash(eventType), eventData);
+    }
+
+    return true;
+}
+
+bool ValueAnimation::SaveJSON(JSONValue& dest) const
+{
+    dest.Set("interpolationmethod", interpMethodNames[interpolationMethod_]);
+    if (interpolationMethod_ == IM_SPLINE)
+        dest.Set("splinetension", (float) splineTension_);
+
+    JSONArray keyFramesArray;
+    keyFramesArray.Reserve(keyFrames_.Size());
+    for (unsigned i = 0; i < keyFrames_.Size(); ++i)
+    {
+        const VAnimKeyFrame& keyFrame = keyFrames_[i];
+        JSONValue keyFrameVal;
+        keyFrameVal.Set("time", keyFrame.time_);
+        JSONValue valueVal;
+        valueVal.SetVariant(keyFrame.value_);
+        keyFrameVal.Set("value", valueVal);
+        keyFramesArray.Push(keyFrameVal);
+    }
+    dest.Set("keyframes", keyFramesArray);
+
+    JSONArray eventFramesArray;
+    eventFramesArray.Reserve(eventFrames_.Size());
+    for (unsigned i = 0; i < eventFrames_.Size(); ++i)
+    {
+        const VAnimEventFrame& eventFrame = eventFrames_[i];
+        JSONValue eventFrameVal;
+        eventFrameVal.Set("time", eventFrame.time_);
+        eventFrameVal.Set("eventtype", eventFrame.eventType_.Value());
+        JSONValue eventDataVal;
+        eventDataVal.SetVariantMap(eventFrame.eventData_);
+        eventFrameVal.Set("eventdata", eventDataVal);
+
+        eventFramesArray.Push(eventFrameVal);
+    }
+    dest.Set("eventframes", eventFramesArray);
+
+    return true;
+}
+
 void ValueAnimation::SetValueType(VariantType valueType)
 {
     if (valueType == valueType_)

+ 5 - 0
Source/Urho3D/Scene/ValueAnimation.h

@@ -29,6 +29,7 @@ namespace Urho3D
 {
 
 class XMLElement;
+class JSONValue;
 
 /// Interpolation method.
 enum InterpMethod
@@ -80,6 +81,10 @@ public:
     bool LoadXML(const XMLElement& source);
     /// Save as XML data. Return true if successful.
     bool SaveXML(XMLElement& dest) const;
+    /// Load from JSON data. Return true if successful.
+    bool LoadJSON(const JSONValue& source);
+    /// Save as XML data. Return true if successful.
+    bool SaveJSON(JSONValue& dest) const;
 
     /// Set owner.
     void SetOwner(void* owner);

+ 86 - 0
Source/Urho3D/Urho2D/SpriteSheet2D.cpp

@@ -30,6 +30,7 @@
 #include "../Resource/PListFile.h"
 #include "../Resource/ResourceCache.h"
 #include "../Resource/XMLFile.h"
+#include "../Resource/JSONFile.h"
 #include "../Urho2D/Sprite2D.h"
 #include "../Urho2D/SpriteSheet2D.h"
 
@@ -67,6 +68,10 @@ bool SpriteSheet2D::BeginLoad(Deserializer& source)
     if (extension == ".xml")
         return BeginLoadFromXMLFile(source);
 
+    if (extension == ".json")
+        return BeginLoadFromJSONFile(source);
+
+
     URHO3D_LOGERROR("Unsupported file type");
     return false;
 }
@@ -79,6 +84,9 @@ bool SpriteSheet2D::EndLoad()
     if (loadXMLFile_)
         return EndLoadFromXMLFile();
 
+    if (loadJSONFile_)
+        return EndLoadFromJSONFile();
+
     return false;
 }
 
@@ -256,4 +264,82 @@ bool SpriteSheet2D::EndLoadFromXMLFile()
     return true;
 }
 
+bool SpriteSheet2D::BeginLoadFromJSONFile(Deserializer& source)
+{
+    loadJSONFile_ = new JSONFile(context_);
+    if (!loadJSONFile_->Load(source))
+    {
+        URHO3D_LOGERROR("Could not load sprite sheet");
+        loadJSONFile_.Reset();
+        return false;
+    }
+
+    SetMemoryUse(source.GetSize());
+
+    JSONValue rootElem = loadJSONFile_->GetRoot();
+    if (rootElem.IsNull())
+    {
+        URHO3D_LOGERROR("Invalid sprite sheet");
+        loadJSONFile_.Reset();
+        return false;
+    }
+
+    // If we're async loading, request the texture now. Finish during EndLoad().
+    loadTextureName_ = GetParentPath(GetName()) + rootElem.Get("imagePath").GetString();
+    if (GetAsyncLoadState() == ASYNC_LOADING)
+        GetSubsystem<ResourceCache>()->BackgroundLoadResource<Texture2D>(loadTextureName_, true, this);
+
+    return true;
+}
+
+bool SpriteSheet2D::EndLoadFromJSONFile()
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    texture_ = cache->GetResource<Texture2D>(loadTextureName_);
+    if (!texture_)
+    {
+        URHO3D_LOGERROR("Could not load texture " + loadTextureName_);
+        loadJSONFile_.Reset();
+        loadTextureName_.Clear();
+        return false;
+    }
+
+    JSONValue rootVal = loadJSONFile_->GetRoot();
+    JSONArray subTextureArray = rootVal.Get("subtextures").GetArray();
+
+    for (unsigned i = 0; i < subTextureArray.Size(); i++)
+    {
+        const JSONValue& subTextureVal = subTextureArray.At(i);
+        String name = subTextureVal.Get("name").GetString();
+
+        int x = subTextureVal.Get("x").GetInt();
+        int y = subTextureVal.Get("y").GetInt();
+        int width = subTextureVal.Get("width").GetInt();
+        int height = subTextureVal.Get("height").GetInt();
+        IntRect rectangle(x, y, x + width, y + height);
+
+        Vector2 hotSpot(0.5f, 0.5f);
+        IntVector2 offset(0, 0);
+        JSONValue frameWidthVal = subTextureVal.Get("frameWidth");
+        JSONValue frameHeightVal = subTextureVal.Get("frameHeight");
+
+        if (!frameHeightVal.IsNull() && !frameHeightVal.IsNull())
+        {
+            offset.x_ = subTextureVal.Get("frameX").GetInt();
+            offset.y_ = subTextureVal.Get("frameY").GetInt();
+            int frameWidth = frameWidthVal.GetInt();
+            int frameHeight = frameHeightVal.GetInt();
+            hotSpot.x_ = ((float)offset.x_ + frameWidth / 2) / width;
+            hotSpot.y_ = 1.0f - ((float)offset.y_ + frameHeight / 2) / height;
+        }
+
+        DefineSprite(name, rectangle, hotSpot, offset);
+
+    }
+
+    loadJSONFile_.Reset();
+    loadTextureName_.Clear();
+    return true;
+}
+
 }

+ 8 - 0
Source/Urho3D/Urho2D/SpriteSheet2D.h

@@ -31,6 +31,7 @@ class PListFile;
 class Sprite2D;
 class Texture2D;
 class XMLFile;
+class JSONFile;
 
 /// Sprite sheet.
 class URHO3D_API SpriteSheet2D : public Resource
@@ -67,10 +68,15 @@ private:
     bool BeginLoadFromPListFile(Deserializer& source);
     /// End load from PList file.
     bool EndLoadFromPListFile();
+
     /// Begin load from XML file.
     bool BeginLoadFromXMLFile(Deserializer& source);
     /// End load from XML file.
     bool EndLoadFromXMLFile();
+    /// Begin load from JSON file.
+    bool BeginLoadFromJSONFile(Deserializer& source);
+    /// End load from JSON file.
+    bool EndLoadFromJSONFile();
 
     /// Texture.
     SharedPtr<Texture2D> texture_;
@@ -80,6 +86,8 @@ private:
     SharedPtr<PListFile> loadPListFile_;
     /// XML file used while loading.
     SharedPtr<XMLFile> loadXMLFile_;
+    /// JSON file used while loading.
+    SharedPtr<JSONFile> loadJSONFile_;
     /// Texture name used while loading.
     String loadTextureName_;
 };

+ 1 - 0
Source/Urho3D/Urho2D/TileMapDefs2D.cpp

@@ -23,6 +23,7 @@
 #include "../Precompiled.h"
 
 #include "../Resource/XMLElement.h"
+#include "../Resource/JSONFile.h"
 #include "../Urho2D/TileMapDefs2D.h"
 
 #include "../DebugNew.h"

+ 2 - 1
bin/Data/Scripts/Editor/AttributeEditor.as

@@ -1175,7 +1175,7 @@ void InitResourcePicker()
     Array<String> scriptFilters = {"*.as", "*.asc"};
     Array<String> soundFilters = {"*.wav","*.ogg"};
     Array<String> textureFilters = {"*.dds", "*.png", "*.jpg", "*.bmp", "*.tga", "*.ktx", "*.pvr"};
-    Array<String> materialFilters = {"*.xml", "*.material"};
+    Array<String> materialFilters = {"*.xml", "*.material", "*.json"};
     Array<String> anmSetFilters = {"*.scml"};
     Array<String> pexFilters = {"*.pex"};
     Array<String> tmxFilters = {"*.tmx"};
@@ -1193,6 +1193,7 @@ void InitResourcePicker()
     resourcePickers.Push(ResourcePicker("TextureCube", "*.xml"));
     resourcePickers.Push(ResourcePicker("Texture3D", "*.xml"));
     resourcePickers.Push(ResourcePicker("XMLFile", "*.xml"));
+    resourcePickers.Push(ResourcePicker("JSONFile", "*.json"));
     resourcePickers.Push(ResourcePicker("Sprite2D", textureFilters, ACTION_PICK | ACTION_OPEN));
     resourcePickers.Push(ResourcePicker("AnimationSet2D", anmSetFilters, ACTION_PICK | ACTION_OPEN));
     resourcePickers.Push(ResourcePicker("ParticleEffect2D", pexFilters, ACTION_PICK | ACTION_OPEN));

+ 126 - 1
bin/Data/Scripts/Editor/EditorResourceBrowser.as

@@ -72,6 +72,23 @@ const StringHash XML_TYPE_TEXTURE_3D("texture3d");
 const StringHash XML_TYPE_CUBEMAP("cubemap");
 const StringHash XML_TYPE_SPRITER_DATA("spriter_data");
 
+const StringHash JSON_TYPE_SCENE("scene");
+const StringHash JSON_TYPE_NODE("node");
+const StringHash JSON_TYPE_MATERIAL("material");
+const StringHash JSON_TYPE_TECHNIQUE("technique");
+const StringHash JSON_TYPE_PARTICLEEFFECT("particleeffect");
+const StringHash JSON_TYPE_PARTICLEEMITTER("particleemitter");
+const StringHash JSON_TYPE_TEXTURE("texture");
+const StringHash JSON_TYPE_ELEMENT("element");
+const StringHash JSON_TYPE_ELEMENTS("elements");
+const StringHash JSON_TYPE_ANIMATION_SETTINGS("animation");
+const StringHash JSON_TYPE_RENDERPATH("renderpath");
+const StringHash JSON_TYPE_TEXTURE_ATLAS("TextureAtlas");
+const StringHash JSON_TYPE_2D_PARTICLE_EFFECT("particleEmitterConfig");
+const StringHash JSON_TYPE_TEXTURE_3D("texture3d");
+const StringHash JSON_TYPE_CUBEMAP("cubemap");
+const StringHash JSON_TYPE_SPRITER_DATA("spriter_data");
+
 const StringHash BINARY_TYPE_SCENE("USCN");
 const StringHash BINARY_TYPE_PACKAGE("UPAK");
 const StringHash BINARY_TYPE_COMPRESSED_PACKAGE("ULZ4");
@@ -1029,7 +1046,7 @@ int GetResourceType(String path)
 
 int GetResourceType(String path, StringHash &out fileType, bool useCache = false)
 {
-    if (GetExtensionType(path, fileType) || GetBinaryType(path, fileType, useCache) || GetXmlType(path, fileType, useCache))
+    if (GetExtensionType(path, fileType) || GetBinaryType(path, fileType, useCache) || GetXmlType(path, fileType, useCache) || GetJsonType(path, fileType, useCache))
         return GetResourceType(fileType);
 
     return RESOURCE_TYPE_UNKNOWN;
@@ -1087,6 +1104,40 @@ int GetResourceType(StringHash fileType)
         return RESOURCE_TYPE_CUBEMAP;
     else if (fileType == XML_TYPE_SPRITER_DATA)
         return RESOURCE_TYPE_2D_ANIMATION_SET;
+   
+    // JSON fileTypes
+    else if (fileType == JSON_TYPE_SCENE)
+        return RESOURCE_TYPE_SCENE;
+    else if (fileType == JSON_TYPE_NODE)
+        return RESOURCE_TYPE_PREFAB;
+    else if(fileType == JSON_TYPE_MATERIAL)
+        return RESOURCE_TYPE_MATERIAL;
+    else if(fileType == JSON_TYPE_TECHNIQUE)
+        return RESOURCE_TYPE_TECHNIQUE;
+    else if(fileType == JSON_TYPE_PARTICLEEFFECT)
+        return RESOURCE_TYPE_PARTICLEEFFECT;
+    else if(fileType == JSON_TYPE_PARTICLEEMITTER)
+        return RESOURCE_TYPE_PARTICLEEMITTER;
+    else if(fileType == JSON_TYPE_TEXTURE)
+        return RESOURCE_TYPE_TEXTURE;
+    else if(fileType == JSON_TYPE_ELEMENT)
+        return RESOURCE_TYPE_UIELEMENT;
+    else if(fileType == JSON_TYPE_ELEMENTS)
+        return RESOURCE_TYPE_UIELEMENTS;
+    else if (fileType == JSON_TYPE_ANIMATION_SETTINGS)
+        return RESOURCE_TYPE_ANIMATION_SETTINGS;
+    else if (fileType == JSON_TYPE_RENDERPATH)
+        return RESOURCE_TYPE_RENDERPATH;
+    else if (fileType == JSON_TYPE_TEXTURE_ATLAS)
+        return RESOURCE_TYPE_TEXTURE_ATLAS;
+    else if (fileType == JSON_TYPE_2D_PARTICLE_EFFECT)
+        return RESOURCE_TYPE_2D_PARTICLE_EFFECT;
+    else if (fileType == JSON_TYPE_TEXTURE_3D)
+        return RESOURCE_TYPE_TEXTURE_3D;
+    else if (fileType == JSON_TYPE_CUBEMAP)
+        return RESOURCE_TYPE_CUBEMAP;
+    else if (fileType == JSON_TYPE_SPRITER_DATA)
+        return RESOURCE_TYPE_2D_ANIMATION_SET;
 
     // extension fileTypes
     else if (fileType == EXTENSION_TYPE_TTF)
@@ -1314,6 +1365,80 @@ bool GetXmlType(String path, StringHash &out fileType, bool useCache = false)
     return found;
 }
 
+bool GetJsonType(String path, StringHash &out fileType, bool useCache = false)
+{
+    String extension = GetExtension(path);
+    if (extension == ".txt" || extension == ".xml" || extension == ".icns" || extension == ".atlas")
+        return false;
+
+    String name;
+    if (useCache)
+    {
+        JSONFile@ json = cache.GetResource("JSONFile", path);
+        if (json is null)
+            return false;
+
+        name = json.root.name;
+    }
+    else
+    {
+        File@ file = File();
+        if (!file.Open(path))
+            return false;
+
+        if (file.size == 0)
+            return false;
+
+        XMLFile@ xml = XMLFile();
+        if (xml.Load(file))
+            name = xml.root.name;
+        else 
+            return false;
+    }
+
+    bool found = false;
+    if (!name.empty)
+    {
+        found = true;
+        StringHash type = StringHash(name);
+        if (type == XML_TYPE_SCENE)
+            fileType = XML_TYPE_SCENE;
+        else if (type == XML_TYPE_NODE)
+            fileType = XML_TYPE_NODE;
+        else if(type == XML_TYPE_MATERIAL)
+            fileType = XML_TYPE_MATERIAL;
+        else if(type == XML_TYPE_TECHNIQUE)
+            fileType = XML_TYPE_TECHNIQUE;
+        else if(type == XML_TYPE_PARTICLEEFFECT)
+            fileType = XML_TYPE_PARTICLEEFFECT;
+        else if(type == XML_TYPE_PARTICLEEMITTER)
+            fileType = XML_TYPE_PARTICLEEMITTER;
+        else if(type == XML_TYPE_TEXTURE)
+            fileType = XML_TYPE_TEXTURE;
+        else if(type == XML_TYPE_ELEMENT)
+            fileType = XML_TYPE_ELEMENT;
+        else if(type == XML_TYPE_ELEMENTS)
+            fileType = XML_TYPE_ELEMENTS;
+        else if (type == XML_TYPE_ANIMATION_SETTINGS)
+            fileType = XML_TYPE_ANIMATION_SETTINGS;
+        else if (type == XML_TYPE_RENDERPATH)
+            fileType = XML_TYPE_RENDERPATH;
+        else if (type == XML_TYPE_TEXTURE_ATLAS)
+            fileType = XML_TYPE_TEXTURE_ATLAS;
+        else if (type == XML_TYPE_2D_PARTICLE_EFFECT)
+            fileType = XML_TYPE_2D_PARTICLE_EFFECT;
+        else if (type == XML_TYPE_TEXTURE_3D)
+            fileType = XML_TYPE_TEXTURE_3D;
+        else if (type == XML_TYPE_CUBEMAP)
+            fileType = XML_TYPE_CUBEMAP;
+        else if (type == XML_TYPE_SPRITER_DATA)
+            fileType = XML_TYPE_SPRITER_DATA;
+        else
+            found = false;
+    }
+    return found;
+}
+
 String ResourceTypeName(int resourceType)
 {
     if (resourceType == RESOURCE_TYPE_UNUSABLE)