227 lines
7.7 KiB
C++
227 lines
7.7 KiB
C++
#include "gamelib/physics/tracemodelcache.h"
|
|
|
|
#include "cm/collisionmodel.h"
|
|
#include "cm/collisionmodelmanager.h"
|
|
#include "idlib/lib_print.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <vector>
|
|
|
|
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<std::uint32_t>(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<int>(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<int>(entry->trm->numVerts);
|
|
const int desiredInteriorPoints = 64 - numVertices;
|
|
if (desiredInteriorPoints < 0) {
|
|
return;
|
|
}
|
|
|
|
std::vector<waterPointBuild_t> interiorPoints;
|
|
float sampleMultiplier = 1.0f;
|
|
for (;;) {
|
|
interiorPoints.clear();
|
|
const float divisions = std::cbrt(
|
|
static_cast<float>(desiredInteriorPoints) * sampleMultiplier);
|
|
if (divisions > 0.0f) {
|
|
const idVec3 step(dimensions.x / divisions,
|
|
dimensions.y / divisions, dimensions.z / divisions);
|
|
for (int x = 0; static_cast<float>(x) < divisions; ++x) {
|
|
for (int y = 0; static_cast<float>(y) < divisions; ++y) {
|
|
for (int z = 0; static_cast<float>(z) < divisions; ++z) {
|
|
waterPointBuild_t point{};
|
|
point.xyz.Set(
|
|
bounds[0].x + (static_cast<float>(x) + 0.5f) * step.x,
|
|
bounds[0].y + (static_cast<float>(y) + 0.5f) * step.y,
|
|
bounds[0].z + (static_cast<float>(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<int>(interiorPoints.size()) >= desiredInteriorPoints) {
|
|
break;
|
|
}
|
|
sampleMultiplier *= 2.0f;
|
|
if (sampleMultiplier > 16.0f) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const float pointWeight = 1.0f /
|
|
static_cast<float>(interiorPoints.size() + numVertices);
|
|
for (waterPointBuild_t& point : interiorPoints) {
|
|
point.weight = pointWeight;
|
|
point.weightSquared = pointWeight * pointWeight;
|
|
}
|
|
|
|
while (static_cast<int>(interiorPoints.size()) > desiredInteriorPoints) {
|
|
float bestMetric = (std::numeric_limits<float>::max)();
|
|
int bestFirst = 0;
|
|
int bestSecond = 1;
|
|
for (int first = 0;
|
|
first < static_cast<int>(interiorPoints.size()); ++first) {
|
|
for (int second = first + 1;
|
|
second < static_cast<int>(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;
|
|
}
|