IRRMeshLoader.cpp 20 KB

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