From 058f98a63658dc1a2579826ba167fd61bed1e21f Mon Sep 17 00:00:00 2001 From: sanine Date: Fri, 4 Mar 2022 10:47:15 -0600 Subject: add assimp submodule --- .../code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp | 680 +++++++++++++++++++++ 1 file changed, 680 insertions(+) create mode 100644 src/mesh/assimp-master/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp (limited to 'src/mesh/assimp-master/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp') diff --git a/src/mesh/assimp-master/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp b/src/mesh/assimp-master/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp new file mode 100644 index 0000000..db9a4a0 --- /dev/null +++ b/src/mesh/assimp-master/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp @@ -0,0 +1,680 @@ +/* +Open Asset Import Library (assimp) +--------------------------------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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_Q3BSP_IMPORTER + +#include "Q3BSPFileImporter.h" +#include "Q3BSPFileData.h" +#include "Q3BSPFileParser.h" + +#include + +#ifdef ASSIMP_BUILD_NO_OWN_ZLIB +#include +#else +#include "../contrib/zlib/zlib.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const aiImporterDesc desc = { + "Quake III BSP Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "bsp pk3" +}; + +namespace Assimp { + +using namespace Q3BSP; + +// ------------------------------------------------------------------------------------------------ +// Local function to create a material key name. +static void createKey(int id1, int id2, std::string &key) { + std::ostringstream str; + str << id1 << "." << id2; + key = str.str(); +} + +// ------------------------------------------------------------------------------------------------ +// Local function to extract the texture ids from a material key-name. +static void extractIds(const std::string &key, int &id1, int &id2) { + id1 = -1; + id2 = -1; + if (key.empty()) { + return; + } + + const std::string::size_type pos = key.find('.'); + if (std::string::npos == pos) { + return; + } + + std::string tmp1 = key.substr(0, pos); + std::string tmp2 = key.substr(pos + 1, key.size() - pos - 1); + id1 = atoi(tmp1.c_str()); + id2 = atoi(tmp2.c_str()); +} + +// ------------------------------------------------------------------------------------------------ +// Local helper function to normalize filenames. +static void normalizePathName(const std::string &rPath, std::string &normalizedPath) { + normalizedPath = std::string(); + if (rPath.empty()) { + return; + } + +#ifdef _WIN32 + std::string sep = "\\"; +#else + std::string sep = "/"; +#endif + + static const unsigned int numDelimiters = 2; + const char delimiters[numDelimiters] = { '/', '\\' }; + normalizedPath = rPath; + for (const char delimiter : delimiters) { + for (size_t j = 0; j < normalizedPath.size(); ++j) { + if (normalizedPath[j] == delimiter) { + normalizedPath[j] = sep[0]; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Constructor. +Q3BSPFileImporter::Q3BSPFileImporter() : + m_pCurrentMesh(nullptr), m_pCurrentFace(nullptr), m_MaterialLookupMap(), mTextures() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor. +Q3BSPFileImporter::~Q3BSPFileImporter() { + // Clear face-to-material map + for (FaceMap::iterator it = m_MaterialLookupMap.begin(); it != m_MaterialLookupMap.end(); ++it) { + const std::string &matName = it->first; + if (!matName.empty()) { + delete it->second; + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Returns true if the loader can read this. +bool Q3BSPFileImporter::CanRead(const std::string &filename, IOSystem * /*pIOHandler*/, bool checkSig) const { + if (!checkSig) { + return SimpleExtensionCheck(filename, "pk3", "bsp"); + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +// Adds extensions. +const aiImporterDesc *Q3BSPFileImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Import method. +void Q3BSPFileImporter::InternReadFile(const std::string &rFile, aiScene *scene, IOSystem *ioHandler) { + ZipArchiveIOSystem Archive(ioHandler, rFile); + if (!Archive.isOpen()) { + throw DeadlyImportError("Failed to open file ", rFile, "."); + } + + std::string archiveName, mapName; + separateMapName(rFile, archiveName, mapName); + + if (mapName.empty()) { + if (!findFirstMapInArchive(Archive, mapName)) { + return; + } + } + + Q3BSPFileParser fileParser(mapName, &Archive); + Q3BSPModel *pBSPModel = fileParser.getModel(); + if (nullptr != pBSPModel) { + CreateDataFromImport(pBSPModel, scene, &Archive); + } +} + +// ------------------------------------------------------------------------------------------------ +// Separates the map name from the import name. +void Q3BSPFileImporter::separateMapName(const std::string &importName, std::string &archiveName, std::string &mapName) { + archiveName = std::string(); + mapName = std::string(); + if (importName.empty()) { + return; + } + + const std::string::size_type pos = importName.rfind(','); + if (std::string::npos == pos) { + archiveName = importName; + return; + } + + archiveName = importName.substr(0, pos); + mapName = importName.substr(pos, importName.size() - pos - 1); +} + +// ------------------------------------------------------------------------------------------------ +// Returns the first map in the map archive. +bool Q3BSPFileImporter::findFirstMapInArchive(ZipArchiveIOSystem &bspArchive, std::string &mapName) { + mapName = std::string(); + std::vector fileList; + bspArchive.getFileListExtension(fileList, "bsp"); + if (fileList.empty()) { + return false; + } + + std::vector::iterator it(fileList.begin()); + for (; it != fileList.end(); ++it) { + const std::string::size_type pos = (*it).find("maps/"); + if (std::string::npos != pos) { + std::string::size_type extPos = (*it).find(".bsp"); + if (std::string::npos != extPos) { + mapName = *it; + return true; + } + } + } + + return false; +} + +// ------------------------------------------------------------------------------------------------ +// Creates the assimp specific data. +void Q3BSPFileImporter::CreateDataFromImport(const Q3BSP::Q3BSPModel *pModel, aiScene *pScene, + ZipArchiveIOSystem *pArchive) { + if (nullptr == pModel || nullptr == pScene) { + return; + } + + pScene->mRootNode = new aiNode; + if (!pModel->m_ModelName.empty()) { + pScene->mRootNode->mName.Set(pModel->m_ModelName); + } + + // Create the face to material relation map + createMaterialMap(pModel); + + // Create all nodes + CreateNodes(pModel, pScene, pScene->mRootNode); + + // Create the assigned materials + createMaterials(pModel, pScene, pArchive); +} + +// ------------------------------------------------------------------------------------------------ +// Creates all assimp nodes. +void Q3BSPFileImporter::CreateNodes(const Q3BSP::Q3BSPModel *pModel, aiScene *pScene, + aiNode *pParent) { + if (nullptr == pModel) { + return; + } + + unsigned int matIdx(0); + std::vector MeshArray; + std::vector NodeArray; + for (FaceMapIt it = m_MaterialLookupMap.begin(); it != m_MaterialLookupMap.end(); ++it) { + std::vector *pArray = (*it).second; + size_t numVerts = countData(*pArray); + if (0 != numVerts) { + aiMesh *pMesh(nullptr); + aiNode *pNode = CreateTopology(pModel, matIdx, *pArray, &pMesh); + if (nullptr != pNode) { + NodeArray.push_back(pNode); + MeshArray.push_back(pMesh); + } + } + matIdx++; + } + + pScene->mNumMeshes = static_cast(MeshArray.size()); + if (pScene->mNumMeshes > 0) { + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + for (size_t i = 0; i < MeshArray.size(); i++) { + aiMesh *pMesh = MeshArray[i]; + if (nullptr != pMesh) { + pScene->mMeshes[i] = pMesh; + } + } + } + + pParent->mNumChildren = static_cast(MeshArray.size()); + pParent->mChildren = new aiNode *[pScene->mRootNode->mNumChildren]; + for (size_t i = 0; i < NodeArray.size(); i++) { + aiNode *pNode = NodeArray[i]; + pNode->mParent = pParent; + pParent->mChildren[i] = pNode; + pParent->mChildren[i]->mMeshes[0] = static_cast(i); + } +} + +// ------------------------------------------------------------------------------------------------ +// Creates the topology. +aiNode *Q3BSPFileImporter::CreateTopology(const Q3BSP::Q3BSPModel *pModel, unsigned int materialIdx, + std::vector &rArray, aiMesh **pMesh) { + size_t numVerts = countData(rArray); + if (0 == numVerts) { + return nullptr; + } + + size_t numFaces = countFaces(rArray); + if (0 == numFaces) { + return nullptr; + } + + aiMesh *mesh = new aiMesh; + size_t numTriangles = countTriangles(rArray); + mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + mesh->mFaces = new aiFace[numTriangles]; + mesh->mNumFaces = static_cast(numTriangles); + + mesh->mNumVertices = static_cast(numVerts); + mesh->mVertices = new aiVector3D[numVerts]; + mesh->mNormals = new aiVector3D[numVerts]; + mesh->mTextureCoords[0] = new aiVector3D[numVerts]; + mesh->mTextureCoords[1] = new aiVector3D[numVerts]; + mesh->mMaterialIndex = materialIdx; + + unsigned int faceIdx = 0; + unsigned int vertIdx = 0; + mesh->mNumUVComponents[0] = 2; + mesh->mNumUVComponents[1] = 2; + for (std::vector::const_iterator it = rArray.begin(); it != rArray.end(); ++it) { + Q3BSP::sQ3BSPFace *pQ3BSPFace = *it; + ai_assert(nullptr != pQ3BSPFace); + if (nullptr == pQ3BSPFace) { + continue; + } + + if (pQ3BSPFace->iNumOfFaceVerts > 0) { + if (pQ3BSPFace->iType == Polygon || pQ3BSPFace->iType == TriangleMesh) { + createTriangleTopology(pModel, pQ3BSPFace, mesh, faceIdx, vertIdx); + } + } + } + + aiNode *pNode = new aiNode; + pNode->mNumMeshes = 1; + pNode->mMeshes = new unsigned int[1]; + *pMesh = mesh; + + return pNode; +} + +// ------------------------------------------------------------------------------------------------ +// Creates the triangle topology from a face array. +void Q3BSPFileImporter::createTriangleTopology(const Q3BSP::Q3BSPModel *pModel, sQ3BSPFace *pQ3BSPFace, + aiMesh *pMesh, unsigned int &faceIdx, unsigned int &vertIdx) { + ai_assert(faceIdx < pMesh->mNumFaces); + + m_pCurrentFace = getNextFace(pMesh, faceIdx); + if (nullptr == m_pCurrentFace) { + return; + } + + m_pCurrentFace->mNumIndices = 3; + m_pCurrentFace->mIndices = new unsigned int[m_pCurrentFace->mNumIndices]; + + size_t idx(0); + for (size_t i = 0; i < (size_t)pQ3BSPFace->iNumOfFaceVerts; ++i) { + const size_t index = pQ3BSPFace->iVertexIndex + pModel->m_Indices[pQ3BSPFace->iFaceVertexIndex + i]; + if (index >= pModel->m_Vertices.size()) { + continue; + } + + sQ3BSPVertex *pVertex = pModel->m_Vertices[index]; + if (nullptr == pVertex) { + continue; + } + if (idx > 2) { + idx = 0; + m_pCurrentFace = getNextFace(pMesh, faceIdx); + if (nullptr != m_pCurrentFace) { + m_pCurrentFace->mNumIndices = 3; + m_pCurrentFace->mIndices = new unsigned int[3]; + m_pCurrentFace->mIndices[idx] = vertIdx; + } + } + + pMesh->mVertices[vertIdx].Set(pVertex->vPosition.x, pVertex->vPosition.y, pVertex->vPosition.z); + pMesh->mNormals[vertIdx].Set(pVertex->vNormal.x, pVertex->vNormal.y, pVertex->vNormal.z); + + pMesh->mTextureCoords[0][vertIdx].Set(pVertex->vTexCoord.x, pVertex->vTexCoord.y, 0.0f); + pMesh->mTextureCoords[1][vertIdx].Set(pVertex->vLightmap.x, pVertex->vLightmap.y, 0.0f); + + vertIdx++; + idx++; + } +} + +// ------------------------------------------------------------------------------------------------ +// Creates all referenced materials. +void Q3BSPFileImporter::createMaterials(const Q3BSP::Q3BSPModel *pModel, aiScene *pScene, + ZipArchiveIOSystem *pArchive) { + if (m_MaterialLookupMap.empty()) { + return; + } + + pScene->mMaterials = new aiMaterial *[m_MaterialLookupMap.size()]; + aiString aiMatName; + int textureId(-1), lightmapId(-1); + for (FaceMapIt it = m_MaterialLookupMap.begin(); it != m_MaterialLookupMap.end(); + ++it) { + const std::string matName(it->first); + if (matName.empty()) { + continue; + } + + aiMatName.Set(matName); + aiMaterial *pMatHelper = new aiMaterial; + pMatHelper->AddProperty(&aiMatName, AI_MATKEY_NAME); + + extractIds(matName, textureId, lightmapId); + + // Adding the texture + if (-1 != textureId) { + sQ3BSPTexture *pTexture = pModel->m_Textures[textureId]; + if (nullptr != pTexture) { + std::string tmp("*"), texName; + tmp += pTexture->strName; + tmp += ".jpg"; + normalizePathName(tmp, texName); + + if (!importTextureFromArchive(pModel, pArchive, pScene, pMatHelper, textureId)) { + ASSIMP_LOG_ERROR("Cannot import texture from archive ", texName); + } + } + } + if (-1 != lightmapId) { + importLightmap(pModel, pScene, pMatHelper, lightmapId); + } + pScene->mMaterials[pScene->mNumMaterials] = pMatHelper; + pScene->mNumMaterials++; + } + pScene->mNumTextures = static_cast(mTextures.size()); + pScene->mTextures = new aiTexture *[pScene->mNumTextures]; + std::copy(mTextures.begin(), mTextures.end(), pScene->mTextures); +} + +// ------------------------------------------------------------------------------------------------ +// Counts the number of referenced vertices. +size_t Q3BSPFileImporter::countData(const std::vector &faceArray) const { + size_t numVerts(0); + for (std::vector::const_iterator it = faceArray.begin(); it != faceArray.end(); + ++it) { + sQ3BSPFace *pQ3BSPFace = *it; + if (pQ3BSPFace->iType == Polygon || pQ3BSPFace->iType == TriangleMesh) { + Q3BSP::sQ3BSPFace *face = *it; + if (nullptr != face) { + numVerts += face->iNumOfFaceVerts; + } + } + } + + return numVerts; +} + +// ------------------------------------------------------------------------------------------------ +// Counts the faces with vertices. +size_t Q3BSPFileImporter::countFaces(const std::vector &rArray) const { + size_t numFaces = 0; + for (std::vector::const_iterator it = rArray.begin(); it != rArray.end(); + ++it) { + Q3BSP::sQ3BSPFace *pQ3BSPFace = *it; + if (pQ3BSPFace->iNumOfFaceVerts > 0) { + numFaces++; + } + } + + return numFaces; +} + +// ------------------------------------------------------------------------------------------------ +// Counts the number of triangles in a Q3-face-array. +size_t Q3BSPFileImporter::countTriangles(const std::vector &rArray) const { + size_t numTriangles = 0; + for (std::vector::const_iterator it = rArray.begin(); it != rArray.end(); + ++it) { + const Q3BSP::sQ3BSPFace *pQ3BSPFace = *it; + if (nullptr != pQ3BSPFace) { + numTriangles += pQ3BSPFace->iNumOfFaceVerts / 3; + } + } + + return numTriangles; +} + +// ------------------------------------------------------------------------------------------------ +// Creates the faces-to-material map. +void Q3BSPFileImporter::createMaterialMap(const Q3BSP::Q3BSPModel *pModel) { + std::string key; + std::vector *pCurFaceArray = nullptr; + for (size_t idx = 0; idx < pModel->m_Faces.size(); idx++) { + Q3BSP::sQ3BSPFace *pQ3BSPFace = pModel->m_Faces[idx]; + const int texId = pQ3BSPFace->iTextureID; + const int lightMapId = pQ3BSPFace->iLightmapID; + createKey(texId, lightMapId, key); + FaceMapIt it = m_MaterialLookupMap.find(key); + if (m_MaterialLookupMap.end() == it) { + pCurFaceArray = new std::vector; + m_MaterialLookupMap[key] = pCurFaceArray; + } else { + pCurFaceArray = (*it).second; + } + ai_assert(nullptr != pCurFaceArray); + if (nullptr != pCurFaceArray) { + pCurFaceArray->push_back(pQ3BSPFace); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Returns the next face. +aiFace *Q3BSPFileImporter::getNextFace(aiMesh *mesh, unsigned int &faceIdx) { + aiFace *face(nullptr); + if (faceIdx < mesh->mNumFaces) { + face = &mesh->mFaces[faceIdx]; + ++faceIdx; + } + + return face; +} + +// ------------------------------------------------------------------------------------------------ +// Imports a texture file. +bool Q3BSPFileImporter::importTextureFromArchive(const Q3BSP::Q3BSPModel *model, + ZipArchiveIOSystem *archive, aiScene *, + aiMaterial *pMatHelper, int textureId) { + if (nullptr == archive || nullptr == pMatHelper) { + return false; + } + + if (textureId < 0 || textureId >= static_cast(model->m_Textures.size())) { + return false; + } + + bool res = true; + sQ3BSPTexture *pTexture = model->m_Textures[textureId]; + if (!pTexture) { + return false; + } + + std::vector supportedExtensions; + supportedExtensions.push_back(".jpg"); + supportedExtensions.push_back(".png"); + supportedExtensions.push_back(".tga"); + std::string textureName, ext; + if (expandFile(archive, pTexture->strName, supportedExtensions, textureName, ext)) { + IOStream *pTextureStream = archive->Open(textureName.c_str()); + if (pTextureStream) { + size_t texSize = pTextureStream->FileSize(); + aiTexture *curTexture = new aiTexture; + curTexture->mHeight = 0; + curTexture->mWidth = static_cast(texSize); + unsigned char *pData = new unsigned char[curTexture->mWidth]; + size_t readSize = pTextureStream->Read(pData, sizeof(unsigned char), curTexture->mWidth); + (void)readSize; + ai_assert(readSize == curTexture->mWidth); + curTexture->pcData = reinterpret_cast(pData); + curTexture->achFormatHint[0] = ext[1]; + curTexture->achFormatHint[1] = ext[2]; + curTexture->achFormatHint[2] = ext[3]; + curTexture->achFormatHint[3] = '\0'; + res = true; + + aiString name; + name.data[0] = '*'; + name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast(MAXLEN - 1), static_cast(mTextures.size())); + + archive->Close(pTextureStream); + + pMatHelper->AddProperty(&name, AI_MATKEY_TEXTURE_DIFFUSE(0)); + mTextures.push_back(curTexture); + } else { + // If it doesn't exist in the archive, it is probably just a reference to an external file. + // We'll leave it up to the user to figure out which extension the file has. + aiString name; + strncpy(name.data, pTexture->strName, sizeof name.data); + name.length = static_cast(strlen(name.data)); + pMatHelper->AddProperty(&name, AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + } + + return res; +} + +// ------------------------------------------------------------------------------------------------ +// Imports a light map file. +bool Q3BSPFileImporter::importLightmap(const Q3BSP::Q3BSPModel *pModel, aiScene *pScene, + aiMaterial *pMatHelper, int lightmapId) { + if (nullptr == pModel || nullptr == pScene || nullptr == pMatHelper) { + return false; + } + + if (lightmapId < 0 || lightmapId >= static_cast(pModel->m_Lightmaps.size())) { + return false; + } + + sQ3BSPLightmap *pLightMap = pModel->m_Lightmaps[lightmapId]; + if (nullptr == pLightMap) { + return false; + } + + aiTexture *pTexture = new aiTexture; + + pTexture->mWidth = CE_BSP_LIGHTMAPWIDTH; + pTexture->mHeight = CE_BSP_LIGHTMAPHEIGHT; + pTexture->pcData = new aiTexel[CE_BSP_LIGHTMAPWIDTH * CE_BSP_LIGHTMAPHEIGHT]; + + ::memcpy(pTexture->pcData, pLightMap->bLMapData, pTexture->mWidth); + size_t p = 0; + for (size_t i = 0; i < CE_BSP_LIGHTMAPWIDTH * CE_BSP_LIGHTMAPHEIGHT; ++i) { + pTexture->pcData[i].r = pLightMap->bLMapData[p++]; + pTexture->pcData[i].g = pLightMap->bLMapData[p++]; + pTexture->pcData[i].b = pLightMap->bLMapData[p++]; + pTexture->pcData[i].a = 0xFF; + } + + aiString name; + name.data[0] = '*'; + name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast(MAXLEN - 1), static_cast(mTextures.size())); + + pMatHelper->AddProperty(&name, AI_MATKEY_TEXTURE_LIGHTMAP(1)); + mTextures.push_back(pTexture); + + return true; +} + +// ------------------------------------------------------------------------------------------------ +// Will search for a supported extension. +bool Q3BSPFileImporter::expandFile(ZipArchiveIOSystem *pArchive, const std::string &rFilename, + const std::vector &rExtList, std::string &rFile, + std::string &rExt) { + ai_assert(nullptr != pArchive); + ai_assert(!rFilename.empty()); + + if (rExtList.empty()) { + rFile = rFilename; + rExt = std::string(); + return true; + } + + bool found = false; + for (std::vector::const_iterator it = rExtList.begin(); it != rExtList.end(); ++it) { + const std::string textureName = rFilename + *it; + if (pArchive->Exists(textureName.c_str())) { + rExt = *it; + rFile = textureName; + found = true; + break; + } + } + + return found; +} + +// ------------------------------------------------------------------------------------------------ + +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_Q3BSP_IMPORTER -- cgit v1.2.1