#include "..\Header\MshFile.h" #include "..\Header\tga.h" #include // helper function to save data from file to any variable type #define F2V(variableName) reinterpret_cast(&variableName) ///////////////////////////////////////////////////////////////////////// // public constructor/destructor MshFile::MshFile(QString path, QObject * parent) : FileInterface(path, parent) { import(); } MshFile::~MshFile() { } ///////////////////////////////////////////////////////////////////////// // private functions void MshFile::import() { // go to file size information m_file.seekg(4); std::uint32_t tmp_fileSize; std::list tmp_mainChunks; // get all chunks under HEDR m_file.read(F2V(tmp_fileSize), sizeof(tmp_fileSize)); loadChunks(tmp_mainChunks, m_file.tellg(), tmp_fileSize); // evaulate HEDR subchunks (= find MSH2) for (ChunkHeader* it : tmp_mainChunks) { if (!strcmp("MSH2", it->name)) { // get all subchunks std::list tmp_msh2Chunks; loadChunks(tmp_msh2Chunks, it->position, it->size); // evaluate MSH2 subchunks analyseMsh2Chunks(tmp_msh2Chunks); // clean up while (!tmp_msh2Chunks.empty()) { ChunkHeader* curs = tmp_msh2Chunks.front(); tmp_msh2Chunks.pop_front(); delete curs; } } } // clean up while (!tmp_mainChunks.empty()) { ChunkHeader* cur = tmp_mainChunks.front(); tmp_mainChunks.pop_front(); delete cur; } } void MshFile::loadChunks(std::list& destination, std::streampos start, const std::uint32_t length) { // jump to first chunk m_file.seekg(start); do { ChunkHeader* tmp_header = new ChunkHeader(); // get information m_file.read(F2V(tmp_header->name[0]), sizeof(tmp_header->name) - 1); m_file.read(F2V(tmp_header->size), sizeof(tmp_header->size)); tmp_header->position = m_file.tellg(); // store information destination.push_back(tmp_header); // jump to next header m_file.seekg(tmp_header->size, std::ios_base::cur); // out of file. Maybe a size information is corrupted if (!m_file.good()) { emit sendMessage("WARNING: corrupted file. Trying to continue..", 1); m_file.clear(); break; } } while (m_file.tellg() - start != length); } void MshFile::analyseMsh2Chunks(std::list& chunkList) { for (auto& it : chunkList) { // scene information if (!strcmp("SINF", it->name)) { // get SINF subchunks std::list tmp_sinfChunks; loadChunks(tmp_sinfChunks, it->position, it->size); // evaluate SINF subchunks for (auto& it : tmp_sinfChunks) { if (!strcmp("BBOX", it->name)) { m_file.seekg(it->position); // read in the quaternion float tmp_quat[4]; for (int i = 0; i < 4; i++) m_file.read(F2V(tmp_quat[i]), sizeof(float)); m_sceneBbox.rotation.setX(tmp_quat[0]); m_sceneBbox.rotation.setY(tmp_quat[1]); m_sceneBbox.rotation.setZ(tmp_quat[2]); m_sceneBbox.rotation.setScalar(tmp_quat[3]); //read in the center for (int i = 0; i < 3; i++) m_file.read(F2V(m_sceneBbox.center[i]), sizeof(float)); //read in the extents for (int i = 0; i < 3; i++) m_file.read(F2V(m_sceneBbox.extents[i]), sizeof(float)); } } // clean up SINF subchunks for (ChunkHeader* it : tmp_sinfChunks) delete it; } // material list else if (!strcmp("MATL", it->name)) { // "useless" information how many MATD follow, jump over it m_file.seekg(it->position); m_file.seekg(sizeof(std::uint32_t), std::ios_base::cur); // get all MATL subchunk std::list tmp_matlChunks; loadChunks(tmp_matlChunks, m_file.tellg(), it->size - 4); // evaluate MATL subchunks for (auto& it : tmp_matlChunks) { // This shouldn't be anything else than MATD if (!strcmp("MATD", it->name)) { // get all subchunks from MATD std::list tmp_matdChunks; loadChunks(tmp_matdChunks, it->position, it->size); m_materials->push_back(Material()); // analyse MATD subchunks analyseMatdChunks(tmp_matdChunks); // clean up MATD subchunks while (!tmp_matdChunks.empty()) { ChunkHeader* cur = tmp_matdChunks.front(); tmp_matdChunks.pop_front(); delete cur; } } } // clean up MATL subchunks while (!tmp_matlChunks.empty()) { ChunkHeader* cur = tmp_matlChunks.front(); tmp_matlChunks.pop_front(); delete cur; } } // model else if (!strcmp("MODL", it->name)) { Model* new_model = new Model; m_currentType = ModelTyp::null; m_currentRenderFlag = -1; // get all MODL subchunks std::list tmp_chunks; loadChunks(tmp_chunks, it->position, it->size); // evaluate MODL subchunks analyseModlChunks(new_model, tmp_chunks); //clean up MODL subchunks while (!tmp_chunks.empty()) { ChunkHeader* cur = tmp_chunks.front(); tmp_chunks.pop_front(); delete cur; } // save Model data m_models->push_back(new_model); } } } void MshFile::analyseMatdChunks(std::list& chunkList) { for (auto& it : chunkList) { // name if (!strcmp("NAME", it->name)) { m_file.seekg(it->position); char* buffer = new char[it->size + 1]; *buffer = { 0 }; m_file.read(buffer, it->size); m_materials->back().name = buffer; delete[] buffer; } // data else if(!strcmp("DATA", it->name)) { m_file.seekg(it->position); // diffuse for (unsigned int i = 0; i < 4; i++) m_file.read(F2V(m_materials->back().diffuseColor[i]), sizeof(float)); // specular for (unsigned int i = 0; i < 4; i++) m_file.read(F2V(m_materials->back().specularColor[i]), sizeof(float)); // ambient for (unsigned int i = 0; i < 4; i++) m_file.read(F2V(m_materials->back().ambientColor[i]), sizeof(float)); // shininess m_file.read(F2V(m_materials->back().shininess), sizeof(float)); } // TODO: evaluate specular, gloss,.. and save values // attributes else if (!strcmp("ATRB", it->name)) { // read the attributes m_file.seekg(it->position); std::uint8_t flag, render, data[2]; m_file.read(F2V(flag), sizeof(flag)); m_file.read(F2V(render), sizeof(render)); m_file.read(F2V(data[0]), sizeof(data[0])); m_file.read(F2V(data[1]), sizeof(data[1])); // flags // 0: emissive // 1: glow // 2: single-sided transparency // 3: double-sided transparency // 4: hard-edged transparency // 5: per-pixel lighting // 6: additive transparency // 7: specular for (unsigned int i = 0; i < 8; i++) m_materials->back().flags[i] = (std::uint8_t)(flag << (7 - i)) >> 7; m_materials->back().transparent = m_materials->back().flags[2] || m_materials->back().flags[3] || m_materials->back().flags[4] || m_materials->back().flags[6]; } // texture zero else if (!strcmp("TX0D", it->name)) { // get the texture name m_file.seekg(it->position); char* buffer = new char[it->size + 1]; *buffer = { 0 }; m_file.read(buffer, it->size); m_materials->back().textureName = buffer; delete[] buffer; // load the texture if the name is not empty if (!m_materials->back().textureName.isEmpty()) loadTexture(m_materials->back().texture, m_filepath + "/" + m_materials->back().textureName); } } } void MshFile::analyseModlChunks(Model * dataDestination, std::list& chunkList) { for (auto& it : chunkList) { // model type if (!strcmp("MTYP", it->name)) { m_file.seekg(it->position); std::uint32_t tmp_type; m_file.read(F2V(tmp_type), sizeof(tmp_type)); m_currentType = (ModelTyp)tmp_type; } // parent name else if (!strcmp("PRNT", it->name)) { m_file.seekg(it->position); char* buffer = new char[it->size + 1]; *buffer = { 0 }; m_file.read(buffer, it->size); dataDestination->parent = buffer; delete[] buffer; } // model name else if (!strcmp("NAME", it->name)) { m_file.seekg(it->position); char* buffer = new char[it->size + 1]; *buffer = { 0 }; m_file.read(buffer, it->size); dataDestination->name = buffer; delete[] buffer; } // render flags else if (!strcmp("FLGS", it->name)) { m_file.seekg(it->position); m_file.read(F2V(m_currentRenderFlag), sizeof(m_currentRenderFlag)); } // translation else if (!strcmp("TRAN", it->name)) { float tmp_scale[3]; float tmp_rotation[4]; float tmp_trans[3]; m_file.seekg(it->position); // read in the data for (int i = 0; i < 3; i++) m_file.read(F2V(tmp_scale[i]), sizeof(float)); for (int i = 0; i < 4; i++) m_file.read(F2V(tmp_rotation[i]), sizeof(float)); for (int i = 0; i < 3; i++) m_file.read(F2V(tmp_trans[i]), sizeof(float)); // modify the matrix and quaternion dataDestination->m4x4Translation.scale(tmp_scale[0], tmp_scale[1], tmp_scale[2]); dataDestination->m4x4Translation.translate(tmp_trans[0], tmp_trans[1], tmp_trans[2]); dataDestination->quadRotation.setVector(QVector3D(tmp_rotation[0], tmp_rotation[1], tmp_rotation[2])); dataDestination->quadRotation.setScalar(tmp_rotation[3]); dataDestination->m4x4Translation = getParentMatrix(dataDestination->parent) * dataDestination->m4x4Translation; dataDestination->quadRotation = getParentRotation(dataDestination->parent) * dataDestination->quadRotation; } // geometry data else if (!strcmp("GEOM", it->name)) { // don't get null, bone, shadowMesh and hidden mesh indices if (m_currentType == null || m_currentType == bone || m_currentType == shadowMesh || m_currentRenderFlag == 1) continue; // get all GEOM subchunks std::list tmp_geomChunks; loadChunks(tmp_geomChunks, it->position, it->size); // evaluate GEOM subchunks analyseGeomChunks(dataDestination, tmp_geomChunks); // clean up GEOM subchunks while (!tmp_geomChunks.empty()) { ChunkHeader* cur = tmp_geomChunks.front(); tmp_geomChunks.pop_front(); delete cur; } } } } void MshFile::analyseGeomChunks(Model * dataDestination, std::list& chunkList) { for (auto& it : chunkList) { // segment if (!strcmp("SEGM", it->name)) { // get all SEGM subchunks std::list tmp_segmChunks; loadChunks(tmp_segmChunks, it->position, it->size); // evaluate SEGM subchunks analyseSegmChunks(dataDestination, tmp_segmChunks); // clean up SEGM subchunk while (!tmp_segmChunks.empty()) { ChunkHeader* cur = tmp_segmChunks.front(); tmp_segmChunks.pop_front(); delete cur; } } // cloth else if (!strcmp("CLTH", it->name)) { // get all CLTH subchunks std::list tmp_clthChunks; loadChunks(tmp_clthChunks, it->position, it->size); // evaluate CLTH subchunks analyseClthChunks(dataDestination, tmp_clthChunks); // clean up CLTH subchunks while (!tmp_clthChunks.empty()) { ChunkHeader* cur = tmp_clthChunks.front(); tmp_clthChunks.pop_front(); delete cur; } } } } void MshFile::analyseSegmChunks(Model * dataDestination, std::list& chunkList) { Segment* new_segment = new Segment; for (auto& it : chunkList) { // material index if (!strcmp("MATI", it->name)) { m_file.seekg(it->position); m_file.read(F2V(new_segment->textureIndex), sizeof(new_segment->textureIndex)); } // position list (vertex) else if (!strcmp("POSL", it->name)) { readVertex(new_segment, it->position); } // normals else if (!strcmp("NRML", it->name)) { std::uint32_t tmp_size; m_file.seekg(it->position); m_file.read(F2V(tmp_size), sizeof(tmp_size)); if (tmp_size < new_segment->vertices.size()) { emit sendMessage("WARNING: too less normals " + QString::number(tmp_size) + " < " + QString::number(new_segment->vertices.size()), 1); for (unsigned int i = new_segment->vertices.size(); i != tmp_size; i--) for (unsigned int j = 0; j < 3; j++) new_segment->vertices[i - 1].normal[j] = 0; } else if (tmp_size > new_segment->vertices.size()) { emit sendMessage("WARNING: too many normals " + QString::number(tmp_size) + " > " + QString::number(new_segment->vertices.size()), 1); tmp_size = new_segment->vertices.size(); } for (unsigned int i = 0; i < tmp_size; i++) for (unsigned int j = 0; j < 3; j++) m_file.read(F2V(new_segment->vertices[i].normal[j]), sizeof(float)); } // uv else if (!strcmp("UV0L", it->name)) { readUV(new_segment, it->position); } // polygons (indices into vertex/uv list) else if (!strcmp("STRP", it->name)) { // jump to the data section and read the size; std::uint32_t tmp_size; m_file.seekg(it->position); m_file.read(F2V(tmp_size), sizeof(tmp_size)); int highBitCount(0); QVector tmp_buffer; for (unsigned int i = 0; i < tmp_size; i++) { // ReadData std::uint16_t tmp_value; m_file.read(F2V(tmp_value), sizeof(tmp_value)); // Check if highbit is set if (tmp_value >> 15) { highBitCount++; // remove the high bit, to get the actually value tmp_value = (std::uint16_t(tmp_value << 1) >> 1); } // save data tmp_buffer.push_back((GLuint)tmp_value); // if the last 2 highBits are set, it was a new poly if (highBitCount == 2) { // reset highBitCount highBitCount = 0; if (tmp_buffer.size() == 5) { for (size_t i = 0; i < 3; i++) new_segment->indices.push_back(tmp_buffer.takeFirst()); } else if (tmp_buffer.size() > 5) { unsigned int tmp_multiPolySize = tmp_buffer.size() - 2; // for every triangle of the multi polygon.. for (unsigned int tri = 0; tri < tmp_multiPolySize - 2; tri++) // ..calculate the edge indices for (int triEdge = 0; triEdge < 3; triEdge++) new_segment->indices.push_back(tmp_buffer[(tri + triEdge - ((tri % 2) * (triEdge - 1) * 2))]); tmp_buffer.remove(0, tmp_multiPolySize); } } // if 2 high bits are set } // for all values // save the last polygon (no 2 high bit followed) if (tmp_buffer.size() == 3) { for (size_t i = 0; i < 3; i++) new_segment->indices.push_back(tmp_buffer.takeFirst()); } else if (tmp_buffer.size() > 3) { unsigned int tmp_multiPolySize = tmp_buffer.size(); // for every triangle of the multi polygon.. for (unsigned int tri = 0; tri < tmp_multiPolySize - 2; tri++) // ..calculate the edge indices for (int triEdge = 0; triEdge < 3; triEdge++) new_segment->indices.push_back(tmp_buffer[(tri + triEdge - ((tri % 2) * (triEdge - 1) * 2))]); } } } dataDestination->segmList.push_back(new_segment); } void MshFile::analyseClthChunks(Model * dataDestination, std::list& chunkList) { Segment* new_segment = new Segment; for (auto& it : chunkList) { // texture name if (!strcmp("CTEX", it->name)) { // read the texture name m_file.seekg(it->position); char* buffer = new char[it->size + 1]; *buffer = { 0 }; m_file.read(buffer, it->size); // search if it is already known bool tmp_found(false); for (unsigned int index = 0; index < m_materials->size(); index++) { if (m_materials->at(index).name == QString(buffer)) { tmp_found = true; new_segment->textureIndex = index; break; } } if(!tmp_found) { m_materials->push_back(Material()); m_materials->back().name = QString(buffer); loadTexture(m_materials->back().texture, m_filepath + "/" + m_materials->back().name); new_segment->textureIndex = m_materials->size() - 1; } delete[] buffer; } // position list (vertex) else if (!strcmp("CPOS", it->name)) { readVertex(new_segment, it->position); } // uv else if (!strcmp("CUV0", it->name)) { readUV(new_segment, it->position); } // triangles (indices into vertex/uv list) else if (!strcmp("CMSH", it->name)) { // jump to the data section and read the size; std::uint32_t tmp_size; m_file.seekg(it->position); m_file.read(F2V(tmp_size), sizeof(tmp_size)); // for every triangle.. for (unsigned int i = 0; i < tmp_size * 3; i++) { std::uint32_t tmp_value; m_file.read(F2V(tmp_value), sizeof(std::uint32_t)); new_segment->indices.push_back((GLuint)tmp_value); } } } dataDestination->segmList.push_back(new_segment); } void MshFile::readVertex(Segment * dataDestination, std::streampos position) { std::uint32_t tmp_size; m_file.seekg(position); m_file.read(F2V(tmp_size), sizeof(tmp_size)); for (unsigned int i = 0; i < tmp_size; i++) { float tmp[3]; for (unsigned int j = 0; j < 3; j++) m_file.read(F2V(tmp[j]), sizeof(float)); VertexData new_data; new_data.position = QVector3D(tmp[0], tmp[1], tmp[2]); dataDestination->vertices.push_back(new_data); } } void MshFile::readUV(Segment * dataDestination, std::streampos position) { std::uint32_t tmp_size; m_file.seekg(position); m_file.read(F2V(tmp_size), sizeof(tmp_size)); if (tmp_size < dataDestination->vertices.size()) { emit sendMessage("WARNING: too less UVs " + QString::number(tmp_size) + " < " + QString::number(dataDestination->vertices.size()),1); for (unsigned int i = dataDestination->vertices.size(); i != tmp_size; i--) for (unsigned int j = 0; j < 2; j++) dataDestination->vertices[i - 1].texCoord[j] = 0; } else if (tmp_size > dataDestination->vertices.size()) { emit sendMessage("WARNING: too many UVs " + QString::number(tmp_size) + " > " + QString::number(dataDestination->vertices.size()), 1); tmp_size = dataDestination->vertices.size(); } for (unsigned int i = 0; i < tmp_size; i++) for (unsigned int j = 0; j < 2; j++) m_file.read(F2V(dataDestination->vertices[i].texCoord[j]), sizeof(float)); } void MshFile::loadTexture(QOpenGLTexture *& destination, QString filepath) { bool loadSuccess(false); QImage img = loadTga(filepath, loadSuccess); if (filepath.isEmpty()) { loadSuccess = true; img = QImage(1, 1, QImage::Format_RGB32); img.fill(Qt::red); } else img = loadTga(filepath, loadSuccess); if (!loadSuccess) { emit sendMessage("WARNING: texture not found or corrupted: " + m_materials->back().name, 1); img = QImage(1, 1, QImage::Format_RGB32); img.fill(QColor(m_materials->back().diffuseColor[0] * 255, m_materials->back().diffuseColor[1] * 255, m_materials->back().diffuseColor[2] * 255)); } // Load image to OglTexture QOpenGLTexture* new_texture = new QOpenGLTexture(img.mirrored()); // Set nearest filtering mode for texture minification new_texture->setMinificationFilter(QOpenGLTexture::Nearest); // Set bilinear filtering mode for texture magnification new_texture->setMagnificationFilter(QOpenGLTexture::Linear); // Wrap texture coordinates by repeating // f.ex. texture coordinate (1.1, 1.2) is same as (0.1, 0.2) new_texture->setWrapMode(QOpenGLTexture::Repeat); destination = new_texture; } QMatrix4x4 MshFile::getParentMatrix(std::string parent) const { QMatrix4x4 matrix; for (auto& it : *m_models) { if (!strcmp(parent.c_str(), it->name.c_str())) { matrix = getParentMatrix(it->parent) * it->m4x4Translation; break; } } return matrix; } QQuaternion MshFile::getParentRotation(std::string parent) const { QQuaternion rotation; for (auto& it : *m_models) { if (!strcmp(parent.c_str(), it->name.c_str())) { rotation = getParentRotation(it->parent) * it->quadRotation; break; } } return rotation; }