#include #include #include #include #include #include #include #include "Rune Engine/Rune/Rune.h" #include "Rune Engine/Rune/Rune Engine NULL.h" #include "Rune Engine/Rune/Scene/Base/RuEntityBase.h" #include "Rune Engine/Rune/Collision/RuType_Collision.h" #include "Rune Engine/Rune/Engine/Geometry/RuMeshBase.h" namespace { struct EntitySummary { CRuEntity *entity = NULL; std::string name; std::string type; bool hasCollisionObject = false; std::string collisionType; bool collisionOptionEnabled = false; bool collisionOptionPathReachable = false; int meshCount = 0; int meshCollisionStructureCount = 0; int serializedCollisionTriangleCount = 0; int completeSerializedCollisionTriangleCount = 0; std::vector meshCollisionTypes; }; void Visit(CRuEntity *entity, bool parentCollisionPathReachable, std::vector &entities) { if(!entity) return; entity->UpdateTransformation(); EntitySummary summary; summary.entity = entity; summary.name = entity->GetObjectName() ? entity->GetObjectName() : ""; summary.type = entity->GetType().GetTypeName(); summary.collisionOptionEnabled = entity->GetOption(ruENTITYOPTION_COLLISION) != 0; summary.collisionOptionPathReachable = parentCollisionPathReachable && summary.collisionOptionEnabled; if(entity->GetCollisionObject()) { summary.hasCollisionObject = true; summary.collisionType = entity->GetCollisionObject()->GetType().GetTypeName(); } if(entity->GetType().IsSubClassOf(IRuEntity_Renderable::Type())) { IRuEntity_Renderable *renderable = static_cast(entity); summary.meshCount = renderable->GetNumMeshes(); for(INT32 i = 0; i < summary.meshCount; ++i) { IRuMesh *mesh = NULL; IRuMaterial *material = NULL; if(renderable->GetMesh(i, &mesh, &material) && mesh && mesh->GetType().IsSubClassOf(CRuMesh::Type())) { IRuCollisionStructure *structure = static_cast(mesh)->GetCollisionStructure(); if(structure) { ++summary.meshCollisionStructureCount; summary.meshCollisionTypes.push_back(structure->GetType().GetTypeName()); } else { summary.meshCollisionTypes.push_back("none(serialized); QueryCollision would build default from render mesh"); } } else { summary.meshCollisionTypes.push_back("unsupported mesh class"); } } } entities.push_back(summary); CRuEntity *child = entity->GetFirstChild(); while(child) { Visit(child, summary.collisionOptionPathReachable, entities); child = child->GetNextSibling(); } } void QueryObject(CRuEntity *entity, IRuCollisionObject *object, CRuCollisionQuery_OBB_TriEnum &query) { CRuMatrix4x4 *originalWorldTransform = query.m_worldTransform; CRuMatrix4x4 *originalInvWorldTransform = query.m_invWorldTransform; query.m_worldTransform = const_cast(&entity->GetWorldTransform()); query.m_invWorldTransform = const_cast(&entity->GetInverseWorldTransform()); object->QueryCollision(&query); query.m_worldTransform = originalWorldTransform; query.m_invWorldTransform = originalInvWorldTransform; } void QuerySerializedDirectOnly(CRuEntity *entity, CRuCollisionQuery_OBB_TriEnum &query) { if(!entity) return; entity->UpdateTransformation(); if(entity->GetCollisionObject()) { QueryObject(entity, entity->GetCollisionObject(), query); return; } if(entity->GetType().IsSubClassOf(CRuModel::Type())) { CRuModel *model = static_cast(entity); for(INT32 i = 0; i < model->GetNumMeshes(); ++i) { IRuMesh *mesh = NULL; IRuMaterial *material = NULL; if(model->GetMesh(i, &mesh, &material) && mesh && mesh->GetType().IsSubClassOf(CRuMesh::Type())) { IRuCollisionStructure *structure = static_cast(mesh)->GetCollisionStructure(); if(structure) { CRuMatrix4x4 *originalWorldTransform = query.m_worldTransform; CRuMatrix4x4 *originalInvWorldTransform = query.m_invWorldTransform; query.m_worldTransform = const_cast(&entity->GetWorldTransform()); query.m_invWorldTransform = const_cast(&entity->GetInverseWorldTransform()); structure->QueryCollision(&query); query.m_worldTransform = originalWorldTransform; query.m_invWorldTransform = originalInvWorldTransform; } } } return; } } void QuerySerializedOnly(CRuEntity *entity, CRuCollisionQuery_OBB_TriEnum &query, bool respectCollisionOptions) { if(!entity) return; entity->UpdateTransformation(); if(respectCollisionOptions && entity->GetOption(ruENTITYOPTION_COLLISION) == 0) return; QuerySerializedDirectOnly(entity, query); if(entity->GetCollisionObject() || entity->GetType().IsSubClassOf(CRuModel::Type())) return; CRuEntity *child = entity->GetFirstChild(); while(child) { QuerySerializedOnly(child, query, respectCollisionOptions); child = child->GetNextSibling(); } } bool WriteReport(const std::string &outputPath, const std::string &resourcePath, const std::string &collisionMode, CRuEntity *root, std::vector &entities) { const bool respectCollisionOptions = collisionMode == "respect-options"; // A broad source-local box asks the native collision API to enumerate all // source collision triangles. The selected query also invokes the actual // engine hierarchy query with the requested entity-option semantics. CRuCollisionQuery_OBB_TriEnum query; query.m_ignoreCollisionFlag = respectCollisionOptions ? FALSE : TRUE; query.m_exclusionMask = 0x00000000; query.m_inclusionMask = 0x00000000; query.m_colOBB = CRuOBB(CRuAABB( CRuVector3(-10000.0f, -10000.0f, -10000.0f), CRuVector3(10000.0f, 10000.0f, 10000.0f))); CRuCollisionQuery_OBB_TriEnum serializedQuery; serializedQuery.m_ignoreCollisionFlag = respectCollisionOptions ? FALSE : TRUE; serializedQuery.m_colOBB = query.m_colOBB; QuerySerializedOnly(root, serializedQuery, respectCollisionOptions); CRuCollisionQuery_OBB_TriEnum completeSerializedQuery; completeSerializedQuery.m_ignoreCollisionFlag = TRUE; completeSerializedQuery.m_colOBB = query.m_colOBB; if(respectCollisionOptions) QuerySerializedOnly(root, completeSerializedQuery, false); CRuCollisionQuery_OBB_TriEnum effectiveSerializedQuery; effectiveSerializedQuery.m_ignoreCollisionFlag = FALSE; effectiveSerializedQuery.m_colOBB = query.m_colOBB; if(!respectCollisionOptions) QuerySerializedOnly(root, effectiveSerializedQuery, true); root->QueryCollision(&query); std::ofstream out(outputPath.c_str(), std::ios::out | std::ios::trunc); if(!out) return false; const INT32 vertexCount = query.m_colTriVerts->Count(); const INT32 serializedVertexCount = serializedQuery.m_colTriVerts->Count(); const INT32 completeSerializedVertexCount = respectCollisionOptions ? completeSerializedQuery.m_colTriVerts->Count() : serializedVertexCount; const INT32 effectiveSerializedVertexCount = respectCollisionOptions ? serializedVertexCount : effectiveSerializedQuery.m_colTriVerts->Count(); for(auto &entity : entities) { if(entity.entity) { CRuCollisionQuery_OBB_TriEnum perEntityQuery; perEntityQuery.m_ignoreCollisionFlag = TRUE; perEntityQuery.m_colOBB = query.m_colOBB; QuerySerializedDirectOnly(entity.entity, perEntityQuery); entity.completeSerializedCollisionTriangleCount = perEntityQuery.m_colTriVerts->Count() / 3; if(entity.collisionOptionPathReachable) { CRuCollisionQuery_OBB_TriEnum effectivePerEntityQuery; effectivePerEntityQuery.m_ignoreCollisionFlag = FALSE; effectivePerEntityQuery.m_colOBB = query.m_colOBB; QuerySerializedDirectOnly(entity.entity, effectivePerEntityQuery); entity.serializedCollisionTriangleCount = effectivePerEntityQuery.m_colTriVerts->Count() / 3; } } } CRuVector3 minima(0.0f, 0.0f, 0.0f); CRuVector3 maxima(0.0f, 0.0f, 0.0f); if(serializedVertexCount > 0) { minima = maxima = (*serializedQuery.m_colTriVerts)[0]; for(INT32 i = 1; i < serializedVertexCount; ++i) { const CRuVector3 &v = (*serializedQuery.m_colTriVerts)[i]; minima.x = min(minima.x, v.x); minima.y = min(minima.y, v.y); minima.z = min(minima.z, v.z); maxima.x = max(maxima.x, v.x); maxima.y = max(maxima.y, v.y); maxima.z = max(maxima.z, v.z); } } out << std::setprecision(9); out << "{\n \"schemaVersion\": 2,\n"; out << " \"sourceModel\": \"" << resourcePath << "\",\n"; out << " \"collisionMode\": \"" << collisionMode << "\",\n"; out << " \"query\": \"CRuEntity::QueryCollision(CRuCollisionQuery_OBB_TriEnum), source-local OBB +/-10000, " << (respectCollisionOptions ? "ignoreCollisionFlag=false; ruENTITYOPTION_COLLISION gates respected" : "ignoreCollisionFlag=true; complete collision-option audit") << "\",\n"; out << " \"geometryOutput\": \"" << outputPath << ".obj\",\n"; out << " \"triangleFlagOutput\": \"" << outputPath << ".flags.bin\",\n"; out << " \"queryMasks\": {\"exclusionMask\": 0, \"inclusionMask\": 0, \"ignoreEntityCollisionOption\": " << (respectCollisionOptions ? "false" : "true") << "},\n"; out << " \"pathCollisionPolicy\": {\"exclusionMask\": 10485760, \"excludedBits\": {\"water\": 2097152, \"foliage\": 8388608}},\n"; out << " \"entityCount\": " << entities.size() << ",\n"; out << " \"entitiesWithCollisionObject\": "; int colliderCount = 0; for(const auto &entity : entities) colliderCount += entity.hasCollisionObject ? 1 : 0; out << colliderCount << ",\n"; out << " \"engineQueryTriangleCountIncludingRenderFallbacks\": " << vertexCount / 3 << ",\n"; out << " \"nativeCollisionTriangleCount\": " << serializedVertexCount / 3 << ",\n"; out << " \"nativeCollisionVertexCount\": " << serializedVertexCount << ",\n"; out << " \"completeNativeCollisionTriangleCount\": " << completeSerializedVertexCount / 3 << ",\n"; out << " \"enabledNativeCollisionTriangleCount\": " << effectiveSerializedVertexCount / 3 << ",\n"; out << " \"disabledSubtreeNativeTriangles\": " << (completeSerializedVertexCount - effectiveSerializedVertexCount) / 3 << ",\n"; out << " \"nativeTrianglesOmittedByCollisionOptions\": " << (completeSerializedVertexCount - effectiveSerializedVertexCount) / 3 << ",\n"; out << " \"renderMeshFallbackTriangleCount\": " << (vertexCount - serializedVertexCount) / 3 << ",\n"; std::map triangleFlagCounts; for(INT32 i = 0; i < serializedQuery.m_colTriFlags->Count(); ++i) ++triangleFlagCounts[(*serializedQuery.m_colTriFlags)[i]]; out << " \"nativeTriangleFlagCounts\": {"; bool firstFlag = true; for(const auto &entry : triangleFlagCounts) { if(!firstFlag) out << ", "; firstFlag = false; out << "\"" << entry.first << "\": " << entry.second; } out << "},\n"; INT32 pathEligibleTriangleCount = 0; for(INT32 i = 0; i < serializedQuery.m_colTriFlags->Count(); ++i) { if(((*serializedQuery.m_colTriFlags)[i] & 0x00A00000) == 0) ++pathEligibleTriangleCount; } out << " \"nativeTrianglesAfterTrekkerWaterFoliageExclusion\": " << pathEligibleTriangleCount << ",\n"; INT32 disabledOptionCount = 0; INT32 unreachableEntityCount = 0; for(const auto &entity : entities) { disabledOptionCount += entity.collisionOptionEnabled ? 0 : 1; unreachableEntityCount += entity.collisionOptionPathReachable ? 0 : 1; } out << " \"disabledCollisionOptionEntityCount\": " << disabledOptionCount << ",\n"; out << " \"disabledEntityCount\": " << disabledOptionCount << ",\n"; out << " \"entityNodesSkippedByCollisionOptionGates\": " << unreachableEntityCount << ",\n"; out << " \"collisionOptionUnreachableEntityCount\": " << unreachableEntityCount << ",\n"; out << " \"nativeCollisionBounds\": {\"min\": [" << minima.x << ", " << minima.y << ", " << minima.z << "], \"max\": [" << maxima.x << ", " << maxima.y << ", " << maxima.z << "]},\n"; out << " \"colliderEntities\": [\n"; bool first = true; for(const auto &entity : entities) { if(!entity.hasCollisionObject) continue; if(!first) out << ",\n"; first = false; out << " {\"name\": \"" << entity.name << "\", \"entityType\": \"" << entity.type << "\", \"collisionType\": \"" << entity.collisionType << "\", \"renderMeshCount\": " << entity.meshCount << "}"; } out << "\n ],\n \"entityNodes\": [\n"; for(std::size_t i = 0; i < entities.size(); ++i) { const auto &entity = entities[i]; if(i) out << ",\n"; out << " {\"name\": \"" << entity.name << "\", \"type\": \"" << entity.type << "\", \"renderMeshCount\": " << entity.meshCount << ", \"hasCollisionObject\": " << (entity.hasCollisionObject ? "true" : "false") << ", \"meshCollisionStructures\": " << entity.meshCollisionStructureCount << ", \"collisionOptionEnabled\": " << (entity.collisionOptionEnabled ? "true" : "false") << ", \"collisionOptionPathReachable\": " << (entity.collisionOptionPathReachable ? "true" : "false") << ", \"serializedCollisionTriangles\": " << entity.serializedCollisionTriangleCount << ", \"completeSerializedCollisionTriangles\": " << entity.completeSerializedCollisionTriangleCount << ", \"meshCollisionTypes\": ["; for(std::size_t meshIndex = 0; meshIndex < entity.meshCollisionTypes.size(); ++meshIndex) { if(meshIndex) out << ", "; out << "\"" << entity.meshCollisionTypes[meshIndex] << "\""; } out << "]}"; } out << "\n ]\n}\n"; out.close(); // The optional raw OBJ is diagnostic evidence only. Its positions are the // native query's model-local, Runewaker left-handed Y-up coordinates. std::ofstream obj((outputPath + ".obj").c_str(), std::ios::out | std::ios::trunc); if(!obj) return false; obj << "# Native Runewaker CRuCollisionQuery_OBB_TriEnum output; model-local LH Y-up\n"; obj << std::setprecision(9); for(INT32 i = 0; i < serializedVertexCount; ++i) { const CRuVector3 &v = (*serializedQuery.m_colTriVerts)[i]; obj << "v " << v.x << " " << v.y << " " << v.z << "\n"; } for(INT32 i = 0; i + 2 < serializedVertexCount; i += 3) obj << "f " << (i + 1) << " " << (i + 2) << " " << (i + 3) << "\n"; std::ofstream flags((outputPath + ".flags.bin").c_str(), std::ios::out | std::ios::binary | std::ios::trunc); if(!flags) return false; for(INT32 i = 0; i < serializedQuery.m_colTriFlags->Count(); ++i) { const UINT32 flag = static_cast((*serializedQuery.m_colTriFlags)[i]); flags.write(reinterpret_cast(&flag), sizeof(flag)); } if(!flags) return false; return static_cast(obj); } } int main(int argc, char **argv) { if(argc != 4 && argc != 5) { std::cerr << "Usage: native-collision-audit [complete|respect-options]\n"; return 2; } const std::string collisionMode = argc == 5 ? argv[4] : "complete"; if(collisionMode != "complete" && collisionMode != "respect-options") { std::cerr << "Collision mode must be 'complete' or 'respect-options'.\n"; return 2; } if(RuInitialize_Core() == FALSE || RuInitialize_NULL() == FALSE) { std::cerr << "Rune initialization failed.\n"; RuShutdown(); return 3; } g_ruResourceManager->SetRootDirectory(argv[1]); CRuEntity *root = g_ruResourceManager->LoadEntity(argv[2]); if(!root) { std::cerr << "Unable to load ROS entity: " << argv[2] << "\n"; RuShutdown(); return 4; } std::vector entities; Visit(root, true, entities); const bool written = WriteReport(argv[3], argv[2], collisionMode, root, entities); ruSAFE_RELEASE(root); RuShutdown(); if(!written) { std::cerr << "Unable to write native collision report.\n"; return 5; } std::cout << "Wrote native collision query report for " << argv[2] << "\n"; return 0; }