#ifndef ASSIMP_BUILD_NO_FEZ_IMPORTER #include "FezLoader.h" #include #include #include #include #include #include #include #include namespace Assimp { static const aiImporterDesc desc = { "FEZ Importer", "Kayden", "", "Unfinished.", aiImporterFlags_Experimental | aiImporterFlags_LimitedSupport | aiImporterFlags_SupportTextFlavour, 0, 0, 0, 0, "xml", }; static const aiVector3D gc_normals[] = { {-1.f, 0.f, 0.f}, {0.f, -1.f, 0.f}, {0.f, 0.f, -1.f}, {1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f} }; static const ai_real gc_orient[] = { AI_MATH_PI, -AI_MATH_HALF_PI, 0,AI_MATH_HALF_PI, }; FezLoader::FezLoader() = default; FezLoader::~FezLoader() = default; bool FezLoader::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool) const { // TODO: Read single art objects too static const char *tokens[] = { " levelFile; levelFile.reset(pIOHandler->Open(pFile)); // generate a XML reader for it if (!mLevelParser.parse(levelFile.get())) { throw DeadlyImportError("Unable to read level file, malformed XML"); } // Load trile sets auto levelRoot = mLevelParser.getRootNode().child("Level"); auto levelName = std::string(levelRoot.attribute("name").as_string()); // Fill in root node pScene->mRootNode = new aiNode(levelName); // Get trile set name in lowercase (all the files from export are lowercased) auto trileSetName = std::string(levelRoot.attribute("trileSetName").as_string()); trileSetName = ai_tolower(trileSetName); // Create material for triles auto myMat = new aiMaterial(); aiString matName("TrileSet"); myMat->AddProperty(&matName, AI_MATKEY_NAME); // Embed texture for triles auto myTex = CreateTex(std::string(mFezAssetDir / "trile sets" / (trileSetName + ".png")), pIOHandler); mTextures.push_back(myTex); // Put tex ref into material aiString tName("*0"); myMat->AddProperty(&tName, AI_MATKEY_TEXTURE(aiTextureType_BASE_COLOR, 0)); //store material in scene mMaterials.push_back(myMat); // Find and read trile set XML file std::unique_ptr trileFile; trileFile.reset(pIOHandler->Open(mFezAssetDir / "trile sets" / (trileSetName + ".xml"))); if (!mTrileParser.parse(trileFile.get())) { throw DeadlyImportError("Unable to read trile file, malformed XML"); } ASSIMP_LOG_VERBOSE_DEBUG("Parsed trile set ", mTrileParser.getRootNode().child("TrileSet").attribute("name").as_string()); // Place triles and their XML nodes into map auto triles = mTrileParser.getRootNode().child("TrileSet").child("Triles"); for (auto t : triles.children()) { if (std::string(t.name()) != "TrileEntry") { ASSIMP_LOG_WARN("Unknown tag found in TrileSet XML."); continue; } auto key = t.attribute("key").as_int(-1); if (key == -1) { ASSIMP_LOG_ERROR("Trile found with an invalid key or non-int key. This could be bad!"); continue; } auto trile = t.child("Trile"); if (mTriles.count(key)) { ASSIMP_LOG_WARN("Duplicate trile key found, skipping..."); continue; } mTriles[key] = trile; } ASSIMP_LOG_VERBOSE_DEBUG("Loaded ", mTriles.size(), " triles from the set."); // Create children, one for the triles auto trileRoot = new aiNode("Triles"); pScene->mRootNode->addChildren(1, &trileRoot); // Begin filling out trile nodes auto xmlTrileT = levelRoot.child("Triles"); AddTrileInstances(xmlTrileT, trileRoot); // Create child for art objects auto aoRoot = new aiNode("Art Objects"); pScene->mRootNode->addChildren(1, &aoRoot); // For some reason, it needs a positioning adjustment... aiMatrix4x4::Translation(aiVector3D(-0.5,-0.5, -0.5), aoRoot->mTransformation); // Begin filling out art objects auto xmlAoRoot = levelRoot.child("ArtObjects"); for (auto t : xmlAoRoot.children()) { XmlNode instance; if (std::string(t.name()) == "Entry") { instance = t.child("ArtObjectInstance"); } else if (std::string(t.name()) == "ArtObjectInstance") { instance = t; } else { ASSIMP_LOG_WARN("AO instance not found in a node."); continue; } auto aoName = std::string(instance.attribute("name").as_string("INVALID")); if (aoName == "INVALID") { ASSIMP_LOG_WARN("AO instance found without a valid name!"); continue; } auto meshID = GetArtObjectFromName(aoName, pIOHandler); // Create node, link found mesh auto aoNode = new aiNode(); aoNode->mNumMeshes = 1; aoNode->mMeshes = new unsigned int[1]; aoNode->mMeshes[0] = meshID; aoNode->mName = aiString("AO_" + aoName); auto pos = GetFezVec3(instance.child("Position")); auto rotate = GetFezQuat(instance.child("Rotation")); auto scale = GetFezVec3(instance.child("Scale")); aoNode->mTransformation = aiMatrix4x4 (scale, rotate, pos); aoRoot->addChildren(1, &aoNode); } // Fill in meshes from vector pScene->mNumMeshes = mMeshes.size(); pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; for (int i = 0; i < mMeshes.size(); i++) { pScene->mMeshes[i] = mMeshes[i]; } // Fill in materials from vector pScene->mNumMaterials = mMaterials.size(); pScene->mMaterials = new aiMaterial * [mMaterials.size()]; for (int i = 0; i < mMaterials.size(); i++) { pScene->mMaterials[i] = mMaterials.at(i); } // Fill in textures from vector pScene->mNumTextures = mTextures.size(); pScene->mTextures = new aiTexture *[mTextures.size()]; for (int i = 0; i < mTextures.size(); i++) { pScene->mTextures[i] = mTextures.at(i); } } aiMesh *FezLoader::CreateMesh(const Assimp::XmlNode &xmlSIIP) { ASSIMP_LOG_VERBOSE_DEBUG("FEZ CreateMesh called"); auto mesh = new aiMesh(); auto xmlVerts = xmlSIIP.child("Vertices"); auto xmlIdx = xmlSIIP.child("Indices"); struct vpnti { aiVector3D pos; int normal; aiVector3D texCoord; }; std::vector vertex; // TODO: check handedness/orientation for (auto v : xmlVerts.children()) { auto pos = GetFezVec3(v.child("Position")); auto normal = v.child("Normal").text().as_int(); auto texCoord = GetFezVec3(v.child("TextureCoord").child("Vector2"), true); texCoord.y = 1 - texCoord.y; vertex.push_back({pos, normal, texCoord}); } // Collect indices into array std::vector idx; for (auto i : xmlIdx.children()) { auto id = i.text().as_uint(); idx.push_back(id); } // Populate mFaces with all triangles mesh->mNumFaces = idx.size() / 3; mesh->mFaces = new aiFace[mesh->mNumFaces]; // Iterate in sets of 3s. for (int i = 0; i < idx.size(); i += 3) { mesh->mFaces[i/3].mNumIndices = 3; mesh->mFaces[i/3].mIndices = new unsigned int[3] { idx.at(i), idx.at(i+2), idx.at(i+1), }; } // Now populate vertices mesh->mNumVertices = vertex.size(); mesh->mVertices = new aiVector3D[vertex.size()]; mesh->mNormals = new aiVector3D[vertex.size()]; mesh->mTextureCoords[0] = new aiVector3D[vertex.size()]; for (int i = 0; i < vertex.size(); i++) { auto v = vertex.at(i); mesh->mVertices[i] = v.pos; mesh->mNormals[i] = gc_normals[v.normal]; mesh->mTextureCoords[0][i] = v.texCoord; } return mesh; } unsigned int FezLoader::GetTrileMeshFromId(const int trileId) { if (auto search = mTrileMap.find(trileId); search != mTrileMap.end()) { return search->second; } // We need to create an aiMesh instance and add it to the vector. auto trileXML = mTriles.at(trileId); auto xmlSIIP = trileXML.child("Geometry").child("ShaderInstancedIndexedPrimitives"); // Create mesh auto mesh = CreateMesh(xmlSIIP); mesh->mName = "Trile#" + std::to_string(trileId); // Only one material for all triles. mesh->mMaterialIndex = 0; // Add entry to trile map and add mesh to vector mTrileMap[trileId] = mMeshes.size(); mMeshes.push_back(mesh); return mTrileMap[trileId]; } unsigned int FezLoader::GetArtObjectFromName(const std::string &name, IOSystem *pIOHandler) { if (auto search = mAOMap.find(name); search != mAOMap.end()) { return search->second; } auto AOXmlPath = mFezAssetDir / "art objects" / (ai_tolower(name) + ".xml"); if (!pIOHandler->Exists(AOXmlPath)) { ASSIMP_LOG_ERROR("Art Object specified that doesn't exist: ", name); return 0; } std::unique_ptr aoXmlFile; aoXmlFile.reset(pIOHandler->Open(AOXmlPath)); XmlParser p; p.parse(aoXmlFile.get()); XmlNode aoXmlRoot = p.getRootNode().child("ArtObject"); if (std::string(aoXmlRoot.name()) != name) { ASSIMP_LOG_WARN("Art Object name mismatch! ", name, " != ", aoXmlRoot.name()); } // Create mesh aiMesh* m = CreateMesh(aoXmlRoot.child("ShaderInstancedIndexedPrimitives")); m->mName = "AO_" + name; // Load texture auto myTex = CreateTex(mFezAssetDir / "art objects" / (ai_tolower(name) + ".png"), pIOHandler); // Create material auto myMat = new aiMaterial(); // Put tex ref into material aiString tName('*' + std::to_string(mMaterials.size())); myMat->AddProperty(&tName, AI_MATKEY_TEXTURE(aiTextureType_BASE_COLOR, 0)); mTextures.push_back(myTex); // Put material ref into mesh m->mMaterialIndex = mMaterials.size(); mMaterials.push_back(myMat); auto i = mMeshes.size(); mAOMap[name] = i; mMeshes.push_back(m); return i; } aiVector3D FezLoader::GetFezVec3(const XmlNode &t, bool direct) { if (direct) { return {t.attribute("x").as_float(), t.attribute("y").as_float(), t.attribute("z").as_float()}; } else { auto c = t.child("Vector3"); return {c.attribute("x").as_float(), c.attribute("y").as_float(), c.attribute("z").as_float()}; } } aiVector2D FezLoader::GetFezVec2(const XmlNode &t, bool direct) { if (direct) { return {t.attribute("x").as_float(), t.attribute("y").as_float()}; } else { auto c = t.child("Vector2"); return {c.attribute("x").as_float(), c.attribute("y").as_float()}; } } void FezLoader::AddTrileInstances(const XmlNode &tis, aiNode *trileRoot) { for (auto t : tis.children()) { XmlNode instance; if (std::string(t.name()) == "Entry") { instance = t.child("TrileInstance"); } else if (std::string(t.name()) == "TrileInstance") { instance = t; } else { ASSIMP_LOG_WARN("Trile instance not found in a node."); continue; } auto trileID = instance.attribute("trileId").as_int(-1); if (trileID == -1) { ASSIMP_LOG_WARN("Trile instance found without a valid trileId!"); continue; } auto meshID = GetTrileMeshFromId(trileID); // Create node, link found mesh auto trileNode = new aiNode(); trileNode->mNumMeshes = 1; trileNode->mMeshes = new unsigned int[1]; trileNode->mMeshes[0] = meshID; trileNode->mName = aiString("Trile_ID#" + std::to_string(trileID)); auto pos = GetFezVec3(instance.child("Position")); auto orient = instance.attribute("orientation").as_int(0); // Create transformation matrix aiMatrix4x4 transform = aiMatrix4x4::Translation(pos, transform); aiMatrix4x4 rotate = aiMatrix4x4::RotationY(gc_orient[orient], rotate); trileNode->mTransformation = transform * rotate; trileRoot->addChildren(1, &trileNode); auto overlapped = instance.child("OverlappedTriles"); if (!overlapped.empty()) { AddTrileInstances(overlapped, trileRoot); } } } aiTexture *FezLoader::CreateTex(const std::string &path, IOSystem *pIOHandler) { // Create embedded texture ref auto myTex = new aiTexture(); myTex->mFilename = path; // PNG file, compressed strcpy(myTex->achFormatHint, "png"); myTex->mHeight = 0; // Open texture file for reading std::unique_ptr texFile; texFile.reset(pIOHandler->Open(path)); // Create buffer, read data, store into texture instance auto texDat = new unsigned char[texFile->FileSize()]; myTex->mWidth = texFile->FileSize(); texFile->Read(texDat, 1, texFile->FileSize()); myTex->pcData = (aiTexel*)texDat; return myTex; } aiQuaternion FezLoader::GetFezQuat(const XmlNode &t, bool direct) { if (direct) { return {t.attribute("w").as_float(), t.attribute("x").as_float(), t.attribute("y").as_float(), t.attribute("z").as_float()}; } else { auto c = t.child("Quaternion"); return {c.attribute("w").as_float(), c.attribute("x").as_float(), c.attribute("y").as_float(), c.attribute("z").as_float()}; } } } #endif