#include "gamelib/physics/tracemodelcache.h" #include "cm/collisionmodel.h" #include "cm/collisionmodelmanager.h" #include "idlib/lib_print.h" #include #include #include #include #include #include bool GameLib_IsMultiplayer(); namespace { std::uint32_t FloatBits(const float value) { std::uint32_t bits = 0; std::memcpy(&bits, &value, sizeof(bits)); return bits; } int TraceModelHashKey(const idTraceModel& traceModel) { const std::uint32_t key = (4u * ((4u * ((16u * static_cast(traceModel.type)) ^ traceModel.numVerts)) ^ traceModel.numEdges)) ^ traceModel.numPolys ^ FloatBits(traceModel.bounds[0].z) ^ FloatBits(traceModel.bounds[0].y) ^ FloatBits(traceModel.bounds[0].x); return static_cast(key); } struct waterPointBuild_t { idVec3 xyz; float weight; float weightSquared; }; } // namespace void idTraceModelCache::FreeTraceModel(const int traceModelIndex) { trmCache_t* const entry = GetEntry(traceModelIndex); if (entry == nullptr || entry->refCount <= 0) { idLibPrint::Warning( "idClipModel::FreeTraceModel: tried to free uncached trace model"); return; } --entry->refCount; } int idTraceModelCache::CopyTraceModel(const int traceModelIndex) { trmCache_t* const entry = GetEntry(traceModelIndex); if (entry == nullptr || entry->refCount <= 0) { idLibPrint::Warning( "idClipModel::CopyTraceModel: tried to copy an uncached trace " "model"); return -1; } ++entry->refCount; return traceModelIndex; } void idTraceModelCache::ClearTraceModelCache() { for (int index = 0; index < cache.Num(); ++index) { trmCache_t* const entry = cache[index]; if (entry == nullptr) { continue; } if (entry->collisionModel != nullptr) { delete entry->collisionModel; entry->collisionModel = nullptr; } entry->waterPoints.ClearFree(); delete entry->trm; entry->trm = nullptr; entry->~trmCache_t(); } cacheAllocator.Shutdown(); cache.ClearFree(); hash.Free(); } void idTraceModelCache::SetupWaterPoints(trmCache_t* const entry) { entry->waterPoints.ClearFree(); entry->hasWater = false; if (entry->trm == nullptr || !entry->trm->isConvex || GameLib_IsMultiplayer()) { return; } const idBounds& bounds = entry->trm->bounds; const idVec3 dimensions = bounds[1] - bounds[0]; if (dimensions.x <= 0.0f || dimensions.y <= 0.0f || dimensions.z <= 0.0f || dimensions.x * dimensions.y * dimensions.z == 0.0f) { return; } const int numVertices = static_cast(entry->trm->numVerts); const int desiredInteriorPoints = 64 - numVertices; if (desiredInteriorPoints < 0) { return; } std::vector interiorPoints; float sampleMultiplier = 1.0f; for (;;) { interiorPoints.clear(); const float divisions = std::cbrt( static_cast(desiredInteriorPoints) * sampleMultiplier); if (divisions > 0.0f) { const idVec3 step(dimensions.x / divisions, dimensions.y / divisions, dimensions.z / divisions); for (int x = 0; static_cast(x) < divisions; ++x) { for (int y = 0; static_cast(y) < divisions; ++y) { for (int z = 0; static_cast(z) < divisions; ++z) { waterPointBuild_t point{}; point.xyz.Set( bounds[0].x + (static_cast(x) + 0.5f) * step.x, bounds[0].y + (static_cast(y) + 0.5f) * step.y, bounds[0].z + (static_cast(z) + 0.5f) * step.z); point.weight = 1.0f; point.weightSquared = 1.0f; if (entry->trm->ContainsPoint(point.xyz)) { interiorPoints.push_back(point); } } } } } if (static_cast(interiorPoints.size()) >= desiredInteriorPoints) { break; } sampleMultiplier *= 2.0f; if (sampleMultiplier > 16.0f) { return; } } const float pointWeight = 1.0f / static_cast(interiorPoints.size() + numVertices); for (waterPointBuild_t& point : interiorPoints) { point.weight = pointWeight; point.weightSquared = pointWeight * pointWeight; } while (static_cast(interiorPoints.size()) > desiredInteriorPoints) { float bestMetric = (std::numeric_limits::max)(); int bestFirst = 0; int bestSecond = 1; for (int first = 0; first < static_cast(interiorPoints.size()); ++first) { for (int second = first + 1; second < static_cast(interiorPoints.size()); ++second) { const idVec3 delta = interiorPoints[first].xyz - interiorPoints[second].xyz; const float metric = delta.LengthSqr() * interiorPoints[first].weight * interiorPoints[second].weight; if (metric < bestMetric) { bestMetric = metric; bestFirst = first; bestSecond = second; } } } waterPointBuild_t& destination = interiorPoints[bestFirst]; destination.xyz = (destination.xyz + interiorPoints[bestSecond].xyz) * 0.5f; destination.weight += interiorPoints[bestSecond].weight; destination.weightSquared = destination.weight * destination.weight; interiorPoints[bestSecond] = interiorPoints.back(); interiorPoints.pop_back(); } entry->waterPoints.SetNum(64); int outputIndex = 0; for (const waterPointBuild_t& point : interiorPoints) { entry->waterPoints[outputIndex++] = {point.xyz, point.weight}; } for (int vertex = 0; vertex < numVertices && outputIndex < 64; ++vertex) { traceModelWater_t& point = entry->waterPoints[outputIndex++]; point.xyz.Set(entry->trm->vertsX[vertex], entry->trm->vertsY[vertex], entry->trm->vertsZ[vertex]); point.weight = pointWeight; } while (outputIndex < 64) { entry->waterPoints[outputIndex++] = {idVec3(0.0f, 0.0f, 0.0f), 0.0f}; } entry->hasWater = true; } int idTraceModelCache::AllocTraceModel(const idTraceModel& traceModel, const idMaterial* const material) { const int key = TraceModelHashKey(traceModel); for (int index = hash.First(key); index != -1; index = hash.Next(index)) { trmCache_t* const entry = GetEntry(index); if (entry != nullptr && entry->trm != nullptr && entry->trm->Compare(traceModel) && entry->material == material) { ++entry->refCount; return index; } } trmCache_t* const entry = cacheAllocator.Alloc(); if (entry == nullptr) { return -1; } entry->trm = new idTraceModel(traceModel); entry->trm->ClearUnused(); entry->trm->GetMassProperties(1.0f, entry->volume, entry->centerOfMass, entry->inertiaTensor); entry->refCount = 1; entry->material = material; const int index = cache.Append(entry); hash.Add(key, index); char modelName[64]; std::snprintf(modelName, sizeof(modelName), "traceModel%d", index); entry->collisionModel = collisionModelManager.ModelFromTrm( modelName, *entry->trm, material); SetupWaterPoints(entry); return index; }