void CAssParser::CalculatePerMeshMinMax(SAssModel* model) { const aiScene* scene = model->scene; model->mesh_minmax.resize(scene->mNumMeshes); for (size_t i = 0; i < scene->mNumMeshes; i++) { const aiMesh& mesh = *scene->mMeshes[i]; SAssModel::MinMax& minmax = model->mesh_minmax[i]; minmax.mins = DEF_MIN_SIZE; minmax.maxs = DEF_MAX_SIZE; for (size_t vertexIndex= 0; vertexIndex < mesh.mNumVertices; vertexIndex++) { const aiVector3D& aiVertex = mesh.mVertices[vertexIndex]; minmax.mins = std::min(minmax.mins, aiVectorToFloat3(aiVertex)); minmax.maxs = std::max(minmax.maxs, aiVectorToFloat3(aiVertex)); } if (minmax.mins == DEF_MIN_SIZE) minmax.mins = ZeroVector; if (minmax.maxs == DEF_MAX_SIZE) minmax.maxs = ZeroVector; } }
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 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->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; }