Browse Source

Merge branch 'master' into master

Kim Kulling 6 years ago
parent
commit
02812f22af

+ 0 - 9
code/Assjson/json_exporter.cpp

@@ -34,15 +34,6 @@ namespace Assimp {
 
 
 void ExportAssimp2Json(const char*, Assimp::IOSystem*, const aiScene*, const Assimp::ExportProperties*);
 void ExportAssimp2Json(const char*, Assimp::IOSystem*, const aiScene*, const Assimp::ExportProperties*);
 
 
-Exporter::ExportFormatEntry Assimp2Json_desc = Assimp::Exporter::ExportFormatEntry(
-    "json",
-    "Plain JSON representation of the Assimp scene data structure",
-    "json",
-    &ExportAssimp2Json,
-    0u
-);
-
-
 // small utility class to simplify serializing the aiScene to Json
 // small utility class to simplify serializing the aiScene to Json
 class JSONWriter {
 class JSONWriter {
 public:
 public:

+ 1 - 1
code/CMakeLists.txt

@@ -810,7 +810,7 @@ ADD_ASSIMP_IMPORTER( MMD
   MMD/MMDVmdParser.h
   MMD/MMDVmdParser.h
 )
 )
 
 
-ADD_ASSIMP_EXPORTER( Assjson
+ADD_ASSIMP_EXPORTER( ASSJSON
   Assjson/cencode.c
   Assjson/cencode.c
   Assjson/cencode.h
   Assjson/cencode.h
   Assjson/json_exporter.cpp
   Assjson/json_exporter.cpp

+ 4 - 4
code/Common/Exporter.cpp

@@ -163,11 +163,11 @@ Exporter::ExportFormatEntry gExporters[] =
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER
 #ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER
-    Exporter::ExportFormatEntry( "assbin", "Assimp Binary", "assbin" , &ExportSceneAssbin, 0 ),
+    Exporter::ExportFormatEntry( "assbin", "Assimp Binary File", "assbin" , &ExportSceneAssbin, 0 ),
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER
 #ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER
-    Exporter::ExportFormatEntry( "assxml", "Assxml Document", "assxml" , &ExportSceneAssxml, 0 ),
+    Exporter::ExportFormatEntry( "assxml", "Assimp XML Document", "assxml" , &ExportSceneAssxml, 0 ),
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_X3D_EXPORTER
 #ifndef ASSIMP_BUILD_NO_X3D_EXPORTER
@@ -183,8 +183,8 @@ Exporter::ExportFormatEntry gExporters[] =
     Exporter::ExportFormatEntry( "3mf", "The 3MF-File-Format", "3mf", &ExportScene3MF, 0 ),
     Exporter::ExportFormatEntry( "3mf", "The 3MF-File-Format", "3mf", &ExportScene3MF, 0 ),
 #endif
 #endif
 
 
-#ifndef ASSIMP_BUILD_NO_Assjson_EXPORTER
-    Exporter::ExportFormatEntry("json", "Plain JSON representation of the Assimp scene data structure", "json", &ExportAssimp2Json, 0)
+#ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER
+    Exporter::ExportFormatEntry( "assjson", "Assimp JSON Document", "json", &ExportAssimp2Json, 0)
 #endif
 #endif
 };
 };
 
 

+ 2 - 12
code/FBX/FBXConverter.cpp

@@ -90,7 +90,6 @@ namespace Assimp {
         , anim_fps()
         , anim_fps()
         , out(out)
         , out(out)
         , doc(doc)
         , doc(doc)
-        , mRemoveEmptyBones( removeEmptyBones )
         , mCurrentUnit(FbxUnit::cm) {
         , mCurrentUnit(FbxUnit::cm) {
             // animations need to be converted first since this will
             // animations need to be converted first since this will
             // populate the node_anim_chain_bits map, which is needed
             // populate the node_anim_chain_bits map, which is needed
@@ -1462,14 +1461,8 @@ namespace Assimp {
 
 
                     const WeightIndexArray& indices = cluster->GetIndices();
                     const WeightIndexArray& indices = cluster->GetIndices();
 
 
-                    if (indices.empty() && mRemoveEmptyBones ) {
-                        continue;
-                    }
-
                     const MatIndexArray& mats = geo.GetMaterialIndices();
                     const MatIndexArray& mats = geo.GetMaterialIndices();
 
 
-                    bool ok = false;
-
                     const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
                     const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
 
 
                     count_out_indices.clear();
                     count_out_indices.clear();
@@ -1509,8 +1502,7 @@ namespace Assimp {
                                     out_indices.push_back(std::distance(outputVertStartIndices->begin(), it));
                                     out_indices.push_back(std::distance(outputVertStartIndices->begin(), it));
                                 }
                                 }
 
 
-                                ++count_out_indices.back();
-                                ok = true;
+                                ++count_out_indices.back();                               
                             }
                             }
                         }
                         }
                     }
                     }
@@ -1518,10 +1510,8 @@ namespace Assimp {
                     // if we found at least one, generate the output bones
                     // if we found at least one, generate the output bones
                     // XXX this could be heavily simplified by collecting the bone
                     // XXX this could be heavily simplified by collecting the bone
                     // data in a single step.
                     // data in a single step.
-                    if (ok && mRemoveEmptyBones) {
-                        ConvertCluster(bones, model, *cluster, out_indices, index_out_indices,
+                    ConvertCluster(bones, model, *cluster, out_indices, index_out_indices,
                             count_out_indices, node_global_transform);
                             count_out_indices, node_global_transform);
-                    }
                 }
                 }
             }
             }
             catch (std::exception&) {
             catch (std::exception&) {

+ 0 - 3
code/FBX/FBXConverter.h

@@ -470,9 +470,6 @@ private:
 
 
     aiScene* const out;
     aiScene* const out;
     const FBX::Document& doc;
     const FBX::Document& doc;
-
-    bool mRemoveEmptyBones;
-
     FbxUnit mCurrentUnit;
     FbxUnit mCurrentUnit;
 };
 };
 
 

+ 0 - 8
code/FBX/FBXDocument.cpp

@@ -90,14 +90,6 @@ const Object* LazyObject::Get(bool dieOnError)
         return object.get();
         return object.get();
     }
     }
 
 
-    // if this is the root object, we return a dummy since there
-    // is no root object int he fbx file - it is just referenced
-    // with id 0.
-    if(id == 0L) {
-        object.reset(new Object(id, element, "Model::RootNode"));
-        return object.get();
-    }
-
     const Token& key = element.KeyToken();
     const Token& key = element.KeyToken();
     const TokenList& tokens = element.Tokens();
     const TokenList& tokens = element.Tokens();
 
 

+ 14 - 39
code/FBX/FBXExporter.cpp

@@ -1219,6 +1219,16 @@ void FBXExporter::WriteObjects ()
         layer.AddChild(le);
         layer.AddChild(le);
         layer.Dump(outstream, binary, indent);
         layer.Dump(outstream, binary, indent);
 
 
+        for(unsigned int lr = 1; lr < m->GetNumUVChannels(); ++ lr)
+        {
+            FBX::Node layerExtra("Layer", int32_t(1));
+            layerExtra.AddChild("Version", int32_t(100));
+            FBX::Node leExtra("LayerElement");
+            leExtra.AddChild("Type", "LayerElementUV");
+            leExtra.AddChild("TypedIndex", int32_t(lr));
+            layerExtra.AddChild(leExtra);
+            layerExtra.Dump(outstream, binary, indent);
+        }
         // finish the node record
         // finish the node record
         indent = 1;
         indent = 1;
         n.End(outstream, binary, indent, true);
         n.End(outstream, binary, indent, true);
@@ -1696,8 +1706,7 @@ void FBXExporter::WriteObjects ()
                     }
                     }
                     if (end) { break; }
                     if (end) { break; }
                 }
                 }
-                limbnodes.insert(parent);
-                skeleton.insert(parent);
+                
                 // if it was the skeleton root we can finish here
                 // if it was the skeleton root we can finish here
                 if (end) { break; }
                 if (end) { break; }
             }
             }
@@ -1838,44 +1847,10 @@ void FBXExporter::WriteObjects ()
             inverse_bone_xform.Inverse();
             inverse_bone_xform.Inverse();
             aiMatrix4x4 tr = inverse_bone_xform * mesh_xform;
             aiMatrix4x4 tr = inverse_bone_xform * mesh_xform;
 
 
-            // this should be the same as the bone's mOffsetMatrix.
-            // if it's not the same, the skeleton isn't in the bind pose.
-            float epsilon = 1e-4f; // some error is to be expected
-            float epsilon_custom = mProperties->GetPropertyFloat("BINDPOSE_EPSILON", -1);
-            if(epsilon_custom > 0)
-                epsilon = epsilon_custom;
-            bool bone_xform_okay = true;
-            if (b && ! tr.Equal(b->mOffsetMatrix, epsilon)) {
-                not_in_bind_pose.insert(b);
-                bone_xform_okay = false;
-            }
+            sdnode.AddChild("Transform", tr);
 
 
-            // if we have a bone we should use the mOffsetMatrix,
-            // otherwise try to just use the calculated transform.
-            if (b) {
-                sdnode.AddChild("Transform", b->mOffsetMatrix);
-            } else {
-                sdnode.AddChild("Transform", tr);
-            }
-            // note: it doesn't matter if we mix these,
-            // because if they disagree we'll throw an exception later.
-            // it could be that the skeleton is not in the bone pose
-            // but all bones are still defined,
-            // in which case this would use the mOffsetMatrix for everything
-            // and a correct skeleton would still be output.
-
-            // transformlink should be the position of the bone in world space.
-            // if the bone is in the bind pose (or nonexistent),
-            // we can just use the matrix we already calculated
-            if (bone_xform_okay) {
-                sdnode.AddChild("TransformLink", bone_xform);
-            // otherwise we can only work it out using the mesh position.
-            } else {
-                aiMatrix4x4 trl = b->mOffsetMatrix;
-                trl.Inverse();
-                trl *= mesh_xform;
-                sdnode.AddChild("TransformLink", trl);
-            }
+
+            sdnode.AddChild("TransformLink", bone_xform);
             // note: this means we ALWAYS rely on the mesh node transform
             // note: this means we ALWAYS rely on the mesh node transform
             // being unchanged from the time the skeleton was bound.
             // being unchanged from the time the skeleton was bound.
             // there's not really any way around this at the moment.
             // there's not really any way around this at the moment.

+ 0 - 2
code/FBX/FBXMeshGeometry.cpp

@@ -115,7 +115,6 @@ MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::strin
 
 
     if(tempVerts.empty()) {
     if(tempVerts.empty()) {
         FBXImporter::LogWarn("encountered mesh with no vertices");
         FBXImporter::LogWarn("encountered mesh with no vertices");
-        return;
     }
     }
 
 
     std::vector<int> tempFaces;
     std::vector<int> tempFaces;
@@ -123,7 +122,6 @@ MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::strin
 
 
     if(tempFaces.empty()) {
     if(tempFaces.empty()) {
         FBXImporter::LogWarn("encountered mesh with no faces");
         FBXImporter::LogWarn("encountered mesh with no faces");
-        return;
     }
     }
 
 
     m_vertices.reserve(tempFaces.size());
     m_vertices.reserve(tempFaces.size());

+ 1 - 1
code/STL/STLLoader.cpp

@@ -225,7 +225,7 @@ void STLImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS
     }
     }
     pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_DIFFUSE);
     pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_DIFFUSE);
     pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_SPECULAR);
     pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_SPECULAR);
-    clrDiffuse = aiColor4D( ai_real(1.0), ai_real(1.0), ai_real(1.0), ai_real(1.0));
+    clrDiffuse = aiColor4D( ai_real(0.05), ai_real(0.05), ai_real(0.05), ai_real(1.0));
     pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_AMBIENT);
     pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_AMBIENT);
 
 
     pScene->mNumMaterials = 1;
     pScene->mNumMaterials = 1;

+ 7 - 1
code/X3D/X3DImporter.cpp

@@ -80,7 +80,13 @@ const aiImporterDesc X3DImporter::Description = {
 //const std::regex X3DImporter::pattern_nws(R"([^, \t\r\n]+)");
 //const std::regex X3DImporter::pattern_nws(R"([^, \t\r\n]+)");
 //const std::regex X3DImporter::pattern_true(R"(^\s*(?:true|1)\s*$)", std::regex::icase);
 //const std::regex X3DImporter::pattern_true(R"(^\s*(?:true|1)\s*$)", std::regex::icase);
 
 
-struct WordIterator: public std::iterator<std::input_iterator_tag, const char*> {
+struct WordIterator {
+    using iterator_category = std::input_iterator_tag;
+    using value_type = const char*;
+    using difference_type = ptrdiff_t;
+    using pointer = value_type*;
+    using reference = value_type&;
+
     static const char *whitespace;
     static const char *whitespace;
     const char *start_, *end_;
     const char *start_, *end_;
     WordIterator(const char *start, const char *end): start_(start), end_(end) {
     WordIterator(const char *start, const char *end): start_(start), end_(end) {

+ 27 - 0
code/glTF2/glTF2Asset.h

@@ -46,6 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  * glTF Extensions Support:
  * glTF Extensions Support:
  *   KHR_materials_pbrSpecularGlossiness full
  *   KHR_materials_pbrSpecularGlossiness full
  *   KHR_materials_unlit full
  *   KHR_materials_unlit full
+ *   KHR_lights_punctual full
  */
  */
 #ifndef GLTF2ASSET_H_INC
 #ifndef GLTF2ASSET_H_INC
 #define GLTF2ASSET_H_INC
 #define GLTF2ASSET_H_INC
@@ -668,6 +669,28 @@ namespace glTF2
         void Read(Value& obj, Asset& r);
         void Read(Value& obj, Asset& r);
     };
     };
 
 
+    //! A light (from KHR_lights_punctual extension)
+    struct Light : public Object
+    {
+        enum Type
+        {
+            Directional,
+            Point,
+            Spot
+        };
+
+        Type type;
+
+        vec3 color;
+        float intensity;
+        Nullable<float> range;
+
+        float innerConeAngle;
+        float outerConeAngle;
+
+        Light() {}
+        void Read(Value& obj, Asset& r);
+    };
 
 
     //! Image data used to create a texture.
     //! Image data used to create a texture.
     struct Image : public Object
     struct Image : public Object
@@ -819,6 +842,7 @@ namespace glTF2
         Nullable<vec3> scale;
         Nullable<vec3> scale;
 
 
         Ref<Camera> camera;
         Ref<Camera> camera;
+        Ref<Light>  light;
 
 
         std::vector< Ref<Node> > skeletons;       //!< The ID of skeleton nodes. Each of which is the root of a node hierarchy.
         std::vector< Ref<Node> > skeletons;       //!< The ID of skeleton nodes. Each of which is the root of a node hierarchy.
         Ref<Skin> skin;                           //!< The ID of the skin referenced by this node.
         Ref<Skin> skin;                           //!< The ID of the skin referenced by this node.
@@ -1050,6 +1074,7 @@ namespace glTF2
         {
         {
             bool KHR_materials_pbrSpecularGlossiness;
             bool KHR_materials_pbrSpecularGlossiness;
             bool KHR_materials_unlit;
             bool KHR_materials_unlit;
+            bool KHR_lights_punctual;
 
 
         } extensionsUsed;
         } extensionsUsed;
 
 
@@ -1063,6 +1088,7 @@ namespace glTF2
         LazyDict<Buffer>      buffers;
         LazyDict<Buffer>      buffers;
         LazyDict<BufferView>  bufferViews;
         LazyDict<BufferView>  bufferViews;
         LazyDict<Camera>      cameras;
         LazyDict<Camera>      cameras;
+        LazyDict<Light>       lights;
         LazyDict<Image>       images;
         LazyDict<Image>       images;
         LazyDict<Material>    materials;
         LazyDict<Material>    materials;
         LazyDict<Mesh>        meshes;
         LazyDict<Mesh>        meshes;
@@ -1083,6 +1109,7 @@ namespace glTF2
             , buffers       (*this, "buffers")
             , buffers       (*this, "buffers")
             , bufferViews   (*this, "bufferViews")
             , bufferViews   (*this, "bufferViews")
             , cameras       (*this, "cameras")
             , cameras       (*this, "cameras")
+            , lights        (*this, "lights", "KHR_lights_punctual")
             , images        (*this, "images")
             , images        (*this, "images")
             , materials     (*this, "materials")
             , materials     (*this, "materials")
             , meshes        (*this, "meshes")
             , meshes        (*this, "meshes")

+ 47 - 0
code/glTF2/glTF2Asset.inl

@@ -1067,6 +1067,39 @@ inline void Camera::Read(Value& obj, Asset& /*r*/)
     }
     }
 }
 }
 
 
+inline void Light::Read(Value& obj, Asset& /*r*/)
+{
+#ifndef M_PI
+    const float M_PI = 3.14159265358979323846f;
+#endif
+
+    std::string type_string;
+    ReadMember(obj, "type", type_string);
+    if (type_string == "directional")
+        type = Light::Directional;
+    else if (type_string == "point")
+        type = Light::Point;
+    else
+        type = Light::Spot;
+
+    name = MemberOrDefault(obj, "name", "");
+
+    SetVector(color, vec3{ 1.0f, 1.0f, 1.0f });
+    ReadMember(obj, "color", color);
+
+    intensity = MemberOrDefault(obj, "intensity", 1.0f);
+
+    ReadMember(obj, "range", range);
+
+    if (type == Light::Spot)
+    {
+        Value* spot = FindObject(obj, "spot");
+        if (!spot) throw DeadlyImportError("GLTF: Light missing its spot parameters");
+        innerConeAngle = MemberOrDefault(*spot, "innerConeAngle", 0.0f);
+        outerConeAngle = MemberOrDefault(*spot, "outerConeAngle", M_PI / 4.0f);
+    }
+}
+
 inline void Node::Read(Value& obj, Asset& r)
 inline void Node::Read(Value& obj, Asset& r)
 {
 {
 
 
@@ -1110,6 +1143,19 @@ inline void Node::Read(Value& obj, Asset& r)
         if (this->camera)
         if (this->camera)
             this->camera->id = this->id;
             this->camera->id = this->id;
     }
     }
+
+    if (Value* extensions = FindObject(obj, "extensions")) {
+        if (r.extensionsUsed.KHR_lights_punctual) {
+
+            if (Value* ext = FindObject(*extensions, "KHR_lights_punctual")) {
+                if (Value* light = FindUInt(*ext, "light")) {
+                    this->light = r.lights.Retrieve(light->GetUint());
+                    if (this->light)
+                        this->light->id = this->id;
+                }
+            }
+        }
+    }
 }
 }
 
 
 inline void Scene::Read(Value& obj, Asset& r)
 inline void Scene::Read(Value& obj, Asset& r)
@@ -1421,6 +1467,7 @@ inline void Asset::ReadExtensionsUsed(Document& doc)
 
 
     CHECK_EXT(KHR_materials_pbrSpecularGlossiness);
     CHECK_EXT(KHR_materials_pbrSpecularGlossiness);
     CHECK_EXT(KHR_materials_unlit);
     CHECK_EXT(KHR_materials_unlit);
+    CHECK_EXT(KHR_lights_punctual);
 
 
     #undef CHECK_EXT
     #undef CHECK_EXT
 }
 }

+ 5 - 0
code/glTF2/glTF2AssetWriter.inl

@@ -202,6 +202,11 @@ namespace glTF2 {
 
 
     }
     }
 
 
+    inline void Write(Value& /*obj*/, Light& /*c*/, AssetWriter& /*w*/)
+    {
+
+    }
+
     inline void Write(Value& obj, Image& img, AssetWriter& w)
     inline void Write(Value& obj, Image& img, AssetWriter& w)
     {
     {
         if (img.bufferView) {
         if (img.bufferView) {

+ 80 - 4
code/glTF2/glTF2Importer.cpp

@@ -140,10 +140,10 @@ static aiTextureMapMode ConvertWrappingMode(SamplerWrap gltfWrapMode)
     }
     }
 }
 }
 
 
-//static void CopyValue(const glTF2::vec3& v, aiColor3D& out)
-//{
-//    out.r = v[0]; out.g = v[1]; out.b = v[2];
-//}
+static void CopyValue(const glTF2::vec3& v, aiColor3D& out)
+{
+    out.r = v[0]; out.g = v[1]; out.b = v[2];
+}
 
 
 static void CopyValue(const glTF2::vec4& v, aiColor4D& out)
 static void CopyValue(const glTF2::vec4& v, aiColor4D& out)
 {
 {
@@ -710,6 +710,69 @@ void glTF2Importer::ImportCameras(glTF2::Asset& r)
     }
     }
 }
 }
 
 
+void glTF2Importer::ImportLights(glTF2::Asset& r)
+{
+    if (!r.lights.Size())
+        return;
+
+    mScene->mNumLights = r.lights.Size();
+    mScene->mLights = new aiLight*[r.lights.Size()];
+
+    for (size_t i = 0; i < r.lights.Size(); ++i) {
+        Light& light = r.lights[i];
+
+        aiLight* ail = mScene->mLights[i] = new aiLight();
+
+        switch (light.type)
+        {
+        case Light::Directional:
+            ail->mType = aiLightSource_DIRECTIONAL; break;
+        case Light::Point:
+            ail->mType = aiLightSource_POINT; break;
+        case Light::Spot:
+            ail->mType = aiLightSource_SPOT; break;
+        }
+
+        if (ail->mType != aiLightSource_POINT)
+        {
+            ail->mDirection = aiVector3D(0.0f, 0.0f, -1.0f);
+            ail->mUp = aiVector3D(0.0f, 1.0f, 0.0f);
+        }
+
+        vec3 colorWithIntensity = { light.color[0] * light.intensity, light.color[1] * light.intensity, light.color[2] * light.intensity };
+        CopyValue(colorWithIntensity, ail->mColorAmbient);
+        CopyValue(colorWithIntensity, ail->mColorDiffuse);
+        CopyValue(colorWithIntensity, ail->mColorSpecular);
+
+        if (ail->mType == aiLightSource_DIRECTIONAL)
+        {
+            ail->mAttenuationConstant = 1.0;
+            ail->mAttenuationLinear = 0.0;
+            ail->mAttenuationQuadratic = 0.0;
+        }
+        else
+        {
+            //in PBR attenuation is calculated using inverse square law which can be expressed
+            //using assimps equation: 1/(att0 + att1 * d + att2 * d*d) with the following parameters
+            //this is correct equation for the case when range (see
+            //https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual)
+            //is not present. When range is not present it is assumed that it is infinite and so numerator is 1.
+            //When range is present then numerator might be any value in range [0,1] and then assimps equation
+            //will not suffice. In this case range is added into metadata in ImportNode function
+            //and its up to implementation to read it when it wants to
+            ail->mAttenuationConstant = 0.0;
+            ail->mAttenuationLinear = 0.0;
+            ail->mAttenuationQuadratic = 1.0;
+        }
+
+        if (ail->mType == aiLightSource_SPOT)
+        {
+            ail->mAngleInnerCone = light.innerConeAngle;
+            ail->mAngleOuterCone = light.outerConeAngle;
+        }
+    }
+}
+
 static void GetNodeTransform(aiMatrix4x4& matrix, const glTF2::Node& node) {
 static void GetNodeTransform(aiMatrix4x4& matrix, const glTF2::Node& node) {
     if (node.matrix.isPresent) {
     if (node.matrix.isPresent) {
         CopyValue(node.matrix.value, matrix);
         CopyValue(node.matrix.value, matrix);
@@ -881,6 +944,18 @@ aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector<unsigned int>&
         pScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName;
         pScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName;
     }
     }
 
 
+    if (node.light) {
+        pScene->mLights[node.light.GetIndex()]->mName = ainode->mName;
+
+        //range is optional - see https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
+        //it is added to meta data of parent node, because there is no other place to put it
+        if (node.light->range.isPresent)
+        {
+            ainode->mMetaData = aiMetadata::Alloc(1);
+            ainode->mMetaData->Set(0, "PBR_LightRange", node.light->range.value);
+        }
+    }
+
     return ainode;
     return ainode;
 }
 }
 
 
@@ -1150,6 +1225,7 @@ void glTF2Importer::InternReadFile(const std::string& pFile, aiScene* pScene, IO
     ImportMeshes(asset);
     ImportMeshes(asset);
 
 
     ImportCameras(asset);
     ImportCameras(asset);
+    ImportLights(asset);
 
 
     ImportNodes(asset);
     ImportNodes(asset);
 
 

+ 1 - 1
include/assimp/config.h.in

@@ -142,7 +142,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 /** @brief  Specifies the maximum angle that may be between two vertex tangents
 /** @brief  Specifies the maximum angle that may be between two vertex tangents
  *         that their tangents and bi-tangents are smoothed.
  *         that their tangents and bi-tangents are smoothed.
  *
  *
- * This applies to the CalcTangentSpace-Step. TFvhe angle is specified
+ * This applies to the CalcTangentSpace-Step. The angle is specified
  * in degrees. The maximum value is 175.
  * in degrees. The maximum value is 175.
  * Property type: float. Default value: 45 degrees
  * Property type: float. Default value: 45 degrees
  */
  */

+ 3 - 0
test/.gitignore

@@ -0,0 +1,3 @@
+# Ignore Unit Test Output files
+
+*_out.*

+ 0 - 24
test/models/PLY/cube_test.ply

@@ -1,24 +0,0 @@
-ply
-format ascii 1.0
-comment Created by Open Asset Import Library - http://assimp.sf.net (v4.1.993695325)
-element vertex 8
-property float x
-property float y
-property float z
-element face 6
-property list uchar int vertex_index
-end_header
-0 0 0
-0 0 1
-0 1 1
-0 1 0
-1 0 0
-1 0 1
-1 1 1
-1 1 0
-4 0 1 2 3
-4 7 6 5 4
-4 0 4 5 1
-4 1 5 6 2
-4 2 6 7 3
-4 3 7 4 0

+ 1 - 1
test/unit/ImportExport/utAssjsonImportExport.cpp

@@ -57,7 +57,7 @@ public:
         const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/OBJ/spider.obj", aiProcess_ValidateDataStructure);
         const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/OBJ/spider.obj", aiProcess_ValidateDataStructure);
 
 
         Exporter exporter;
         Exporter exporter;
-        aiReturn res = exporter.Export(scene, "json", "./spider_test.json");
+        aiReturn res = exporter.Export(scene, "assjson", "./spider_test.json");
         return aiReturn_SUCCESS == res;
         return aiReturn_SUCCESS == res;
     }
     }
 };
 };

+ 29 - 0
test/unit/ImportExport/utExporter.cpp

@@ -71,3 +71,32 @@ TEST_F(ExporterTest, ProgressHandlerTest) {
     TestProgressHandler *ph(new TestProgressHandler);
     TestProgressHandler *ph(new TestProgressHandler);
     exporter.SetProgressHandler(ph);
     exporter.SetProgressHandler(ph);
 }
 }
+
+// Make sure all the registered exporters have useful descriptions
+TEST_F(ExporterTest, ExporterIdTest) {
+    Exporter exporter;
+    size_t exportFormatCount = exporter.GetExportFormatCount();
+    EXPECT_NE(0u, exportFormatCount) << "No registered exporters";
+    typedef std::map<std::string, const aiExportFormatDesc*> ExportIdMap;
+    ExportIdMap exporterMap;
+    for (size_t i = 0; i < exportFormatCount; ++i)
+    {
+        // Check that the exporter description exists and makes sense
+        const aiExportFormatDesc* desc = exporter.GetExportFormatDescription(i);
+        ASSERT_NE(nullptr, desc) << "Missing aiExportFormatDesc at index " << i;
+        EXPECT_NE(nullptr, desc->id) << "Null exporter ID at index " << i;
+        EXPECT_STRNE("", desc->id) << "Empty exporter ID at index " << i;
+        EXPECT_NE(nullptr, desc->description) << "Null exporter description at index " << i;
+        EXPECT_STRNE("", desc->description) << "Empty exporter description at index " << i;
+        EXPECT_NE(nullptr, desc->fileExtension) << "Null exporter file extension at index " << i;
+        EXPECT_STRNE("", desc->fileExtension) << "Empty exporter file extension at index " << i;
+
+        // Check the ID is unique
+        std::string key(desc->id);
+        std::pair<ExportIdMap::iterator, bool> result = exporterMap.emplace(key, desc);
+        EXPECT_TRUE(result.second) << "Duplicate exported id: '" << key << "' " << desc->description << " *." << desc->fileExtension << " at index " << i;
+    }
+
+    const aiExportFormatDesc* desc = exporter.GetExportFormatDescription(exportFormatCount);
+    EXPECT_EQ(nullptr, desc) << "More exporters than claimed";
+}

+ 2 - 2
test/unit/utAssbinImportExport.cpp

@@ -56,8 +56,8 @@ public:
         const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/OBJ/spider.obj", aiProcess_ValidateDataStructure );
         const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/OBJ/spider.obj", aiProcess_ValidateDataStructure );
 
 
         Exporter exporter;
         Exporter exporter;
-        EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "assbin", ASSIMP_TEST_MODELS_DIR "/OBJ/spider_test.assbin" ) );
-        const aiScene *newScene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/OBJ/spider_test.assbin", aiProcess_ValidateDataStructure );
+        EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "assbin", ASSIMP_TEST_MODELS_DIR "/OBJ/spider_out.assbin" ) );
+        const aiScene *newScene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/OBJ/spider_out.assbin", aiProcess_ValidateDataStructure );
 
 
         return newScene != nullptr;
         return newScene != nullptr;
     }
     }

+ 3 - 3
test/unit/utObjImportExport.cpp

@@ -205,8 +205,8 @@ protected:
         ::Assimp::Exporter exporter;
         ::Assimp::Exporter exporter;
         const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/OBJ/spider.obj", aiProcess_ValidateDataStructure );
         const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/OBJ/spider.obj", aiProcess_ValidateDataStructure );
         EXPECT_NE( nullptr, scene );
         EXPECT_NE( nullptr, scene );
-        EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "obj", ASSIMP_TEST_MODELS_DIR "/OBJ/spider_test.obj" ) );
-        EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "objnomtl", ASSIMP_TEST_MODELS_DIR "/OBJ/spider_nomtl_test.obj" ) );
+        EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "obj", ASSIMP_TEST_MODELS_DIR "/OBJ/spider_out.obj" ) );
+        EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "objnomtl", ASSIMP_TEST_MODELS_DIR "/OBJ/spider_nomtl_out.obj" ) );
         
         
         return true;
         return true;
     }
     }
@@ -263,7 +263,7 @@ TEST_F( utObjImportExport, issue809_vertex_color_Test ) {
 
 
 #ifndef ASSIMP_BUILD_NO_EXPORT
 #ifndef ASSIMP_BUILD_NO_EXPORT
     ::Assimp::Exporter exporter;
     ::Assimp::Exporter exporter;
-    EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "obj", ASSIMP_TEST_MODELS_DIR "/OBJ/test.obj" ) );
+    EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "obj", ASSIMP_TEST_MODELS_DIR "/OBJ/test_out.obj" ) );
 #endif // ASSIMP_BUILD_NO_EXPORT
 #endif // ASSIMP_BUILD_NO_EXPORT
 }
 }
 
 

+ 1 - 1
test/unit/utPLYImportExport.cpp

@@ -71,7 +71,7 @@ public:
         Exporter exporter;
         Exporter exporter;
         const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/PLY/cube.ply", aiProcess_ValidateDataStructure);
         const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/PLY/cube.ply", aiProcess_ValidateDataStructure);
         EXPECT_NE(nullptr, scene);
         EXPECT_NE(nullptr, scene);
-        EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "ply", ASSIMP_TEST_MODELS_DIR "/PLY/cube_test.ply"));
+        EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "ply", ASSIMP_TEST_MODELS_DIR "/PLY/cube_out.ply"));
 
 
         return true;
         return true;
     }
     }