| /* |
| --------------------------------------------------------------------------- |
| Open Asset Import Library (assimp) |
| --------------------------------------------------------------------------- |
| |
| Copyright (c) 2006-2016, 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_MMD_IMPORTER |
| |
| #include "MMDImporter.h" |
| #include "MMDPmdParser.h" |
| #include "MMDPmxParser.h" |
| #include "MMDVmdParser.h" |
| #include "ConvertToLHProcess.h" |
| #include <assimp/DefaultIOSystem.h> |
| #include <assimp/Importer.hpp> |
| #include <assimp/ai_assert.h> |
| #include <assimp/scene.h> |
| #include <fstream> |
| #include <iomanip> |
| #include <memory> |
| |
| static const aiImporterDesc desc = {"MMD Importer", |
| "", |
| "", |
| "surfaces supported?", |
| aiImporterFlags_SupportTextFlavour, |
| 0, |
| 0, |
| 0, |
| 0, |
| "pmx"}; |
| |
| namespace Assimp { |
| |
| using namespace std; |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Default constructor |
| MMDImporter::MMDImporter() |
| : m_Buffer(), |
| // m_pRootObject( NULL ), |
| m_strAbsPath("") { |
| DefaultIOSystem io; |
| m_strAbsPath = io.getOsSeparator(); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Destructor. |
| MMDImporter::~MMDImporter() { |
| // delete m_pRootObject; |
| // m_pRootObject = NULL; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Returns true, if file is an pmx file. |
| bool MMDImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, |
| bool checkSig) const { |
| if (!checkSig) // Check File Extension |
| { |
| return SimpleExtensionCheck(pFile, "pmx"); |
| } else // Check file Header |
| { |
| static const char *pTokens[] = {"PMX "}; |
| return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, pTokens, |
| 1); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| const aiImporterDesc *MMDImporter::GetInfo() const { return &desc; } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // MMD import implementation |
| void MMDImporter::InternReadFile(const std::string &file, aiScene *pScene, |
| IOSystem * /*pIOHandler*/) { |
| // Read file by istream |
| std::filebuf fb; |
| if (!fb.open(file, std::ios::in | std::ios::binary)) { |
| throw DeadlyImportError("Failed to open file " + file + "."); |
| } |
| |
| std::istream fileStream(&fb); |
| |
| // Get the file-size and validate it, throwing an exception when fails |
| fileStream.seekg(0, fileStream.end); |
| size_t fileSize = static_cast<size_t>(fileStream.tellg()); |
| fileStream.seekg(0, fileStream.beg); |
| |
| if (fileSize < sizeof(pmx::PmxModel)) { |
| throw DeadlyImportError(file + " is too small."); |
| } |
| |
| pmx::PmxModel model; |
| model.Read(&fileStream); |
| |
| CreateDataFromImport(&model, pScene); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void MMDImporter::CreateDataFromImport(const pmx::PmxModel *pModel, |
| aiScene *pScene) { |
| if (pModel == NULL) { |
| return; |
| } |
| |
| aiNode *pNode = new aiNode; |
| if (!pModel->model_name.empty()) { |
| pNode->mName.Set(pModel->model_name); |
| } else { |
| ai_assert(false); |
| } |
| |
| pScene->mRootNode = pNode; |
| |
| pNode = new aiNode; |
| pScene->mRootNode->addChildren(1, &pNode); |
| pNode->mName.Set(string(pModel->model_name) + string("_mesh")); |
| |
| // split mesh by materials |
| pNode->mNumMeshes = pModel->material_count; |
| pNode->mMeshes = new unsigned int[pNode->mNumMeshes]; |
| for (unsigned int index = 0; index < pNode->mNumMeshes; index++) { |
| pNode->mMeshes[index] = index; |
| } |
| |
| pScene->mNumMeshes = pModel->material_count; |
| pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; |
| for (unsigned int i = 0, indexStart = 0; i < pScene->mNumMeshes; i++) { |
| const int indexCount = pModel->materials[i].index_count; |
| |
| pScene->mMeshes[i] = CreateMesh(pModel, indexStart, indexCount); |
| pScene->mMeshes[i]->mName = pModel->materials[i].material_name; |
| pScene->mMeshes[i]->mMaterialIndex = i; |
| indexStart += indexCount; |
| } |
| |
| // create node hierarchy for bone position |
| aiNode **ppNode = new aiNode *[pModel->bone_count]; |
| for (auto i = 0; i < pModel->bone_count; i++) { |
| ppNode[i] = new aiNode(pModel->bones[i].bone_name); |
| } |
| |
| for (auto i = 0; i < pModel->bone_count; i++) { |
| const pmx::PmxBone &bone = pModel->bones[i]; |
| |
| if (bone.parent_index < 0) { |
| pScene->mRootNode->addChildren(1, ppNode + i); |
| } else { |
| ppNode[bone.parent_index]->addChildren(1, ppNode + i); |
| |
| aiVector3D v3 = aiVector3D( |
| bone.position[0] - pModel->bones[bone.parent_index].position[0], |
| bone.position[1] - pModel->bones[bone.parent_index].position[1], |
| bone.position[2] - pModel->bones[bone.parent_index].position[2]); |
| aiMatrix4x4::Translation(v3, ppNode[i]->mTransformation); |
| } |
| } |
| |
| // create materials |
| pScene->mNumMaterials = pModel->material_count; |
| pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; |
| for (unsigned int i = 0; i < pScene->mNumMaterials; i++) { |
| pScene->mMaterials[i] = CreateMaterial(&pModel->materials[i], pModel); |
| } |
| |
| // Convert everything to OpenGL space |
| MakeLeftHandedProcess convertProcess; |
| convertProcess.Execute(pScene); |
| |
| FlipUVsProcess uvFlipper; |
| uvFlipper.Execute(pScene); |
| |
| FlipWindingOrderProcess windingFlipper; |
| windingFlipper.Execute(pScene); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| aiMesh *MMDImporter::CreateMesh(const pmx::PmxModel *pModel, |
| const int indexStart, const int indexCount) { |
| aiMesh *pMesh = new aiMesh; |
| |
| pMesh->mNumVertices = indexCount; |
| |
| pMesh->mNumFaces = indexCount / 3; |
| pMesh->mFaces = new aiFace[pMesh->mNumFaces]; |
| |
| const int numIndices = 3; // trianglular face |
| for (unsigned int index = 0; index < pMesh->mNumFaces; index++) { |
| pMesh->mFaces[index].mNumIndices = numIndices; |
| unsigned int *indices = new unsigned int[numIndices]; |
| indices[0] = numIndices * index; |
| indices[1] = numIndices * index + 1; |
| indices[2] = numIndices * index + 2; |
| pMesh->mFaces[index].mIndices = indices; |
| } |
| |
| pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; |
| pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; |
| pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices]; |
| pMesh->mNumUVComponents[0] = 2; |
| |
| // additional UVs |
| for (int i = 1; i <= pModel->setting.uv; i++) { |
| pMesh->mTextureCoords[i] = new aiVector3D[pMesh->mNumVertices]; |
| pMesh->mNumUVComponents[i] = 4; |
| } |
| |
| map<int, vector<aiVertexWeight>> bone_vertex_map; |
| |
| // fill in contents and create bones |
| for (int index = 0; index < indexCount; index++) { |
| const pmx::PmxVertex *v = |
| &pModel->vertices[pModel->indices[indexStart + index]]; |
| const float *position = v->position; |
| pMesh->mVertices[index].Set(position[0], position[1], position[2]); |
| const float *normal = v->normal; |
| |
| pMesh->mNormals[index].Set(normal[0], normal[1], normal[2]); |
| pMesh->mTextureCoords[0][index].x = v->uv[0]; |
| pMesh->mTextureCoords[0][index].y = v->uv[1]; |
| |
| for (int i = 1; i <= pModel->setting.uv; i++) { |
| // TODO: wrong here? use quaternion transform? |
| pMesh->mTextureCoords[i][index].x = v->uva[i][0]; |
| pMesh->mTextureCoords[i][index].y = v->uva[i][1]; |
| } |
| |
| // handle bone map |
| const auto vsBDEF1_ptr = |
| dynamic_cast<pmx::PmxVertexSkinningBDEF1 *>(v->skinning.get()); |
| const auto vsBDEF2_ptr = |
| dynamic_cast<pmx::PmxVertexSkinningBDEF2 *>(v->skinning.get()); |
| const auto vsBDEF4_ptr = |
| dynamic_cast<pmx::PmxVertexSkinningBDEF4 *>(v->skinning.get()); |
| const auto vsSDEF_ptr = |
| dynamic_cast<pmx::PmxVertexSkinningSDEF *>(v->skinning.get()); |
| switch (v->skinning_type) { |
| case pmx::PmxVertexSkinningType::BDEF1: |
| bone_vertex_map[vsBDEF1_ptr->bone_index].push_back( |
| aiVertexWeight(index, 1.0)); |
| break; |
| case pmx::PmxVertexSkinningType::BDEF2: |
| bone_vertex_map[vsBDEF2_ptr->bone_index1].push_back( |
| aiVertexWeight(index, vsBDEF2_ptr->bone_weight)); |
| bone_vertex_map[vsBDEF2_ptr->bone_index2].push_back( |
| aiVertexWeight(index, 1.0f - vsBDEF2_ptr->bone_weight)); |
| break; |
| case pmx::PmxVertexSkinningType::BDEF4: |
| bone_vertex_map[vsBDEF4_ptr->bone_index1].push_back( |
| aiVertexWeight(index, vsBDEF4_ptr->bone_weight1)); |
| bone_vertex_map[vsBDEF4_ptr->bone_index2].push_back( |
| aiVertexWeight(index, vsBDEF4_ptr->bone_weight2)); |
| bone_vertex_map[vsBDEF4_ptr->bone_index3].push_back( |
| aiVertexWeight(index, vsBDEF4_ptr->bone_weight3)); |
| bone_vertex_map[vsBDEF4_ptr->bone_index4].push_back( |
| aiVertexWeight(index, vsBDEF4_ptr->bone_weight4)); |
| break; |
| case pmx::PmxVertexSkinningType::SDEF: // TODO: how to use sdef_c, sdef_r0, |
| // sdef_r1? |
| bone_vertex_map[vsSDEF_ptr->bone_index1].push_back( |
| aiVertexWeight(index, vsSDEF_ptr->bone_weight)); |
| bone_vertex_map[vsSDEF_ptr->bone_index2].push_back( |
| aiVertexWeight(index, 1.0f - vsSDEF_ptr->bone_weight)); |
| break; |
| case pmx::PmxVertexSkinningType::QDEF: |
| const auto vsQDEF_ptr = |
| dynamic_cast<pmx::PmxVertexSkinningQDEF *>(v->skinning.get()); |
| bone_vertex_map[vsQDEF_ptr->bone_index1].push_back( |
| aiVertexWeight(index, vsQDEF_ptr->bone_weight1)); |
| bone_vertex_map[vsQDEF_ptr->bone_index2].push_back( |
| aiVertexWeight(index, vsQDEF_ptr->bone_weight2)); |
| bone_vertex_map[vsQDEF_ptr->bone_index3].push_back( |
| aiVertexWeight(index, vsQDEF_ptr->bone_weight3)); |
| bone_vertex_map[vsQDEF_ptr->bone_index4].push_back( |
| aiVertexWeight(index, vsQDEF_ptr->bone_weight4)); |
| break; |
| } |
| } |
| |
| // make all bones for each mesh |
| // assign bone weights to skinned bones (otherwise just initialize) |
| auto bone_ptr_ptr = new aiBone *[pModel->bone_count]; |
| pMesh->mNumBones = pModel->bone_count; |
| pMesh->mBones = bone_ptr_ptr; |
| for (auto ii = 0; ii < pModel->bone_count; ++ii) { |
| auto pBone = new aiBone; |
| const auto &pmxBone = pModel->bones[ii]; |
| pBone->mName = pmxBone.bone_name; |
| aiVector3D pos(pmxBone.position[0], pmxBone.position[1], pmxBone.position[2]); |
| aiMatrix4x4::Translation(-pos, pBone->mOffsetMatrix); |
| auto it = bone_vertex_map.find(ii); |
| if (it != bone_vertex_map.end()) { |
| pBone->mNumWeights = static_cast<unsigned int>(it->second.size()); |
| pBone->mWeights = it->second.data(); |
| it->second.swap(*(new vector<aiVertexWeight>)); |
| } |
| bone_ptr_ptr[ii] = pBone; |
| } |
| |
| return pMesh; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| aiMaterial *MMDImporter::CreateMaterial(const pmx::PmxMaterial *pMat, |
| const pmx::PmxModel *pModel) { |
| aiMaterial *mat = new aiMaterial(); |
| aiString name(pMat->material_english_name); |
| mat->AddProperty(&name, AI_MATKEY_NAME); |
| |
| aiColor3D diffuse(pMat->diffuse[0], pMat->diffuse[1], pMat->diffuse[2]); |
| mat->AddProperty(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE); |
| aiColor3D specular(pMat->specular[0], pMat->specular[1], pMat->specular[2]); |
| mat->AddProperty(&specular, 1, AI_MATKEY_COLOR_SPECULAR); |
| aiColor3D ambient(pMat->ambient[0], pMat->ambient[1], pMat->ambient[2]); |
| mat->AddProperty(&ambient, 1, AI_MATKEY_COLOR_AMBIENT); |
| |
| float opacity = pMat->diffuse[3]; |
| mat->AddProperty(&opacity, 1, AI_MATKEY_OPACITY); |
| float shininess = pMat->specularlity; |
| mat->AddProperty(&shininess, 1, AI_MATKEY_SHININESS_STRENGTH); |
| |
| aiString texture_path(pModel->textures[pMat->diffuse_texture_index]); |
| mat->AddProperty(&texture_path, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0)); |
| int mapping_uvwsrc = 0; |
| mat->AddProperty(&mapping_uvwsrc, 1, |
| AI_MATKEY_UVWSRC(aiTextureType_DIFFUSE, 0)); |
| |
| return mat; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| |
| } // Namespace Assimp |
| |
| #endif // !! ASSIMP_BUILD_NO_MMD_IMPORTER |