1/*
2---------------------------------------------------------------------------
3Open Asset Import Library (assimp)
4---------------------------------------------------------------------------
5
6Copyright (c) 2006-2016, assimp team
7
8All rights reserved.
9
10Redistribution and use of this software in source and binary forms,
11with or without modification, are permitted provided that the following
12conditions are met:
13
14* Redistributions of source code must retain the above
15 copyright notice, this list of conditions and the
16 following disclaimer.
17
18* Redistributions in binary form must reproduce the above
19 copyright notice, this list of conditions and the
20 following disclaimer in the documentation and/or other
21 materials provided with the distribution.
22
23* Neither the name of the assimp team, nor the names of its
24 contributors may be used to endorse or promote products
25 derived from this software without specific prior
26 written permission of the assimp team.
27
28THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39---------------------------------------------------------------------------
40*/
41
42#ifndef ASSIMP_BUILD_NO_MMD_IMPORTER
43
44#include "MMDImporter.h"
45#include "MMDPmdParser.h"
46#include "MMDPmxParser.h"
47#include "MMDVmdParser.h"
48#include "ConvertToLHProcess.h"
49#include <assimp/DefaultIOSystem.h>
50#include <assimp/Importer.hpp>
51#include <assimp/ai_assert.h>
52#include <assimp/scene.h>
53#include <fstream>
54#include <iomanip>
55#include <memory>
56
57static const aiImporterDesc desc = {"MMD Importer",
58 "",
59 "",
60 "surfaces supported?",
61 aiImporterFlags_SupportTextFlavour,
62 0,
63 0,
64 0,
65 0,
66 "pmx"};
67
68namespace Assimp {
69
70using namespace std;
71
72// ------------------------------------------------------------------------------------------------
73// Default constructor
74MMDImporter::MMDImporter()
75 : m_Buffer(),
76 // m_pRootObject( NULL ),
77 m_strAbsPath("") {
78 DefaultIOSystem io;
79 m_strAbsPath = io.getOsSeparator();
80}
81
82// ------------------------------------------------------------------------------------------------
83// Destructor.
84MMDImporter::~MMDImporter() {
85 // delete m_pRootObject;
86 // m_pRootObject = NULL;
87}
88
89// ------------------------------------------------------------------------------------------------
90// Returns true, if file is an pmx file.
91bool MMDImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler,
92 bool checkSig) const {
93 if (!checkSig) // Check File Extension
94 {
95 return SimpleExtensionCheck(pFile, "pmx");
96 } else // Check file Header
97 {
98 static const char *pTokens[] = {"PMX "};
99 return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, pTokens,
100 1);
101 }
102}
103
104// ------------------------------------------------------------------------------------------------
105const aiImporterDesc *MMDImporter::GetInfo() const { return &desc; }
106
107// ------------------------------------------------------------------------------------------------
108// MMD import implementation
109void MMDImporter::InternReadFile(const std::string &file, aiScene *pScene,
110 IOSystem * /*pIOHandler*/) {
111 // Read file by istream
112 std::filebuf fb;
113 if (!fb.open(file, std::ios::in | std::ios::binary)) {
114 throw DeadlyImportError("Failed to open file " + file + ".");
115 }
116
117 std::istream fileStream(&fb);
118
119 // Get the file-size and validate it, throwing an exception when fails
120 fileStream.seekg(0, fileStream.end);
121 size_t fileSize = static_cast<size_t>(fileStream.tellg());
122 fileStream.seekg(0, fileStream.beg);
123
124 if (fileSize < sizeof(pmx::PmxModel)) {
125 throw DeadlyImportError(file + " is too small.");
126 }
127
128 pmx::PmxModel model;
129 model.Read(&fileStream);
130
131 CreateDataFromImport(&model, pScene);
132}
133
134// ------------------------------------------------------------------------------------------------
135void MMDImporter::CreateDataFromImport(const pmx::PmxModel *pModel,
136 aiScene *pScene) {
137 if (pModel == NULL) {
138 return;
139 }
140
141 aiNode *pNode = new aiNode;
142 if (!pModel->model_name.empty()) {
143 pNode->mName.Set(pModel->model_name);
144 } else {
145 ai_assert(false);
146 }
147
148 pScene->mRootNode = pNode;
149
150 pNode = new aiNode;
151 pScene->mRootNode->addChildren(1, &pNode);
152 pNode->mName.Set(string(pModel->model_name) + string("_mesh"));
153
154 // split mesh by materials
155 pNode->mNumMeshes = pModel->material_count;
156 pNode->mMeshes = new unsigned int[pNode->mNumMeshes];
157 for (unsigned int index = 0; index < pNode->mNumMeshes; index++) {
158 pNode->mMeshes[index] = index;
159 }
160
161 pScene->mNumMeshes = pModel->material_count;
162 pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
163 for (unsigned int i = 0, indexStart = 0; i < pScene->mNumMeshes; i++) {
164 const int indexCount = pModel->materials[i].index_count;
165
166 pScene->mMeshes[i] = CreateMesh(pModel, indexStart, indexCount);
167 pScene->mMeshes[i]->mName = pModel->materials[i].material_name;
168 pScene->mMeshes[i]->mMaterialIndex = i;
169 indexStart += indexCount;
170 }
171
172 // create node hierarchy for bone position
173 aiNode **ppNode = new aiNode *[pModel->bone_count];
174 for (auto i = 0; i < pModel->bone_count; i++) {
175 ppNode[i] = new aiNode(pModel->bones[i].bone_name);
176 }
177
178 for (auto i = 0; i < pModel->bone_count; i++) {
179 const pmx::PmxBone &bone = pModel->bones[i];
180
181 if (bone.parent_index < 0) {
182 pScene->mRootNode->addChildren(1, ppNode + i);
183 } else {
184 ppNode[bone.parent_index]->addChildren(1, ppNode + i);
185
186 aiVector3D v3 = aiVector3D(
187 bone.position[0] - pModel->bones[bone.parent_index].position[0],
188 bone.position[1] - pModel->bones[bone.parent_index].position[1],
189 bone.position[2] - pModel->bones[bone.parent_index].position[2]);
190 aiMatrix4x4::Translation(v3, ppNode[i]->mTransformation);
191 }
192 }
193
194 // create materials
195 pScene->mNumMaterials = pModel->material_count;
196 pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
197 for (unsigned int i = 0; i < pScene->mNumMaterials; i++) {
198 pScene->mMaterials[i] = CreateMaterial(&pModel->materials[i], pModel);
199 }
200
201 // Convert everything to OpenGL space
202 MakeLeftHandedProcess convertProcess;
203 convertProcess.Execute(pScene);
204
205 FlipUVsProcess uvFlipper;
206 uvFlipper.Execute(pScene);
207
208 FlipWindingOrderProcess windingFlipper;
209 windingFlipper.Execute(pScene);
210}
211
212// ------------------------------------------------------------------------------------------------
213aiMesh *MMDImporter::CreateMesh(const pmx::PmxModel *pModel,
214 const int indexStart, const int indexCount) {
215 aiMesh *pMesh = new aiMesh;
216
217 pMesh->mNumVertices = indexCount;
218
219 pMesh->mNumFaces = indexCount / 3;
220 pMesh->mFaces = new aiFace[pMesh->mNumFaces];
221
222 const int numIndices = 3; // trianglular face
223 for (unsigned int index = 0; index < pMesh->mNumFaces; index++) {
224 pMesh->mFaces[index].mNumIndices = numIndices;
225 unsigned int *indices = new unsigned int[numIndices];
226 indices[0] = numIndices * index;
227 indices[1] = numIndices * index + 1;
228 indices[2] = numIndices * index + 2;
229 pMesh->mFaces[index].mIndices = indices;
230 }
231
232 pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
233 pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
234 pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices];
235 pMesh->mNumUVComponents[0] = 2;
236
237 // additional UVs
238 for (int i = 1; i <= pModel->setting.uv; i++) {
239 pMesh->mTextureCoords[i] = new aiVector3D[pMesh->mNumVertices];
240 pMesh->mNumUVComponents[i] = 4;
241 }
242
243 map<int, vector<aiVertexWeight>> bone_vertex_map;
244
245 // fill in contents and create bones
246 for (int index = 0; index < indexCount; index++) {
247 const pmx::PmxVertex *v =
248 &pModel->vertices[pModel->indices[indexStart + index]];
249 const float *position = v->position;
250 pMesh->mVertices[index].Set(position[0], position[1], position[2]);
251 const float *normal = v->normal;
252
253 pMesh->mNormals[index].Set(normal[0], normal[1], normal[2]);
254 pMesh->mTextureCoords[0][index].x = v->uv[0];
255 pMesh->mTextureCoords[0][index].y = v->uv[1];
256
257 for (int i = 1; i <= pModel->setting.uv; i++) {
258 // TODO: wrong here? use quaternion transform?
259 pMesh->mTextureCoords[i][index].x = v->uva[i][0];
260 pMesh->mTextureCoords[i][index].y = v->uva[i][1];
261 }
262
263 // handle bone map
264 const auto vsBDEF1_ptr =
265 dynamic_cast<pmx::PmxVertexSkinningBDEF1 *>(v->skinning.get());
266 const auto vsBDEF2_ptr =
267 dynamic_cast<pmx::PmxVertexSkinningBDEF2 *>(v->skinning.get());
268 const auto vsBDEF4_ptr =
269 dynamic_cast<pmx::PmxVertexSkinningBDEF4 *>(v->skinning.get());
270 const auto vsSDEF_ptr =
271 dynamic_cast<pmx::PmxVertexSkinningSDEF *>(v->skinning.get());
272 switch (v->skinning_type) {
273 case pmx::PmxVertexSkinningType::BDEF1:
274 bone_vertex_map[vsBDEF1_ptr->bone_index].push_back(
275 aiVertexWeight(index, 1.0));
276 break;
277 case pmx::PmxVertexSkinningType::BDEF2:
278 bone_vertex_map[vsBDEF2_ptr->bone_index1].push_back(
279 aiVertexWeight(index, vsBDEF2_ptr->bone_weight));
280 bone_vertex_map[vsBDEF2_ptr->bone_index2].push_back(
281 aiVertexWeight(index, 1.0f - vsBDEF2_ptr->bone_weight));
282 break;
283 case pmx::PmxVertexSkinningType::BDEF4:
284 bone_vertex_map[vsBDEF4_ptr->bone_index1].push_back(
285 aiVertexWeight(index, vsBDEF4_ptr->bone_weight1));
286 bone_vertex_map[vsBDEF4_ptr->bone_index2].push_back(
287 aiVertexWeight(index, vsBDEF4_ptr->bone_weight2));
288 bone_vertex_map[vsBDEF4_ptr->bone_index3].push_back(
289 aiVertexWeight(index, vsBDEF4_ptr->bone_weight3));
290 bone_vertex_map[vsBDEF4_ptr->bone_index4].push_back(
291 aiVertexWeight(index, vsBDEF4_ptr->bone_weight4));
292 break;
293 case pmx::PmxVertexSkinningType::SDEF: // TODO: how to use sdef_c, sdef_r0,
294 // sdef_r1?
295 bone_vertex_map[vsSDEF_ptr->bone_index1].push_back(
296 aiVertexWeight(index, vsSDEF_ptr->bone_weight));
297 bone_vertex_map[vsSDEF_ptr->bone_index2].push_back(
298 aiVertexWeight(index, 1.0f - vsSDEF_ptr->bone_weight));
299 break;
300 case pmx::PmxVertexSkinningType::QDEF:
301 const auto vsQDEF_ptr =
302 dynamic_cast<pmx::PmxVertexSkinningQDEF *>(v->skinning.get());
303 bone_vertex_map[vsQDEF_ptr->bone_index1].push_back(
304 aiVertexWeight(index, vsQDEF_ptr->bone_weight1));
305 bone_vertex_map[vsQDEF_ptr->bone_index2].push_back(
306 aiVertexWeight(index, vsQDEF_ptr->bone_weight2));
307 bone_vertex_map[vsQDEF_ptr->bone_index3].push_back(
308 aiVertexWeight(index, vsQDEF_ptr->bone_weight3));
309 bone_vertex_map[vsQDEF_ptr->bone_index4].push_back(
310 aiVertexWeight(index, vsQDEF_ptr->bone_weight4));
311 break;
312 }
313 }
314
315 // make all bones for each mesh
316 // assign bone weights to skinned bones (otherwise just initialize)
317 auto bone_ptr_ptr = new aiBone *[pModel->bone_count];
318 pMesh->mNumBones = pModel->bone_count;
319 pMesh->mBones = bone_ptr_ptr;
320 for (auto ii = 0; ii < pModel->bone_count; ++ii) {
321 auto pBone = new aiBone;
322 const auto &pmxBone = pModel->bones[ii];
323 pBone->mName = pmxBone.bone_name;
324 aiVector3D pos(pmxBone.position[0], pmxBone.position[1], pmxBone.position[2]);
325 aiMatrix4x4::Translation(-pos, pBone->mOffsetMatrix);
326 auto it = bone_vertex_map.find(ii);
327 if (it != bone_vertex_map.end()) {
328 pBone->mNumWeights = static_cast<unsigned int>(it->second.size());
329 pBone->mWeights = it->second.data();
330 it->second.swap(*(new vector<aiVertexWeight>));
331 }
332 bone_ptr_ptr[ii] = pBone;
333 }
334
335 return pMesh;
336}
337
338// ------------------------------------------------------------------------------------------------
339aiMaterial *MMDImporter::CreateMaterial(const pmx::PmxMaterial *pMat,
340 const pmx::PmxModel *pModel) {
341 aiMaterial *mat = new aiMaterial();
342 aiString name(pMat->material_english_name);
343 mat->AddProperty(&name, AI_MATKEY_NAME);
344
345 aiColor3D diffuse(pMat->diffuse[0], pMat->diffuse[1], pMat->diffuse[2]);
346 mat->AddProperty(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
347 aiColor3D specular(pMat->specular[0], pMat->specular[1], pMat->specular[2]);
348 mat->AddProperty(&specular, 1, AI_MATKEY_COLOR_SPECULAR);
349 aiColor3D ambient(pMat->ambient[0], pMat->ambient[1], pMat->ambient[2]);
350 mat->AddProperty(&ambient, 1, AI_MATKEY_COLOR_AMBIENT);
351
352 float opacity = pMat->diffuse[3];
353 mat->AddProperty(&opacity, 1, AI_MATKEY_OPACITY);
354 float shininess = pMat->specularlity;
355 mat->AddProperty(&shininess, 1, AI_MATKEY_SHININESS_STRENGTH);
356
357 aiString texture_path(pModel->textures[pMat->diffuse_texture_index]);
358 mat->AddProperty(&texture_path, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0));
359 int mapping_uvwsrc = 0;
360 mat->AddProperty(&mapping_uvwsrc, 1,
361 AI_MATKEY_UVWSRC(aiTextureType_DIFFUSE, 0));
362
363 return mat;
364}
365
366// ------------------------------------------------------------------------------------------------
367
368} // Namespace Assimp
369
370#endif // !! ASSIMP_BUILD_NO_MMD_IMPORTER
371