IRRMeshLoader.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. /*
  2. ---------------------------------------------------------------------------
  3. Open Asset Import Library (assimp)
  4. ---------------------------------------------------------------------------
  5. Copyright (c) 2006-2025, assimp team
  6. All rights reserved.
  7. Redistribution and use of this software in source and binary forms,
  8. with or without modification, are permitted provided that the following
  9. conditions are met:
  10. * Redistributions of source code must retain the above
  11. copyright notice, this list of conditions and the
  12. following disclaimer.
  13. * Redistributions in binary form must reproduce the above
  14. copyright notice, this list of conditions and the
  15. following disclaimer in the documentation and/or other
  16. materials provided with the distribution.
  17. * Neither the name of the assimp team, nor the names of its
  18. contributors may be used to endorse or promote products
  19. derived from this software without specific prior
  20. written permission of the assimp team.
  21. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  22. "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  23. LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  24. A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  25. OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  26. SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  27. LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  28. DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  29. THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  30. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  31. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32. ---------------------------------------------------------------------------
  33. */
  34. /** @file Implementation of the IrrMesh importer class */
  35. #ifndef ASSIMP_BUILD_NO_IRRMESH_IMPORTER
  36. #include "IRRMeshLoader.h"
  37. #include <assimp/ParsingUtils.h>
  38. #include <assimp/fast_atof.h>
  39. #include <assimp/importerdesc.h>
  40. #include <assimp/material.h>
  41. #include <assimp/mesh.h>
  42. #include <assimp/scene.h>
  43. #include <assimp/DefaultLogger.hpp>
  44. #include <assimp/IOSystem.hpp>
  45. #include <memory>
  46. using namespace Assimp;
  47. static constexpr aiImporterDesc desc = {
  48. "Irrlicht Mesh Reader",
  49. "",
  50. "",
  51. "http://irrlicht.sourceforge.net/",
  52. aiImporterFlags_SupportTextFlavour,
  53. 0,
  54. 0,
  55. 0,
  56. 0,
  57. "xml irrmesh"
  58. };
  59. // ------------------------------------------------------------------------------------------------
  60. // Returns whether the class can handle the format of the given file.
  61. bool IRRMeshImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
  62. /* NOTE: A simple check for the file extension is not enough
  63. * here. Irrmesh and irr are easy, but xml is too generic
  64. * and could be collada, too. So we need to open the file and
  65. * search for typical tokens.
  66. */
  67. static const char *tokens[] = { "irrmesh" };
  68. return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
  69. }
  70. // ------------------------------------------------------------------------------------------------
  71. // Get a list of all file extensions which are handled by this class
  72. const aiImporterDesc *IRRMeshImporter::GetInfo() const {
  73. return &desc;
  74. }
  75. static void releaseMaterial(aiMaterial **mat) {
  76. if (*mat != nullptr) {
  77. delete *mat;
  78. *mat = nullptr;
  79. }
  80. }
  81. static void releaseMesh(aiMesh **mesh) {
  82. if (*mesh != nullptr) {
  83. delete *mesh;
  84. *mesh = nullptr;
  85. }
  86. }
  87. // ------------------------------------------------------------------------------------------------
  88. // Imports the given file into the given scene structure.
  89. void IRRMeshImporter::InternReadFile(const std::string &pFile,
  90. aiScene *pScene, IOSystem *pIOHandler) {
  91. std::unique_ptr<IOStream> file(pIOHandler->Open(pFile));
  92. // Check whether we can read from the file
  93. if (file == nullptr) {
  94. throw DeadlyImportError("Failed to open IRRMESH file ", pFile);
  95. }
  96. // Construct the irrXML parser
  97. XmlParser parser;
  98. if (!parser.parse(file.get())) {
  99. throw DeadlyImportError("XML parse error while loading IRRMESH file ", pFile);
  100. }
  101. XmlNode root = parser.getRootNode();
  102. // final data
  103. std::vector<aiMaterial *> materials;
  104. std::vector<aiMesh *> meshes;
  105. materials.reserve(5);
  106. meshes.reserve(5);
  107. // temporary data - current mesh buffer
  108. // TODO move all these to inside loop
  109. aiMaterial *curMat = nullptr;
  110. aiMesh *curMesh = nullptr;
  111. unsigned int curMatFlags = 0;
  112. std::vector<aiVector3D> curVertices, curNormals, curTangents, curBitangents;
  113. std::vector<aiColor4D> curColors;
  114. std::vector<aiVector3D> curUVs, curUV2s;
  115. // some temporary variables
  116. // textMeaning is a 15 year old variable, that could've been an enum
  117. // int textMeaning = 0; // 0=none? 1=vertices 2=indices
  118. // int vertexFormat = 0; // 0 = normal; 1 = 2 tcoords, 2 = tangents
  119. bool useColors = false;
  120. // irrmesh files have a top level <mesh> owning multiple <buffer> nodes.
  121. // Each <buffer> contains <material>, <vertices>, and <indices>
  122. // <material> tags here directly owns the material data specs
  123. // <vertices> are a vertex per line, contains position, UV1 coords, maybe UV2, normal, tangent, bitangent
  124. // <boundingbox> is ignored, I think assimp recalculates those?
  125. // Parse the XML file
  126. pugi::xml_node const &meshNode = root.child("mesh");
  127. for (pugi::xml_node bufferNode : meshNode.children()) {
  128. if (ASSIMP_stricmp(bufferNode.name(), "buffer")) {
  129. // Might be a useless warning
  130. ASSIMP_LOG_WARN("IRRMESH: Ignoring non buffer node <", bufferNode.name(), "> in mesh declaration");
  131. continue;
  132. }
  133. curMat = nullptr;
  134. curMesh = nullptr;
  135. curVertices.clear();
  136. curColors.clear();
  137. curNormals.clear();
  138. curUV2s.clear();
  139. curUVs.clear();
  140. curTangents.clear();
  141. curBitangents.clear();
  142. // TODO ensure all three nodes are present and populated
  143. // before allocating everything
  144. // Get first material node
  145. pugi::xml_node materialNode = bufferNode.child("material");
  146. if (materialNode) {
  147. curMat = ParseMaterial(materialNode, curMatFlags);
  148. // Warn if there's more materials
  149. if (materialNode.next_sibling("material")) {
  150. ASSIMP_LOG_WARN("IRRMESH: Only one material description per buffer, please");
  151. }
  152. } else {
  153. ASSIMP_LOG_ERROR("IRRMESH: Buffer must contain one material");
  154. continue;
  155. }
  156. // Get first vertices node
  157. pugi::xml_node verticesNode = bufferNode.child("vertices");
  158. if (verticesNode) {
  159. pugi::xml_attribute vertexCountAttrib = verticesNode.attribute("vertexCount");
  160. int vertexCount = vertexCountAttrib.as_int();
  161. if (vertexCount == 0) {
  162. // This is possible ... remove the mesh from the list and skip further reading
  163. ASSIMP_LOG_WARN("IRRMESH: Found mesh with zero vertices");
  164. releaseMaterial(&curMat);
  165. continue; // Bail out early
  166. };
  167. curVertices.reserve(vertexCount);
  168. curNormals.reserve(vertexCount);
  169. curColors.reserve(vertexCount);
  170. curUVs.reserve(vertexCount);
  171. VertexFormat vertexFormat;
  172. // Determine the file format
  173. pugi::xml_attribute typeAttrib = verticesNode.attribute("type");
  174. if (!ASSIMP_stricmp("2tcoords", typeAttrib.value())) {
  175. curUV2s.reserve(vertexCount);
  176. vertexFormat = VertexFormat::t2coord;
  177. if (curMatFlags & AI_IRRMESH_EXTRA_2ND_TEXTURE) {
  178. // *********************************************************
  179. // We have a second texture! So use this UV channel
  180. // for it. The 2nd texture can be either a normal
  181. // texture (solid_2layer or lightmap_xxx) or a normal
  182. // map (normal_..., parallax_...)
  183. // *********************************************************
  184. int idx = 1;
  185. aiMaterial *mat = (aiMaterial *)curMat;
  186. if (curMatFlags & AI_IRRMESH_MAT_lightmap) {
  187. mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_LIGHTMAP(0));
  188. } else if (curMatFlags & AI_IRRMESH_MAT_normalmap_solid) {
  189. mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_NORMALS(0));
  190. } else if (curMatFlags & AI_IRRMESH_MAT_solid_2layer) {
  191. mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_DIFFUSE(1));
  192. }
  193. }
  194. } else if (!ASSIMP_stricmp("tangents", typeAttrib.value())) {
  195. curTangents.reserve(vertexCount);
  196. curBitangents.reserve(vertexCount);
  197. vertexFormat = VertexFormat::tangent;
  198. } else if (!ASSIMP_stricmp("standard", typeAttrib.value())) {
  199. vertexFormat = VertexFormat::standard;
  200. } else {
  201. // Unsupported format, discard whole buffer/mesh
  202. // Assuming we have a correct material, then release it
  203. // We don't have a correct mesh for sure here
  204. releaseMaterial(&curMat);
  205. ASSIMP_LOG_ERROR("IRRMESH: Unknown vertex format");
  206. continue; // Skip rest of buffer
  207. };
  208. // We know what format buffer is, collect numbers
  209. std::string v = verticesNode.text().get();
  210. const char *end = v.c_str() + v.size();
  211. ParseBufferVertices(v.c_str(), end, vertexFormat,
  212. curVertices, curNormals,
  213. curTangents, curBitangents,
  214. curUVs, curUV2s, curColors, useColors);
  215. }
  216. // Get indices
  217. // At this point we have some vertices and a valid material
  218. // Collect indices and create aiMesh at the same time
  219. pugi::xml_node indicesNode = bufferNode.child("indices");
  220. if (indicesNode) {
  221. // start a new mesh
  222. curMesh = new aiMesh();
  223. // allocate storage for all faces
  224. pugi::xml_attribute attr = indicesNode.attribute("indexCount");
  225. curMesh->mNumVertices = attr.as_int();
  226. if (!curMesh->mNumVertices) {
  227. // This is possible ... remove the mesh from the list and skip further reading
  228. ASSIMP_LOG_WARN("IRRMESH: Found mesh with zero indices");
  229. // mesh - away
  230. releaseMesh(&curMesh);
  231. // material - away
  232. releaseMaterial(&curMat);
  233. continue; // Go to next buffer
  234. }
  235. if (curMesh->mNumVertices % 3) {
  236. ASSIMP_LOG_WARN("IRRMESH: Number if indices isn't divisible by 3");
  237. }
  238. curMesh->mNumFaces = curMesh->mNumVertices / 3;
  239. curMesh->mFaces = new aiFace[curMesh->mNumFaces];
  240. // setup some members
  241. curMesh->mMaterialIndex = (unsigned int)materials.size();
  242. curMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
  243. // allocate storage for all vertices
  244. curMesh->mVertices = new aiVector3D[curMesh->mNumVertices];
  245. if (curNormals.size() == curVertices.size()) {
  246. curMesh->mNormals = new aiVector3D[curMesh->mNumVertices];
  247. }
  248. if (curTangents.size() == curVertices.size()) {
  249. curMesh->mTangents = new aiVector3D[curMesh->mNumVertices];
  250. }
  251. if (curBitangents.size() == curVertices.size()) {
  252. curMesh->mBitangents = new aiVector3D[curMesh->mNumVertices];
  253. }
  254. if (curColors.size() == curVertices.size() && useColors) {
  255. curMesh->mColors[0] = new aiColor4D[curMesh->mNumVertices];
  256. }
  257. if (curUVs.size() == curVertices.size()) {
  258. curMesh->mTextureCoords[0] = new aiVector3D[curMesh->mNumVertices];
  259. }
  260. if (curUV2s.size() == curVertices.size()) {
  261. curMesh->mTextureCoords[1] = new aiVector3D[curMesh->mNumVertices];
  262. }
  263. // read indices
  264. aiFace *curFace = curMesh->mFaces;
  265. aiFace *const faceEnd = curMesh->mFaces + curMesh->mNumFaces;
  266. aiVector3D *pcV = curMesh->mVertices;
  267. aiVector3D *pcN = curMesh->mNormals;
  268. aiVector3D *pcT = curMesh->mTangents;
  269. aiVector3D *pcB = curMesh->mBitangents;
  270. aiColor4D *pcC0 = curMesh->mColors[0];
  271. aiVector3D *pcT0 = curMesh->mTextureCoords[0];
  272. aiVector3D *pcT1 = curMesh->mTextureCoords[1];
  273. unsigned int curIdx = 0;
  274. unsigned int total = 0;
  275. // NOTE this might explode for UTF-16 and wchars
  276. const char *sz = indicesNode.text().get();
  277. const char *end = sz + std::strlen(sz);
  278. // For each index loop over aiMesh faces
  279. while (SkipSpacesAndLineEnd(&sz, end)) {
  280. if (curFace >= faceEnd) {
  281. ASSIMP_LOG_ERROR("IRRMESH: Too many indices");
  282. break;
  283. }
  284. // if new face
  285. if (!curIdx) {
  286. curFace->mNumIndices = 3;
  287. curFace->mIndices = new unsigned int[3];
  288. }
  289. // Read index base 10
  290. // function advances the pointer
  291. unsigned int idx = strtoul10(sz, &sz);
  292. if (idx >= curVertices.size()) {
  293. ASSIMP_LOG_ERROR("IRRMESH: Index out of range");
  294. idx = 0;
  295. }
  296. // make up our own indices?
  297. curFace->mIndices[curIdx] = total++;
  298. // Copy over data to aiMesh
  299. *pcV++ = curVertices[idx];
  300. if (pcN)
  301. *pcN++ = curNormals[idx];
  302. if (pcT)
  303. *pcT++ = curTangents[idx];
  304. if (pcB)
  305. *pcB++ = curBitangents[idx];
  306. if (pcC0)
  307. *pcC0++ = curColors[idx];
  308. if (pcT0)
  309. *pcT0++ = curUVs[idx];
  310. if (pcT1)
  311. *pcT1++ = curUV2s[idx];
  312. // start new face
  313. if (++curIdx == 3) {
  314. ++curFace;
  315. curIdx = 0;
  316. }
  317. }
  318. // We should be at the end of mFaces
  319. if (curFace != faceEnd) {
  320. ASSIMP_LOG_ERROR("IRRMESH: Not enough indices");
  321. }
  322. }
  323. // Finish processing the mesh - do some small material workarounds
  324. if (curMatFlags & AI_IRRMESH_MAT_trans_vertex_alpha && !useColors) {
  325. // Take the opacity value of the current material
  326. // from the common vertex color alpha
  327. aiMaterial *mat = (aiMaterial *)curMat;
  328. mat->AddProperty(&curColors[0].a, 1, AI_MATKEY_OPACITY);
  329. }
  330. // end of previous buffer. A material and a mesh should be there
  331. if (!curMat || !curMesh) {
  332. ASSIMP_LOG_ERROR("IRRMESH: A buffer must contain a mesh and a material");
  333. releaseMaterial(&curMat);
  334. releaseMesh(&curMesh);
  335. } else {
  336. materials.push_back(curMat);
  337. meshes.push_back(curMesh);
  338. }
  339. }
  340. // If one is empty then so is the other
  341. if (materials.empty() || meshes.empty()) {
  342. throw DeadlyImportError("IRRMESH: Unable to read a mesh from this file");
  343. }
  344. // now generate the output scene
  345. pScene->mNumMeshes = (unsigned int)meshes.size();
  346. pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
  347. for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
  348. pScene->mMeshes[i] = meshes[i];
  349. // clean this value ...
  350. pScene->mMeshes[i]->mNumUVComponents[3] = 0;
  351. }
  352. pScene->mNumMaterials = (unsigned int)materials.size();
  353. pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
  354. ::memcpy(pScene->mMaterials, &materials[0], sizeof(void *) * pScene->mNumMaterials);
  355. pScene->mRootNode = new aiNode();
  356. pScene->mRootNode->mName.Set("<IRRMesh>");
  357. pScene->mRootNode->mNumMeshes = pScene->mNumMeshes;
  358. pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes];
  359. for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
  360. pScene->mRootNode->mMeshes[i] = i;
  361. };
  362. }
  363. void IRRMeshImporter::ParseBufferVertices(const char *sz, const char *end, VertexFormat vertexFormat,
  364. std::vector<aiVector3D> &vertices, std::vector<aiVector3D> &normals,
  365. std::vector<aiVector3D> &tangents, std::vector<aiVector3D> &bitangents,
  366. std::vector<aiVector3D> &UVs, std::vector<aiVector3D> &UV2s,
  367. std::vector<aiColor4D> &colors, bool &useColors) {
  368. // read vertices
  369. do {
  370. SkipSpacesAndLineEnd(&sz, end);
  371. aiVector3D temp;
  372. aiColor4D c;
  373. // Read the vertex position
  374. sz = fast_atoreal_move<float>(sz, (float &)temp.x);
  375. SkipSpaces(&sz, end);
  376. sz = fast_atoreal_move<float>(sz, (float &)temp.y);
  377. SkipSpaces(&sz, end);
  378. sz = fast_atoreal_move<float>(sz, (float &)temp.z);
  379. SkipSpaces(&sz, end);
  380. vertices.push_back(temp);
  381. // Read the vertex normals
  382. sz = fast_atoreal_move<float>(sz, (float &)temp.x);
  383. SkipSpaces(&sz, end);
  384. sz = fast_atoreal_move<float>(sz, (float &)temp.y);
  385. SkipSpaces(&sz, end);
  386. sz = fast_atoreal_move<float>(sz, (float &)temp.z);
  387. SkipSpaces(&sz, end);
  388. normals.push_back(temp);
  389. // read the vertex colors
  390. uint32_t clr = strtoul16(sz, &sz);
  391. ColorFromARGBPacked(clr, c);
  392. // If we're pushing more than one distinct color
  393. if (!colors.empty() && c != *(colors.end() - 1))
  394. useColors = true;
  395. colors.push_back(c);
  396. SkipSpaces(&sz, end);
  397. // read the first UV coordinate set
  398. sz = fast_atoreal_move<float>(sz, (float &)temp.x);
  399. SkipSpaces(&sz, end);
  400. sz = fast_atoreal_move<float>(sz, (float &)temp.y);
  401. SkipSpaces(&sz, end);
  402. temp.z = 0.f;
  403. temp.y = 1.f - temp.y; // DX to OGL
  404. UVs.push_back(temp);
  405. // NOTE these correspond to specific S3DVertex* structs in irr sourcecode
  406. // So by definition, all buffers have either UV2 or tangents or neither
  407. // read the (optional) second UV coordinate set
  408. if (vertexFormat == VertexFormat::t2coord) {
  409. sz = fast_atoreal_move<float>(sz, (float &)temp.x);
  410. SkipSpaces(&sz, end);
  411. sz = fast_atoreal_move<float>(sz, (float &)temp.y);
  412. temp.y = 1.f - temp.y; // DX to OGL
  413. UV2s.push_back(temp);
  414. }
  415. // read optional tangent and bitangent vectors
  416. else if (vertexFormat == VertexFormat::tangent) {
  417. // tangents
  418. sz = fast_atoreal_move<float>(sz, (float &)temp.x);
  419. SkipSpaces(&sz, end);
  420. sz = fast_atoreal_move<float>(sz, (float &)temp.z);
  421. SkipSpaces(&sz, end);
  422. sz = fast_atoreal_move<float>(sz, (float &)temp.y);
  423. SkipSpaces(&sz, end);
  424. temp.y *= -1.0f;
  425. tangents.push_back(temp);
  426. // bitangents
  427. sz = fast_atoreal_move<float>(sz, (float &)temp.x);
  428. SkipSpaces(&sz, end);
  429. sz = fast_atoreal_move<float>(sz, (float &)temp.z);
  430. SkipSpaces(&sz, end);
  431. sz = fast_atoreal_move<float>(sz, (float &)temp.y);
  432. SkipSpaces(&sz, end);
  433. temp.y *= -1.0f;
  434. bitangents.push_back(temp);
  435. }
  436. } while (SkipLine(&sz, end));
  437. // IMPORTANT: We assume that each vertex is specified in one
  438. // line. So we can skip the rest of the line - unknown vertex
  439. // elements are ignored.
  440. }
  441. #endif // !! ASSIMP_BUILD_NO_IRRMESH_IMPORTER