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