Browse Source

Merge pull request #141 from AtomicGameEngine/JME-WORK-MATERIALS

Work on multi-material support, improve model import error handling (remove process exits!), hooking up some more techniques to material editor, adding support for setting model submaterials from script, JSON scene import imrovements
JoshEngebretson 10 years ago
parent
commit
6747ef5192

+ 64 - 49
Script/AtomicEditor/ui/frames/inspector/ComponentInspector.ts

@@ -207,6 +207,65 @@ class ComponentInspector extends Atomic.UISection {
 
     }
 
+    createMaterialClosure(layout: Atomic.UILayout, staticModel: Atomic.StaticModel, index: number) {
+
+        var o = InspectorUtils.createAttrEditFieldWithSelectButton("Material " + index, layout);
+        var materialField = o.editField;
+        materialField.readOnly = true;
+
+        var select = o.selectButton;
+
+        select.onClick = () => {
+
+            EditorUI.getModelOps().showResourceSelection("Select Material", "MaterialImporter", function(asset: ToolCore.Asset) {
+
+                staticModel.setMaterialIndex(index, <Atomic.Material> Atomic.cache.getResource("Material", asset.path));
+
+                if (staticModel.getMaterial())
+                    materialField.text = staticModel.getMaterial().name;
+                else
+                    materialField.text = "";
+
+            });
+
+        }
+
+        var material = staticModel.getMaterial();
+
+        if (material) {
+
+            materialField.text = material.name;
+
+        }
+
+        // handle dropping of material on field
+        materialField.subscribeToEvent(materialField, "DragEnded", (ev: Atomic.DragEndedEvent) => {
+
+            if (ev.target == materialField) {
+
+                var importer = this.acceptAssetDrag("MaterialImporter", ev);
+
+                if (importer) {
+
+                    var materialImporter = <ToolCore.MaterialImporter> importer;
+                    var asset = materialImporter.asset;
+
+                    var material = <Atomic.Material> Atomic.cache.getResource("Material", asset.path);
+
+                    if (material) {
+
+                        staticModel.setMaterialIndex(index, material);
+                        ev.target.text = material.name;
+
+                    }
+                }
+            }
+
+        });
+
+
+    }
+
     addModelUI(layout: Atomic.UILayout, typeName: string) {
 
         var staticModel = <Atomic.StaticModel> this.component;
@@ -267,62 +326,18 @@ class ComponentInspector extends Atomic.UISection {
 
         });
 
-        // MATERIAL FIELD (single material, not multimaterial for now)
-
-        o = InspectorUtils.createAttrEditFieldWithSelectButton("Material", layout);
-        var materialField = o.editField;
-        materialField.readOnly = true;
-
-        select = o.selectButton;
-
-        select.onClick = () => {
-
-            EditorUI.getModelOps().showResourceSelection("Select Material", "MaterialImporter", function(asset: ToolCore.Asset) {
-
-                staticModel.setMaterial(<Atomic.Material> Atomic.cache.getResource("Material", asset.path));
-
-                if (staticModel.getMaterial())
-                    materialField.text = staticModel.getMaterial().name;
-                else
-                    materialField.text = "";
-
-            });
-
+        var numGeometries = staticModel.numGeometries;
+        if (typeName == "Skybox") {
+          numGeometries = 1;
         }
 
-        var material = staticModel.getMaterial();
+        for (var x = 0; x < staticModel.numGeometries; x++) {
 
-        if (material) {
+            this.createMaterialClosure(layout, staticModel, x);
 
-            materialField.text = material.name;
 
         }
 
-        // handle dropping of material on field
-        materialField.subscribeToEvent(materialField, "DragEnded", (ev: Atomic.DragEndedEvent) => {
-
-            if (ev.target == materialField) {
-
-                var importer = this.acceptAssetDrag("MaterialImporter", ev);
-
-                if (importer) {
-
-                    var materialImporter = <ToolCore.MaterialImporter> importer;
-                    var asset = materialImporter.asset;
-
-                    var material = <Atomic.Material> Atomic.cache.getResource("Material", asset.path);
-
-                    if (material) {
-
-                        staticModel.material = material;
-                        ev.target.text = material.name;
-
-                    }
-                }
-            }
-
-        });
-
 
     }
 

+ 4 - 1
Script/AtomicEditor/ui/frames/inspector/MaterialInspector.ts

@@ -42,7 +42,10 @@ var techniqueLookup = {
     "Techniques/Diff.xml": "Diffuse",
     "Techniques/DiffEmissive.xml": "Diffuse Emissive",
     "Techniques/DiffNormal.xml": "Diffuse Normal",
-    "Techniques/DiffNormalSpec.xml": "Diffuse Normal Specular"
+    "Techniques/DiffNormalSpec.xml": "Diffuse Normal Specular",
+    "Techniques/DiffAlpha.xml": "Alpha",
+    "Techniques/DiffAlphaMask.xml": "Alpha Mask",
+    "Techniques/DiffAdd.xml": "Additive"
 }
 
 var techniqueReverseLookup = {};

+ 7 - 0
Script/Packages/Atomic/Atomic3D.json

@@ -17,6 +17,13 @@
 			"AnimationState" : ["AnimatedModel", "Animation"]
 		}
 
+	},
+	"typescript_decl" : {
+
+		"StaticModel" : [
+			"setMaterialIndex(index:number, material:Material);"
+		]
 	}
 
+
 }

+ 1 - 0
Script/TypeScript/Atomic.d.ts

@@ -4341,6 +4341,7 @@ declare module Atomic {
       isInside(point: Vector3): boolean;
       // Determines if the given local space point is within the model geometry.
       isInsideLocal(point: Vector3): boolean;
+      setMaterialIndex(index:number, material:Material);
 
    }
 

+ 1 - 1
Script/TypeScript/ToolCore.d.ts

@@ -227,7 +227,7 @@ declare module ToolCore {
 
       load(assetPath: string): boolean;
       getErrorMessage(): string;
-      exportModel(outName: string, animName?: string, animationOnly?: boolean): void;
+      exportModel(outName: string, animName?: string, animationOnly?: boolean): boolean;
       setImportNode(node: Atomic.Node): void;
       setStartTime(startTime: number): void;
       setEndTime(endTime: number): void;

+ 6 - 0
Source/AtomicJS/Javascript/JSAtomic.cpp

@@ -21,6 +21,9 @@
 #include "JSCore.h"
 #include "JSFileSystem.h"
 #include "JSGraphics.h"
+#ifdef ATOMIC_3D
+#include "JSAtomic3D.h"
+#endif
 #include "JSIO.h"
 #include "JSUIAPI.h"
 #include "JSScene.h"
@@ -258,6 +261,9 @@ void jsapi_init_atomic(JSVM* vm)
     jsapi_init_network(vm);
 #endif
     jsapi_init_graphics(vm);
+#ifdef ATOMIC_3D
+    jsapi_init_atomic3d(vm);
+#endif
     jsapi_init_ui(vm);
     jsapi_init_scene(vm);
 

+ 38 - 0
Source/AtomicJS/Javascript/JSAtomic3D.cpp

@@ -0,0 +1,38 @@
+
+#include <Atomic/Atomic3D/StaticModel.h>
+
+#include "JSAtomic3D.h"
+
+namespace Atomic
+{
+
+
+static int StaticModel_SetMaterialIndex(duk_context* ctx) {
+
+    unsigned index = (unsigned) duk_require_number(ctx, 0);
+    Material* material = js_to_class_instance<Material>(ctx, 1, 0);
+
+    duk_push_this(ctx);
+
+    // event receiver
+    StaticModel* model = js_to_class_instance<StaticModel>(ctx, -1, 0);
+
+
+    model->SetMaterial(index, material);
+
+
+    return 0;
+}
+
+void jsapi_init_atomic3d(JSVM* vm)
+{
+    duk_context* ctx = vm->GetJSContext();
+
+    js_class_get_prototype(ctx, "Atomic", "StaticModel");
+    duk_push_c_function(ctx, StaticModel_SetMaterialIndex, 2);
+    duk_put_prop_string(ctx, -2, "setMaterialIndex");
+    duk_pop(ctx); // pop AObject prototype
+
+}
+
+}

+ 16 - 0
Source/AtomicJS/Javascript/JSAtomic3D.h

@@ -0,0 +1,16 @@
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// Please see LICENSE.md in repository root for license information
+// https://github.com/AtomicGameEngine/AtomicGameEngine
+
+#pragma once
+
+#include "JSAPI.h"
+
+namespace Atomic
+{
+
+class JSVM;
+
+void jsapi_init_atomic3d(JSVM* vm);
+
+}

+ 2 - 2
Source/AtomicTool/AtomicTool.cpp

@@ -271,8 +271,8 @@ void AtomicTool::Start()
 
         if (!tsystem->LoadProject(projectFile))
         {
-            ErrorExit(ToString("Failed to load project: %s", projectFile.CString()));
-            return;
+            //ErrorExit(ToString("Failed to load project: %s", projectFile.CString()));
+            //return;
         }
 
         // Set the build path

+ 5 - 5
Source/ToolCore/Command/ImportCmd.cpp

@@ -51,9 +51,9 @@ bool ImportCmd::Parse(const Vector<String>& arguments, unsigned startIndex, Stri
 
 void ImportCmd::Run()
 {
-    //ToolSystem* tsystem = GetSubsystem<ToolSystem>();
-    //Project* project = tsystem->GetProject();
-    //String resourcePath = project->GetResourcePath();
+    ToolSystem* tsystem = GetSubsystem<ToolSystem>();
+    Project* project = tsystem->GetProject();
+    String resourcePath = project->GetResourcePath();
 
     String ext = GetExtension(assetFilename_);
 
@@ -75,8 +75,8 @@ void ImportCmd::Run()
 
         SharedPtr<JSONSceneProcess> sceneProcess;
         sceneProcess = new JSONSceneProcess(context_, jimporter);
-        //sceneProcess->Process(resourcePath);
-        //sceneProcess->Write();
+        sceneProcess->Process(resourcePath);
+        sceneProcess->Write();
     }
     else
     {

+ 1 - 1
Source/ToolCore/Command/ImportCmd.h

@@ -20,7 +20,7 @@ public:
     bool Parse(const Vector<String>& arguments, unsigned startIndex, String& errorMsg);
     void Run();
 
-    bool RequiresProjectLoad() { return false; }
+    bool RequiresProjectLoad() { return true; }
 
 private:
 

+ 2 - 0
Source/ToolCore/Import/JSONSceneImporter.cpp

@@ -585,6 +585,8 @@ bool JSONSceneImporter::ParseMaterials(const rapidjson::Value& value)
                     else if (!strcmp(oitr->name.GetString(), "shader"))
                     {
                         shader = oitr->value.GetString();
+
+                        shader.Replace("Legacy Shaders/", "");
                     }
                     else if (!strcmp(oitr->name.GetString(), "mainTexture"))
                     {

+ 3 - 3
Source/ToolCore/Import/JSONSceneProcess.cpp

@@ -129,7 +129,7 @@ bool JSONSceneProcess::ProcessMaterials()
         SharedPtr<Material> material;
         material = new Material(context_);
 
-        material->SetName("Materials/" + jmaterial->GetName() + ".xml");
+        material->SetName("Materials/" + jmaterial->GetName() + ".material");
 
         Technique* _technique = cache->GetResource<Technique>("Techniques/" + technique);
         assert(_technique);
@@ -662,7 +662,7 @@ bool JSONSceneProcess::ProcessComponent(Node* node, const JSONTerrain* jterrain)
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     Image* heightMap = cache->GetResource<Image>(heightMapPath);
 
-    Material* material = cache->GetResource<Material>("Materials/DemoTerrain.xml");
+    Material* material = cache->GetResource<Material>("Materials/DemoTerrain.material");
 
     Terrain* terrain = node->CreateComponent<Terrain>();
 
@@ -962,7 +962,7 @@ bool JSONSceneProcess::Process(const String &resourcePath)
     Node* zoneNode = scene_->CreateChild("Zone");
     Zone* zone = zoneNode->CreateComponent<Zone>();
     zone->SetBoundingBox(BoundingBox(-10000.0f, 10000.f));
-    zone->SetAmbientColor(Color(1, 1, 1));
+    zone->SetAmbientColor(Color(.4f, .4f, .4f));
 
     ProcessTextures();
     ProcessLightmaps();

+ 75 - 23
Source/ToolCore/Import/OpenAssetImporter.cpp

@@ -81,8 +81,7 @@ OpenAssetImporter::OpenAssetImporter(Context* context) : Object(context) ,
         aiProcess_Triangulate |
         aiProcess_GenSmoothNormals |
         aiProcess_LimitBoneWeights |
-        aiProcess_ImproveCacheLocality |
-        aiProcess_RemoveRedundantMaterials |
+        aiProcess_ImproveCacheLocality |        
         aiProcess_FixInfacingNormals |
         aiProcess_FindInvalidData |
         aiProcess_GenUVCoords |
@@ -203,26 +202,34 @@ void OpenAssetImporter::ApplyScale()
 
 }
 
-void OpenAssetImporter::ExportModel(const String& outName, const String &animName, bool animationOnly)
+bool OpenAssetImporter::ExportModel(const String& outName, const String &animName, bool animationOnly)
 {
     if (outName.Empty())
-        ErrorExit("No output file defined");
+    {
+        errorMessage_ = "No output file defined";
+        return false;
+    }
 
     OutModel model;
     model.rootNode_ = rootNode_;
     model.outName_ = outName + ".mdl";
 
     CollectMeshes(scene_, model, model.rootNode_);
-    CollectBones(model, animationOnly);
+    if (!CollectBones(model, animationOnly))
+        return false;
     BuildBoneCollisionInfo(model);
 
     if (!animationOnly)
-        BuildAndSaveModel(model);
+    {
+        if (!BuildAndSaveModel(model))
+            return false;
+    }
 
     if (!noAnimations_)
     {
         CollectAnimations(&model);
-        BuildAndSaveAnimations(&model, animName);
+        if (!BuildAndSaveAnimations(&model, animName))
+            return false;
 
         // Save scene-global animations
         // CollectAnimations();
@@ -236,7 +243,10 @@ void OpenAssetImporter::ExportModel(const String& outName, const String &animNam
     }
 
     if (importNode_.Null())
-        return;
+    {
+        errorMessage_ = "NULL importNode_";
+        return false;
+    }
 
     ResourceCache* cache = GetSubsystem<ResourceCache>();    
     Model* mdl = cache->GetResource<Model>( model.outName_);
@@ -245,7 +255,10 @@ void OpenAssetImporter::ExportModel(const String& outName, const String &animNam
     cache->ReloadResource(mdl);
 
     if (!mdl)
-        return;
+    {
+        errorMessage_ = "Unable to load " + model.outName_ + " from Cache";
+        return false;
+    }
 
     StaticModel* modelComponent = 0;
 
@@ -274,15 +287,24 @@ void OpenAssetImporter::ExportModel(const String& outName, const String &animNam
         }
 
     }
+
+    return true;
 }
 
-void OpenAssetImporter::BuildAndSaveModel(OutModel& model)
+bool OpenAssetImporter::BuildAndSaveModel(OutModel& model)
 {
     if (!model.rootNode_)
-        ErrorExit("Null root node for model");
+    {
+        errorMessage_ = "Null root node for model";
+        return false;
+    }
+
     String rootNodeName = FromAIString(model.rootNode_->mName);
     if (!model.meshes_.Size())
-        ErrorExit("No geometries found starting from node " + rootNodeName);
+    {
+        errorMessage_ = "No geometries found starting from node " + rootNodeName;
+        return false;
+    }
 
     //PrintLine("Writing model " + rootNodeName);
 
@@ -410,7 +432,10 @@ void OpenAssetImporter::BuildAndSaveModel(OutModel& model)
         Vector<PODVector<float> > blendWeights;
         PODVector<unsigned> boneMappings;
         if (model.bones_.Size())
-            GetBlendData(model, mesh, boneMappings, blendIndices, blendWeights, maxBones_);
+        {
+            if (!GetBlendData(model, mesh, boneMappings, blendIndices, blendWeights, errorMessage_, maxBones_))
+                return false;
+        }
 
         float* dest = (float*)((unsigned char*)vertexData + startVertexOffset * vb->GetVertexSize());
         for (unsigned j = 0; j < mesh->mNumVertices; ++j)
@@ -507,7 +532,11 @@ void OpenAssetImporter::BuildAndSaveModel(OutModel& model)
 
     File outFile(context_);
     if (!outFile.Open(model.outName_, FILE_WRITE))
-        ErrorExit("Could not open output file " + model.outName_);
+    {
+        errorMessage_ = "Could not open output file " + model.outName_;
+        return false;
+    }
+
     outModel->Save(outFile);
 
     // If exporting materials, also save material list for use by the editor
@@ -525,6 +554,8 @@ void OpenAssetImporter::BuildAndSaveModel(OutModel& model)
             PrintLine("Warning: could not write material list file " + materialListName);
         }
     }
+
+    return true;
 }
 
 String OpenAssetImporter::GetMeshMaterialName(aiMesh* mesh)
@@ -635,7 +666,7 @@ void OpenAssetImporter::CollectSceneModels(OutScene& scene, aiNode* node)
         CollectSceneModels(scene, node->mChildren[i]);
 }
 
-void OpenAssetImporter::CollectBones(OutModel& model, bool animationOnly)
+bool OpenAssetImporter::CollectBones(OutModel& model, bool animationOnly)
 {
     HashSet<aiNode*> necessary;
     HashSet<aiNode*> rootNodes;
@@ -688,7 +719,11 @@ void OpenAssetImporter::CollectBones(OutModel& model, bool animationOnly)
                 String boneName(FromAIString(bone->mName));
                 aiNode* boneNode = GetNode(boneName, scene_->mRootNode, true);
                 if (!boneNode)
-                    ErrorExit("Could not find scene node for bone " + boneName);
+                {
+                    errorMessage_ = "Could not find scene node for bone " + boneName;
+                    return false;
+                }
+
                 necessary.Insert(boneNode);
                 rootNode = boneNode;
 
@@ -716,7 +751,10 @@ void OpenAssetImporter::CollectBones(OutModel& model, bool animationOnly)
             if (*i != commonParent)
             {
                 if (!commonParent || (*i)->mParent != commonParent)
-                    ErrorExit("Skeleton with multiple root nodes found, not supported");
+                {
+                    errorMessage_ = "Skeleton with multiple root nodes found, not supported";
+                    return false;
+                }
             }
         }
         rootNodes.Clear();
@@ -725,7 +763,7 @@ void OpenAssetImporter::CollectBones(OutModel& model, bool animationOnly)
     }
 
     if (rootNodes.Empty())
-        return;
+        return true;
 
     model.rootBone_ = *rootNodes.Begin();
     CollectBonesFinal(model.bones_, necessary, model.rootBone_);
@@ -737,6 +775,8 @@ void OpenAssetImporter::CollectBones(OutModel& model, bool animationOnly)
         model.boneRadii_[i] = 0.0f;
         model.boneHitboxes_[i] = BoundingBox(0.0f, 0.0f);
     }
+
+    return true;
 }
 
 void OpenAssetImporter::CollectBonesFinal(PODVector<aiNode*>& dest, const HashSet<aiNode*>& necessary, aiNode* node)
@@ -850,7 +890,7 @@ void OpenAssetImporter::BuildBoneCollisionInfo(OutModel& model)
     }
 }
 
-void OpenAssetImporter::BuildAndSaveAnimations(OutModel* model, const String &animNameOverride)
+bool OpenAssetImporter::BuildAndSaveAnimations(OutModel* model, const String &animNameOverride)
 {
     const PODVector<aiAnimation*>& animations = model ? model->animations_ : sceneAnimations_;
 
@@ -1067,7 +1107,11 @@ void OpenAssetImporter::BuildAndSaveAnimations(OutModel* model, const String &an
 
         File outFile(context_);
         if (!outFile.Open(animOutName, FILE_WRITE))
-            ErrorExit("Could not open output file " + animOutName);
+        {
+            errorMessage_ = "Could not open output file " + animOutName;
+            return false;
+        }
+
         outAnim->Save(outFile);
 
         AnimationInfo info;
@@ -1075,6 +1119,8 @@ void OpenAssetImporter::BuildAndSaveAnimations(OutModel* model, const String &an
         info.cacheFilename_ = animOutName;
         animationInfos_.Push(info);
     }
+
+    return true;
 }
 
 // Materials
@@ -1089,7 +1135,7 @@ void OpenAssetImporter::ExportMaterials(HashSet<String>& usedTextures)
         BuildAndSaveMaterial(scene_->mMaterials[i], usedTextures);
 }
 
-void OpenAssetImporter::BuildAndSaveMaterial(aiMaterial* material, HashSet<String>& usedTextures)
+bool OpenAssetImporter::BuildAndSaveMaterial(aiMaterial* material, HashSet<String>& usedTextures)
 {
     aiString matNameStr;
     material->Get(AI_MATKEY_NAME, matNameStr);
@@ -1234,15 +1280,21 @@ void OpenAssetImporter::BuildAndSaveMaterial(aiMaterial* material, HashSet<Strin
     if (noOverwriteMaterial_ && fileSystem->FileExists(outFileName))
     {
         PrintLine("Skipping save of existing material " + matName);
-        return;
+        return true;
     }
 
     PrintLine("Writing material " + matName);
 
     File outFile(context_);
     if (!outFile.Open(outFileName, FILE_WRITE))
-        ErrorExit("Could not open output file " + outFileName);
+    {
+        errorMessage_ = "Could not open output file " + outFileName;
+        return false;
+    }
+
     outMaterial.Save(outFile);
+
+    return true;
 }
 
 void OpenAssetImporter::DumpNodes(aiNode* rootNode, unsigned level)

+ 5 - 5
Source/ToolCore/Import/OpenAssetImporter.h

@@ -55,7 +55,7 @@ public:
 
     const String& GetErrorMessage() { return errorMessage_; }
 
-    void ExportModel(const String& outName, const String& animName = String::EMPTY, bool animationOnly = false);
+    bool ExportModel(const String& outName, const String& animName = String::EMPTY, bool animationOnly = false);
 
     void SetImportNode(Node* node) { importNode_ = node; }
     void SetStartTime(float startTime) { startTime_ = startTime; }
@@ -72,14 +72,14 @@ private:
     void ApplyScale();
     void ApplyScale(aiNode* node);
 
-    void BuildAndSaveModel(OutModel& model);
-    void BuildAndSaveAnimations(OutModel* model = 0, const String& animNameOverride = String::EMPTY);
+    bool BuildAndSaveModel(OutModel& model);
+    bool BuildAndSaveAnimations(OutModel* model = 0, const String& animNameOverride = String::EMPTY);
 
     void ExportMaterials(HashSet<String>& usedTextures);
-    void BuildAndSaveMaterial(aiMaterial* material, HashSet<String>& usedTextures);
+    bool BuildAndSaveMaterial(aiMaterial* material, HashSet<String>& usedTextures);
 
     void CollectSceneModels(OutScene& scene, aiNode* node);
-    void CollectBones(OutModel& model, bool animationOnly = false);
+    bool CollectBones(OutModel& model, bool animationOnly = false);
     void CollectBonesFinal(PODVector<aiNode*>& dest, const HashSet<aiNode*>& necessary, aiNode* node);
     void BuildBoneCollisionInfo(OutModel& model);
     void CollectAnimations(OutModel* model = 0);

+ 16 - 7
Source/ToolCore/Import/OpenAssetUtils.cpp

@@ -303,8 +303,8 @@ void GetPosRotScale(const aiMatrix4x4& transform, Vector3& pos, Quaternion& rot,
     scale = ToVector3(aiScale);
 }
 
-void GetBlendData(OutModel& model, aiMesh* mesh, PODVector<unsigned>& boneMappings, Vector<PODVector<unsigned char> >&
-    blendIndices, Vector<PODVector<float> >& blendWeights, unsigned maxBones)
+bool GetBlendData(OutModel& model, aiMesh* mesh, PODVector<unsigned>& boneMappings, Vector<PODVector<unsigned char> >&
+    blendIndices, Vector<PODVector<float> >& blendWeights, String& errorMessage, unsigned maxBones)
 {
     blendIndices.Resize(mesh->mNumVertices);
     blendWeights.Resize(mesh->mNumVertices);
@@ -315,10 +315,11 @@ void GetBlendData(OutModel& model, aiMesh* mesh, PODVector<unsigned>& boneMappin
     {
         if (mesh->mNumBones > maxBones)
         {
-            ErrorExit(
+            errorMessage =
                 "Geometry (submesh) has over " + String(maxBones) + " bone influences. Try splitting to more submeshes\n"
-                "that each stay at " + String(maxBones) + " bones or below."
-            );
+                "that each stay at " + String(maxBones) + " bones or below.";
+
+             return false;
         }
         boneMappings.Resize(mesh->mNumBones);
         for (unsigned i = 0; i < mesh->mNumBones; ++i)
@@ -347,17 +348,25 @@ void GetBlendData(OutModel& model, aiMesh* mesh, PODVector<unsigned>& boneMappin
             String boneName = FromAIString(bone->mName);
             unsigned globalIndex = GetBoneIndex(model, boneName);
             if (globalIndex == M_MAX_UNSIGNED)
-                ErrorExit("Bone " + boneName + " not found");
+            {
+                errorMessage = "Bone " + boneName + " not found";
+                return false;
+            }
             for (unsigned j = 0; j < bone->mNumWeights; ++j)
             {
                 unsigned vertex = bone->mWeights[j].mVertexId;
                 blendIndices[vertex].Push(globalIndex);
                 blendWeights[vertex].Push(bone->mWeights[j].mWeight);
                 if (blendWeights[vertex].Size() > 4)
-                    ErrorExit("More than 4 bone influences on vertex");
+                {
+                    errorMessage = "More than 4 bone influences on vertex";
+                    return false;
+                }
             }
         }
     }
+
+    return true;
 }
 
 String FromAIString(const aiString& str)

+ 2 - 2
Source/ToolCore/Import/OpenAssetUtils.h

@@ -105,8 +105,8 @@ aiBone* GetMeshBone(OutModel& model, const String& boneName);
 Matrix3x4 GetOffsetMatrix(OutModel& model, const String& boneName);
 unsigned GetNumValidFaces(aiMesh* mesh);
 
-void GetBlendData(OutModel& model, aiMesh* mesh, PODVector<unsigned>& boneMappings, Vector<PODVector<unsigned char> >&
-    blendIndices, Vector<PODVector<float> >& blendWeights, unsigned maxBones = 64);
+bool GetBlendData(OutModel& model, aiMesh* mesh, PODVector<unsigned>& boneMappings, Vector<PODVector<unsigned char> >&
+    blendIndices, Vector<PODVector<float> >& blendWeights, String &errorMessage, unsigned maxBones = 64);
 
 void CollectMeshes(const aiScene* scene, OutModel& model, aiNode* node);