add Inter-Quake Model (IQM) Importer

pull/4265/head
Garux 2021-12-18 15:34:58 +03:00
parent 6693e7e08c
commit 46f618a6ef
8 changed files with 545 additions and 0 deletions

View File

@ -0,0 +1,321 @@
/*
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 ){
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 size_t nVerts = imesh->num_vertexes;
const size_t 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

View File

@ -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

View File

@ -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

View File

@ -373,6 +373,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

View File

@ -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());

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.