//! Because of metadata overrides we don't know the true hierarchy until all pieces have been loaded void CAssParser::BuildPieceHierarchy(S3DModel* model) { //! Loop through all pieces and create missing hierarchy info for (ModelPieceMap::const_iterator it = model->pieces.begin(); it != model->pieces.end(); ++it) { S3DModelPiece* piece = it->second; if (piece->name == "root") { piece->parent = NULL; assert(model->GetRootPiece() == NULL); model->SetRootPiece(piece); // FIXME what if called multiple times? continue; } if (piece->parentName != "") { if ((piece->parent = model->FindPiece(piece->parentName)) == NULL) { LOG_SL(LOG_SECTION_PIECE, L_ERROR, "Missing piece '%s' declared as parent of '%s'.", piece->parentName.c_str(), piece->name.c_str()); } else { piece->parent->children.push_back(piece); } continue; } //! A piece with no parent that isn't the root (orphan) if ((piece->parent = model->FindPiece("root")) == NULL) { LOG_SL(LOG_SECTION_PIECE, L_ERROR, "Missing root piece"); } else { piece->parent->children.push_back(piece); } } }
// Because of metadata overrides we don't know the true hierarchy until all pieces have been loaded void CAssParser::BuildPieceHierarchy(S3DModel* model) { // Loop through all pieces and create missing hierarchy info for (ModelPieceMap::const_iterator it = model->pieceMap.begin(); it != model->pieceMap.end(); ++it) { SAssPiece* piece = static_cast<SAssPiece*>(it->second); if (piece == model->GetRootPiece()) { assert(piece->parent == NULL); assert(model->GetRootPiece() == piece); continue; } if (!piece->parentName.empty()) { if ((piece->parent = model->FindPiece(piece->parentName)) == NULL) { LOG_SL(LOG_SECTION_PIECE, L_ERROR, "Missing piece '%s' declared as parent of '%s'.", piece->parentName.c_str(), piece->name.c_str()); } else { piece->parent->children.push_back(piece); } continue; } // a piece with no named parent that isn't the root (orphan) // link these to the root piece if it exists (which it should) if ((piece->parent = model->GetRootPiece()) == NULL) { LOG_SL(LOG_SECTION_PIECE, L_ERROR, "Missing root piece"); } else { piece->parent->children.push_back(piece); } } }
SAssPiece* CAssParser::LoadPiece( S3DModel* model, const aiNode* pieceNode, const aiScene* scene, const LuaTable& modelTable, ModelPieceMap& pieceMap, ParentNameMap& parentMap ) { ++model->numPieces; SAssPiece* piece = new SAssPiece(); if (pieceNode->mParent == nullptr) { // set the model's root piece ASAP, needed later assert(pieceNode == scene->mRootNode); assert(model->GetRootPiece() == nullptr); model->SetRootPiece(piece); } SetPieceName(piece, model, pieceNode, pieceMap); LOG_SL(LOG_SECTION_PIECE, L_INFO, "Converting node '%s' to piece '%s' (%d meshes).", pieceNode->mName.data, piece->name.c_str(), pieceNode->mNumMeshes); // Load additional piece properties from metadata const LuaTable& pieceTable = modelTable.SubTable("pieces").SubTable(piece->name); if (pieceTable.IsValid()) { LOG_SL(LOG_SECTION_PIECE, L_INFO, "Found metadata for piece '%s'", piece->name.c_str()); } LoadPieceTransformations(piece, model, pieceNode, pieceTable); LoadPieceGeometry(piece, pieceNode, scene); SetPieceParentName(piece, model, pieceNode, pieceTable, parentMap); { // operator[] creates an empty string if piece is not in map const auto parentNameIt = parentMap.find(piece); const std::string& parentName = (parentNameIt != parentMap.end())? (parentNameIt->second).c_str(): "[null]"; // Verbose logging of piece properties LOG_SL(LOG_SECTION_PIECE, L_INFO, "Loaded model piece: %s with %d meshes", piece->name.c_str(), pieceNode->mNumMeshes); LOG_SL(LOG_SECTION_PIECE, L_INFO, "piece->name: %s", piece->name.c_str()); LOG_SL(LOG_SECTION_PIECE, L_INFO, "piece->parent: %s", parentName.c_str()); } // Recursively process all child pieces for (unsigned int i = 0; i < pieceNode->mNumChildren; ++i) { LoadPiece(model, pieceNode->mChildren[i], scene, modelTable, pieceMap, parentMap); } pieceMap[piece->name] = piece; return piece; }
SAssPiece* CAssParser::LoadPiece( S3DModel* model, const aiNode* pieceNode, const aiScene* scene, const LuaTable& modelTable ) { ++model->numPieces; SAssPiece* piece = new SAssPiece(); if (pieceNode->mParent == NULL) { // set the model's root piece ASAP, needed later assert(pieceNode == scene->mRootNode); assert(model->GetRootPiece() == NULL); model->SetRootPiece(piece); } SetPieceName(piece, model, pieceNode); LOG_SL(LOG_SECTION_PIECE, L_INFO, "Converting node '%s' to piece '%s' (%d meshes).", pieceNode->mName.data, piece->name.c_str(), pieceNode->mNumMeshes); // Load additional piece properties from metadata const LuaTable& pieceTable = modelTable.SubTable("pieces").SubTable(piece->name); if (pieceTable.IsValid()) { LOG_SL(LOG_SECTION_PIECE, L_INFO, "Found metadata for piece '%s'", piece->name.c_str()); } // Load transforms LoadPieceTransformations(piece, model, pieceNode, pieceTable); if (SetModelRadiusAndHeight(model, piece, pieceNode, pieceTable)) return NULL; LoadPieceGeometry(piece, pieceNode, scene); SetPieceParentName(piece, model, pieceNode, pieceTable); // Verbose logging of piece properties LOG_SL(LOG_SECTION_PIECE, L_INFO, "Loaded model piece: %s with %d meshes", piece->name.c_str(), pieceNode->mNumMeshes); LOG_SL(LOG_SECTION_PIECE, L_INFO, "piece->name: %s", piece->name.c_str()); LOG_SL(LOG_SECTION_PIECE, L_INFO, "piece->parent: %s", piece->parentName.c_str()); // Recursively process all child pieces for (unsigned int i = 0; i < pieceNode->mNumChildren; ++i) { LoadPiece(model, pieceNode->mChildren[i], scene, modelTable); } model->pieceMap[piece->name] = piece; return piece; }
bool CArchiveScanner::CheckCompression(const IArchive* ar,const std::string& fullName, std::string& error) { if (!ar->CheckForSolid()) return true; for (unsigned fid = 0; fid != ar->NumFiles(); ++fid) { std::string name; int size; ar->FileInfo(fid, name, size); const std::string lowerName = StringToLower(name); const auto metaFileClass = CArchiveScanner::GetMetaFileClass(lowerName); if ((metaFileClass == 0) || ar->HasLowReadingCost(fid)) continue; // is a meta-file and not cheap to read if (metaFileClass == 1) { // 1st class error = "Unpacking/reading cost for meta file " + name + " is too high, please repack the archive (make sure to use a non-solid algorithm, if applicable)"; return false; } else if (metaFileClass == 2) { // 2nd class LOG_SL(LOG_SECTION_ARCHIVESCANNER, L_WARNING, "Archive %s: The cost for reading a 2nd-class meta-file is too high: %s", fullName.c_str(), name.c_str()); } } return true; }
bool CArchiveScanner::CheckCompression(const IArchive* ar, const std::string& fullName, std::string& error) { if (!ar->CheckForSolid()) return true; for (unsigned fid = 0; fid != ar->NumFiles(); ++fid) { if (ar->HasLowReadingCost(fid)) continue; const std::pair<std::string, int>& info = ar->FileInfo(fid); switch (GetMetaFileClass(StringToLower(info.first))) { case 1: { error += "reading primary meta-file " + info.first + " too expensive; "; error += "please repack this archive with non-solid compression"; return false; } break; case 2: { LOG_SL(LOG_SECTION_ARCHIVESCANNER, L_WARNING, "Archive %s: reading secondary meta-file %s too expensive", fullName.c_str(), info.first.c_str()); } break; case 0: default: { continue; } break; } } return true; }
std::string CArchiveScanner::MapNameToMapFile(const std::string& s) const { // Convert map name to map archive for (std::map<std::string, ArchiveInfo>::const_iterator aii = archiveInfos.begin(); aii != archiveInfos.end(); ++aii) { if (s == aii->second.archiveData.GetNameVersioned()) { return aii->second.archiveData.GetMapFile(); } } LOG_SL(LOG_SECTION_ARCHIVESCANNER, L_WARNING, "map file of %s not found", s.c_str()); return s; }
void CWordCompletion::AddWord(const std::string& word, bool startOfLine, bool unitName, bool miniMap) { if (!word.empty()) { if (words.find(word) != words.end()) { LOG_SL("WordCompletion", L_DEBUG, "Tried to add already present word: %s", word.c_str()); return; } words[word] = WordProperties(startOfLine, unitName, miniMap); } }
std::string CArchiveScanner::MapNameToMapFile(const std::string& s) const { // Convert map name to map archive const auto pred = [&s](const decltype(archiveInfos)::value_type& p) { return ((p.second).archiveData.GetNameVersioned() == s); }; const auto iter = std::find_if(archiveInfos.cbegin(), archiveInfos.cend(), pred); if (iter != archiveInfos.cend()) return ((iter->second).archiveData.GetMapFile()); LOG_SL(LOG_SECTION_ARCHIVESCANNER, L_WARNING, "map file of %s not found", s.c_str()); return s; }
// Because of metadata overrides we don't know the true hierarchy until all pieces have been loaded void CAssParser::BuildPieceHierarchy(S3DModel* model, ModelPieceMap& pieceMap, const ParentNameMap& parentMap) { const char* fmt1 = "Missing piece '%s' declared as parent of '%s'."; const char* fmt2 = "Missing root piece (parent of orphan '%s')"; // Loop through all pieces and create missing hierarchy info for (auto it = pieceMap.cbegin(); it != pieceMap.cend(); ++it) { SAssPiece* piece = static_cast<SAssPiece*>(it->second); if (piece == model->GetRootPiece()) { assert(piece->parent == nullptr); assert(model->GetRootPiece() == piece); continue; } const auto parentNameIt = parentMap.find(piece); if (parentNameIt != parentMap.end()) { const std::string& parentName = parentNameIt->second; const auto pieceIt = pieceMap.find(parentName); if (pieceIt != pieceMap.end()) { piece->parent = pieceIt->second; piece->parent->children.push_back(piece); } else { LOG_SL(LOG_SECTION_PIECE, L_ERROR, fmt1, parentName.c_str(), piece->name.c_str()); } continue; } // a piece with no named parent that isn't the root (orphan) // link these to the root piece if it exists (which it should) if ((piece->parent = model->GetRootPiece()) == nullptr) { LOG_SL(LOG_SECTION_PIECE, L_ERROR, fmt2, piece->name.c_str()); } else { piece->parent->children.push_back(piece); } } }
unsigned int CArchiveScanner::GetSingleArchiveChecksum(const std::string& name) const { std::string lcname = FileSystem::GetFilename(name); StringToLowerInPlace(lcname); std::map<std::string, ArchiveInfo>::const_iterator aii = archiveInfos.find(lcname); if (aii == archiveInfos.end()) { LOG_SL(LOG_SECTION_ARCHIVESCANNER, L_WARNING, "%s checksum: not found (0)", name.c_str()); return 0; } LOG_S(LOG_SECTION_ARCHIVESCANNER,"%s checksum: %d/%u", name.c_str(), aii->second.checksum, aii->second.checksum); return aii->second.checksum; }
unsigned int CArchiveScanner::GetSingleArchiveChecksum(const std::string& filePath) { ComputeChecksumForArchive(filePath); const std::string lcname = std::move(StringToLower(FileSystem::GetFilename(filePath))); const auto aii = archiveInfos.find(lcname); if (aii == archiveInfos.end()) { LOG_SL(LOG_SECTION_ARCHIVESCANNER, L_WARNING, "%s checksum: not found (0)", filePath.c_str()); return 0; } LOG_S(LOG_SECTION_ARCHIVESCANNER,"%s checksum: %d/%u", filePath.c_str(), aii->second.checksum, aii->second.checksum); return aii->second.checksum; }
void write(const char* message) { LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "Assimp: %s", message); }
void gmlPrintCallChainWarning(const char *func) { LOG_SL("Threading", L_ERROR, "Invalid attempt (%d/%d) to invoke LuaUI (%s) from another Lua environment, " "certain widgets require LuaThreadingModel > %d to work properly with multithreading", gmlCallChainWarning, GML_MAX_CALL_CHAIN_WARNINGS, func, (int)MT_LUA_SINGLE_BATCH); }
void SAssPiece::DrawForList() const { if (isEmpty) return; LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Compiling piece %s", name.c_str()); vboAttributes.Bind(GL_ARRAY_BUFFER); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, sizeof(SAssVertex), vboAttributes.GetPtr(offsetof(SAssVertex, pos))); glEnableClientState(GL_NORMAL_ARRAY); glNormalPointer(GL_FLOAT, sizeof(SAssVertex), vboAttributes.GetPtr(offsetof(SAssVertex, normal))); glClientActiveTexture(GL_TEXTURE0); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(2, GL_FLOAT, sizeof(SAssVertex), vboAttributes.GetPtr(offsetof(SAssVertex, texCoord))); glClientActiveTexture(GL_TEXTURE1); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(2, GL_FLOAT, sizeof(SAssVertex), vboAttributes.GetPtr(offsetof(SAssVertex, texCoord))); if (hasTexCoord2) { glClientActiveTexture(GL_TEXTURE2); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(2, GL_FLOAT, sizeof(SAssVertex), vboAttributes.GetPtr(offsetof(SAssVertex, texCoord2))); } glClientActiveTexture(GL_TEXTURE5); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(3, GL_FLOAT, sizeof(SAssVertex), vboAttributes.GetPtr(offsetof(SAssVertex, sTangent))); glClientActiveTexture(GL_TEXTURE6); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(3, GL_FLOAT, sizeof(SAssVertex), vboAttributes.GetPtr(offsetof(SAssVertex, tTangent))); vboAttributes.Unbind(); vboIndices.Bind(GL_ELEMENT_ARRAY_BUFFER); /* * since aiProcess_SortByPType is being used, * we're sure we'll get only 1 type here, * so combination check isn't needed, also * anything more complex than triangles is * being split thanks to aiProcess_Triangulate */ glDrawRangeElements(GL_TRIANGLES, 0, vertices.size() - 1, vertexDrawIndices.size(), GL_UNSIGNED_INT, vboIndices.GetPtr()); vboIndices.Unbind(); glClientActiveTexture(GL_TEXTURE6); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTexture(GL_TEXTURE5); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTexture(GL_TEXTURE2); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTexture(GL_TEXTURE1); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTexture(GL_TEXTURE0); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Completed compiling piece %s", name.c_str()); }
SAssPiece* CAssParser::LoadPiece(SAssModel* model, aiNode* node, const LuaTable& metaTable) { // Create new piece ++model->numPieces; SAssPiece* piece = new SAssPiece; piece->type = MODELTYPE_OTHER; piece->node = node; piece->isEmpty = (node->mNumMeshes == 0); if (node->mParent) { piece->name = std::string(node->mName.data); } else { //FIXME is this really smart? piece->name = "root"; //! The real model root } // find a new name if none given or if a piece with the same name already exists if (piece->name.empty()) { piece->name = "piece"; } ModelPieceMap::const_iterator it = model->pieces.find(piece->name); if (it != model->pieces.end()) { char buf[64]; int i = 0; while (it != model->pieces.end()) { SNPRINTF(buf, 64, "%s%02i", piece->name.c_str(), i++); it = model->pieces.find(buf); } piece->name = buf; } LOG_S(LOG_SECTION_PIECE, "Converting node '%s' to piece '%s' (%d meshes).", node->mName.data, piece->name.c_str(), node->mNumMeshes); // Load additional piece properties from metadata const LuaTable& pieceTable = metaTable.SubTable("pieces").SubTable(piece->name); if (pieceTable.IsValid()) { LOG_S(LOG_SECTION_PIECE, "Found metadata for piece '%s'", piece->name.c_str()); } // Load transforms LoadPieceTransformations(model, piece, pieceTable); // Update piece min/max extents for (unsigned meshListIndex = 0; meshListIndex < node->mNumMeshes; meshListIndex++) { const unsigned int meshIndex = node->mMeshes[meshListIndex]; const SAssModel::MinMax& minmax = model->mesh_minmax[meshIndex]; piece->mins = std::min(piece->mins, minmax.mins); piece->maxs = std::max(piece->maxs, minmax.maxs); } // Check if piece is special (ie, used to set Spring model properties) if (strcmp(node->mName.data, "SpringHeight") == 0) { // Set the model height to this nodes Z value if (!metaTable.KeyExists("height")) { model->height = piece->offset.z; LOG_S(LOG_SECTION_MODEL, "Model height of %f set by special node 'SpringHeight'", model->height); } --model->numPieces; delete piece; return NULL; } if (strcmp(node->mName.data, "SpringRadius") == 0) { if (!metaTable.KeyExists("midpos")) { model->relMidPos = piece->scaleRotMatrix.Mul(piece->offset); LOG_S(LOG_SECTION_MODEL, "Model midpos of (%f,%f,%f) set by special node 'SpringRadius'", model->relMidPos.x, model->relMidPos.y, model->relMidPos.z); } if (!metaTable.KeyExists("radius")) { if (piece->maxs.x <= 0.00001f) { aiVector3D _scale, _offset; aiQuaternion _rotate; piece->node->mTransformation.Decompose(_scale,_rotate,_offset); model->radius = aiVectorToFloat3(_scale).x; // the blender import script only sets the scale property } else { model->radius = piece->maxs.x; // use the transformed mesh extents } LOG_S(LOG_SECTION_MODEL, "Model radius of %f set by special node 'SpringRadius'", model->radius); } --model->numPieces; delete piece; return NULL; } //! Get vertex data from node meshes for (unsigned meshListIndex = 0; meshListIndex < node->mNumMeshes; ++meshListIndex) { unsigned int meshIndex = node->mMeshes[meshListIndex]; LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Fetching mesh %d from scene", meshIndex); const aiMesh* mesh = model->scene->mMeshes[meshIndex]; std::vector<unsigned> mesh_vertex_mapping; // extract vertex data LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Processing vertices for mesh %d (%d vertices)", meshIndex, mesh->mNumVertices); LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Normals: %s Tangents/Bitangents: %s TexCoords: %s", (mesh->HasNormals() ? "Y" : "N"), (mesh->HasTangentsAndBitangents() ? "Y" : "N"), (mesh->HasTextureCoords(0) ? "Y" : "N")); piece->vertices.reserve(piece->vertices.size() + mesh->mNumVertices); for (unsigned vertexIndex = 0; vertexIndex < mesh->mNumVertices; ++vertexIndex) { SAssVertex vertex; // vertex coordinates const aiVector3D& aiVertex = mesh->mVertices[vertexIndex]; vertex.pos = aiVectorToFloat3(aiVertex); // vertex normal LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Fetching normal for vertex %d", vertexIndex); const aiVector3D& aiNormal = mesh->mNormals[vertexIndex]; if (!IS_QNAN(aiNormal)) { vertex.normal = aiVectorToFloat3(aiNormal); } // vertex tangent, x is positive in texture axis if (mesh->HasTangentsAndBitangents()) { LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Fetching tangent for vertex %d", vertexIndex ); const aiVector3D& aiTangent = mesh->mTangents[vertexIndex]; const aiVector3D& aiBitangent = mesh->mBitangents[vertexIndex]; vertex.sTangent = aiVectorToFloat3(aiTangent); vertex.tTangent = aiVectorToFloat3(aiBitangent); } // vertex texcoords if (mesh->HasTextureCoords(0)) { vertex.texCoord.x = mesh->mTextureCoords[0][vertexIndex].x; vertex.texCoord.y = mesh->mTextureCoords[0][vertexIndex].y; } if (mesh->HasTextureCoords(1)) { piece->hasTexCoord2 = true, vertex.texCoord2.x = mesh->mTextureCoords[1][vertexIndex].x; vertex.texCoord2.y = mesh->mTextureCoords[1][vertexIndex].y; } mesh_vertex_mapping.push_back(piece->vertices.size()); piece->vertices.push_back(vertex); } // extract face data LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Processing faces for mesh %d (%d faces)", meshIndex, mesh->mNumFaces); /* * since aiProcess_SortByPType is being used, * we're sure we'll get only 1 type here, * so combination check isn't needed, also * anything more complex than triangles is * being split thanks to aiProcess_Triangulate */ piece->vertexDrawIndices.reserve(piece->vertexDrawIndices.size() + mesh->mNumFaces * 3); for (unsigned faceIndex = 0; faceIndex < mesh->mNumFaces; ++faceIndex) { const aiFace& face = mesh->mFaces[faceIndex]; // some models contain lines (mNumIndices == 2) // we cannot render those (esp. they would need to be called in a 2nd drawcall) if (face.mNumIndices != 3) continue; for (unsigned vertexListID = 0; vertexListID < face.mNumIndices; ++vertexListID) { const unsigned int vertexFaceIdx = face.mIndices[vertexListID]; const unsigned int vertexDrawIdx = mesh_vertex_mapping[vertexFaceIdx]; piece->vertexDrawIndices.push_back(vertexDrawIdx); } } } piece->isEmpty = piece->vertices.empty(); //! Get parent name from metadata or model if (pieceTable.KeyExists("parent")) { piece->parentName = pieceTable.GetString("parent", ""); } else if (node->mParent) { if (node->mParent->mParent) { piece->parentName = std::string(node->mParent->mName.data); } else { // my parent is the root, which gets renamed piece->parentName = "root"; } } else { piece->parentName = ""; } LOG_S(LOG_SECTION_PIECE, "Loaded model piece: %s with %d meshes", piece->name.c_str(), node->mNumMeshes); // Verbose logging of piece properties LOG_S(LOG_SECTION_PIECE, "piece->name: %s", piece->name.c_str()); LOG_S(LOG_SECTION_PIECE, "piece->parent: %s", piece->parentName.c_str()); // Recursively process all child pieces for (unsigned int i = 0; i < node->mNumChildren; ++i) { LoadPiece(model, node->mChildren[i], metaTable); } model->pieces[piece->name] = piece; return piece; }
S3DModel* CAssParser::Load(const std::string& modelFilePath) { LOG_SL(LOG_SECTION_MODEL, L_INFO, "Loading model: %s", modelFilePath.c_str()); const std::string& modelPath = FileSystem::GetDirectory(modelFilePath); const std::string& modelName = FileSystem::GetBasename(modelFilePath); // Load the lua metafile. This contains properties unique to Spring models and must return a table std::string metaFileName = modelFilePath + ".lua"; if (!CFileHandler::FileExists(metaFileName, SPRING_VFS_ZIP)) { // Try again without the model file extension metaFileName = modelPath + '/' + modelName + ".lua"; } if (!CFileHandler::FileExists(metaFileName, SPRING_VFS_ZIP)) { LOG_SL(LOG_SECTION_MODEL, L_INFO, "No meta-file '%s'. Using defaults.", metaFileName.c_str()); } LuaParser metaFileParser(metaFileName, SPRING_VFS_MOD_BASE, SPRING_VFS_ZIP); if (!metaFileParser.Execute()) { LOG_SL(LOG_SECTION_MODEL, L_INFO, "'%s': %s. Using defaults.", metaFileName.c_str(), metaFileParser.GetErrorLog().c_str()); } // Get the (root-level) model table const LuaTable& modelTable = metaFileParser.GetRoot(); if (!modelTable.IsValid()) { LOG_SL(LOG_SECTION_MODEL, L_INFO, "No valid model metadata in '%s' or no meta-file", metaFileName.c_str()); } // Create a model importer instance Assimp::Importer importer; // Give the importer an IO class that handles Spring's VFS importer.SetIOHandler(new AssVFSSystem()); // Speed-up processing by skipping things we don't need importer.SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS, ASS_IMPORTER_OPTIONS); #ifndef BITMAP_NO_OPENGL { importer.SetPropertyInteger(AI_CONFIG_PP_SLM_VERTEX_LIMIT, maxVertices); importer.SetPropertyInteger(AI_CONFIG_PP_SLM_TRIANGLE_LIMIT, maxIndices / 3); } #endif // Read the model file to build a scene object LOG_SL(LOG_SECTION_MODEL, L_INFO, "Importing model file: %s", modelFilePath.c_str()); const aiScene* scene = nullptr; { // ASSIMP spams many SIGFPEs atm in normal & tangent generation ScopedDisableFpuExceptions fe; scene = importer.ReadFile(modelFilePath, ASS_POSTPROCESS_OPTIONS); } if (scene != nullptr) { LOG_SL(LOG_SECTION_MODEL, L_INFO, "Processing scene for model: %s (%d meshes / %d materials / %d textures)", modelFilePath.c_str(), scene->mNumMeshes, scene->mNumMaterials, scene->mNumTextures); } else { throw content_error("[AssimpParser] Model Import: " + std::string(importer.GetErrorString())); } ModelPieceMap pieceMap; ParentNameMap parentMap; S3DModel* model = new S3DModel(); model->name = modelFilePath; model->type = MODELTYPE_ASS; // Load textures FindTextures(model, scene, modelTable, modelPath, modelName); LOG_SL(LOG_SECTION_MODEL, L_INFO, "Loading textures. Tex1: '%s' Tex2: '%s'", model->texs[0].c_str(), model->texs[1].c_str()); texturehandlerS3O->PreloadTexture(model, modelTable.GetBool("fliptextures", true), modelTable.GetBool("invertteamcolor", true)); // Load all pieces in the model LOG_SL(LOG_SECTION_MODEL, L_INFO, "Loading pieces from root node '%s'", scene->mRootNode->mName.data); LoadPiece(model, scene->mRootNode, scene, modelTable, pieceMap, parentMap); // Update piece hierarchy based on metadata BuildPieceHierarchy(model, pieceMap, parentMap); CalculateModelProperties(model, modelTable); // Verbose logging of model properties LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->name: %s", model->name.c_str()); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->numobjects: %d", model->numPieces); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->radius: %f", model->radius); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->height: %f", model->height); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->mins: (%f,%f,%f)", model->mins[0], model->mins[1], model->mins[2]); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->maxs: (%f,%f,%f)", model->maxs[0], model->maxs[1], model->maxs[2]); LOG_SL(LOG_SECTION_MODEL, L_INFO, "Model %s Imported.", model->name.c_str()); return model; }
bool CAssParser::SetModelRadiusAndHeight( S3DModel* model, const SAssPiece* piece, const aiNode* pieceNode, const LuaTable& pieceTable ) { // check if this piece is "special" (ie, used to set Spring model properties) // if so, extract them and then remove the piece from the hierarchy entirely // if (piece->name == "SpringHeight") { // set the model height to this node's Y-value (FIXME: 'y' is Assimp/Blender-specific) if (!pieceTable.KeyExists("height")) { model->height = piece->offset.y; LOG_SL(LOG_SECTION_MODEL, L_INFO, "Model height of %f set by special node 'SpringHeight'", model->height); } --model->numPieces; delete piece; return true; } if (piece->name == "SpringRadius") { if (!pieceTable.KeyExists("midpos")) { CMatrix44f scaleRotMat; piece->ComposeTransform(scaleRotMat, ZeroVector, ZeroVector, piece->scales); // NOTE: // this makes little sense because the "SpringRadius" // piece can be placed anywhere within the hierarchy model->relMidPos = scaleRotMat.Mul(piece->offset); LOG_SL(LOG_SECTION_MODEL, L_INFO, "Model midpos of (%f,%f,%f) set by special node 'SpringRadius'", model->relMidPos.x, model->relMidPos.y, model->relMidPos.z); } if (!pieceTable.KeyExists("radius")) { if (true || piece->maxs.x <= 0.00001f) { // scales have been set at this point // the Blender import script only sets the scale property [?] // // model->radius = piece->scales.Length(); model->radius = piece->scales.x; } else { // FIXME: // geometry bounds are calculated by LoadPieceGeometry // which is called after SetModelRadiusAndHeight -> can // not take this branch yet // use the transformed mesh extents (FIXME: the bounds are NOT // actually transformed but derived from raw vertex positions!) // // model->radius = ((piece->maxs - piece->mins) * 0.5f).Length(); model->radius = piece->maxs.x; } LOG_SL(LOG_SECTION_MODEL, L_INFO, "Model radius of %f set by special node 'SpringRadius'", model->radius); } --model->numPieces; delete piece; return true; } return false; }
S3DModel* CAssParser::Load(const std::string& modelFilePath) { LOG_SL(LOG_SECTION_MODEL, L_INFO, "Loading model: %s", modelFilePath.c_str()); const std::string& modelPath = FileSystem::GetDirectory(modelFilePath); const std::string& modelName = FileSystem::GetBasename(modelFilePath); // Load the lua metafile. This contains properties unique to Spring models and must return a table std::string metaFileName = modelFilePath + ".lua"; if (!CFileHandler::FileExists(metaFileName, SPRING_VFS_ZIP)) { // Try again without the model file extension metaFileName = modelPath + '/' + modelName + ".lua"; } if (!CFileHandler::FileExists(metaFileName, SPRING_VFS_ZIP)) { LOG_SL(LOG_SECTION_MODEL, L_INFO, "No meta-file '%s'. Using defaults.", metaFileName.c_str()); } LuaParser metaFileParser(metaFileName, SPRING_VFS_MOD_BASE, SPRING_VFS_ZIP); if (!metaFileParser.Execute()) { LOG_SL(LOG_SECTION_MODEL, L_ERROR, "'%s': %s. Using defaults.", metaFileName.c_str(), metaFileParser.GetErrorLog().c_str()); } // Get the (root-level) model table const LuaTable& modelTable = metaFileParser.GetRoot(); if (!modelTable.IsValid()) { LOG_SL(LOG_SECTION_MODEL, L_INFO, "No valid model metadata in '%s' or no meta-file", metaFileName.c_str()); } // Create a model importer instance Assimp::Importer importer; // Create a logger for debugging model loading issues Assimp::DefaultLogger::create("", Assimp::Logger::VERBOSE); Assimp::DefaultLogger::get()->attachStream(new AssLogStream(), ASS_LOGGING_OPTIONS); // Give the importer an IO class that handles Spring's VFS importer.SetIOHandler(new AssVFSSystem()); // Speed-up processing by skipping things we don't need importer.SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS, ASS_IMPORTER_OPTIONS); #ifndef BITMAP_NO_OPENGL { // Optimize VBO-Mesh sizes/ranges GLint maxIndices = 1024; GLint maxVertices = 1024; // FIXME returns non-optimal data, at best compute it ourselves (pre-TL cache size!) glGetIntegerv(GL_MAX_ELEMENTS_INDICES, &maxIndices); glGetIntegerv(GL_MAX_ELEMENTS_VERTICES, &maxVertices); importer.SetPropertyInteger(AI_CONFIG_PP_SLM_VERTEX_LIMIT, maxVertices); importer.SetPropertyInteger(AI_CONFIG_PP_SLM_TRIANGLE_LIMIT, maxIndices / 3); } #endif // Read the model file to build a scene object LOG_SL(LOG_SECTION_MODEL, L_INFO, "Importing model file: %s", modelFilePath.c_str()); const aiScene* scene; { // ASSIMP spams many SIGFPEs atm in normal & tangent generation ScopedDisableFpuExceptions fe; scene = importer.ReadFile(modelFilePath, ASS_POSTPROCESS_OPTIONS); } if (scene != NULL) { LOG_SL(LOG_SECTION_MODEL, L_INFO, "Processing scene for model: %s (%d meshes / %d materials / %d textures)", modelFilePath.c_str(), scene->mNumMeshes, scene->mNumMaterials, scene->mNumTextures); } else { throw content_error("[AssimpParser] Model Import: " + std::string(importer.GetErrorString())); } S3DModel* model = new S3DModel(); model->name = modelFilePath; model->type = MODELTYPE_ASS; // Load textures FindTextures(model, scene, modelTable, modelPath, modelName); LOG_SL(LOG_SECTION_MODEL, L_INFO, "Loading textures. Tex1: '%s' Tex2: '%s'", model->tex1.c_str(), model->tex2.c_str()); texturehandlerS3O->LoadS3OTexture(model); // Load all pieces in the model LOG_SL(LOG_SECTION_MODEL, L_INFO, "Loading pieces from root node '%s'", scene->mRootNode->mName.data); LoadPiece(model, scene->mRootNode, scene, modelTable); // Update piece hierarchy based on metadata BuildPieceHierarchy(model); CalculateModelProperties(model, modelTable); // Verbose logging of model properties LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->name: %s", model->name.c_str()); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->numobjects: %d", model->numPieces); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->radius: %f", model->radius); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->height: %f", model->height); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->drawRadius: %f", model->drawRadius); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->mins: (%f,%f,%f)", model->mins[0], model->mins[1], model->mins[2]); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->maxs: (%f,%f,%f)", model->maxs[0], model->maxs[1], model->maxs[2]); LOG_SL(LOG_SECTION_MODEL, L_INFO, "Model %s Imported.", model->name.c_str()); return model; }
void CAssParser::LoadPieceTransformations( SAssPiece* piece, const S3DModel* model, const aiNode* pieceNode, const LuaTable& pieceTable ) { aiVector3D aiScaleVec, aiTransVec; aiQuaternion aiRotateQuat; // process transforms pieceNode->mTransformation.Decompose(aiScaleVec, aiRotateQuat, aiTransVec); // metadata-scaling piece->scales = pieceTable.GetFloat3("scale", aiVectorToFloat3(aiScaleVec)); piece->scales.x = pieceTable.GetFloat("scalex", piece->scales.x); piece->scales.y = pieceTable.GetFloat("scaley", piece->scales.y); piece->scales.z = pieceTable.GetFloat("scalez", piece->scales.z); if (piece->scales.x != piece->scales.y || piece->scales.y != piece->scales.z) { // LOG_SL(LOG_SECTION_MODEL, L_WARNING, "Spring doesn't support non-uniform scaling"); piece->scales.y = piece->scales.x; piece->scales.z = piece->scales.x; } // metadata-translation piece->offset = pieceTable.GetFloat3("offset", aiVectorToFloat3(aiTransVec)); piece->offset.x = pieceTable.GetFloat("offsetx", piece->offset.x); piece->offset.y = pieceTable.GetFloat("offsety", piece->offset.y); piece->offset.z = pieceTable.GetFloat("offsetz", piece->offset.z); // metadata-rotation // NOTE: // these rotations are "pre-scripting" but "post-modelling" // together with the (baked) aiRotateQuad they determine the // model's pose *before* any animations execute // // float3 rotAngles = pieceTable.GetFloat3("rotate", aiQuaternionToRadianAngles(aiRotateQuat) * RADTODEG); float3 pieceRotAngles = pieceTable.GetFloat3("rotate", ZeroVector); pieceRotAngles.x = pieceTable.GetFloat("rotatex", pieceRotAngles.x); pieceRotAngles.y = pieceTable.GetFloat("rotatey", pieceRotAngles.y); pieceRotAngles.z = pieceTable.GetFloat("rotatez", pieceRotAngles.z); pieceRotAngles *= DEGTORAD; LOG_SL(LOG_SECTION_PIECE, L_INFO, "(%d:%s) Assimp offset (%f,%f,%f), rotate (%f,%f,%f,%f), scale (%f,%f,%f)", model->numPieces, piece->name.c_str(), aiTransVec.x, aiTransVec.y, aiTransVec.z, aiRotateQuat.w, aiRotateQuat.x, aiRotateQuat.y, aiRotateQuat.z, aiScaleVec.x, aiScaleVec.y, aiScaleVec.z ); LOG_SL(LOG_SECTION_PIECE, L_INFO, "(%d:%s) Relative offset (%f,%f,%f), rotate (%f,%f,%f), scale (%f,%f,%f)", model->numPieces, piece->name.c_str(), piece->offset.x, piece->offset.y, piece->offset.z, pieceRotAngles.x, pieceRotAngles.y, pieceRotAngles.z, piece->scales.x, piece->scales.y, piece->scales.z ); // NOTE: // at least collada (.dae) files generated by Blender represent // a coordinate-system that differs from the "standard" formats // (3DO, S3O, ...) for which existing tools at least have prior // knowledge of Spring's expectations --> let the user override // the ROOT rotational transform and the rotation-axis mapping // used by animation scripts (but re-modelling/re-exporting is // always preferred!) even though AssImp should convert models // to its own system which matches that of Spring // // .dae : x=Rgt, y=-Fwd, z= Up, as=(-1, -1, 1), am=AXIS_XZY (if Z_UP) // .dae : x=Rgt, y=-Fwd, z= Up, as=(-1, -1, 1), am=AXIS_XZY (if Y_UP) [!?] // .blend: ???? piece->bakedRotMatrix = aiMatrixToMatrix(aiMatrix4x4t<float>(aiRotateQuat.GetMatrix())); if (piece == model->GetRootPiece()) { const float3 xaxis = pieceTable.GetFloat3("xaxis", piece->bakedRotMatrix.GetX()); const float3 yaxis = pieceTable.GetFloat3("yaxis", piece->bakedRotMatrix.GetY()); const float3 zaxis = pieceTable.GetFloat3("zaxis", piece->bakedRotMatrix.GetZ()); if (math::fabs(xaxis.SqLength() - yaxis.SqLength()) < 0.01f && math::fabs(yaxis.SqLength() - zaxis.SqLength()) < 0.01f) { piece->bakedRotMatrix = CMatrix44f(ZeroVector, xaxis, yaxis, zaxis); } } piece->rotAxisSigns = pieceTable.GetFloat3("rotAxisSigns", float3(-OnesVector)); piece->axisMapType = AxisMappingType(pieceTable.GetInt("rotAxisMap", AXIS_MAPPING_XYZ)); // construct 'baked' part of the piece-space matrix // AssImp order is translate * rotate * scale * v; // we leave the translation and scale parts out and // put those in <offset> and <scales> --> transform // is just R instead of T * R * S // // note: for all non-AssImp models this is identity! // piece->ComposeRotation(piece->bakedRotMatrix, pieceRotAngles); piece->SetHasIdentityRotation(piece->bakedRotMatrix.IsIdentity() == 0); assert(piece->bakedRotMatrix.IsOrthoNormal() == 0); }
SAssPiece* CAssParser::LoadPiece(SAssModel* model, aiNode* node, const LuaTable& metaTable) { //! Create new piece ++model->numPieces; SAssPiece* piece = new SAssPiece; piece->type = MODELTYPE_OTHER; piece->node = node; piece->model = model; piece->isEmpty = (node->mNumMeshes == 0); if (node->mParent) { piece->name = std::string(node->mName.data); } else { //FIXME is this really smart? piece->name = "root"; //! The real model root } //! find a new name if none given or if a piece with the same name already exists if (piece->name.empty()) { piece->name = "piece"; } ModelPieceMap::const_iterator it = model->pieces.find(piece->name); if (it != model->pieces.end()) { char buf[64]; int i = 0; while (it != model->pieces.end()) { SNPRINTF(buf, 64, "%s%02i", piece->name.c_str(), i++); it = model->pieces.find(buf); } piece->name = buf; } LOG_S(LOG_SECTION_PIECE, "Converting node '%s' to piece '%s' (%d meshes).", node->mName.data, piece->name.c_str(), node->mNumMeshes); //! Load additional piece properties from metadata const LuaTable& pieceTable = metaTable.SubTable("pieces").SubTable(piece->name); if (pieceTable.IsValid()) { LOG_S(LOG_SECTION_PIECE, "Found metadata for piece '%s'", piece->name.c_str()); } //! Load transforms LoadPieceTransformations(piece, pieceTable); //! Update piece min/max extents for (unsigned meshListIndex = 0; meshListIndex < node->mNumMeshes; meshListIndex++) { unsigned int meshIndex = node->mMeshes[meshListIndex]; SAssModel::MinMax& minmax = model->mesh_minmax[meshIndex]; piece->mins.x = std::min(piece->mins.x, minmax.mins.x); piece->mins.y = std::min(piece->mins.y, minmax.mins.y); piece->mins.z = std::min(piece->mins.z, minmax.mins.z); piece->maxs.x = std::max(piece->maxs.x, minmax.maxs.x); piece->maxs.y = std::max(piece->maxs.y, minmax.maxs.y); piece->maxs.z = std::max(piece->maxs.z, minmax.maxs.z); } //! Check if piece is special (ie, used to set Spring model properties) if (strcmp(node->mName.data, "SpringHeight") == 0) { //! Set the model height to this nodes Z value if (!metaTable.KeyExists("height")) { model->height = piece->offset.z; LOG_S(LOG_SECTION_MODEL, "Model height of %f set by special node 'SpringHeight'", model->height); } --model->numPieces; delete piece; return NULL; } if (strcmp(node->mName.data, "SpringRadius") == 0) { if (!metaTable.KeyExists("midpos")) { model->relMidPos = float3(piece->offset.x, piece->offset.z, piece->offset.y); //! Y and Z are swapped because this piece isn't rotated LOG_S(LOG_SECTION_MODEL, "Model midpos of (%f,%f,%f) set by special node 'SpringRadius'", model->relMidPos.x, model->relMidPos.y, model->relMidPos.z); } if (!metaTable.KeyExists("radius")) { if (piece->maxs.x <= 0.00001f) { model->radius = piece->scale.x; //! the blender import script only sets the scale property } else { model->radius = piece->maxs.x; //! use the transformed mesh extents } LOG_S(LOG_SECTION_MODEL, "Model radius of %f set by special node 'SpringRadius'", model->radius); } --model->numPieces; delete piece; return NULL; } //! Get vertex data from node meshes for (unsigned meshListIndex = 0; meshListIndex < node->mNumMeshes; ++meshListIndex) { unsigned int meshIndex = node->mMeshes[meshListIndex]; LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Fetching mesh %d from scene", meshIndex); aiMesh* mesh = model->scene->mMeshes[meshIndex]; std::vector<unsigned> mesh_vertex_mapping; //! extract vertex data LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Processing vertices for mesh %d (%d vertices)", meshIndex, mesh->mNumVertices); LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Normals: %s Tangents/Bitangents: %s TexCoords: %s", (mesh->HasNormals() ? "Y" : "N"), (mesh->HasTangentsAndBitangents() ? "Y" : "N"), (mesh->HasTextureCoords(0) ? "Y" : "N")); // FIXME add piece->vertices.reserve() for (unsigned vertexIndex= 0; vertexIndex < mesh->mNumVertices; ++vertexIndex) { SAssVertex vertex; //! vertex coordinates //LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Fetching vertex %d from mesh", vertexIndex); const aiVector3D& aiVertex = mesh->mVertices[vertexIndex]; vertex.pos.x = aiVertex.x; vertex.pos.y = aiVertex.y; vertex.pos.z = aiVertex.z; //LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "vertex position %d: %f %f %f", vertexIndex, vertex.pos.x, vertex.pos.y, vertex.pos.z); //! vertex normal LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Fetching normal for vertex %d", vertexIndex); const aiVector3D& aiNormal = mesh->mNormals[vertexIndex]; vertex.hasNormal = !IS_QNAN(aiNormal); if (vertex.hasNormal) { vertex.normal.x = aiNormal.x; vertex.normal.y = aiNormal.y; vertex.normal.z = aiNormal.z; //LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "vertex normal %d: %f %f %f",vertexIndex, vertex.normal.x, vertex.normal.y,vertex.normal.z); } //! vertex tangent, x is positive in texture axis if (mesh->HasTangentsAndBitangents()) { LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Fetching tangent for vertex %d", vertexIndex ); const aiVector3D& aiTangent = mesh->mTangents[vertexIndex]; const aiVector3D& aiBitangent = mesh->mBitangents[vertexIndex]; vertex.hasTangent = !IS_QNAN(aiBitangent) && !IS_QNAN(aiTangent); const float3 tangent(aiTangent.x, aiTangent.y, aiTangent.z); //LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "vertex tangent %d: %f %f %f",vertexIndex, tangent.x, tangent.y,tangent.z); piece->sTangents.push_back(tangent); const float3 bitangent(aiBitangent.x, aiBitangent.y, aiBitangent.z); //LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "vertex bitangent %d: %f %f %f",vertexIndex, bitangent.x, bitangent.y,bitangent.z); piece->tTangents.push_back(bitangent); } //! vertex texcoords if (mesh->HasTextureCoords(0)) { vertex.textureX = mesh->mTextureCoords[0][vertexIndex].x; vertex.textureY = mesh->mTextureCoords[0][vertexIndex].y; //LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "vertex texcoords %d: %f %f", vertexIndex, vertex.textureX, vertex.textureY); } mesh_vertex_mapping.push_back(piece->vertices.size()); piece->vertices.push_back(vertex); } //! extract face data // FIXME add piece->vertexDrawOrder.reserve() LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Processing faces for mesh %d (%d faces)", meshIndex, mesh->mNumFaces); for (unsigned faceIndex = 0; faceIndex < mesh->mNumFaces; ++faceIndex) { const aiFace& face = mesh->mFaces[faceIndex]; //! get the vertex belonging to the mesh for (unsigned vertexListID = 0; vertexListID < face.mNumIndices; ++vertexListID) { unsigned int vertexID = mesh_vertex_mapping[face.mIndices[vertexListID]]; //LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "face %d vertex %d", faceIndex, vertexID); piece->vertexDrawOrder.push_back(vertexID); } } } //! collision volume for piece (not sure about these coords) // FIXME add metatable tags for this!!!! const float3 cvScales = piece->maxs - piece->mins; const float3 cvOffset = (piece->maxs - piece->offset) + (piece->mins - piece->offset); //const float3 cvOffset(piece->offset.x, piece->offset.y, piece->offset.z); piece->colvol = new CollisionVolume("box", cvScales, cvOffset, CollisionVolume::COLVOL_HITTEST_CONT); //! Get parent name from metadata or model if (pieceTable.KeyExists("parent")) { piece->parentName = pieceTable.GetString("parent", ""); } else if (node->mParent) { if (node->mParent->mParent) { piece->parentName = std::string(node->mParent->mName.data); } else { //! my parent is the root, which gets renamed piece->parentName = "root"; } } else { piece->parentName = ""; } LOG_S(LOG_SECTION_PIECE, "Loaded model piece: %s with %d meshes", piece->name.c_str(), node->mNumMeshes); //! Verbose logging of piece properties LOG_S(LOG_SECTION_PIECE, "piece->name: %s", piece->name.c_str()); LOG_S(LOG_SECTION_PIECE, "piece->parent: %s", piece->parentName.c_str()); //! Recursively process all child pieces for (unsigned int i = 0; i < node->mNumChildren; ++i) { LoadPiece(model, node->mChildren[i], metaTable); } model->pieces[piece->name] = piece; return piece; }
void CAssParser::LoadPieceGeometry(SAssPiece* piece, const aiNode* pieceNode, const aiScene* scene) { // Get vertex data from node meshes for (unsigned meshListIndex = 0; meshListIndex < pieceNode->mNumMeshes; ++meshListIndex) { const unsigned int meshIndex = pieceNode->mMeshes[meshListIndex]; const aiMesh* mesh = scene->mMeshes[meshIndex]; LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Fetching mesh %d from scene", meshIndex); LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Processing vertices for mesh %d (%d vertices)", meshIndex, mesh->mNumVertices); LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Normals: %s Tangents/Bitangents: %s TexCoords: %s", (mesh->HasNormals() ? "Y" : "N"), (mesh->HasTangentsAndBitangents() ? "Y" : "N"), (mesh->HasTextureCoords(0) ? "Y" : "N")); piece->vertices.reserve(piece->vertices.size() + mesh->mNumVertices); piece->vertexDrawIndices.reserve(piece->vertexDrawIndices.size() + mesh->mNumFaces * 3); std::vector<unsigned> mesh_vertex_mapping; // extract vertex data per mesh for (unsigned vertexIndex = 0; vertexIndex < mesh->mNumVertices; ++vertexIndex) { const aiVector3D& aiVertex = mesh->mVertices[vertexIndex]; SAssVertex vertex; // vertex coordinates vertex.pos = aiVectorToFloat3(aiVertex); // update piece min/max extents piece->mins = float3::min(piece->mins, vertex.pos); piece->maxs = float3::max(piece->maxs, vertex.pos); // vertex normal LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Fetching normal for vertex %d", vertexIndex); const aiVector3D& aiNormal = mesh->mNormals[vertexIndex]; if (!IS_QNAN(aiNormal)) { vertex.normal = aiVectorToFloat3(aiNormal); } // vertex tangent, x is positive in texture axis if (mesh->HasTangentsAndBitangents()) { LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Fetching tangent for vertex %d", vertexIndex); const aiVector3D& aiTangent = mesh->mTangents[vertexIndex]; const aiVector3D& aiBitangent = mesh->mBitangents[vertexIndex]; vertex.sTangent = aiVectorToFloat3(aiTangent); vertex.tTangent = aiVectorToFloat3(aiBitangent); } // vertex tex-coords per channel for (unsigned int uvChanIndex = 0; uvChanIndex < NUM_MODEL_UVCHANNS; uvChanIndex++) { if (!mesh->HasTextureCoords(uvChanIndex)) break; piece->SetNumTexCoorChannels(uvChanIndex + 1); vertex.texCoords[uvChanIndex].x = mesh->mTextureCoords[uvChanIndex][vertexIndex].x; vertex.texCoords[uvChanIndex].y = mesh->mTextureCoords[uvChanIndex][vertexIndex].y; } mesh_vertex_mapping.push_back(piece->vertices.size()); piece->vertices.push_back(vertex); } // extract face data LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Processing faces for mesh %d (%d faces)", meshIndex, mesh->mNumFaces); /* * since aiProcess_SortByPType is being used, * we're sure we'll get only 1 type here, * so combination check isn't needed, also * anything more complex than triangles is * being split thanks to aiProcess_Triangulate */ for (unsigned faceIndex = 0; faceIndex < mesh->mNumFaces; ++faceIndex) { const aiFace& face = mesh->mFaces[faceIndex]; // some models contain lines (mNumIndices == 2) which // we cannot render and they would need a 2nd drawcall) if (face.mNumIndices != 3) continue; for (unsigned vertexListID = 0; vertexListID < face.mNumIndices; ++vertexListID) { const unsigned int vertexFaceIdx = face.mIndices[vertexListID]; const unsigned int vertexDrawIdx = mesh_vertex_mapping[vertexFaceIdx]; piece->vertexDrawIndices.push_back(vertexDrawIdx); } } } piece->SetHasGeometryData(!piece->vertices.empty()); }
void SAssPiece::DrawForList() const { if (isEmpty) { return; } LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Compiling piece %s", name.c_str()); //! Add GL commands to the pieces displaylist const SAssVertex* sAssV = &vertices[0]; //! pass the tangents as 3D texture coordinates //! (array elements are float3's, which are 12 //! bytes in size and each represent a single //! xyz triple) //! TODO: test if we have this many texunits //! (if not, could only send the s-tangents)? if (!sTangents.empty()) { glClientActiveTexture(GL_TEXTURE5); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(3, GL_FLOAT, sizeof(float3), &sTangents[0].x); } if (!tTangents.empty()) { glClientActiveTexture(GL_TEXTURE6); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(3, GL_FLOAT, sizeof(float3), &tTangents[0].x); } glClientActiveTexture(GL_TEXTURE0); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(2, GL_FLOAT, sizeof(SAssVertex), &sAssV->textureX); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, sizeof(SAssVertex), &sAssV->pos.x); glEnableClientState(GL_NORMAL_ARRAY); glNormalPointer(GL_FLOAT, sizeof(SAssVertex), &sAssV->normal.x); /* * since aiProcess_SortByPType is being used, * we're sure we'll get only 1 type here, * so combination check isn't needed, also * anything more complex than triangles is * being split thanks to aiProcess_Triangulate */ glDrawElements(GL_TRIANGLES, vertexDrawOrder.size(), GL_UNSIGNED_INT, &vertexDrawOrder[0]); if (!sTangents.empty()) { glClientActiveTexture(GL_TEXTURE6); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } if (!tTangents.empty()) { glClientActiveTexture(GL_TEXTURE5); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } glClientActiveTexture(GL_TEXTURE0); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); LOG_SL(LOG_SECTION_PIECE, L_DEBUG, "Completed compiling piece %s", name.c_str()); }
S3DModel* CAssParser::Load(const std::string& modelFilePath) { LOG_S(LOG_SECTION_MODEL, "Loading model: %s", modelFilePath.c_str() ); const std::string modelPath = FileSystem::GetDirectory(modelFilePath); const std::string modelName = FileSystem::GetBasename(modelFilePath); //! LOAD METADATA //! Load the lua metafile. This contains properties unique to Spring models and must return a table std::string metaFileName = modelFilePath + ".lua"; if (!CFileHandler::FileExists(metaFileName, SPRING_VFS_ZIP)) { //! Try again without the model file extension metaFileName = modelPath + '/' + modelName + ".lua"; } LuaParser metaFileParser(metaFileName, SPRING_VFS_MOD_BASE, SPRING_VFS_ZIP); if (!CFileHandler::FileExists(metaFileName, SPRING_VFS_ZIP)) { LOG_S(LOG_SECTION_MODEL, "No meta-file '%s'. Using defaults.", metaFileName.c_str()); } else if (!metaFileParser.Execute()) { LOG_SL(LOG_SECTION_MODEL, L_ERROR, "'%s': %s. Using defaults.", metaFileName.c_str(), metaFileParser.GetErrorLog().c_str()); } //! Get the (root-level) model table const LuaTable& metaTable = metaFileParser.GetRoot(); if (metaTable.IsValid()) { LOG_S(LOG_SECTION_MODEL, "Found valid model metadata in '%s'", metaFileName.c_str()); } //! LOAD MODEL DATA //! Create a model importer instance Assimp::Importer importer; //! Create a logger for debugging model loading issues Assimp::DefaultLogger::create("",Assimp::Logger::VERBOSE); const unsigned int severity = Assimp::Logger::Debugging|Assimp::Logger::Info|Assimp::Logger::Err|Assimp::Logger::Warn; Assimp::DefaultLogger::get()->attachStream( new AssLogStream(), severity ); //! Give the importer an IO class that handles Spring's VFS importer.SetIOHandler( new AssVFSSystem() ); //! Speed-up processing by skipping things we don't need importer.SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS, aiComponent_CAMERAS|aiComponent_LIGHTS|aiComponent_TEXTURES|aiComponent_ANIMATIONS); #ifndef BITMAP_NO_OPENGL //! Optimize VBO-Mesh sizes/ranges GLint maxIndices = 1024; GLint maxVertices = 1024; glGetIntegerv(GL_MAX_ELEMENTS_INDICES, &maxIndices); glGetIntegerv(GL_MAX_ELEMENTS_VERTICES, &maxVertices); //FIXME returns not optimal data, at best compute it ourself! (pre-TL cache size!) importer.SetPropertyInteger(AI_CONFIG_PP_SLM_VERTEX_LIMIT, maxVertices); importer.SetPropertyInteger(AI_CONFIG_PP_SLM_TRIANGLE_LIMIT, maxIndices/3); #endif //! Read the model file to build a scene object LOG_S(LOG_SECTION_MODEL, "Importing model file: %s", modelFilePath.c_str() ); const aiScene* scene = importer.ReadFile( modelFilePath, ASS_POSTPROCESS_OPTIONS ); if (scene != NULL) { LOG_S(LOG_SECTION_MODEL, "Processing scene for model: %s (%d meshes / %d materials / %d textures)", modelFilePath.c_str(), scene->mNumMeshes, scene->mNumMaterials, scene->mNumTextures ); } else { LOG_SL(LOG_SECTION_MODEL, L_ERROR, "Model Import: %s", importer.GetErrorString()); } SAssModel* model = new SAssModel; model->name = modelFilePath; model->type = MODELTYPE_ASS; model->scene = scene; //model->meta = &metaTable; //! Gather per mesh info CalculatePerMeshMinMax(model); //! Assign textures //! The S3O texture handler uses two textures. //! The first contains diffuse color (RGB) and teamcolor (A) //! The second contains glow (R), reflectivity (G) and 1-bit Alpha (A). if (metaTable.KeyExists("tex1")) { model->tex1 = metaTable.GetString("tex1", "default.png"); } else { //! Search for a texture std::vector<std::string> files = CFileHandler::FindFiles("unittextures/", modelName + ".*"); for(std::vector<std::string>::iterator fi = files.begin(); fi != files.end(); ++fi) { model->tex1 = FileSystem::GetFilename(*fi); break; //! there can be only one! } } if (metaTable.KeyExists("tex2")) { model->tex2 = metaTable.GetString("tex2", ""); } else { //! Search for a texture std::vector<std::string> files = CFileHandler::FindFiles("unittextures/", modelName + "2.*"); for(std::vector<std::string>::iterator fi = files.begin(); fi != files.end(); ++fi) { model->tex2 = FileSystem::GetFilename(*fi); break; //! there can be only one! } } model->flipTexY = metaTable.GetBool("fliptextures", true); //! Flip texture upside down model->invertTexAlpha = metaTable.GetBool("invertteamcolor", true); //! Reverse teamcolor levels //! Load textures LOG_S(LOG_SECTION_MODEL, "Loading textures. Tex1: '%s' Tex2: '%s'", model->tex1.c_str(), model->tex2.c_str()); texturehandlerS3O->LoadS3OTexture(model); //! Load all pieces in the model LOG_S(LOG_SECTION_MODEL, "Loading pieces from root node '%s'", scene->mRootNode->mName.data); LoadPiece(model, scene->mRootNode, metaTable); //! Update piece hierarchy based on metadata BuildPieceHierarchy( model ); //! Simplified dimensions used for rough calculations model->radius = metaTable.GetFloat("radius", model->radius); model->height = metaTable.GetFloat("height", model->height); model->relMidPos = metaTable.GetFloat3("midpos", model->relMidPos); model->mins = metaTable.GetFloat3("mins", model->mins); model->maxs = metaTable.GetFloat3("maxs", model->maxs); //! Calculate model dimensions if not set if (!metaTable.KeyExists("mins") || !metaTable.KeyExists("maxs")) CalculateMinMax( model->rootPiece ); if (model->radius < 0.0001f) CalculateRadius( model ); if (model->height < 0.0001f) CalculateHeight( model ); //! Verbose logging of model properties LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->name: %s", model->name.c_str()); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->numobjects: %d", model->numPieces); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->radius: %f", model->radius); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->height: %f", model->height); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->mins: (%f,%f,%f)", model->mins[0], model->mins[1], model->mins[2]); LOG_SL(LOG_SECTION_MODEL, L_DEBUG, "model->maxs: (%f,%f,%f)", model->maxs[0], model->maxs[1], model->maxs[2]); LOG_S(LOG_SECTION_MODEL, "Model %s Imported.", model->name.c_str()); return model; }
void CArchiveScanner::ScanArchive(const std::string& fullName, bool doChecksum) { const std::string fn = FileSystem::GetFilename(fullName); const std::string fpath = FileSystem::GetDirectory(fullName); const std::string lcfn = StringToLower(fn); // Cache variables std::map<std::string, ArchiveInfo>::iterator aii; bool cached = false; // Stat file struct stat info = {0}; int statfailed = stat(fullName.c_str(), &info); // If stat fails, assume the archive is not broken nor cached if (!statfailed) { // Determine whether this archive has earlier be found to be broken std::map<std::string, BrokenArchive>::iterator bai = brokenArchives.find(lcfn); if (bai != brokenArchives.end()) { if ((unsigned)info.st_mtime == bai->second.modified && fpath == bai->second.path) { bai->second.updated = true; return; } } // Determine whether to rely on the cached info or not if ((aii = archiveInfos.find(lcfn)) != archiveInfos.end()) { // This archive may have been obsoleted, do not process it if so if (aii->second.replaced.length() > 0) { return; } if ((unsigned)info.st_mtime == aii->second.modified && fpath == aii->second.path) { cached = true; aii->second.updated = true; } // If we are here, we could have invalid info in the cache // Force a reread if it is a directory archive (.sdd), as // st_mtime only reflects changes to the directory itself, // not the contents. if (!cached) { archiveInfos.erase(aii); } } } // Time to parse the info we are interested in if (cached) { // If cached is true, aii will point to the archive if (doChecksum && (aii->second.checksum == 0)) aii->second.checksum = GetCRC(fullName); } else { IArchive* ar = archiveLoader.OpenArchive(fullName); if (!ar || !ar->IsOpen()) { LOG("Unable to open archive: %s", fullName.c_str()); return; } ArchiveInfo ai; std::string error; std::string mapfile; const bool hasModinfo = ar->FileExists("modinfo.lua"); const bool hasMapinfo = ar->FileExists("mapinfo.lua"); // check for smf/sm3 and if the uncompression of important files is too costy for (unsigned fid = 0; fid != ar->NumFiles(); ++fid) { std::string name; int size; ar->FileInfo(fid, name, size); const std::string lowerName = StringToLower(name); const std::string ext = FileSystem::GetExtension(lowerName); if ((ext == "smf") || (ext == "sm3")) { mapfile = name; } const unsigned char metaFileClass = GetMetaFileClass(lowerName); if ((metaFileClass != 0) && !(ar->HasLowReadingCost(fid))) { // is a meta-file and not cheap to read if (metaFileClass == 1) { // 1st class error = "Unpacking/reading cost for meta file " + name + " is too high, please repack the archive (make sure to use a non-solid algorithm, if applicable)"; break; } else if (metaFileClass == 2) { // 2nd class LOG_SL(LOG_SECTION_ARCHIVESCANNER, L_WARNING, "Archive %s: The cost for reading a 2nd-class meta-file is too high: %s", fullName.c_str(), name.c_str()); } } } /* if (!error.empty()) { // we already have an error, no further evaluation required } */ if (hasMapinfo || !mapfile.empty()) { // it is a map if (hasMapinfo) { ScanArchiveLua(ar, "mapinfo.lua", ai, error); } else if (hasModinfo) { // backwards-compat for modinfo.lua in maps ScanArchiveLua(ar, "modinfo.lua", ai, error); } if (ai.archiveData.GetName().empty()) { // FIXME The name will never be empty, if version is set (see HACK in ArchiveData) ai.archiveData.SetInfoItemValueString("name_pure", FileSystem::GetBasename(mapfile)); ai.archiveData.SetInfoItemValueString("name", FileSystem::GetBasename(mapfile)); } if (ai.archiveData.GetMapFile().empty()) { ai.archiveData.SetInfoItemValueString("mapfile", mapfile); } AddDependency(ai.archiveData.GetDependencies(), "Map Helper v1"); ai.archiveData.SetInfoItemValueInteger("modType", modtype::map); LOG_S(LOG_SECTION_ARCHIVESCANNER, "Found new map: %s", ai.archiveData.GetNameVersioned().c_str()); } else if (hasModinfo) { // it is a mod ScanArchiveLua(ar, "modinfo.lua", ai, error); if (ai.archiveData.GetModType() == modtype::primary) { AddDependency(ai.archiveData.GetDependencies(), "Spring content v1"); } LOG_S(LOG_SECTION_ARCHIVESCANNER, "Found new game: %s", ai.archiveData.GetNameVersioned().c_str()); } else { // neither a map nor a mod: error error = "missing modinfo.lua/mapinfo.lua"; } delete ar; if (!error.empty()) { // for some reason, the archive is marked as broken LOG_L(L_WARNING, "Failed to scan %s (%s)", fullName.c_str(), error.c_str()); // record it as broken, so we don't need to look inside everytime BrokenArchive ba; ba.path = fpath; ba.modified = info.st_mtime; ba.updated = true; ba.problem = error; brokenArchives[lcfn] = ba; return; } ai.path = fpath; ai.modified = info.st_mtime; ai.origName = fn; ai.updated = true; // Optionally calculate a checksum for the file // To prevent reading all files in all directory (.sdd) archives // every time this function is called, directory archive checksums // are calculated on the fly. if (doChecksum) { ai.checksum = GetCRC(fullName); } else { ai.checksum = 0; } archiveInfos[lcfn] = ai; } }