forked from phenom/healer-man
386 lines
16 KiB
C++
386 lines
16 KiB
C++
#include <cstdint>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#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<std::string> meshCollisionTypes;
|
|
};
|
|
|
|
void Visit(CRuEntity *entity, bool parentCollisionPathReachable, std::vector<EntitySummary> &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<IRuEntity_Renderable *>(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<CRuMesh *>(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<CRuMatrix4x4 *>(&entity->GetWorldTransform());
|
|
query.m_invWorldTransform = const_cast<CRuMatrix4x4 *>(&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<CRuModel *>(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<CRuMesh *>(mesh)->GetCollisionStructure();
|
|
if(structure)
|
|
{
|
|
CRuMatrix4x4 *originalWorldTransform = query.m_worldTransform;
|
|
CRuMatrix4x4 *originalInvWorldTransform = query.m_invWorldTransform;
|
|
query.m_worldTransform = const_cast<CRuMatrix4x4 *>(&entity->GetWorldTransform());
|
|
query.m_invWorldTransform = const_cast<CRuMatrix4x4 *>(&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<EntitySummary> &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<UINT32, INT32> 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<UINT32>((*serializedQuery.m_colTriFlags)[i]);
|
|
flags.write(reinterpret_cast<const char *>(&flag), sizeof(flag));
|
|
}
|
|
if(!flags)
|
|
return false;
|
|
return static_cast<bool>(obj);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
if(argc != 4 && argc != 5)
|
|
{
|
|
std::cerr << "Usage: native-collision-audit <resource-root> <model-resource-path> <output-json> [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<EntitySummary> 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;
|
|
}
|