ソースを参照

Merge branch 'master' into krishty-new-file-detection

Kim Kulling 3 年 前
コミット
efd1d36aa9

+ 30 - 15
code/AssetLib/Collada/ColladaParser.cpp

@@ -331,7 +331,16 @@ void ColladaParser::ReadAssetInfo(XmlNode &node) {
         const std::string &currentName = currentNode.name();
         if (currentName == "unit") {
             mUnitSize = 1.f;
-            XmlParser::getRealAttribute(currentNode, "meter", mUnitSize);
+            std::string tUnitSizeString;
+            if (XmlParser::getStdStrAttribute(currentNode, "meter", tUnitSizeString)) {
+                try {
+                    fast_atoreal_move<ai_real>(tUnitSizeString.data(), mUnitSize);
+                } catch (const DeadlyImportError& die) {
+                    std::string warning("Collada: Failed to parse meter parameter to real number. Exception:\n");
+                    warning.append(die.what());
+                    ASSIMP_LOG_WARN(warning.data());
+                }
+            }
         } else if (currentName == "up_axis") {
             std::string v;
             if (!XmlParser::getValueAsString(currentNode, v)) {
@@ -2242,20 +2251,26 @@ void ColladaParser::ReadNodeGeometry(XmlNode &node, Node *pNode) {
         if (currentName == "bind_material") {
             XmlNode techNode = currentNode.child("technique_common");
             if (techNode) {
-                XmlNode instanceMatNode = techNode.child("instance_material");
-                // read ID of the geometry subgroup and the target material
-                std::string group;
-                XmlParser::getStdStrAttribute(instanceMatNode, "symbol", group);
-                XmlParser::getStdStrAttribute(instanceMatNode, "target", url);
-                const char *urlMat = url.c_str();
-                Collada::SemanticMappingTable s;
-                if (urlMat[0] == '#')
-                    urlMat++;
-
-                s.mMatName = urlMat;
-                // store the association
-                instance.mMaterials[group] = s;
-                ReadMaterialVertexInputBinding(instanceMatNode, s);
+                for (XmlNode instanceMatNode = techNode.child("instance_material"); instanceMatNode; instanceMatNode = instanceMatNode.next_sibling())
+                {
+                    const std::string &instance_name = instanceMatNode.name();
+                    if (instance_name == "instance_material")
+                    {
+                        // read ID of the geometry subgroup and the target material
+                        std::string group;
+                        XmlParser::getStdStrAttribute(instanceMatNode, "symbol", group);
+                        XmlParser::getStdStrAttribute(instanceMatNode, "target", url);
+                        const char *urlMat = url.c_str();
+                        Collada::SemanticMappingTable s;
+                        if (urlMat[0] == '#')
+                            urlMat++;
+
+                        s.mMatName = urlMat;
+                        // store the association
+                        instance.mMaterials[group] = s;
+                        ReadMaterialVertexInputBinding(instanceMatNode, s);
+                    }
+                }
             }
         }
     }

+ 322 - 0
code/AssetLib/IQM/IQMImporter.cpp

@@ -0,0 +1,322 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2021, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+#ifndef ASSIMP_BUILD_NO_IQM_IMPORTER
+
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/IOStreamBuffer.h>
+#include <assimp/ai_assert.h>
+#include <assimp/importerdesc.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
+#include <assimp/ByteSwapper.h>
+#include <memory>
+#include <numeric>
+
+#include "IQMImporter.h"
+#include "iqm.h"
+
+// RESOURCES:
+// http://sauerbraten.org/iqm/
+// https://github.com/lsalzman/iqm
+
+
+inline void swap_block( uint32_t *block, size_t size ){
+    (void)block; // suppress 'unreferenced formal parameter' MSVC warning
+    size >>= 2;
+    for ( size_t i = 0; i < size; ++i )
+        AI_SWAP4( block[ i ] );
+}
+
+static const aiImporterDesc desc = {
+    "Inter-Quake Model Importer",
+    "",
+    "",
+    "",
+    aiImporterFlags_SupportBinaryFlavour,
+    0,
+    0,
+    0,
+    0,
+    "iqm"
+};
+
+namespace Assimp {
+
+// ------------------------------------------------------------------------------------------------
+//  Default constructor
+IQMImporter::IQMImporter() :
+        mScene(nullptr) {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Returns true, if file is a binary Inter-Quake Model file.
+bool IQMImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
+    const std::string extension = GetExtension(pFile);
+
+    if (extension == "iqm")
+        return true;
+    else if (!extension.length() || checkSig) {
+        if (!pIOHandler) {
+            return true;
+        }
+        /*
+         * don't use CheckMagicToken because that checks with swapped bytes too, leading to false
+         * positives. This magic is not uint32_t, but char[4], so memcmp is the best way
+
+        const char* tokens[] = {"3DMO", "3dmo"};
+        return CheckMagicToken(pIOHandler,pFile,tokens,2,0,4);
+        */
+        std::unique_ptr<IOStream> pStream(pIOHandler->Open(pFile, "rb"));
+        unsigned char data[15];
+        if (!pStream || 15 != pStream->Read(data, 1, 15)) {
+            return false;
+        }
+        return !memcmp(data, "INTERQUAKEMODEL", 15);
+    }
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiImporterDesc *IQMImporter::GetInfo() const {
+    return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Model 3D import implementation
+void IQMImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSystem *pIOHandler) {
+    // Read file into memory
+    std::unique_ptr<IOStream> pStream(pIOHandler->Open(file, "rb"));
+    if (!pStream.get()) {
+        throw DeadlyImportError("Failed to open file ", file, ".");
+    }
+
+    // Get the file-size and validate it, throwing an exception when fails
+    const size_t fileSize = pStream->FileSize();
+    if (fileSize < sizeof( iqmheader )) {
+        throw DeadlyImportError("IQM-file ", file, " is too small.");
+    }
+    std::vector<unsigned char> buffer(fileSize);
+    unsigned char *data = buffer.data();
+    if (fileSize != pStream->Read(data, 1, fileSize)) {
+        throw DeadlyImportError("Failed to read the file ", file, ".");
+    }
+
+    // get header
+    iqmheader &hdr = reinterpret_cast<iqmheader&>( *data );
+    swap_block( &hdr.version, sizeof( iqmheader ) - sizeof( iqmheader::magic ) );
+
+    // extra check for header
+    if (memcmp(data, IQM_MAGIC, sizeof( IQM_MAGIC ) )
+     || hdr.version != IQM_VERSION
+     || hdr.filesize != fileSize) {
+        throw DeadlyImportError("Bad binary header in file ", file, ".");
+    }
+
+    ASSIMP_LOG_DEBUG("IQM: loading ", file);
+
+    // create the root node
+    pScene->mRootNode = new aiNode( "<IQMRoot>" );
+    // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system
+    pScene->mRootNode->mTransformation = aiMatrix4x4(
+            1.f, 0.f, 0.f, 0.f,
+            0.f, 0.f, 1.f, 0.f,
+            0.f, -1.f, 0.f, 0.f,
+            0.f, 0.f, 0.f, 1.f);
+    pScene->mRootNode->mNumMeshes = hdr.num_meshes;
+    pScene->mRootNode->mMeshes = new unsigned int[hdr.num_meshes];
+    std::iota( pScene->mRootNode->mMeshes, pScene->mRootNode->mMeshes + pScene->mRootNode->mNumMeshes, 0 );
+
+    mScene = pScene;
+
+    // Allocate output storage
+    pScene->mNumMeshes = 0;
+    pScene->mMeshes = new aiMesh *[hdr.num_meshes](); // Set arrays to zero to ensue proper destruction if an exception is raised
+
+    pScene->mNumMaterials = 0;
+    pScene->mMaterials = new aiMaterial *[hdr.num_meshes]();
+
+    // swap vertex arrays beforehand...
+    for( auto array = reinterpret_cast<iqmvertexarray*>( data + hdr.ofs_vertexarrays ), end = array + hdr.num_vertexarrays; array != end; ++array )
+    {
+        swap_block( &array->type, sizeof( iqmvertexarray ) );
+    }
+
+    // Read all surfaces from the file
+    for( auto imesh = reinterpret_cast<iqmmesh*>( data + hdr.ofs_meshes ), end_ = imesh + hdr.num_meshes; imesh != end_; ++imesh )
+    {
+        swap_block( &imesh->name, sizeof( iqmmesh ) );
+        // Allocate output mesh & material
+        auto mesh = pScene->mMeshes[pScene->mNumMeshes++] = new aiMesh();
+        mesh->mMaterialIndex = pScene->mNumMaterials;
+        auto mat = pScene->mMaterials[pScene->mNumMaterials++] = new aiMaterial();
+
+        {
+            auto text = reinterpret_cast<char*>( data + hdr.ofs_text );
+            aiString name( text + imesh->material );
+            mat->AddProperty( &name, AI_MATKEY_NAME );
+            mat->AddProperty( &name, AI_MATKEY_TEXTURE_DIFFUSE(0) );
+        }
+
+        // Fill mesh information
+        mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
+        mesh->mNumFaces = 0;
+        mesh->mFaces = new aiFace[imesh->num_triangles];
+
+        // Fill in all triangles
+        for( auto tri = reinterpret_cast<iqmtriangle*>( data + hdr.ofs_triangles ) + imesh->first_triangle, end = tri + imesh->num_triangles; tri != end; ++tri )
+        {
+            swap_block( tri->vertex, sizeof( tri->vertex ) );
+            auto& face = mesh->mFaces[mesh->mNumFaces++];
+            face.mNumIndices = 3;
+            face.mIndices = new unsigned int[3]{ tri->vertex[0] - imesh->first_vertex,
+                                                 tri->vertex[2] - imesh->first_vertex,
+                                                 tri->vertex[1] - imesh->first_vertex };
+        }
+
+        // Fill in all vertices
+        for( auto array = reinterpret_cast<const iqmvertexarray*>( data + hdr.ofs_vertexarrays ), end__ = array + hdr.num_vertexarrays; array != end__; ++array )
+        {
+            const unsigned int nVerts = imesh->num_vertexes;
+            const unsigned int step = array->size;
+
+            switch ( array->type )
+            {
+            case IQM_POSITION:
+                if( array->format == IQM_FLOAT && step >= 3 ){
+                    mesh->mNumVertices = nVerts;
+                    auto v = mesh->mVertices = new aiVector3D[nVerts];
+                    for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step,
+                        end = f + nVerts * step; f != end; f += step, ++v )
+                    {
+                        *v = { AI_BE( f[0] ),
+                               AI_BE( f[1] ),
+                               AI_BE( f[2] ) };
+                    }
+                }
+                break;
+               case IQM_TEXCOORD:
+                if( array->format == IQM_FLOAT && step >= 2)
+                {
+                    auto v = mesh->mTextureCoords[0] = new aiVector3D[nVerts];
+                    mesh->mNumUVComponents[0] = 2;
+                    for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step,
+                        end = f + nVerts * step; f != end; f += step, ++v )
+                    {
+                        *v = { AI_BE( f[0] ),
+                           1 - AI_BE( f[1] ), 0 };
+                    }
+                }
+                break;
+            case IQM_NORMAL:
+                if (array->format == IQM_FLOAT && step >= 3)
+                {
+                    auto v = mesh->mNormals = new aiVector3D[nVerts];
+                    for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step,
+                        end = f + nVerts * step; f != end; f += step, ++v )
+                    {
+                        *v = { AI_BE( f[0] ),
+                               AI_BE( f[1] ),
+                               AI_BE( f[2] ) };
+                    }
+                }
+                break;
+            case IQM_COLOR:
+                if (array->format == IQM_UBYTE && step >= 3)
+                {
+                    auto v = mesh->mColors[0] = new aiColor4D[nVerts];
+                    for( auto f = ( data + array->offset ) + imesh->first_vertex * step,
+                        end = f + nVerts * step; f != end; f += step, ++v )
+                    {
+                        *v = { ( f[0] ) / 255.f,
+                               ( f[1] ) / 255.f,
+                               ( f[2] ) / 255.f,
+                               step == 3? 1 : ( f[3] ) / 255.f };
+                    }
+                }
+                else if (array->format == IQM_FLOAT && step >= 3)
+                {
+                    auto v = mesh->mColors[0] = new aiColor4D[nVerts];
+                    for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step,
+                        end = f + nVerts * step; f != end; f += step, ++v )
+                    {
+                        *v = { AI_BE( f[0] ),
+                               AI_BE( f[1] ),
+                               AI_BE( f[2] ),
+                               step == 3? 1 : AI_BE( f[3] ) };
+                    }
+                }
+                break;
+            case IQM_TANGENT:
+#if 0
+                if (array->format == IQM_FLOAT && step >= 3)
+                {
+                    auto v = mesh->mTangents = new aiVector3D[nVerts];
+                    for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step,
+                        end = f + nVerts * step; f != end; f += step, ++v )
+                    {
+                        *v = { AI_BE( f[0] ),
+                               AI_BE( f[1] ),
+                               AI_BE( f[2] ) };
+                    }
+                }
+#endif
+                break;
+            case IQM_BLENDINDEXES:
+            case IQM_BLENDWEIGHTS:
+            case IQM_CUSTOM:
+                break; // these attributes are not relevant.
+
+            default:
+                break;
+            }
+        }
+    }
+}
+
+
+// ------------------------------------------------------------------------------------------------
+
+} // Namespace Assimp
+
+#endif // !! ASSIMP_BUILD_NO_IQM_IMPORTER

+ 78 - 0
code/AssetLib/IQM/IQMImporter.h

@@ -0,0 +1,78 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2021, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file IQMImporter.h
+*   @brief Declares the importer class to read a scene from an Inter-Quake Model file
+*/
+
+#pragma once
+
+#ifndef ASSIMP_BUILD_NO_IQM_IMPORTER
+
+#include <assimp/BaseImporter.h>
+#include <assimp/material.h>
+
+namespace Assimp {
+
+class IQMImporter : public BaseImporter {
+public:
+	/// \brief  Default constructor
+	IQMImporter();
+    ~IQMImporter() override {}
+
+	/// \brief  Returns whether the class can handle the format of the given file.
+	/// \remark See BaseImporter::CanRead() for details.
+	bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override;
+
+protected:
+    //! \brief  Appends the supported extension.
+    const aiImporterDesc *GetInfo() const override;
+
+    //! \brief  File import implementation.
+    void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
+
+private:
+    aiScene *mScene = nullptr; // the scene to import to
+};
+
+} // Namespace Assimp
+
+#endif // ASSIMP_BUILD_NO_IQM_IMPORTER

+ 134 - 0
code/AssetLib/IQM/iqm.h

@@ -0,0 +1,134 @@
+#ifndef __IQM_H__
+#define __IQM_H__
+
+#define IQM_MAGIC "INTERQUAKEMODEL"
+#define IQM_VERSION 2
+
+struct iqmheader
+{
+    char magic[16];
+    unsigned int version;
+    unsigned int filesize;
+    unsigned int flags;
+    unsigned int num_text, ofs_text;
+    unsigned int num_meshes, ofs_meshes;
+    unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays;
+    unsigned int num_triangles, ofs_triangles, ofs_adjacency;
+    unsigned int num_joints, ofs_joints;
+    unsigned int num_poses, ofs_poses;
+    unsigned int num_anims, ofs_anims;
+    unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds;
+    unsigned int num_comment, ofs_comment;
+    unsigned int num_extensions, ofs_extensions;
+};
+
+struct iqmmesh
+{
+    unsigned int name;
+    unsigned int material;
+    unsigned int first_vertex, num_vertexes;
+    unsigned int first_triangle, num_triangles;
+};
+
+enum
+{
+    IQM_POSITION     = 0,
+    IQM_TEXCOORD     = 1,
+    IQM_NORMAL       = 2,
+    IQM_TANGENT      = 3,
+    IQM_BLENDINDEXES = 4,
+    IQM_BLENDWEIGHTS = 5,
+    IQM_COLOR        = 6,
+    IQM_CUSTOM       = 0x10
+};
+
+enum
+{
+    IQM_BYTE   = 0,
+    IQM_UBYTE  = 1,
+    IQM_SHORT  = 2,
+    IQM_USHORT = 3,
+    IQM_INT    = 4,
+    IQM_UINT   = 5,
+    IQM_HALF   = 6,
+    IQM_FLOAT  = 7,
+    IQM_DOUBLE = 8
+};
+
+struct iqmtriangle
+{
+    unsigned int vertex[3];
+};
+
+struct iqmadjacency
+{
+    unsigned int triangle[3];
+};
+
+struct iqmjointv1
+{
+    unsigned int name;
+    int parent;
+    float translate[3], rotate[3], scale[3];
+};
+
+struct iqmjoint
+{
+    unsigned int name;
+    int parent;
+    float translate[3], rotate[4], scale[3];
+};
+
+struct iqmposev1
+{
+    int parent;
+    unsigned int mask;
+    float channeloffset[9];
+    float channelscale[9];
+};
+
+struct iqmpose
+{
+    int parent;
+    unsigned int mask;
+    float channeloffset[10];
+    float channelscale[10];
+};
+
+struct iqmanim
+{
+    unsigned int name;
+    unsigned int first_frame, num_frames;
+    float framerate;
+    unsigned int flags;
+};
+
+enum
+{
+    IQM_LOOP = 1<<0
+};
+
+struct iqmvertexarray
+{
+    unsigned int type;
+    unsigned int flags;
+    unsigned int format;
+    unsigned int size;
+    unsigned int offset;
+};
+
+struct iqmbounds
+{
+    float bbmin[3], bbmax[3];
+    float xyradius, radius;
+};
+
+struct iqmextension
+{
+    unsigned int name;
+    unsigned int num_data, ofs_data;
+    unsigned int ofs_extensions; // pointer to next extension
+};
+
+#endif
+

+ 6 - 0
code/CMakeLists.txt

@@ -376,6 +376,12 @@ ADD_ASSIMP_IMPORTER( IRRMESH
   AssetLib/Irr/IRRShared.h
 )
 
+ADD_ASSIMP_IMPORTER( IQM
+  AssetLib/IQM/IQMImporter.cpp
+  AssetLib/IQM/iqm.h
+  AssetLib/IQM/IQMImporter.h
+)
+
 ADD_ASSIMP_IMPORTER( IRR
   AssetLib/Irr/IRRLoader.cpp
   AssetLib/Irr/IRRLoader.h

+ 6 - 0
code/Common/ImporterRegistry.cpp

@@ -202,6 +202,9 @@ corresponding preprocessor flag to selectively disable formats.
 #ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
 #include "AssetLib/M3D/M3DImporter.h"
 #endif
+#ifndef ASSIMP_BUILD_NO_IQM_IMPORTER
+#include "AssetLib/IQM/IQMImporter.h"
+#endif
 
 namespace Assimp {
 
@@ -370,6 +373,9 @@ void GetImporterInstanceList(std::vector<BaseImporter *> &out) {
 #endif
 #ifndef ASSIMP_BUILD_NO_MMD_IMPORTER
     out.push_back(new MMDImporter());
+#endif
+#ifndef ASSIMP_BUILD_NO_IQM_IMPORTER
+    out.push_back(new IQMImporter());
 #endif
     //#ifndef ASSIMP_BUILD_NO_STEP_IMPORTER
     //    out.push_back(new StepFile::StepFileImporter());

BIN
test/models/IQM/Body.jpg


BIN
test/models/IQM/Head.jpg


BIN
test/models/IQM/mrfixit.iqm