1 | /* |
2 | --------------------------------------------------------------------------- |
3 | Open Asset Import Library (assimp) |
4 | --------------------------------------------------------------------------- |
5 | |
6 | Copyright (c) 2006-2017, assimp team |
7 | |
8 | |
9 | All rights reserved. |
10 | |
11 | Redistribution and use of this software in source and binary forms, |
12 | with or without modification, are permitted provided that the following |
13 | conditions are met: |
14 | |
15 | * Redistributions of source code must retain the above |
16 | copyright notice, this list of conditions and the |
17 | following disclaimer. |
18 | |
19 | * Redistributions in binary form must reproduce the above |
20 | copyright notice, this list of conditions and the |
21 | following disclaimer in the documentation and/or other |
22 | materials provided with the distribution. |
23 | |
24 | * Neither the name of the assimp team, nor the names of its |
25 | contributors may be used to endorse or promote products |
26 | derived from this software without specific prior |
27 | written permission of the assimp team. |
28 | |
29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
30 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
31 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
32 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
33 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
34 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
35 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
36 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
37 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
38 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
39 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
40 | --------------------------------------------------------------------------- |
41 | */ |
42 | |
43 | /** @file Implementation of the IrrMesh importer class */ |
44 | |
45 | |
46 | |
47 | #ifndef ASSIMP_BUILD_NO_IRRMESH_IMPORTER |
48 | |
49 | #include "IRRMeshLoader.h" |
50 | #include "ParsingUtils.h" |
51 | #include "fast_atof.h" |
52 | #include <memory> |
53 | #include <assimp/IOSystem.hpp> |
54 | #include <assimp/mesh.h> |
55 | #include <assimp/DefaultLogger.hpp> |
56 | #include <assimp/material.h> |
57 | #include <assimp/scene.h> |
58 | #include <assimp/importerdesc.h> |
59 | #include "Macros.h" |
60 | |
61 | using namespace Assimp; |
62 | using namespace irr; |
63 | using namespace irr::io; |
64 | |
65 | static const aiImporterDesc desc = { |
66 | "Irrlicht Mesh Reader" , |
67 | "" , |
68 | "" , |
69 | "http://irrlicht.sourceforge.net/" , |
70 | aiImporterFlags_SupportTextFlavour, |
71 | 0, |
72 | 0, |
73 | 0, |
74 | 0, |
75 | "xml irrmesh" |
76 | }; |
77 | |
78 | // ------------------------------------------------------------------------------------------------ |
79 | // Constructor to be privately used by Importer |
80 | IRRMeshImporter::IRRMeshImporter() |
81 | {} |
82 | |
83 | // ------------------------------------------------------------------------------------------------ |
84 | // Destructor, private as well |
85 | IRRMeshImporter::~IRRMeshImporter() |
86 | {} |
87 | |
88 | // ------------------------------------------------------------------------------------------------ |
89 | // Returns whether the class can handle the format of the given file. |
90 | bool IRRMeshImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const |
91 | { |
92 | /* NOTE: A simple check for the file extension is not enough |
93 | * here. Irrmesh and irr are easy, but xml is too generic |
94 | * and could be collada, too. So we need to open the file and |
95 | * search for typical tokens. |
96 | */ |
97 | const std::string extension = GetExtension(pFile); |
98 | |
99 | if (extension == "irrmesh" )return true; |
100 | else if (extension == "xml" || checkSig) |
101 | { |
102 | /* If CanRead() is called to check whether the loader |
103 | * supports a specific file extension in general we |
104 | * must return true here. |
105 | */ |
106 | if (!pIOHandler)return true; |
107 | const char* tokens[] = {"irrmesh" }; |
108 | return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1); |
109 | } |
110 | return false; |
111 | } |
112 | |
113 | // ------------------------------------------------------------------------------------------------ |
114 | // Get a list of all file extensions which are handled by this class |
115 | const aiImporterDesc* IRRMeshImporter::GetInfo () const |
116 | { |
117 | return &desc; |
118 | } |
119 | |
120 | static void releaseMaterial( aiMaterial **mat ) { |
121 | if(*mat!= nullptr) { |
122 | delete *mat; |
123 | *mat = nullptr; |
124 | } |
125 | } |
126 | |
127 | static void releaseMesh( aiMesh **mesh ) { |
128 | if (*mesh != nullptr){ |
129 | delete *mesh; |
130 | *mesh = nullptr; |
131 | } |
132 | } |
133 | |
134 | // ------------------------------------------------------------------------------------------------ |
135 | // Imports the given file into the given scene structure. |
136 | void IRRMeshImporter::InternReadFile( const std::string& pFile, |
137 | aiScene* pScene, IOSystem* pIOHandler) |
138 | { |
139 | std::unique_ptr<IOStream> file( pIOHandler->Open( pFile)); |
140 | |
141 | // Check whether we can read from the file |
142 | if( file.get() == NULL) |
143 | throw DeadlyImportError( "Failed to open IRRMESH file " + pFile + "" ); |
144 | |
145 | // Construct the irrXML parser |
146 | CIrrXML_IOStreamReader st(file.get()); |
147 | reader = createIrrXMLReader((IFileReadCallBack*) &st); |
148 | |
149 | // final data |
150 | std::vector<aiMaterial*> materials; |
151 | std::vector<aiMesh*> meshes; |
152 | materials.reserve (5); |
153 | meshes.reserve(5); |
154 | |
155 | // temporary data - current mesh buffer |
156 | aiMaterial* curMat = nullptr; |
157 | aiMesh* curMesh = nullptr; |
158 | unsigned int curMatFlags = 0; |
159 | |
160 | std::vector<aiVector3D> curVertices,curNormals,curTangents,curBitangents; |
161 | std::vector<aiColor4D> curColors; |
162 | std::vector<aiVector3D> curUVs,curUV2s; |
163 | |
164 | // some temporary variables |
165 | int textMeaning = 0; |
166 | int vertexFormat = 0; // 0 = normal; 1 = 2 tcoords, 2 = tangents |
167 | bool useColors = false; |
168 | |
169 | // Parse the XML file |
170 | while (reader->read()) { |
171 | switch (reader->getNodeType()) { |
172 | case EXN_ELEMENT: |
173 | |
174 | if (!ASSIMP_stricmp(reader->getNodeName(),"buffer" ) && (curMat || curMesh)) { |
175 | // end of previous buffer. A material and a mesh should be there |
176 | if ( !curMat || !curMesh) { |
177 | DefaultLogger::get()->error("IRRMESH: A buffer must contain a mesh and a material" ); |
178 | releaseMaterial( &curMat ); |
179 | releaseMesh( &curMesh ); |
180 | } else { |
181 | materials.push_back(curMat); |
182 | meshes.push_back(curMesh); |
183 | } |
184 | curMat = nullptr; |
185 | curMesh = nullptr; |
186 | |
187 | curVertices.clear(); |
188 | curColors.clear(); |
189 | curNormals.clear(); |
190 | curUV2s.clear(); |
191 | curUVs.clear(); |
192 | curTangents.clear(); |
193 | curBitangents.clear(); |
194 | } |
195 | |
196 | |
197 | if (!ASSIMP_stricmp(reader->getNodeName(),"material" )) { |
198 | if (curMat) { |
199 | DefaultLogger::get()->warn("IRRMESH: Only one material description per buffer, please" ); |
200 | releaseMaterial( &curMat ); |
201 | } |
202 | curMat = ParseMaterial(curMatFlags); |
203 | } |
204 | /* no else here! */ if (!ASSIMP_stricmp(reader->getNodeName(),"vertices" )) |
205 | { |
206 | int num = reader->getAttributeValueAsInt("vertexCount" ); |
207 | |
208 | if (!num) { |
209 | // This is possible ... remove the mesh from the list and skip further reading |
210 | DefaultLogger::get()->warn("IRRMESH: Found mesh with zero vertices" ); |
211 | |
212 | releaseMaterial( &curMat ); |
213 | releaseMesh( &curMesh ); |
214 | textMeaning = 0; |
215 | continue; |
216 | } |
217 | |
218 | curVertices.reserve(num); |
219 | curNormals.reserve(num); |
220 | curColors.reserve(num); |
221 | curUVs.reserve(num); |
222 | |
223 | // Determine the file format |
224 | const char* t = reader->getAttributeValueSafe("type" ); |
225 | if (!ASSIMP_stricmp("2tcoords" , t)) { |
226 | curUV2s.reserve (num); |
227 | vertexFormat = 1; |
228 | |
229 | if (curMatFlags & AI_IRRMESH_EXTRA_2ND_TEXTURE) { |
230 | // ********************************************************* |
231 | // We have a second texture! So use this UV channel |
232 | // for it. The 2nd texture can be either a normal |
233 | // texture (solid_2layer or lightmap_xxx) or a normal |
234 | // map (normal_..., parallax_...) |
235 | // ********************************************************* |
236 | int idx = 1; |
237 | aiMaterial* mat = ( aiMaterial* ) curMat; |
238 | |
239 | if (curMatFlags & AI_IRRMESH_MAT_lightmap){ |
240 | mat->AddProperty(&idx,1,AI_MATKEY_UVWSRC_LIGHTMAP(0)); |
241 | } |
242 | else if (curMatFlags & AI_IRRMESH_MAT_normalmap_solid){ |
243 | mat->AddProperty(&idx,1,AI_MATKEY_UVWSRC_NORMALS(0)); |
244 | } |
245 | else if (curMatFlags & AI_IRRMESH_MAT_solid_2layer) { |
246 | mat->AddProperty(&idx,1,AI_MATKEY_UVWSRC_DIFFUSE(1)); |
247 | } |
248 | } |
249 | } |
250 | else if (!ASSIMP_stricmp("tangents" , t)) { |
251 | curTangents.reserve (num); |
252 | curBitangents.reserve (num); |
253 | vertexFormat = 2; |
254 | } |
255 | else if (ASSIMP_stricmp("standard" , t)) { |
256 | releaseMaterial( &curMat ); |
257 | DefaultLogger::get()->warn("IRRMESH: Unknown vertex format" ); |
258 | } |
259 | else vertexFormat = 0; |
260 | textMeaning = 1; |
261 | } |
262 | else if (!ASSIMP_stricmp(reader->getNodeName(),"indices" )) { |
263 | if (curVertices.empty() && curMat) { |
264 | releaseMaterial( &curMat ); |
265 | throw DeadlyImportError("IRRMESH: indices must come after vertices" ); |
266 | } |
267 | |
268 | textMeaning = 2; |
269 | |
270 | // start a new mesh |
271 | curMesh = new aiMesh(); |
272 | |
273 | // allocate storage for all faces |
274 | curMesh->mNumVertices = reader->getAttributeValueAsInt("indexCount" ); |
275 | if (!curMesh->mNumVertices) { |
276 | // This is possible ... remove the mesh from the list and skip further reading |
277 | DefaultLogger::get()->warn("IRRMESH: Found mesh with zero indices" ); |
278 | |
279 | // mesh - away |
280 | releaseMesh( &curMesh ); |
281 | |
282 | // material - away |
283 | releaseMaterial( &curMat ); |
284 | |
285 | textMeaning = 0; |
286 | continue; |
287 | } |
288 | |
289 | if (curMesh->mNumVertices % 3) { |
290 | DefaultLogger::get()->warn("IRRMESH: Number if indices isn't divisible by 3" ); |
291 | } |
292 | |
293 | curMesh->mNumFaces = curMesh->mNumVertices / 3; |
294 | curMesh->mFaces = new aiFace[curMesh->mNumFaces]; |
295 | |
296 | // setup some members |
297 | curMesh->mMaterialIndex = (unsigned int)materials.size(); |
298 | curMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; |
299 | |
300 | // allocate storage for all vertices |
301 | curMesh->mVertices = new aiVector3D[curMesh->mNumVertices]; |
302 | |
303 | if (curNormals.size() == curVertices.size()) { |
304 | curMesh->mNormals = new aiVector3D[curMesh->mNumVertices]; |
305 | } |
306 | if (curTangents.size() == curVertices.size()) { |
307 | curMesh->mTangents = new aiVector3D[curMesh->mNumVertices]; |
308 | } |
309 | if (curBitangents.size() == curVertices.size()) { |
310 | curMesh->mBitangents = new aiVector3D[curMesh->mNumVertices]; |
311 | } |
312 | if (curColors.size() == curVertices.size() && useColors) { |
313 | curMesh->mColors[0] = new aiColor4D[curMesh->mNumVertices]; |
314 | } |
315 | if (curUVs.size() == curVertices.size()) { |
316 | curMesh->mTextureCoords[0] = new aiVector3D[curMesh->mNumVertices]; |
317 | } |
318 | if (curUV2s.size() == curVertices.size()) { |
319 | curMesh->mTextureCoords[1] = new aiVector3D[curMesh->mNumVertices]; |
320 | } |
321 | } |
322 | break; |
323 | |
324 | case EXN_TEXT: |
325 | { |
326 | const char* sz = reader->getNodeData(); |
327 | if (textMeaning == 1) { |
328 | textMeaning = 0; |
329 | |
330 | // read vertices |
331 | do { |
332 | SkipSpacesAndLineEnd(&sz); |
333 | aiVector3D temp;aiColor4D c; |
334 | |
335 | // Read the vertex position |
336 | sz = fast_atoreal_move<float>(sz,(float&)temp.x); |
337 | SkipSpaces(&sz); |
338 | |
339 | sz = fast_atoreal_move<float>(sz,(float&)temp.y); |
340 | SkipSpaces(&sz); |
341 | |
342 | sz = fast_atoreal_move<float>(sz,(float&)temp.z); |
343 | SkipSpaces(&sz); |
344 | curVertices.push_back(temp); |
345 | |
346 | // Read the vertex normals |
347 | sz = fast_atoreal_move<float>(sz,(float&)temp.x); |
348 | SkipSpaces(&sz); |
349 | |
350 | sz = fast_atoreal_move<float>(sz,(float&)temp.y); |
351 | SkipSpaces(&sz); |
352 | |
353 | sz = fast_atoreal_move<float>(sz,(float&)temp.z); |
354 | SkipSpaces(&sz); |
355 | curNormals.push_back(temp); |
356 | |
357 | // read the vertex colors |
358 | uint32_t clr = strtoul16(sz,&sz); |
359 | ColorFromARGBPacked(clr,c); |
360 | |
361 | if (!curColors.empty() && c != *(curColors.end()-1)) |
362 | useColors = true; |
363 | |
364 | curColors.push_back(c); |
365 | SkipSpaces(&sz); |
366 | |
367 | |
368 | // read the first UV coordinate set |
369 | sz = fast_atoreal_move<float>(sz,(float&)temp.x); |
370 | SkipSpaces(&sz); |
371 | |
372 | sz = fast_atoreal_move<float>(sz,(float&)temp.y); |
373 | SkipSpaces(&sz); |
374 | temp.z = 0.f; |
375 | temp.y = 1.f - temp.y; // DX to OGL |
376 | curUVs.push_back(temp); |
377 | |
378 | // read the (optional) second UV coordinate set |
379 | if (vertexFormat == 1) { |
380 | sz = fast_atoreal_move<float>(sz,(float&)temp.x); |
381 | SkipSpaces(&sz); |
382 | |
383 | sz = fast_atoreal_move<float>(sz,(float&)temp.y); |
384 | temp.y = 1.f - temp.y; // DX to OGL |
385 | curUV2s.push_back(temp); |
386 | } |
387 | // read optional tangent and bitangent vectors |
388 | else if (vertexFormat == 2) { |
389 | // tangents |
390 | sz = fast_atoreal_move<float>(sz,(float&)temp.x); |
391 | SkipSpaces(&sz); |
392 | |
393 | sz = fast_atoreal_move<float>(sz,(float&)temp.z); |
394 | SkipSpaces(&sz); |
395 | |
396 | sz = fast_atoreal_move<float>(sz,(float&)temp.y); |
397 | SkipSpaces(&sz); |
398 | temp.y *= -1.0f; |
399 | curTangents.push_back(temp); |
400 | |
401 | // bitangents |
402 | sz = fast_atoreal_move<float>(sz,(float&)temp.x); |
403 | SkipSpaces(&sz); |
404 | |
405 | sz = fast_atoreal_move<float>(sz,(float&)temp.z); |
406 | SkipSpaces(&sz); |
407 | |
408 | sz = fast_atoreal_move<float>(sz,(float&)temp.y); |
409 | SkipSpaces(&sz); |
410 | temp.y *= -1.0f; |
411 | curBitangents.push_back(temp); |
412 | } |
413 | } |
414 | |
415 | /* IMPORTANT: We assume that each vertex is specified in one |
416 | line. So we can skip the rest of the line - unknown vertex |
417 | elements are ignored. |
418 | */ |
419 | |
420 | while (SkipLine(&sz)); |
421 | } |
422 | else if (textMeaning == 2) { |
423 | textMeaning = 0; |
424 | |
425 | // read indices |
426 | aiFace* curFace = curMesh->mFaces; |
427 | aiFace* const faceEnd = curMesh->mFaces + curMesh->mNumFaces; |
428 | |
429 | aiVector3D* pcV = curMesh->mVertices; |
430 | aiVector3D* pcN = curMesh->mNormals; |
431 | aiVector3D* pcT = curMesh->mTangents; |
432 | aiVector3D* pcB = curMesh->mBitangents; |
433 | aiColor4D* pcC0 = curMesh->mColors[0]; |
434 | aiVector3D* pcT0 = curMesh->mTextureCoords[0]; |
435 | aiVector3D* pcT1 = curMesh->mTextureCoords[1]; |
436 | |
437 | unsigned int curIdx = 0; |
438 | unsigned int total = 0; |
439 | while(SkipSpacesAndLineEnd(&sz)) { |
440 | if (curFace >= faceEnd) { |
441 | DefaultLogger::get()->error("IRRMESH: Too many indices" ); |
442 | break; |
443 | } |
444 | if (!curIdx) { |
445 | curFace->mNumIndices = 3; |
446 | curFace->mIndices = new unsigned int[3]; |
447 | } |
448 | |
449 | unsigned int idx = strtoul10(sz,&sz); |
450 | if (idx >= curVertices.size()) { |
451 | DefaultLogger::get()->error("IRRMESH: Index out of range" ); |
452 | idx = 0; |
453 | } |
454 | |
455 | curFace->mIndices[curIdx] = total++; |
456 | |
457 | *pcV++ = curVertices[idx]; |
458 | if (pcN)*pcN++ = curNormals[idx]; |
459 | if (pcT)*pcT++ = curTangents[idx]; |
460 | if (pcB)*pcB++ = curBitangents[idx]; |
461 | if (pcC0)*pcC0++ = curColors[idx]; |
462 | if (pcT0)*pcT0++ = curUVs[idx]; |
463 | if (pcT1)*pcT1++ = curUV2s[idx]; |
464 | |
465 | if (++curIdx == 3) { |
466 | ++curFace; |
467 | curIdx = 0; |
468 | } |
469 | } |
470 | |
471 | if (curFace != faceEnd) |
472 | DefaultLogger::get()->error("IRRMESH: Not enough indices" ); |
473 | |
474 | // Finish processing the mesh - do some small material workarounds |
475 | if (curMatFlags & AI_IRRMESH_MAT_trans_vertex_alpha && !useColors) { |
476 | // Take the opacity value of the current material |
477 | // from the common vertex color alpha |
478 | aiMaterial* mat = (aiMaterial*)curMat; |
479 | mat->AddProperty(&curColors[0].a,1,AI_MATKEY_OPACITY); |
480 | } |
481 | }} |
482 | break; |
483 | |
484 | default: |
485 | // GCC complains here ... |
486 | break; |
487 | |
488 | }; |
489 | } |
490 | |
491 | // End of the last buffer. A material and a mesh should be there |
492 | if (curMat || curMesh) { |
493 | if ( !curMat || !curMesh) { |
494 | DefaultLogger::get()->error("IRRMESH: A buffer must contain a mesh and a material" ); |
495 | releaseMaterial( &curMat ); |
496 | releaseMesh( &curMesh ); |
497 | } |
498 | else { |
499 | materials.push_back(curMat); |
500 | meshes.push_back(curMesh); |
501 | } |
502 | } |
503 | |
504 | if (materials.empty()) |
505 | throw DeadlyImportError("IRRMESH: Unable to read a mesh from this file" ); |
506 | |
507 | |
508 | // now generate the output scene |
509 | pScene->mNumMeshes = (unsigned int)meshes.size(); |
510 | pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; |
511 | for (unsigned int i = 0; i < pScene->mNumMeshes;++i) { |
512 | pScene->mMeshes[i] = meshes[i]; |
513 | |
514 | // clean this value ... |
515 | pScene->mMeshes[i]->mNumUVComponents[3] = 0; |
516 | } |
517 | |
518 | pScene->mNumMaterials = (unsigned int)materials.size(); |
519 | pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]; |
520 | ::memcpy(pScene->mMaterials,&materials[0],sizeof(void*)*pScene->mNumMaterials); |
521 | |
522 | pScene->mRootNode = new aiNode(); |
523 | pScene->mRootNode->mName.Set("<IRRMesh>" ); |
524 | pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; |
525 | pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes]; |
526 | |
527 | for (unsigned int i = 0; i < pScene->mNumMeshes;++i) |
528 | pScene->mRootNode->mMeshes[i] = i; |
529 | |
530 | // clean up and return |
531 | delete reader; |
532 | AI_DEBUG_INVALIDATE_PTR(reader); |
533 | } |
534 | |
535 | #endif // !! ASSIMP_BUILD_NO_IRRMESH_IMPORTER |
536 | |