/// \file AMFImporter_Postprocess.cpp /// \brief Convert built scenegraph and objects to Assimp scenegraph. /// \date 2016 /// \author smal.root@gmail.com #ifndef ASSIMP_BUILD_NO_AMF_IMPORTER #include "AMFImporter.hpp" // Header files, Assimp. #include "SceneCombiner.h" #include "StandardShapes.h" // Header files, stdlib. #include #include namespace Assimp { aiColor4D AMFImporter::SPP_Material::GetColor(const float pX, const float pY, const float pZ) const { aiColor4D tcol; // Check if stored data are supported. if(Composition.size() != 0) { throw DeadlyImportError("IME. GetColor for composition"); } else if(Color->Composed) { throw DeadlyImportError("IME. GetColor, composed color"); } else { tcol = Color->Color; } // Check if default color must be used if((tcol.r == 0) && (tcol.g == 0) && (tcol.b == 0) && (tcol.a == 0)) { tcol.r = 0.5f; tcol.g = 0.5f; tcol.b = 0.5f; tcol.a = 1; } return tcol; } void AMFImporter::PostprocessHelper_CreateMeshDataArray(const CAMFImporter_NodeElement_Mesh& pNodeElement, std::vector& pVertexCoordinateArray, std::vector& pVertexColorArray) const { CAMFImporter_NodeElement_Vertices* vn = nullptr; size_t col_idx; // All data stored in "vertices", search for it. for(CAMFImporter_NodeElement* ne_child: pNodeElement.Child) { if(ne_child->Type == CAMFImporter_NodeElement::ENET_Vertices) vn = (CAMFImporter_NodeElement_Vertices*)ne_child; } // If "vertices" not found then no work for us. if(vn == nullptr) return; pVertexCoordinateArray.reserve(vn->Child.size());// all coordinates stored as child and we need to reserve space for future push_back's. pVertexColorArray.resize(vn->Child.size());// colors count equal vertices count. col_idx = 0; // Inside vertices collect all data and place to arrays for(CAMFImporter_NodeElement* vn_child: vn->Child) { // vertices, colors if(vn_child->Type == CAMFImporter_NodeElement::ENET_Vertex) { // by default clear color for current vertex pVertexColorArray[col_idx] = nullptr; for(CAMFImporter_NodeElement* vtx: vn_child->Child) { if(vtx->Type == CAMFImporter_NodeElement::ENET_Coordinates) { pVertexCoordinateArray.push_back(((CAMFImporter_NodeElement_Coordinates*)vtx)->Coordinate); continue; } if(vtx->Type == CAMFImporter_NodeElement::ENET_Color) { pVertexColorArray[col_idx] = (CAMFImporter_NodeElement_Color*)vtx; continue; } }// for(CAMFImporter_NodeElement* vtx: vn_child->Child) col_idx++; }// if(vn_child->Type == CAMFImporter_NodeElement::ENET_Vertex) }// for(CAMFImporter_NodeElement* vn_child: vn->Child) } size_t AMFImporter::PostprocessHelper_GetTextureID_Or_Create(const std::string& pID_R, const std::string& pID_G, const std::string& pID_B, const std::string& pID_A) { size_t TextureConverted_Index; std::string TextureConverted_ID; // check input data if(pID_R.empty() && pID_G.empty() && pID_B.empty() && pID_A.empty()) throw DeadlyImportError("PostprocessHelper_GetTextureID_Or_Create. At least one texture ID must be defined."); // Create ID TextureConverted_ID = pID_R + "_" + pID_G + "_" + pID_B + "_" + pID_A; // Check if texture specified by set of IDs is converted already. TextureConverted_Index = 0; for(const SPP_Texture& tex_convd: mTexture_Converted) { if(tex_convd.ID == TextureConverted_ID) return TextureConverted_Index; else TextureConverted_Index++; } // // Converted texture not found, create it. // CAMFImporter_NodeElement_Texture* src_texture[4]{nullptr}; std::vector src_texture_4check; SPP_Texture converted_texture; {// find all specified source textures CAMFImporter_NodeElement* t_tex; // R if(!pID_R.empty()) { if(!Find_NodeElement(pID_R, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_R); src_texture[0] = (CAMFImporter_NodeElement_Texture*)t_tex; src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex); } else { src_texture[0] = nullptr; } // G if(!pID_G.empty()) { if(!Find_NodeElement(pID_G, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_G); src_texture[1] = (CAMFImporter_NodeElement_Texture*)t_tex; src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex); } else { src_texture[1] = nullptr; } // B if(!pID_B.empty()) { if(!Find_NodeElement(pID_B, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_B); src_texture[2] = (CAMFImporter_NodeElement_Texture*)t_tex; src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex); } else { src_texture[2] = nullptr; } // A if(!pID_A.empty()) { if(!Find_NodeElement(pID_A, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_A); src_texture[3] = (CAMFImporter_NodeElement_Texture*)t_tex; src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex); } else { src_texture[3] = nullptr; } }// END: find all specified source textures // check that all textures has same size if(src_texture_4check.size() > 1) { for(uint8_t i = 0, i_e = (src_texture_4check.size() - 1); i < i_e; i++) { if((src_texture_4check[i]->Width != src_texture_4check[i + 1]->Width) || (src_texture_4check[i]->Height != src_texture_4check[i + 1]->Height) || (src_texture_4check[i]->Depth != src_texture_4check[i + 1]->Depth)) { throw DeadlyImportError("PostprocessHelper_GetTextureID_Or_Create. Source texture must has the same size."); } } }// if(src_texture_4check.size() > 1) // set texture attributes converted_texture.Width = src_texture_4check[0]->Width; converted_texture.Height = src_texture_4check[0]->Height; converted_texture.Depth = src_texture_4check[0]->Depth; // if one of source texture is tiled then converted texture is tiled too. converted_texture.Tiled = false; for(uint8_t i = 0; i < src_texture_4check.size(); i++) converted_texture.Tiled |= src_texture_4check[i]->Tiled; // Create format hint. strcpy(converted_texture.FormatHint, "rgba0000");// copy initial string. if(!pID_R.empty()) converted_texture.FormatHint[4] = '8'; if(!pID_G.empty()) converted_texture.FormatHint[5] = '8'; if(!pID_B.empty()) converted_texture.FormatHint[6] = '8'; if(!pID_A.empty()) converted_texture.FormatHint[7] = '8'; // // Сopy data of textures. // size_t tex_size = 0; size_t step = 0; size_t off_g = 0; size_t off_b = 0; // Calculate size of the target array and rule how data will be copied. if(!pID_R.empty()) { tex_size += src_texture[0]->Data.size(); step++, off_g++, off_b++; } if(!pID_G.empty()) { tex_size += src_texture[1]->Data.size(); step++, off_b++; } if(!pID_B.empty()) { tex_size += src_texture[2]->Data.size(); step++; } if(!pID_A.empty()) { tex_size += src_texture[3]->Data.size(); step++; } // Create target array. converted_texture.Data = new uint8_t[tex_size]; // And copy data auto CopyTextureData = [&](const std::string& pID, const size_t pOffset, const size_t pStep, const uint8_t pSrcTexNum) -> void { if(!pID.empty()) { for(size_t idx_target = pOffset, idx_src = 0; idx_target < tex_size; idx_target += pStep, idx_src++) converted_texture.Data[idx_target] = src_texture[pSrcTexNum]->Data.at(idx_src); } };// auto CopyTextureData = [&](const size_t pOffset, const size_t pStep, const uint8_t pSrcTexNum) -> void CopyTextureData(pID_R, 0, step, 0); CopyTextureData(pID_G, off_g, step, 1); CopyTextureData(pID_B, off_b, step, 2); CopyTextureData(pID_A, step - 1, step, 3); // Store new converted texture ID converted_texture.ID = TextureConverted_ID; // Store new converted texture mTexture_Converted.push_back(converted_texture); return TextureConverted_Index; } void AMFImporter::PostprocessHelper_SplitFacesByTextureID(std::list& pInputList, std::list >& pOutputList_Separated) { auto texmap_is_equal = [](const CAMFImporter_NodeElement_TexMap* pTexMap1, const CAMFImporter_NodeElement_TexMap* pTexMap2) -> bool { if((pTexMap1 == nullptr) && (pTexMap2 == nullptr)) return true; if(pTexMap1 == nullptr) return false; if(pTexMap2 == nullptr) return false; if(pTexMap1->TextureID_R != pTexMap2->TextureID_R) return false; if(pTexMap1->TextureID_G != pTexMap2->TextureID_G) return false; if(pTexMap1->TextureID_B != pTexMap2->TextureID_B) return false; if(pTexMap1->TextureID_A != pTexMap2->TextureID_A) return false; return true; }; pOutputList_Separated.clear(); if(pInputList.size() == 0) return; do { SComplexFace face_start = pInputList.front(); std::list face_list_cur; for(std::list::const_iterator it = pInputList.cbegin(), it_end = pInputList.cend(); it != it_end;) { if(texmap_is_equal(face_start.TexMap, it->TexMap)) { auto it_old = it; it++; face_list_cur.push_back(*it_old); pInputList.erase(it_old); } else { it++; } } if(face_list_cur.size() > 0) pOutputList_Separated.push_back(face_list_cur); } while(pInputList.size() > 0); } void AMFImporter::Postprocess_AddMetadata(const std::list& pMetadataList, aiNode& pSceneNode) const { if(pMetadataList.size() > 0) { if(pSceneNode.mMetaData != nullptr) throw DeadlyImportError("Postprocess. MetaData member in node are not nullptr. Something went wrong."); // copy collected metadata to output node. pSceneNode.mMetaData = new aiMetadata(); pSceneNode.mMetaData->mNumProperties = pMetadataList.size(); pSceneNode.mMetaData->mKeys = new aiString[pSceneNode.mMetaData->mNumProperties]; pSceneNode.mMetaData->mValues = new aiMetadataEntry[pSceneNode.mMetaData->mNumProperties]; size_t meta_idx = 0; for(const CAMFImporter_NodeElement_Metadata& metadata: pMetadataList) { pSceneNode.mMetaData->Set(meta_idx++, metadata.Type, metadata.Value.c_str()); } }// if(pMetadataList.size() > 0) } void AMFImporter::Postprocess_BuildNodeAndObject(const CAMFImporter_NodeElement_Object& pNodeElement, std::list& pMeshList, aiNode** pSceneNode) { CAMFImporter_NodeElement_Color* object_color = nullptr; // create new aiNode and set name as has. *pSceneNode = new aiNode; (*pSceneNode)->mName = pNodeElement.ID; // read mesh and color for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child) { std::vector vertex_arr; std::vector color_arr; // color for object if(ne_child->Type == CAMFImporter_NodeElement::ENET_Color) object_color = (CAMFImporter_NodeElement_Color*)ne_child; if(ne_child->Type == CAMFImporter_NodeElement::ENET_Mesh) { // Create arrays from children of mesh: vertices. PostprocessHelper_CreateMeshDataArray(*((CAMFImporter_NodeElement_Mesh*)ne_child), vertex_arr, color_arr); // Use this arrays as a source when creating every aiMesh Postprocess_BuildMeshSet(*((CAMFImporter_NodeElement_Mesh*)ne_child), vertex_arr, color_arr, object_color, pMeshList, **pSceneNode); } }// for(const CAMFImporter_NodeElement* ne_child: pNodeElement) } void AMFImporter::Postprocess_BuildMeshSet(const CAMFImporter_NodeElement_Mesh& pNodeElement, const std::vector& pVertexCoordinateArray, const std::vector& pVertexColorArray, const CAMFImporter_NodeElement_Color* pObjectColor, std::list& pMeshList, aiNode& pSceneNode) { std::list mesh_idx; // all data stored in "volume", search for it. for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child) { const CAMFImporter_NodeElement_Color* ne_volume_color = nullptr; const SPP_Material* cur_mat = nullptr; if(ne_child->Type == CAMFImporter_NodeElement::ENET_Volume) { /******************* Get faces *******************/ const CAMFImporter_NodeElement_Volume* ne_volume = reinterpret_cast(ne_child); std::list complex_faces_list;// List of the faces of the volume. std::list > complex_faces_toplist;// List of the face list for every mesh. // check if volume use material if(!ne_volume->MaterialID.empty()) { if(!Find_ConvertedMaterial(ne_volume->MaterialID, &cur_mat)) Throw_ID_NotFound(ne_volume->MaterialID); } // inside "volume" collect all data and place to arrays or create new objects for(const CAMFImporter_NodeElement* ne_volume_child: ne_volume->Child) { // color for volume if(ne_volume_child->Type == CAMFImporter_NodeElement::ENET_Color) { ne_volume_color = reinterpret_cast(ne_volume_child); } else if(ne_volume_child->Type == CAMFImporter_NodeElement::ENET_Triangle)// triangles, triangles colors { const CAMFImporter_NodeElement_Triangle& tri_al = *reinterpret_cast(ne_volume_child); SComplexFace complex_face; // initialize pointers complex_face.Color = nullptr; complex_face.TexMap = nullptr; // get data from triangle children: color, texture coordinates. if(tri_al.Child.size()) { for(const CAMFImporter_NodeElement* ne_triangle_child: tri_al.Child) { if(ne_triangle_child->Type == CAMFImporter_NodeElement::ENET_Color) complex_face.Color = reinterpret_cast(ne_triangle_child); else if(ne_triangle_child->Type == CAMFImporter_NodeElement::ENET_TexMap) complex_face.TexMap = reinterpret_cast(ne_triangle_child); } }// if(tri_al.Child.size()) // create new face and store it. complex_face.Face.mNumIndices = 3; complex_face.Face.mIndices = new unsigned int[3]; complex_face.Face.mIndices[0] = tri_al.V[0]; complex_face.Face.mIndices[1] = tri_al.V[1]; complex_face.Face.mIndices[2] = tri_al.V[2]; complex_faces_list.push_back(complex_face); } }// for(const CAMFImporter_NodeElement* ne_volume_child: ne_volume->Child) /**** Split faces list: one list per mesh ****/ PostprocessHelper_SplitFacesByTextureID(complex_faces_list, complex_faces_toplist); /***** Create mesh for every faces list ******/ for(std::list& face_list_cur: complex_faces_toplist) { auto VertexIndex_GetMinimal = [](const std::list& pFaceList, const size_t* pBiggerThan) -> size_t { size_t rv; if(pBiggerThan != nullptr) { bool found = false; for(const SComplexFace& face: pFaceList) { for(size_t idx_vert = 0; idx_vert < face.Face.mNumIndices; idx_vert++) { if(face.Face.mIndices[idx_vert] > *pBiggerThan) { rv = face.Face.mIndices[idx_vert]; found = true; break; } } if(found) break; } if(!found) return *pBiggerThan; } else { rv = pFaceList.front().Face.mIndices[0]; }// if(pBiggerThan != nullptr) else for(const SComplexFace& face: pFaceList) { for(size_t vi = 0; vi < face.Face.mNumIndices; vi++) { if(face.Face.mIndices[vi] < rv) { if(pBiggerThan != nullptr) { if(face.Face.mIndices[vi] > *pBiggerThan) rv = face.Face.mIndices[vi]; } else { rv = face.Face.mIndices[vi]; } } } }// for(const SComplexFace& face: pFaceList) return rv; };// auto VertexIndex_GetMinimal = [](const std::list& pFaceList, const size_t* pBiggerThan) -> size_t auto VertexIndex_Replace = [](std::list& pFaceList, const size_t pIdx_From, const size_t pIdx_To) -> void { for(const SComplexFace& face: pFaceList) { for(size_t vi = 0; vi < face.Face.mNumIndices; vi++) { if(face.Face.mIndices[vi] == pIdx_From) face.Face.mIndices[vi] = pIdx_To; } } };// auto VertexIndex_Replace = [](std::list& pFaceList, const size_t pIdx_From, const size_t pIdx_To) -> void auto Vertex_CalculateColor = [&](const size_t pIdx) -> aiColor4D { // Color priorities(In descending order): // 1. triangle color; // 2. vertex color; // 3. volume color; // 4. object color; // 5. material; // 6. default - invisible coat. // // Fill vertices colors in color priority list above that's points from 1 to 6. if((pIdx < pVertexColorArray.size()) && (pVertexColorArray[pIdx] != nullptr))// check for vertex color { if(pVertexColorArray[pIdx]->Composed) throw DeadlyImportError("IME: vertex color composed"); else return pVertexColorArray[pIdx]->Color; } else if(ne_volume_color != nullptr)// check for volume color { if(ne_volume_color->Composed) throw DeadlyImportError("IME: volume color composed"); else return ne_volume_color->Color; } else if(pObjectColor != nullptr)// check for object color { if(pObjectColor->Composed) throw DeadlyImportError("IME: object color composed"); else return pObjectColor->Color; } else if(cur_mat != nullptr)// check for material { return cur_mat->GetColor(pVertexCoordinateArray.at(pIdx).x, pVertexCoordinateArray.at(pIdx).y, pVertexCoordinateArray.at(pIdx).z); } else// set default color. { return {0, 0, 0, 0}; }// if((vi < pVertexColorArray.size()) && (pVertexColorArray[vi] != nullptr)) else };// auto Vertex_CalculateColor = [&](const size_t pIdx) -> aiColor4D aiMesh* tmesh = new aiMesh; tmesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;// Only triangles is supported by AMF. // // set geometry and colors (vertices) // // copy faces/triangles tmesh->mNumFaces = face_list_cur.size(); tmesh->mFaces = new aiFace[tmesh->mNumFaces]; // Create vertices list and optimize indices. Optimisation mean following.In AMF all volumes use one big list of vertices. And one volume // can use only part of vertices list, for example: vertices list contain few thousands of vertices and volume use vertices 1, 3, 10. // Do you need all this thousands of garbage? Of course no. So, optimisation step transformate sparse indices set to continuous. const size_t VertexCount_Max = tmesh->mNumFaces * 3;// 3 - triangles. std::vector vert_arr, texcoord_arr; std::vector col_arr; vert_arr.reserve(VertexCount_Max * 2);// "* 2" - see below TODO. col_arr.reserve(VertexCount_Max * 2); {// fill arrays size_t vert_idx_from, vert_idx_to; // first iteration. vert_idx_to = 0; vert_idx_from = VertexIndex_GetMinimal(face_list_cur, nullptr); vert_arr.push_back(pVertexCoordinateArray.at(vert_idx_from)); col_arr.push_back(Vertex_CalculateColor(vert_idx_from)); if(vert_idx_from != vert_idx_to) VertexIndex_Replace(face_list_cur, vert_idx_from, vert_idx_to); // rest iterations do { vert_idx_from = VertexIndex_GetMinimal(face_list_cur, &vert_idx_to); if(vert_idx_from == vert_idx_to) break;// all indices are transfered, vert_arr.push_back(pVertexCoordinateArray.at(vert_idx_from)); col_arr.push_back(Vertex_CalculateColor(vert_idx_from)); vert_idx_to++; if(vert_idx_from != vert_idx_to) VertexIndex_Replace(face_list_cur, vert_idx_from, vert_idx_to); } while(true); }// fill arrays. END. // // check if triangle colors are used and create additional faces if needed. // for(const SComplexFace& face_cur: face_list_cur) { if(face_cur.Color != nullptr) { aiColor4D face_color; size_t vert_idx_new = vert_arr.size(); if(face_cur.Color->Composed) throw DeadlyImportError("IME: face color composed"); else face_color = face_cur.Color->Color; for(size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++) { vert_arr.push_back(vert_arr.at(face_cur.Face.mIndices[idx_ind])); col_arr.push_back(face_color); face_cur.Face.mIndices[idx_ind] = vert_idx_new++; } }// if(face_cur.Color != nullptr) }// for(const SComplexFace& face_cur: face_list_cur) // // if texture is used then copy texture coordinates too. // if(face_list_cur.front().TexMap != nullptr) { size_t idx_vert_new = vert_arr.size(); ///TODO: clean unused vertices. "* 2": in certain cases - mesh full of triangle colors - vert_arr will contain duplicated vertices for /// colored triangles and initial vertices (for colored vertices) which in real became unused. This part need more thinking about /// optimisation. bool idx_vert_used[VertexCount_Max * 2]{false}; // This ID's will be used when set materials ID in scene. tmesh->mMaterialIndex = PostprocessHelper_GetTextureID_Or_Create(face_list_cur.front().TexMap->TextureID_R, face_list_cur.front().TexMap->TextureID_G, face_list_cur.front().TexMap->TextureID_B, face_list_cur.front().TexMap->TextureID_A); texcoord_arr.resize(VertexCount_Max * 2); for(const SComplexFace& face_cur: face_list_cur) { for(size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++) { const size_t idx_vert = face_cur.Face.mIndices[idx_ind]; if(!idx_vert_used[idx_vert]) { texcoord_arr.at(idx_vert) = face_cur.TexMap->TextureCoordinate[idx_ind]; idx_vert_used[idx_vert] = true; } else if(texcoord_arr.at(idx_vert) != face_cur.TexMap->TextureCoordinate[idx_ind]) { // in that case one vertex is shared with many texture coordinates. We need to duplicate vertex with another texture // coordinates. vert_arr.push_back(vert_arr.at(idx_vert)); col_arr.push_back(col_arr.at(idx_vert)); texcoord_arr.at(idx_vert_new) = face_cur.TexMap->TextureCoordinate[idx_ind]; face_cur.Face.mIndices[idx_ind] = idx_vert_new++; } }// for(size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++) }// for(const SComplexFace& face_cur: face_list_cur) // shrink array texcoord_arr.resize(idx_vert_new); }// if(face_list_cur.front().TexMap != nullptr) // // copy collected data to mesh // tmesh->mNumVertices = vert_arr.size(); tmesh->mVertices = new aiVector3D[tmesh->mNumVertices]; tmesh->mColors[0] = new aiColor4D[tmesh->mNumVertices]; tmesh->mFaces = new aiFace[face_list_cur.size()]; memcpy(tmesh->mVertices, vert_arr.data(), tmesh->mNumVertices * sizeof(aiVector3D)); memcpy(tmesh->mColors[0], col_arr.data(), tmesh->mNumVertices * sizeof(aiColor4D)); if(texcoord_arr.size() > 0) { tmesh->mTextureCoords[0] = new aiVector3D[tmesh->mNumVertices]; memcpy(tmesh->mTextureCoords[0], texcoord_arr.data(), tmesh->mNumVertices * sizeof(aiVector3D)); tmesh->mNumUVComponents[0] = 2;// U and V stored in "x", "y" of aiVector3D. } size_t idx_face = 0; for(const SComplexFace& face_cur: face_list_cur) tmesh->mFaces[idx_face++] = face_cur.Face; // store new aiMesh mesh_idx.push_back(pMeshList.size()); pMeshList.push_back(tmesh); }// for(const std::list& face_list_cur: complex_faces_toplist) }// if(ne_child->Type == CAMFImporter_NodeElement::ENET_Volume) }// for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child) // if meshes was created then assign new indices with current aiNode if(mesh_idx.size() > 0) { std::list::const_iterator mit = mesh_idx.begin(); pSceneNode.mNumMeshes = mesh_idx.size(); pSceneNode.mMeshes = new unsigned int[pSceneNode.mNumMeshes]; for(size_t i = 0; i < pSceneNode.mNumMeshes; i++) pSceneNode.mMeshes[i] = *mit++; }// if(mesh_idx.size() > 0) } void AMFImporter::Postprocess_BuildMaterial(const CAMFImporter_NodeElement_Material& pMaterial) { SPP_Material new_mat; new_mat.ID = pMaterial.ID; for(const CAMFImporter_NodeElement* mat_child: pMaterial.Child) { if(mat_child->Type == CAMFImporter_NodeElement::ENET_Color) { new_mat.Color = (CAMFImporter_NodeElement_Color*)mat_child; } else if(mat_child->Type == CAMFImporter_NodeElement::ENET_Metadata) { new_mat.Metadata.push_back((CAMFImporter_NodeElement_Metadata*)mat_child); } }// for(const CAMFImporter_NodeElement* mat_child; pMaterial.Child) // place converted material to special list mMaterial_Converted.push_back(new_mat); } void AMFImporter::Postprocess_BuildConstellation(CAMFImporter_NodeElement_Constellation& pConstellation, std::list& pNodeList) const { aiNode* con_node; std::list ch_node; // We will build next hierarchy: // aiNode as parent () for set of nodes as a children // |- aiNode for transformation ( -> , ) - aiNode for pointing to object ("objectid") // ... // \_ aiNode for transformation ( -> , ) - aiNode for pointing to object ("objectid") con_node = new aiNode; con_node->mName = pConstellation.ID; // Walk thru children and search for instances of another objects, constellations. for(const CAMFImporter_NodeElement* ne: pConstellation.Child) { aiMatrix4x4 tmat; aiNode* t_node; aiNode* found_node; if(ne->Type == CAMFImporter_NodeElement::ENET_Metadata) continue; if(ne->Type != CAMFImporter_NodeElement::ENET_Instance) throw DeadlyImportError("Only nodes can be in ."); // create alias for conveniance CAMFImporter_NodeElement_Instance& als = *((CAMFImporter_NodeElement_Instance*)ne); // find referenced object if(!Find_ConvertedNode(als.ObjectID, pNodeList, &found_node)) Throw_ID_NotFound(als.ObjectID); // create node for apllying transformation t_node = new aiNode; t_node->mParent = con_node; // apply transformation aiMatrix4x4::Translation(als.Delta, tmat), t_node->mTransformation *= tmat; aiMatrix4x4::RotationX(als.Rotation.x, tmat), t_node->mTransformation *= tmat; aiMatrix4x4::RotationY(als.Rotation.y, tmat), t_node->mTransformation *= tmat; aiMatrix4x4::RotationZ(als.Rotation.z, tmat), t_node->mTransformation *= tmat; // create array for one child node t_node->mNumChildren = 1; t_node->mChildren = new aiNode*[t_node->mNumChildren]; SceneCombiner::Copy(&t_node->mChildren[0], found_node); t_node->mChildren[0]->mParent = t_node; ch_node.push_back(t_node); }// for(const CAMFImporter_NodeElement* ne: pConstellation.Child) // copy found aiNode's as children if(ch_node.size() == 0) throw DeadlyImportError(" must have at least one ."); size_t ch_idx = 0; con_node->mNumChildren = ch_node.size(); con_node->mChildren = new aiNode*[con_node->mNumChildren]; for(aiNode* node: ch_node) con_node->mChildren[ch_idx++] = node; // and place "root" of node to node list pNodeList.push_back(con_node); } void AMFImporter::Postprocess_BuildScene(aiScene* pScene) { std::list node_list; std::list mesh_list; std::list meta_list; // // Because for AMF "material" is just complex colors mixing so aiMaterial will not be used. // For building aiScene we are must to do few steps: // at first creating root node for aiScene. pScene->mRootNode = new aiNode; pScene->mRootNode->mParent = nullptr; pScene->mFlags |= AI_SCENE_FLAGS_ALLOW_SHARED; // search for root() element CAMFImporter_NodeElement* root_el = nullptr; for(CAMFImporter_NodeElement* ne: mNodeElement_List) { if(ne->Type != CAMFImporter_NodeElement::ENET_Root) continue; root_el = ne; break; }// for(const CAMFImporter_NodeElement* ne: mNodeElement_List) // Check if root element are found. if(root_el == nullptr) throw DeadlyImportError("Root() element not found."); // after that walk thru children of root and collect data. Five types of nodes can be placed at top level - in : , , , // and . But at first we must read and because they will be used in . can be read // at any moment. // // 1. // 2. will be converted later when processing triangles list. \sa Postprocess_BuildMeshSet for(const CAMFImporter_NodeElement* root_child: root_el->Child) { if(root_child->Type == CAMFImporter_NodeElement::ENET_Material) Postprocess_BuildMaterial(*((CAMFImporter_NodeElement_Material*)root_child)); } // After "appearance" nodes we must read because it will be used in -> . // // 3. for(const CAMFImporter_NodeElement* root_child: root_el->Child) { if(root_child->Type == CAMFImporter_NodeElement::ENET_Object) { aiNode* tnode = nullptr; // for mesh and node must be built: object ID assigned to aiNode name and will be used in future for Postprocess_BuildNodeAndObject(*((CAMFImporter_NodeElement_Object*)root_child), mesh_list, &tnode); if(tnode != nullptr) node_list.push_back(tnode); } }// for(const CAMFImporter_NodeElement* root_child: root_el->Child) // And finally read rest of nodes. // for(const CAMFImporter_NodeElement* root_child: root_el->Child) { // 4. if(root_child->Type == CAMFImporter_NodeElement::ENET_Constellation) { // and at top of self abstraction use aiNode. So we can use only aiNode list for creating new aiNode's. Postprocess_BuildConstellation(*((CAMFImporter_NodeElement_Constellation*)root_child), node_list); } // 5, if(root_child->Type == CAMFImporter_NodeElement::ENET_Metadata) meta_list.push_back((CAMFImporter_NodeElement_Metadata*)root_child); }// for(const CAMFImporter_NodeElement* root_child: root_el->Child) // at now we can add collected metadata to root node Postprocess_AddMetadata(meta_list, *pScene->mRootNode); // // Check constellation children // // As said in specification: // "When multiple objects and constellations are defined in a single file, only the top level objects and constellations are available for printing." // What that means? For example: if some object is used in constellation then you must show only constellation but not original object. // And at this step we are checking that relations. nl_clean_loop: if(node_list.size() > 1) { // walk thru all nodes for(std::list::iterator nl_it = node_list.begin(); nl_it != node_list.end(); nl_it++) { // and try to find them in another top nodes. std::list::const_iterator next_it = nl_it; next_it++; for(; next_it != node_list.end(); next_it++) { if((*next_it)->FindNode((*nl_it)->mName) != nullptr) { // if current top node(nl_it) found in another top node then erase it from node_list and restart search loop. node_list.erase(nl_it); goto nl_clean_loop; } }// for(; next_it != node_list.end(); next_it++) }// for(std::list::const_iterator nl_it = node_list.begin(); nl_it != node_list.end(); nl_it++) } // // move created objects to aiScene // // // Nodes if(node_list.size() > 0) { std::list::const_iterator nl_it = node_list.begin(); pScene->mRootNode->mNumChildren = node_list.size(); pScene->mRootNode->mChildren = new aiNode*[pScene->mRootNode->mNumChildren]; for(size_t i = 0; i < pScene->mRootNode->mNumChildren; i++) { // Objects and constellation that must be showed placed at top of hierarchy in node. So all aiNode's in node_list must have // mRootNode only as parent. (*nl_it)->mParent = pScene->mRootNode; pScene->mRootNode->mChildren[i] = *nl_it++; } }// if(node_list.size() > 0) // // Meshes if(mesh_list.size() > 0) { std::list::const_iterator ml_it = mesh_list.begin(); pScene->mNumMeshes = mesh_list.size(); pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; for(size_t i = 0; i < pScene->mNumMeshes; i++) pScene->mMeshes[i] = *ml_it++; }// if(mesh_list.size() > 0) // // Textures pScene->mNumTextures = mTexture_Converted.size(); if(pScene->mNumTextures > 0) { size_t idx; idx = 0; pScene->mTextures = new aiTexture*[pScene->mNumTextures]; for(const SPP_Texture& tex_convd: mTexture_Converted) { pScene->mTextures[idx] = new aiTexture; pScene->mTextures[idx]->mWidth = tex_convd.Width; pScene->mTextures[idx]->mHeight = tex_convd.Height; pScene->mTextures[idx]->pcData = (aiTexel*)tex_convd.Data; // texture format description. strcpy(pScene->mTextures[idx]->achFormatHint, tex_convd.FormatHint); idx++; }// for(const SPP_Texture& tex_convd: mTexture_Converted) // Create materials for embedded textures. idx = 0; pScene->mNumMaterials = mTexture_Converted.size(); pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]; for(const SPP_Texture& tex_convd: mTexture_Converted) { const aiString texture_id(AI_EMBEDDED_TEXNAME_PREFIX + std::to_string(idx)); const int mode = aiTextureOp_Multiply; const int repeat = tex_convd.Tiled ? 1 : 0; pScene->mMaterials[idx] = new aiMaterial; pScene->mMaterials[idx]->AddProperty(&texture_id, AI_MATKEY_TEXTURE_DIFFUSE(0)); pScene->mMaterials[idx]->AddProperty(&mode, 1, AI_MATKEY_TEXOP_DIFFUSE(0)); pScene->mMaterials[idx]->AddProperty(&repeat, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0)); pScene->mMaterials[idx]->AddProperty(&repeat, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0)); idx++; } }// if(pScene->mNumTextures > 0) }// END: after that walk thru children of root and collect data }// namespace Assimp #endif // !ASSIMP_BUILD_NO_AMF_IMPORTER