From a7fdbe32ad2850cdac9198f6c9916a661ce855c8 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Sun, 9 Aug 2026 01:29:43 -0700 Subject: [PATCH] Added collision model code. --- source/engine/cm/collisiongrid.cpp | 432 +++ source/engine/cm/collisiongrid.h | 158 +- source/engine/cm/collisionmodel.cpp | 591 +++ source/engine/cm/collisionmodel.h | 146 +- source/engine/cm/collisionmodelbuilder.cpp | 3212 +++++++++++++++++ source/engine/cm/collisionmodelbuilder.h | 515 ++- .../cm/collisionmodelbuilder_geometry.cpp | 901 +++++ .../cm/collisionmodelbuilder_sphere.cpp | 120 + source/engine/cm/collisionmodelmanager.cpp | 944 +++++ source/engine/cm/collisionmodelmanager.h | 171 +- .../engine/cm/collisionmodelmanager_debug.cpp | 235 ++ source/engine/cm/collisionqueryjobmanager.cpp | 1051 ++++++ source/engine/cm/collisionqueryjobmanager.h | 264 +- source/engine/cm/collisiontypes.h | 211 ++ source/engine/cm/jobs/collisionmerge.cpp | 537 +++ source/engine/cm/jobs/collisionmerge.h | 74 +- source/engine/cm/jobs/collisionquery.cpp | 361 ++ source/engine/cm/jobs/collisionquery.h | 133 +- source/engine/cm/jobs/collisionresults.h | 87 + .../cm/jobs/polygonmodel/polygonmodel.h | 316 +- .../jobs/polygonmodel/polygonmodel_cache.cpp | 135 + .../jobs/polygonmodel/polygonmodel_clip.cpp | 157 + .../polygonmodel/polygonmodel_contacts.cpp | 334 ++ .../polygonmodel/polygonmodel_contents.cpp | 447 +++ .../jobs/polygonmodel/polygonmodel_inline.h | 39 + .../jobs/polygonmodel/polygonmodel_rotate.cpp | 773 ++++ .../jobs/polygonmodel/polygonmodel_trace.cpp | 378 ++ .../polygonmodel/polygonmodel_translate.cpp | 620 ++++ .../cm/jobs/polygonmodel/polygonmodeldata.h | 45 + .../cm/jobs/spheremodel/spheremodel.cpp | 106 + .../engine/cm/jobs/spheremodel/spheremodel.h | 48 +- 31 files changed, 13317 insertions(+), 224 deletions(-) create mode 100644 source/engine/cm/collisiongrid.cpp create mode 100644 source/engine/cm/collisionmodel.cpp create mode 100644 source/engine/cm/collisionmodelbuilder.cpp create mode 100644 source/engine/cm/collisionmodelbuilder_geometry.cpp create mode 100644 source/engine/cm/collisionmodelbuilder_sphere.cpp create mode 100644 source/engine/cm/collisionmodelmanager.cpp create mode 100644 source/engine/cm/collisionmodelmanager_debug.cpp create mode 100644 source/engine/cm/collisionqueryjobmanager.cpp create mode 100644 source/engine/cm/collisiontypes.h create mode 100644 source/engine/cm/jobs/collisionmerge.cpp create mode 100644 source/engine/cm/jobs/collisionquery.cpp create mode 100644 source/engine/cm/jobs/collisionresults.h create mode 100644 source/engine/cm/jobs/polygonmodel/polygonmodel_cache.cpp create mode 100644 source/engine/cm/jobs/polygonmodel/polygonmodel_clip.cpp create mode 100644 source/engine/cm/jobs/polygonmodel/polygonmodel_contacts.cpp create mode 100644 source/engine/cm/jobs/polygonmodel/polygonmodel_contents.cpp create mode 100644 source/engine/cm/jobs/polygonmodel/polygonmodel_inline.h create mode 100644 source/engine/cm/jobs/polygonmodel/polygonmodel_rotate.cpp create mode 100644 source/engine/cm/jobs/polygonmodel/polygonmodel_trace.cpp create mode 100644 source/engine/cm/jobs/polygonmodel/polygonmodel_translate.cpp create mode 100644 source/engine/cm/jobs/polygonmodel/polygonmodeldata.h create mode 100644 source/engine/cm/jobs/spheremodel/spheremodel.cpp diff --git a/source/engine/cm/collisiongrid.cpp b/source/engine/cm/collisiongrid.cpp new file mode 100644 index 0000000..2c31da8 --- /dev/null +++ b/source/engine/cm/collisiongrid.cpp @@ -0,0 +1,432 @@ +#include "cm/collisiongrid.h" + +#include "idlib/filesystem/file.h" +#include "idlib/filesystem/filesystem.h" +#include "idlib/lib_print.h" + +#include +#include +#include + +namespace { + +constexpr std::uint32_t COLLISION_GRID_MAGIC = 0x42434703u; + +bool ReadBytes(idFile* const file, void* const data, + const unsigned int size) { + return file != nullptr && file->Read(data, size) == size; +} + +bool ReadU8(idFile* const file, std::uint8_t& value) { + return ReadBytes(file, &value, sizeof(value)); +} + +bool ReadU16BE(idFile* const file, std::uint16_t& value) { + std::uint8_t bytes[2]; + if (!ReadBytes(file, bytes, sizeof(bytes))) { + return false; + } + value = static_cast( + (static_cast(bytes[0]) << 8) | bytes[1]); + return true; +} + +bool ReadI16BE(idFile* const file, std::int16_t& value) { + std::uint16_t bits; + if (!ReadU16BE(file, bits)) { + return false; + } + value = static_cast(bits); + return true; +} + +bool ReadU32BE(idFile* const file, std::uint32_t& value) { + std::uint8_t bytes[4]; + if (!ReadBytes(file, bytes, sizeof(bytes))) { + return false; + } + value = (static_cast(bytes[0]) << 24) | + (static_cast(bytes[1]) << 16) | + (static_cast(bytes[2]) << 8) | + static_cast(bytes[3]); + return true; +} + +bool ReadI32BE(idFile* const file, int& value) { + std::uint32_t bits; + if (!ReadU32BE(file, bits)) { + return false; + } + value = static_cast(bits); + return true; +} + +bool ReadFloatBE(idFile* const file, float& value) { + std::uint32_t bits; + if (!ReadU32BE(file, bits)) { + return false; + } + std::memcpy(&value, &bits, sizeof(value)); + return true; +} + +bool ReadBoundsShortBE(idFile* const file, idBoundsShort& bounds) { + for (int side = 0; side < 2; ++side) { + for (int axis = 0; axis < 3; ++axis) { + if (!ReadI16BE(file, bounds.b[side][axis])) { + return false; + } + } + } + return true; +} + +bool ReadBoundsBE(idFile* const file, idBounds& bounds) { + for (int side = 0; side < 2; ++side) { + for (int axis = 0; axis < 3; ++axis) { + if (!ReadFloatBE(file, bounds[side][axis])) { + return false; + } + } + } + return true; +} + +int FloorDivide(const int numerator, const int denominator) { + const int quotient = numerator / denominator; + const int remainder = numerator % denominator; + return remainder < 0 ? quotient - 1 : quotient; +} + +std::int64_t Cross(const idVec2i& a, const idVec2i& b, + const idVec2i& c) { + return static_cast(b.x - a.x) * (c.y - a.y) - + static_cast(b.y - a.y) * (c.x - a.x); +} + +bool PointOnSegment(const idVec2i& point, const idVec2i& start, + const idVec2i& end) { + return Cross(start, end, point) == 0 && + point.x >= (std::min)(start.x, end.x) && + point.x <= (std::max)(start.x, end.x) && + point.y >= (std::min)(start.y, end.y) && + point.y <= (std::max)(start.y, end.y); +} + +bool SegmentsIntersect(const idVec2i& a, const idVec2i& b, + const idVec2i& c, const idVec2i& d) { + const std::int64_t abC = Cross(a, b, c); + const std::int64_t abD = Cross(a, b, d); + const std::int64_t cdA = Cross(c, d, a); + const std::int64_t cdB = Cross(c, d, b); + if (((abC < 0 && abD > 0) || (abC > 0 && abD < 0)) && + ((cdA < 0 && cdB > 0) || (cdA > 0 && cdB < 0))) { + return true; + } + return (abC == 0 && PointOnSegment(c, a, b)) || + (abD == 0 && PointOnSegment(d, a, b)) || + (cdA == 0 && PointOnSegment(a, c, d)) || + (cdB == 0 && PointOnSegment(b, c, d)); +} + +bool PointInPolygon(const idVec2i& point, const idVec2i* const positions, + const int num) { + bool inside = false; + for (int current = 0, previous = num - 1; current < num; + previous = current++) { + const idVec2i& a = positions[previous]; + const idVec2i& b = positions[current]; + if (PointOnSegment(point, a, b)) { + return true; + } + if ((a.y > point.y) != (b.y > point.y)) { + const double intersectionX = static_cast(b.x - a.x) * + static_cast(point.y - a.y) / + static_cast(b.y - a.y) + + static_cast(a.x); + if (static_cast(point.x) < intersectionX) { + inside = !inside; + } + } + } + return inside; +} + +bool PolygonTouchesCell(const idVec2i* const positions, const int num, + const int minX, const int minY, const int maxX, const int maxY) { + for (int index = 0; index < num; ++index) { + if (positions[index].x >= minX && positions[index].x <= maxX && + positions[index].y >= minY && positions[index].y <= maxY) { + return true; + } + } + + const idVec2i corners[4] = { + idVec2i(minX, minY), idVec2i(maxX, minY), + idVec2i(maxX, maxY), idVec2i(minX, maxY)}; + for (const idVec2i& corner : corners) { + if (PointInPolygon(corner, positions, num)) { + return true; + } + } + for (int current = 0, previous = num - 1; current < num; + previous = current++) { + for (int edge = 0; edge < 4; ++edge) { + if (SegmentsIntersect(positions[previous], positions[current], + corners[edge], corners[(edge + 1) & 3])) { + return true; + } + } + } + return false; +} + +} // namespace + +idResourceList idCollisionGridLocal::resourceList("cg"); + +idCollisionGridLocal::idCollisionGridLocal() + : binaryTimeStamp(-1), sourceTimeStamp(-1) { + grid.numX = 0; + grid.numY = 0; + grid.offset.Zero(); + grid.dimension = 0; +} + +idCollisionGridLocal::~idCollisionGridLocal() { + FreeData(); +} + +idResourceList* idCollisionGridLocal::GetResourceList() { + return &resourceList; +} + +void idCollisionGridLocal::CreateState(idCollisionGridState& state) { + state.Create(grid.indices.Num(), nullptr); + for (int index = 0; index < grid.indices.Num(); ++index) { + if (grid.indices[index] == idGenGridModel::INVALID_INDEX) { + state.Inactivate(static_cast(index)); + } else { + state.Activate(static_cast(index)); + } + } +} + +bool idCollisionGridLocal::IsValid() const { + return grid.parts.Num() != 0; +} + +idStr idCollisionGridLocal::GetBinaryFileName( + const char* const modelName) const { + idStr result; + if (modelName == nullptr) { + return result; + } + if (_strnicmp(modelName, "maps/", 5) != 0) { + char generatedName[256] = {}; + fileSystem->FixLongFilename("generated", "bcg", modelName, + generatedName, sizeof(generatedName)); + result = generatedName; + } else { + result = modelName; + result.SetFileExtension("bcg"); + } + return result; +} + +bool idCollisionGridLocal::ReloadIfStale() { + const idStr binaryName = GetBinaryFileName(GetName()); + if (static_cast(fileSystem->GetTimestamp( + binaryName.c_str(), false)) == binaryTimeStamp) { + if (_strnicmp(binaryName.c_str(), "maps/", 5) == 0 || + static_cast(fileSystem->GetTimestamp( + GetName(), false)) == sourceTimeStamp) { + return false; + } + fileSystem->RemoveFile(binaryName.c_str(), FSPATH_BASE); + } + LoadResource(); + return true; +} + +void idCollisionGridLocal::FreeData() { + grid.indices.ClearFree(); + grid.vertices.ClearFree(); + grid.edges.ClearFree(); + grid.polygonEdges.ClearFree(); + grid.polygons.ClearFree(); + grid.parts.ClearFree(); + grid.nodes.ClearFree(); + grid.numX = 0; + grid.numY = 0; + grid.offset.Zero(); + grid.dimension = 0; + binaryTimeStamp = -1; + sourceTimeStamp = -1; +} + +void idGridRasterize::RasterizePolygon(const idGenGridModel& grid, + idCollisionGridState& state, const idVec2i* const positions, + const int num) { + if (positions == nullptr || num < 3 || grid.numX <= 0 || + grid.numY <= 0 || grid.dimension <= 0) { + return; + } + + int polygonMinX = positions[0].x; + int polygonMaxX = positions[0].x; + int polygonMinY = positions[0].y; + int polygonMaxY = positions[0].y; + for (int index = 1; index < num; ++index) { + polygonMinX = (std::min)(polygonMinX, positions[index].x); + polygonMaxX = (std::max)(polygonMaxX, positions[index].x); + polygonMinY = (std::min)(polygonMinY, positions[index].y); + polygonMaxY = (std::max)(polygonMaxY, positions[index].y); + } + + const int firstX = (std::max)(0, FloorDivide( + polygonMinX - grid.offset.x, grid.dimension)); + const int lastX = (std::min)(grid.numX - 1, FloorDivide( + polygonMaxX - grid.offset.x, grid.dimension)); + const int firstY = (std::max)(0, FloorDivide( + polygonMinY - grid.offset.y, grid.dimension)); + const int lastY = (std::min)(grid.numY - 1, FloorDivide( + polygonMaxY - grid.offset.y, grid.dimension)); + + for (int y = firstY; y <= lastY; ++y) { + const int cellMinY = grid.offset.y + y * grid.dimension; + const int cellMaxY = cellMinY + grid.dimension; + for (int x = firstX; x <= lastX; ++x) { + const int cellMinX = grid.offset.x + x * grid.dimension; + const int cellMaxX = cellMinX + grid.dimension; + if (PolygonTouchesCell(positions, num, cellMinX, cellMinY, + cellMaxX, cellMaxY)) { + state.Inactivate(static_cast( + x + y * grid.numX)); + } + } + } +} + +void idCollisionGridLocal::InactivateFill(const idVec2i* const positions, + const int num, idCollisionGridState& state) { + idGridRasterize::RasterizePolygon(grid, state, positions, num); +} + +bool idGenGridModel::LoadBinary(idFile* const file) { + if (file == nullptr) { + return false; + } + + int counts[7] = {}; + for (int& count : counts) { + if (!ReadI32BE(file, count) || count < 0) { + return false; + } + } + + indices.ClearFree(); + vertices.ClearFree(); + edges.ClearFree(); + polygonEdges.ClearFree(); + polygons.ClearFree(); + parts.ClearFree(); + nodes.ClearFree(); + if (!vertices.SetNum(counts[0]) || !edges.SetNum(counts[1]) || + !polygonEdges.SetNum(counts[2]) || !polygons.SetNum(counts[3]) || + !parts.SetNum(counts[4]) || !indices.SetNum(counts[5]) || + !nodes.SetNum(counts[6])) { + return false; + } + + for (int index = 0; index < vertices.Num(); ++index) { + idVec3& vertex = vertices[index]; + if (!ReadFloatBE(file, vertex.x) || + !ReadFloatBE(file, vertex.y) || + !ReadFloatBE(file, vertex.z)) { + return false; + } + } + for (int index = 0; index < edges.Num(); ++index) { + cm_edge_t& edge = edges[index]; + if (!ReadU16BE(file, edge.vertexNum[0]) || + !ReadU16BE(file, edge.vertexNum[1])) { + return false; + } + } + for (int index = 0; index < polygonEdges.Num(); ++index) { + if (!ReadU16BE(file, polygonEdges[index])) { + return false; + } + } + for (int index = 0; index < polygons.Num(); ++index) { + cm_polygon_t& polygon = polygons[index]; + if (!ReadU8(file, polygon.material) || + !ReadU16BE(file, polygon.firstEdge) || + !ReadU8(file, polygon.numEdges) || + !ReadBoundsShortBE(file, polygon.bounds)) { + return false; + } + } + for (int index = 0; index < parts.Num(); ++index) { + cm_gridPart_t& part = parts[index]; + if (!ReadBoundsShortBE(file, part.bounds) || + !ReadU16BE(file, part.nodeIndex) || + !ReadU16BE(file, part.firstPolygonIndex) || + !ReadU16BE(file, part.numPolygons)) { + return false; + } + } + for (int index = 0; index < indices.Num(); ++index) { + if (!ReadU16BE(file, indices[index])) { + return false; + } + } + for (int index = 0; index < nodes.Num(); ++index) { + cm_gridNodeBSP_t& node = nodes[index]; + std::uint8_t planeType; + if (!ReadBoundsBE(file, node.bounds) || + !ReadFloatBE(file, node.planeDist) || + !ReadU16BE(file, node.children[0]) || + !ReadU16BE(file, node.children[1]) || + !ReadU8(file, planeType)) { + return false; + } + node.planeType = static_cast(planeType); + } + return ReadI32BE(file, numX) && ReadI32BE(file, numY) && + ReadI32BE(file, dimension) && ReadI32BE(file, offset.x) && + ReadI32BE(file, offset.y); +} + +bool idCollisionGridLocal::Load_Binary() { + const idStr binaryName = GetBinaryFileName(GetName()); + idFileLocal file(fileSystem->OpenFileRead( + binaryName.c_str(), true, false)); + if (file.file == nullptr) { + return false; + } + + binaryTimeStamp = static_cast(file->Timestamp()); + std::uint32_t magic; + if (!ReadU32BE(file.file, magic) || magic != COLLISION_GRID_MAGIC) { + idLibPrint::Warning("%s is not a binary collision grid file", + binaryName.c_str()); + FreeData(); + return false; + } + if (!ReadI32BE(file.file, sourceTimeStamp) || + !grid.LoadBinary(file.file)) { + return false; + } + return true; +} + +bool idCollisionGridLocal::LoadBinary() { + return Load_Binary(); +} + +void idCollisionGridLocal::LoadResource() { + FreeData(); + Load_Binary(); +} diff --git a/source/engine/cm/collisiongrid.h b/source/engine/cm/collisiongrid.h index 47d950f..c37e558 100644 --- a/source/engine/cm/collisiongrid.h +++ b/source/engine/cm/collisiongrid.h @@ -1,56 +1,122 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\cm\collisiongrid.h -// Recovered logical types: 3 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "cm/collisiontypes.h" +#include "framework/resource.h" +#include "framework/resourcelist.h" +#include "idlib/containers/bitarray.h" +#include "idlib/containers/list.h" +#include "idlib/math/vectori.h" +#include "idlib/sys/sys_alloc.h" +#include "idlib/text/str.h" +#include -// IDA Local Type ordinal 15896; PDB kind: class. -class idCollisionGridState -{ +class idFile; +class idMaterial; + +class idCollisionGridState { public: - const idMaterial *material; - int numActive; - int firstActive; - idBitArray active; + idCollisionGridState(); + ~idCollisionGridState() = default; + + void Create(int num, const idMaterial* material); + void Activate(unsigned int id); + void Inactivate(unsigned int id); + int FirstActive() const; + int NextActive(int id) const; + + const idMaterial* material; + int numActive; + int firstActive; + idBitArray active; }; -// IDA Local Type ordinal 15897; PDB kind: class. -class idCollisionGrid : public idResource -{ -public: - // Recovered virtual interface; IDA vtable ordinal 15898. - virtual ~idCollisionGrid(); - virtual void LoadResource(); - virtual bool ReloadIfStale(); - virtual void WriteResourceFile(); - virtual idResourceList *GetResourceList(); - virtual void Print(); - virtual void List(); - virtual void CreateState(idCollisionGridState *); - virtual void InactivateFill(const idVec2i *, int, idCollisionGridState *); - virtual bool IsValid(); - +struct cm_gridPart_t { + idBoundsShort bounds; + std::uint16_t nodeIndex; + std::uint16_t firstPolygonIndex; + std::uint16_t numPolygons; }; -// IDA Local Type ordinal 23778; PDB kind: class. -class idCollisionGridLocal : public idCollisionGrid -{ -public: - // Recovered virtual interface; IDA vtable ordinal 23779. - virtual ~idCollisionGridLocal(); - virtual void LoadResource(); - virtual bool ReloadIfStale(); - virtual void WriteResourceFile(); - virtual idResourceList *GetResourceList(); - virtual void Print(); - virtual void List(); - virtual void CreateState(idCollisionGridState *); - virtual void InactivateFill(const idVec2i *, int, idCollisionGridState *); - virtual bool IsValid(); - - idGenGridModel grid; - int binaryTimeStamp; - int sourceTimeStamp; +struct alignas(4) cm_gridNodeBSP_t { + idBounds bounds; + float planeDist; + std::uint16_t children[2]; + char planeType; }; + +class idGenGridModel { +public: + static constexpr std::uint16_t INVALID_INDEX = 0xFFFFu; + + bool LoadBinary(idFile* file); + + idList indices; + idList vertices; + idList edges; + idList polygonEdges; + idList polygons; + idList parts; + idList nodes; + int numX; + int numY; + idVec2i offset; + int dimension; +}; + +class idCollisionGrid : public idResource { +public: + ~idCollisionGrid() override = default; + + virtual void CreateState(idCollisionGridState& state) = 0; + virtual void InactivateFill(const idVec2i* positions, int num, + idCollisionGridState& state) = 0; + virtual bool IsValid() const = 0; +}; + +class idCollisionGridLocal final : public idCollisionGrid { +public: + idCollisionGridLocal(); + ~idCollisionGridLocal() override; + + void LoadResource() override; + bool ReloadIfStale() override; + idResourceList* GetResourceList() override; + void CreateState(idCollisionGridState& state) override; + void InactivateFill(const idVec2i* positions, int num, + idCollisionGridState& state) override; + bool IsValid() const override; + + idStr GetBinaryFileName(const char* modelName) const; + void FreeData(); + bool Load_Binary(); + bool LoadBinary(); + + static idResourceList resourceList; + + idGenGridModel grid; + int binaryTimeStamp; + int sourceTimeStamp; +}; + +class idGridRasterize { +public: + static void RasterizePolygon(const idGenGridModel& grid, + idCollisionGridState& state, const idVec2i* positions, int num); +}; + +static_assert(sizeof(cm_gridPart_t) == 18, + "Recovered cm_gridPart_t ABI changed"); +static_assert(sizeof(cm_gridNodeBSP_t) == 36, + "Recovered cm_gridNodeBSP_t ABI changed"); + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idCollisionGridState) == 24, + "Recovered idCollisionGridState ABI changed"); +static_assert(sizeof(idGenGridModel) == 132, + "Recovered idGenGridModel ABI changed"); +static_assert(sizeof(idCollisionGrid) == 36, + "Recovered idCollisionGrid ABI changed"); +static_assert(sizeof(idCollisionGridLocal) == 176, + "Recovered idCollisionGridLocal ABI changed"); +#endif diff --git a/source/engine/cm/collisionmodel.cpp b/source/engine/cm/collisionmodel.cpp new file mode 100644 index 0000000..c658425 --- /dev/null +++ b/source/engine/cm/collisionmodel.cpp @@ -0,0 +1,591 @@ +#include "cm/collisionmodel.h" + +#include "cm/collisionmodelbuilder.h" +#include "cm/jobs/polygonmodel/polygonmodel.h" +#include "cm/jobs/polygonmodel/polygonmodeldata.h" +#include "cm/jobs/spheremodel/spheremodel.h" +#include "framework/resourcelist.h" +#include "idlib/filesystem/file.h" +#include "idlib/filesystem/filesystem.h" +#include "idlib/text/str.h" + +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include +#include +#include + +namespace { + +constexpr std::uint32_t BCM_FILE_ID = 1111706934u; +constexpr int MAX_CM_FILE_COUNT = 1 << 20; +constexpr int MAX_CM_SUBMODEL_SIZE = 16 << 20; + +bool ReadExact(idFile* const file, void* const data, + const unsigned int size) { + return file != nullptr && file->Read(data, size) == size; +} + +bool WriteExact(idFile* const file, const void* const data, + const unsigned int size) { + return file != nullptr && file->Write(data, size) == size; +} + +} // namespace + +void SetupStreamAreaPtrs(streamAreasHeader_t* const header, + streamAreasPtrs_t& pointers) { + pointers.streamAreas = reinterpret_cast(header + 1); + pointers.streamAreaSubModels = reinterpret_cast( + pointers.streamAreas + header->numStreamAreas); + pointers.streamAreaNameBytes = reinterpret_cast( + pointers.streamAreaSubModels + header->numStreamAreaSubModels); +} + +const cm_subModelData_t* AcquireSubModelData( + const cm_subModel_t& subModel) { + InterlockedIncrement(reinterpret_cast( + const_cast(&subModel.numUsers))); + if (*subModel.state == SUBMODEL_STATE_LOADED) { + return subModel.data; + } + InterlockedDecrement(reinterpret_cast( + const_cast(&subModel.numUsers))); + return reinterpret_cast(&subModel); +} + +void ReleaseSubModelData(const cm_subModel_t& subModel, + const cm_subModelData_t* const data) { + if (data != reinterpret_cast(&subModel)) { + InterlockedDecrement(reinterpret_cast( + const_cast(&subModel.numUsers))); + } +} + +idResourceList idCollisionModelLocal::resourceList("cm"); + +void* idCollisionModelLocal::operator new(const std::size_t size) { + return _aligned_malloc(size, 16); +} + +void idCollisionModelLocal::operator delete(void* const memory) { + _aligned_free(memory); +} + +idCollisionModelLocal::idCollisionModelLocal() + : binaryFileTime(static_cast(-1)), + sourceFileTime(static_cast(-1)), + modelType(CM_POLYGONMODEL), bounds(), contents(0), + isWorldModel(false), isTraceModel(false), isConvex(false), + isStreamed(false), streamFilePtr(nullptr), polygonModel{}, + sphereModel(nullptr), streamAreas(nullptr), memoryMappedFile(nullptr) { + bounds[0].Set(FLT_MAX, FLT_MAX, FLT_MAX); + bounds[1].Set(-FLT_MAX, -FLT_MAX, -FLT_MAX); +} + +idCollisionModelLocal::~idCollisionModelLocal() { + FreeData(); +} + +idResourceList* idCollisionModelLocal::GetResourceList() { + return &resourceList; +} + +int idCollisionModelLocal::GetTotalMemory() const { + if (modelType == CM_SPHEREMODEL) { + return static_cast(sizeof(*this)) + + (sphereModel != nullptr + ? static_cast(sphereModel->totalSize) : 0); + } + int total = static_cast(sizeof(*this)) + + static_cast(sizeof(cm_modelTreeNode_t)) + * polygonModel.numModelTreeNodes + + static_cast(sizeof(cm_subModel_t)) + * polygonModel.numSubModels + + polygonModel.numSubModels; + for (int index = 0; index < polygonModel.numSubModels; ++index) { + total += polygonModel.subModels[index].header.totalSize; + } + return total; +} + +int idCollisionModelLocal::GetLoadedMemory() const { + if (modelType == CM_SPHEREMODEL) { + return GetTotalMemory(); + } + int total = static_cast(sizeof(*this)) + + static_cast(sizeof(cm_modelTreeNode_t)) + * polygonModel.numModelTreeNodes + + static_cast(sizeof(cm_subModel_t)) + * polygonModel.numSubModels + + polygonModel.numSubModels; + for (int index = 0; index < polygonModel.numSubModels; ++index) { + const cm_subModel_t& subModel = polygonModel.subModels[index]; + const cm_subModelData_t* const data = AcquireSubModelData(subModel); + if (data != nullptr) { + total += data->header.loadedSize; + } + ReleaseSubModelData(subModel, data); + } + return total; +} + +int idCollisionModelLocal::GetMaxResidentMemory(idVec3* const location) const { + if (location != nullptr) { + location->Zero(); + } + return GetTotalMemory(); +} + +void idCollisionModelLocal::MakeDefault() { + FreeData(); + modelType = CM_POLYGONMODEL; + bounds[0].Set(-8.0f, -8.0f, -8.0f); + bounds[1].Set(8.0f, 8.0f, 8.0f); + contents = 1; + isWorldModel = false; + isTraceModel = false; + isConvex = true; + isStreamed = false; + polygonModel.numModelTreeNodes = 0; + polygonModel.modelTreeNodes = nullptr; + polygonModel.numSubModels = 1; + polygonModel.subModels = static_cast( + _aligned_malloc(sizeof(cm_subModel_t), 16)); + polygonModel.subModelState = static_cast( + _aligned_malloc(1, 16)); + cm_subModelData_t* const data = static_cast( + _aligned_malloc(768, 16)); + if (polygonModel.subModels == nullptr + || polygonModel.subModelState == nullptr || data == nullptr) { + _aligned_free(data); + FreeData(); + return; + } + std::memset(polygonModel.subModels, 0, sizeof(cm_subModel_t)); + cm_subModel_t& subModel = polygonModel.subModels[0]; + subModel.header.bounds = bounds; + subModel.header.totalSize = 608; + subModel.header.loadedSize = 608; + subModel.data = idPolygonModelCollisionDetection::SetupSubModelForBounds( + data, 768, bounds); + subModel.fileOffset = -1; + subModel.numUsers = 0; + subModel.state = polygonModel.subModelState; + *subModel.state = SUBMODEL_STATE_LOADED; +} + +void idCollisionModelLocal::GetBinaryFileName(const char* const modelName, + idStr& binaryFileName, bool& inMapFolder, bool& isWorld) { + binaryFileName.Clear(); + inMapFolder = false; + isWorld = false; + if (modelName == nullptr) { + return; + } + if (_strnicmp(modelName, "maps/", 5) != 0) { + char generatedName[256] = {}; + fileSystem->FixLongFilename("generated", "bcm", modelName, + generatedName, sizeof(generatedName)); + binaryFileName = generatedName; + return; + } + inMapFolder = true; + const std::size_t length = std::strlen(modelName); + isWorld = length >= 5 && _stricmp(modelName + length - 5, "world") == 0; + binaryFileName = modelName; + binaryFileName.SetFileExtension("bcm"); +} + +bool idCollisionModelLocal::ReloadIfStale() { + idStr binaryName; + bool inMapFolder = false; + bool world = false; + GetBinaryFileName(GetName(), binaryName, inMapFolder, world); + const std::uint32_t binaryTime = fileSystem->GetTimestamp( + binaryName.c_str(), false); + if (binaryTime == static_cast(-1) + && idCollisionModelBuilder::IsAnimatedRenderModel(GetName())) { + return false; + } + if (binaryTime == binaryFileTime + && (inMapFolder || fileSystem->GetTimestamp(GetName(), false) + == sourceFileTime)) { + return false; + } + if (binaryTime == binaryFileTime && !inMapFolder) { + fileSystem->RemoveFile(binaryName.c_str(), FSPATH_BASE); + } + LoadResource(); + return true; +} + +bool idCollisionModelLocal::Write_Binary() { + if (modelType != CM_POLYGONMODEL || isStreamed) { + return false; + } + idStr binaryName; + bool inMapFolder = false; + bool world = false; + GetBinaryFileName(GetName(), binaryName, inMapFolder, world); + idFileLocal file(fileSystem->OpenFileWrite(binaryName.c_str(), + FSPATH_BASE)); + if (file.file == nullptr) { + return false; + } + const std::uint8_t flags[4] = { + static_cast(isWorldModel), + static_cast(isTraceModel), + static_cast(isConvex), 0 }; + if (!WriteExact(file.file, &BCM_FILE_ID, 4) + || !WriteExact(file.file, &sourceFileTime, 4) + || !WriteExact(file.file, &bounds, sizeof(bounds)) + || !WriteExact(file.file, &contents, 4) + || !WriteExact(file.file, flags, sizeof(flags)) + || !WriteExact(file.file, &polygonModel.numModelTreeNodes, 4) + || (polygonModel.numModelTreeNodes > 0 + && !WriteExact(file.file, polygonModel.modelTreeNodes, + static_cast(sizeof(cm_modelTreeNode_t) + * polygonModel.numModelTreeNodes))) + || !WriteExact(file.file, &polygonModel.numSubModels, 4)) { + return false; + } + for (int index = 0; index < polygonModel.numSubModels; ++index) { + const cm_subModel_t& subModel = polygonModel.subModels[index]; + const cm_subModelData_t* const data = AcquireSubModelData(subModel); + const bool ok = data != nullptr + && WriteExact(file.file, &subModel.header, sizeof(subModel.header)) + && (subModel.header.totalSize == 32 + || WriteExact(file.file, data, + static_cast(subModel.header.totalSize))); + ReleaseSubModelData(subModel, data); + if (!ok) { + return false; + } + } + return WriteExact(file.file, &BCM_FILE_ID, 4); +} + +bool idCollisionModelLocal::Load_Binary() { + idStr binaryName; + bool inMapFolder = false; + bool world = false; + GetBinaryFileName(GetName(), binaryName, inMapFolder, world); + idFileLocal file(fileSystem->OpenFileRead(binaryName.c_str(), true, + false)); + if (file.file == nullptr) { + return false; + } + std::uint32_t magic = 0; + std::uint8_t flags[4] = {}; + int numTreeNodes = 0; + int numSubModels = 0; + if (!ReadExact(file.file, &magic, 4) || magic != BCM_FILE_ID + || !ReadExact(file.file, &sourceFileTime, 4) + || !ReadExact(file.file, &bounds, sizeof(bounds)) + || !ReadExact(file.file, &contents, 4) + || !ReadExact(file.file, flags, sizeof(flags)) + || !ReadExact(file.file, &numTreeNodes, 4) + || numTreeNodes < 0 || numTreeNodes > MAX_CM_FILE_COUNT) { + return false; + } + isWorldModel = flags[0] != 0; + isTraceModel = flags[1] != 0; + isConvex = flags[2] != 0; + isStreamed = false; + modelType = CM_POLYGONMODEL; + polygonModel.numModelTreeNodes = numTreeNodes; + if (numTreeNodes > 0) { + polygonModel.modelTreeNodes = static_cast( + _aligned_malloc(sizeof(cm_modelTreeNode_t) * numTreeNodes, 16)); + if (polygonModel.modelTreeNodes == nullptr + || !ReadExact(file.file, polygonModel.modelTreeNodes, + static_cast(sizeof(cm_modelTreeNode_t) + * numTreeNodes))) { + FreeData(); + return false; + } + } + if (!ReadExact(file.file, &numSubModels, 4) || numSubModels < 0 + || numSubModels > MAX_CM_FILE_COUNT) { + FreeData(); + return false; + } + polygonModel.numSubModels = numSubModels; + if (numSubModels > 0) { + polygonModel.subModels = static_cast( + _aligned_malloc(sizeof(cm_subModel_t) * numSubModels, 16)); + polygonModel.subModelState = static_cast( + _aligned_malloc(numSubModels, 16)); + if (polygonModel.subModels == nullptr + || polygonModel.subModelState == nullptr) { + FreeData(); + return false; + } + std::memset(polygonModel.subModels, 0, + sizeof(cm_subModel_t) * numSubModels); + } + for (int index = 0; index < numSubModels; ++index) { + cm_subModel_t& subModel = polygonModel.subModels[index]; + if (!ReadExact(file.file, &subModel.header, sizeof(subModel.header)) + || subModel.header.totalSize < 32 + || subModel.header.totalSize > MAX_CM_SUBMODEL_SIZE) { + FreeData(); + return false; + } + subModel.fileOffset = -1; + subModel.numUsers = 0; + subModel.state = &polygonModel.subModelState[index]; + if (subModel.header.totalSize > 32) { + subModel.data = static_cast(_aligned_malloc( + subModel.header.totalSize, 16)); + if (subModel.data == nullptr || !ReadExact(file.file, + subModel.data, subModel.header.totalSize)) { + FreeData(); + return false; + } + *subModel.state = SUBMODEL_STATE_LOADED; + } else { + subModel.data = nullptr; + *subModel.state = SUBMODEL_STATE_UNLOADED; + } + } + if (!ReadExact(file.file, &magic, 4) || magic != BCM_FILE_ID) { + FreeData(); + return false; + } + binaryFileTime = file.file->Timestamp(); + return true; +} + +void idCollisionModelLocal::LoadResource() { + FreeData(); + if (!Load_Binary()) { + MakeDefault(); + } +} + +bool idCollisionModelLocal::GetBounds(idBounds& outputBounds) const { + outputBounds = bounds; + return true; +} + +bool idCollisionModelLocal::GetBox(idBox& box) const { + box = idBox(bounds); + return true; +} + +bool idCollisionModelLocal::GetContents(int& outputContents) const { + outputContents = contents; + return true; +} + +bool idCollisionModelLocal::GetVertex(const int vertexFeature, + idVec3& vertex) const { + if (modelType == CM_SPHEREMODEL) { + return false; + } + const int subModelIndex = (vertexFeature >> 16) & 0x1FFF; + const int vertexIndex = vertexFeature & 0xFFFF; + if (subModelIndex < 0 || subModelIndex >= polygonModel.numSubModels) { + return false; + } + + const cm_subModel_t& subModel = polygonModel.subModels[subModelIndex]; + const cm_subModelData_t* const data = AcquireSubModelData(subModel); + if (data == nullptr || data->header.loadedSize == 32 || + vertexIndex < 0 || vertexIndex >= data->numVertices) { + ReleaseSubModelData(subModel, data); + return false; + } + cm_subModelPtrs_t pointers{}; + idPolygonModelCollisionDetection::SetupSubModelPtrsFromData( + pointers, data); + vertex = pointers.vertices[vertexIndex].p; + ReleaseSubModelData(subModel, data); + return true; +} + +bool idCollisionModelLocal::GetEdge(const int edgeFeature, idVec3& start, + idVec3& end) const { + if (modelType == CM_SPHEREMODEL) { + return false; + } + const int subModelIndex = (edgeFeature >> 16) & 0x1FFF; + const int edgeIndex = edgeFeature & 0xFFFF; + if (subModelIndex < 0 || subModelIndex >= polygonModel.numSubModels) { + return false; + } + + const cm_subModel_t& subModel = polygonModel.subModels[subModelIndex]; + const cm_subModelData_t* const data = AcquireSubModelData(subModel); + if (data == nullptr || data->header.loadedSize == 32 || edgeIndex < 0 || + edgeIndex >= data->numEdges) { + ReleaseSubModelData(subModel, data); + return false; + } + cm_subModelPtrs_t pointers{}; + idPolygonModelCollisionDetection::SetupSubModelPtrsFromData( + pointers, data); + const cm_edge_t& edge = pointers.edges[edgeIndex]; + start = pointers.vertices[edge.vertexNum[0]].p; + end = pointers.vertices[edge.vertexNum[1]].p; + ReleaseSubModelData(subModel, data); + return true; +} + +bool idCollisionModelLocal::GetPolygon(const int polygonFeature, + idFixedWinding& winding) const { + if (modelType == CM_SPHEREMODEL) { + return false; + } + const int subModelIndex = (polygonFeature >> 16) & 0x1FFF; + const int polygonIndex = polygonFeature & 0xFFFF; + if (subModelIndex < 0 || subModelIndex >= polygonModel.numSubModels) { + return false; + } + + const cm_subModel_t& subModel = polygonModel.subModels[subModelIndex]; + const cm_subModelData_t* const data = AcquireSubModelData(subModel); + if (data == nullptr || data->header.loadedSize == 32 || + polygonIndex < 0 || polygonIndex >= data->numPolygons) { + ReleaseSubModelData(subModel, data); + return false; + } + cm_subModelPtrs_t pointers{}; + idPolygonModelCollisionDetection::SetupSubModelPtrsFromData( + pointers, data); + winding.Clear(); + const cm_polygon_t& polygon = pointers.polygons[polygonIndex]; + for (int index = 0; index < polygon.numEdges; ++index) { + const std::uint16_t edgeReference = + pointers.polygonEdges[polygon.firstEdge + index]; + const cm_edge_t& edge = + pointers.edges[CM_EdgeIndex(edgeReference)]; + const cm_vertex_t& source = pointers.vertices[ + CM_EdgeStartVertex(edge, edgeReference)]; + idVec5 point; + point.x = source.p.x; + point.y = source.p.y; + point.z = source.p.z; + point.s = static_cast(source.st[0]); + point.t = static_cast(source.st[1]); + winding.AddPoint(point); + } + ReleaseSubModelData(subModel, data); + return true; +} + +int idCollisionModelLocal::GetPolytope(const int polytopeFeature, + idPlane* const planes, const int maxPlanes) const { + if (modelType == CM_SPHEREMODEL || planes == nullptr || + maxPlanes <= 0) { + return 0; + } + const int subModelIndex = (polytopeFeature >> 16) & 0x1FFF; + const int polytopeIndex = polytopeFeature & 0xFFFF; + if (subModelIndex < 0 || subModelIndex >= polygonModel.numSubModels) { + return 0; + } + + const cm_subModel_t& subModel = polygonModel.subModels[subModelIndex]; + const cm_subModelData_t* const data = AcquireSubModelData(subModel); + if (data == nullptr || data->header.loadedSize == 32 || + polytopeIndex < 0 || polytopeIndex >= data->numPolytopes) { + ReleaseSubModelData(subModel, data); + return 0; + } + cm_subModelPtrs_t pointers{}; + idPolygonModelCollisionDetection::SetupSubModelPtrsFromData( + pointers, data); + const cm_polytope_t& polytope = pointers.polytopes[polytopeIndex]; + const int count = (std::min)(maxPlanes, + static_cast(polytope.numPlanes)); + for (int index = 0; index < count; ++index) { + planes[index] = pointers.polytopePlanes[polytope.firstPlane + index]; + } + ReleaseSubModelData(subModel, data); + return count; +} + +int idCollisionModelLocal::GetPolytopes(int* const polytopeNumPlanes, + const int maxPolytopes, idPlane* const planes, + const int maxPlanes) const { + if (modelType == CM_SPHEREMODEL || polytopeNumPlanes == nullptr || + planes == nullptr || maxPolytopes <= 0 || maxPlanes <= 0) { + return 0; + } + + int outputPolytopes = 0; + int outputPlanes = 0; + for (int subModelIndex = 0; + subModelIndex < polygonModel.numSubModels && + outputPolytopes < maxPolytopes; + ++subModelIndex) { + const cm_subModel_t& subModel = + polygonModel.subModels[subModelIndex]; + const cm_subModelData_t* const data = AcquireSubModelData(subModel); + if (data == nullptr || data->header.loadedSize == 32) { + ReleaseSubModelData(subModel, data); + continue; + } + cm_subModelPtrs_t pointers{}; + idPolygonModelCollisionDetection::SetupSubModelPtrsFromData( + pointers, data); + for (int polytopeIndex = 0; + polytopeIndex < data->numPolytopes && + outputPolytopes < maxPolytopes; + ++polytopeIndex) { + const cm_polytope_t& polytope = + pointers.polytopes[polytopeIndex]; + const int count = (std::min)( + static_cast(polytope.numPlanes), + maxPlanes - outputPlanes); + if (count <= 0) { + break; + } + polytopeNumPlanes[outputPolytopes++] = count; + for (int index = 0; index < count; ++index) { + planes[outputPlanes++] = pointers.polytopePlanes[ + polytope.firstPlane + index]; + } + } + ReleaseSubModelData(subModel, data); + } + return outputPolytopes; +} + +idIndex idCollisionModelLocal::GetJoint( + const int sphereFeature) const { + if (sphereModel == nullptr || sphereFeature < 0 || + sphereFeature >= sphereModel->numSpheres) { + return idIndex(); + } + cm_sphereModelPtrs_t pointers{}; + idSphereModelCollisionDetection::SetupCollisionSpherePtrs( + sphereModel, pointers); + return idIndex( + static_cast(pointers.joint[sphereFeature])); +} + +void idCollisionModelLocal::FreeData() { + if (memoryMappedFile == nullptr) { + for (int index = 0; index < polygonModel.numSubModels; ++index) { + _aligned_free(polygonModel.subModels[index].data); + } + } + _aligned_free(polygonModel.modelTreeNodes); + _aligned_free(polygonModel.subModels); + _aligned_free(const_cast(polygonModel.subModelState)); + polygonModel = {}; + _aligned_free(sphereModel); + sphereModel = nullptr; + _aligned_free(streamAreas); + streamAreas = nullptr; + streamFilePtr = nullptr; + memoryMappedFile = nullptr; + binaryFileTime = static_cast(-1); + sourceFileTime = static_cast(-1); +} diff --git a/source/engine/cm/collisionmodel.h b/source/engine/cm/collisionmodel.h index 925a0cf..23842af 100644 --- a/source/engine/cm/collisionmodel.h +++ b/source/engine/cm/collisionmodel.h @@ -1,47 +1,117 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\cm\collisionmodel.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "cm/collisiontypes.h" +#include "cm/jobs/collisionquery.h" +#include "framework/resource.h" +#include "idlib/bv/box.h" +#include "idlib/geometry/winding.h" +#include "idlib/index.h" +#include -// IDA Local Type ordinal 14043; PDB kind: class. -class idCollisionModel : public idResource -{ -public: - // Recovered virtual interface; IDA vtable ordinal 14044. - virtual ~idCollisionModel(); - virtual void LoadResource(); - virtual bool ReloadIfStale(); - virtual void WriteResourceFile(); - virtual idResourceList *GetResourceList(); - virtual void Print(); - virtual void List(); - virtual cmType_t GetModelType(); - virtual bool GetBounds(idBounds *); - virtual bool GetBox(idBox *); - virtual bool GetContents(int *); - virtual bool GetVertex(int, idVec3 *); - virtual bool GetEdge(int, idVec3 *, idVec3 *); - virtual bool GetPolygon(int, idFixedWinding *); - virtual int GetPolytope(int, idPlane *, int); - virtual int GetPolytopes(int *, int, idPlane *, int); - virtual idIndex *GetJoint(idIndex *result, int); +class idFile; +class idJointMat; +class idMemoryMappedFile; +class idResourceList; +class idStr; +enum invalidJointIndex_t : int { + INVALID_JOINT_INDEX = -1 }; -// IDA Local Type ordinal 18656; PDB kind: class. -class idPositionedCollisionModel -{ +class idCollisionModel : public idResource { public: - idCollisionModel *model; - const idJointMat *modelJoints; - idVec3 modelOrigin; - idMat3 modelAxis; - int modelEntityNum; - int modelPhysicsId; - int modelBodyId; - int modelContentsOverride; - idCollisionQuery modelQuery; + ~idCollisionModel() override = default; + + virtual cmType_t GetModelType() const = 0; + virtual bool GetBounds(idBounds& bounds) const = 0; + virtual bool GetBox(idBox& box) const = 0; + virtual bool GetContents(int& contents) const = 0; + virtual bool GetVertex(int vertexFeature, idVec3& vertex) const = 0; + virtual bool GetEdge(int edgeFeature, idVec3& start, + idVec3& end) const = 0; + virtual bool GetPolygon(int polygonFeature, + idFixedWinding& winding) const = 0; + virtual int GetPolytope(int polytopeFeature, idPlane* planes, + int maxPlanes) const = 0; + virtual int GetPolytopes(int* polytopeNumPlanes, int maxPolytopes, + idPlane* planes, int maxPlanes) const = 0; + virtual idIndex GetJoint( + int sphereFeature) const = 0; }; + +class idCollisionModelLocal final : public idCollisionModel { +public: + static void* operator new(std::size_t size); + static void operator delete(void* memory); + + idCollisionModelLocal(); + ~idCollisionModelLocal() override; + + idResourceList* GetResourceList() override; + cmType_t GetModelType() const override { return modelType; } + bool GetBounds(idBounds& outputBounds) const override; + bool GetBox(idBox& box) const override; + bool GetContents(int& outputContents) const override; + bool GetVertex(int vertexFeature, idVec3& vertex) const override; + bool GetEdge(int edgeFeature, idVec3& start, + idVec3& end) const override; + bool GetPolygon(int polygonFeature, + idFixedWinding& winding) const override; + int GetPolytope(int polytopeFeature, idPlane* planes, + int maxPlanes) const override; + int GetPolytopes(int* polytopeNumPlanes, int maxPolytopes, + idPlane* planes, int maxPlanes) const override; + idIndex GetJoint( + int sphereFeature) const override; + + int GetTotalMemory() const; + int GetLoadedMemory() const; + int GetMaxResidentMemory(idVec3* location) const; + void MakeDefault(); + static void GetBinaryFileName(const char* modelName, + idStr& binaryFileName, bool& inMapFolder, bool& isWorld); + bool Write_Binary(); + bool Load_Binary(); + void LoadResource() override; + bool ReloadIfStale() override; + void FreeData(); + + static idResourceList resourceList; + + std::uint32_t binaryFileTime; + std::uint32_t sourceFileTime; + cmType_t modelType; + idBounds bounds; + int contents; + bool isWorldModel; + bool isTraceModel; + bool isConvex; + bool isStreamed; + idFile* streamFilePtr; + cm_polygonModel_t polygonModel; + cm_sphereModel_t* sphereModel; + streamAreasHeader_t* streamAreas; + idMemoryMappedFile* memoryMappedFile; +}; + +struct idPositionedCollisionModel { + idCollisionModel* model; + const idJointMat* modelJoints; + idVec3 modelOrigin; + idMat3 modelAxis; + int modelEntityNum; + int modelPhysicsId; + int modelBodyId; + int modelContentsOverride; + idCollisionQuery modelQuery; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idCollisionModel) == 36, + "Recovered idCollisionModel ABI changed"); +static_assert(sizeof(idCollisionModelLocal) == 116, + "Recovered idCollisionModelLocal ABI changed"); +static_assert(sizeof(idPositionedCollisionModel) == 80, + "Recovered idPositionedCollisionModel ABI changed"); +#endif diff --git a/source/engine/cm/collisionmodelbuilder.cpp b/source/engine/cm/collisionmodelbuilder.cpp new file mode 100644 index 0000000..997440e --- /dev/null +++ b/source/engine/cm/collisionmodelbuilder.cpp @@ -0,0 +1,3212 @@ +#include "cm/collisionmodelbuilder.h" + +#include "cm/collisiongrid.h" +#include "cm/collisionmodel.h" +#include "cm/jobs/polygonmodel/polygonmodel.h" +#include "cm/jobs/polygonmodel/polygonmodeldata.h" +#include "idlib/geometry/tracemodel.h" +#include "idlib/containers/hashindex.h" +#include "idlib/lib_print.h" +#include "idlib/sys/sys_alloc.h" +#include "idlib/text/lexer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +struct cm_procNode_t { + idPlane plane; + int children[2]; +}; + +struct idBuildData { + int numProcNodes = 0; + cm_procNode_t* procNodes = nullptr; + cm_windingList_t* cm_windingList = nullptr; + cm_windingList_t* cm_outList = nullptr; + cm_windingList_t* cm_tmpList = nullptr; + idHashIndex* cm_vertexHash = nullptr; + idHashIndex* cm_edgeHash = nullptr; + idBounds cm_modelBounds; + int cm_vertexShift = 1; +}; + +idBuildData buildData; + +int Align(const int value, const int alignment) { + return (value + alignment - 1) & ~(alignment - 1); +} + +void* AllocBuildMemory(const std::size_t size, const bool clear = false) { + return mem.AllocWithLocation( + "engine/cm/collisionmodelbuilder.cpp : TAG_COLLISION", + static_cast(size), TAG_COLLISION, clear, ALIGN_16, + HEAP_DEFAULTHEAP); +} + +template +Type* GrowBuildArray(Type* const oldArray, const int oldCount, + const int newCapacity) { + Type* const array = static_cast(AllocBuildMemory( + sizeof(Type) * static_cast(newCapacity))); + if (array == nullptr) { + return nullptr; + } + if (oldArray != nullptr && oldCount > 0) { + std::memcpy(array, oldArray, + sizeof(Type) * static_cast(oldCount)); + } + mem.Free(oldArray, ALIGN_16); + return array; +} + +void AddBounds(idBounds& destination, const idBounds& source) { + for (int axis = 0; axis < 3; ++axis) { + destination[0][axis] = (std::min)( + destination[0][axis], source[0][axis]); + destination[1][axis] = (std::max)( + destination[1][axis], source[1][axis]); + } +} + +bool BoundsOverlap(const idBounds& first, const idBounds& second, + const float epsilon = 0.0f) { + for (int axis = 0; axis < 3; ++axis) { + if (first[0][axis] > second[1][axis] + epsilon + || first[1][axis] < second[0][axis] - epsilon) { + return false; + } + } + return true; +} + +int DirectedEdgeStart(const cm_buildModel_t* const model, + const int edgeReference) { + const cm_buildEdge_t& edge = model->edges[std::abs(edgeReference)]; + return edge.vertexNum[edgeReference < 0 ? 1 : 0]; +} + +int DirectedEdgeEnd(const cm_buildModel_t* const model, + const int edgeReference) { + const cm_buildEdge_t& edge = model->edges[std::abs(edgeReference)]; + return edge.vertexNum[edgeReference < 0 ? 0 : 1]; +} + +void CopyPolygonFields(cm_buildPolygon_t& destination, + const cm_buildPolygon_t& source) { + destination.plane = source.plane; + destination.bounds = source.bounds; + destination.contents = source.contents; + destination.material = source.material; + destination.primitiveNum = source.primitiveNum; + destination.checkCount = 0; +} + +class ProcTokenScanner { +public: + ProcTokenScanner(const char* const begin, const char* const end) + : current(begin), end(end) { + } + + bool Read(std::string& token) { + SkipWhitespace(); + token.clear(); + if (current == nullptr || current >= end) { + return false; + } + const char first = *current; + if (first == '{' || first == '}' || first == '(' + || first == ')' || first == '[' || first == ']') { + token.push_back(*current++); + return true; + } + if (first == '"') { + ++current; + while (current < end && *current != '"') { + token.push_back(*current++); + } + if (current < end) { + ++current; + } + return true; + } + while (current < end && !std::isspace( + static_cast(*current)) + && std::strchr("{}()[]", *current) == nullptr) { + token.push_back(*current++); + } + return !token.empty(); + } + + bool Expect(const char* const expected) { + std::string token; + return Read(token) && token == expected; + } + + bool ReadInt(int& value) { + std::string token; + if (!Read(token)) { + return false; + } + char* endPointer = nullptr; + const long parsed = std::strtol(token.c_str(), &endPointer, 10); + if (endPointer == token.c_str() || *endPointer != '\0') { + return false; + } + value = static_cast(parsed); + return true; + } + + bool ReadFloat(float& value) { + std::string token; + if (!Read(token)) { + return false; + } + char* endPointer = nullptr; + value = std::strtof(token.c_str(), &endPointer); + return endPointer != token.c_str() && *endPointer == '\0'; + } + + bool SkipBracedSection() { + if (!Expect("{")) { + return false; + } + int depth = 1; + std::string token; + while (depth > 0 && Read(token)) { + if (token == "{") { + ++depth; + } else if (token == "}") { + --depth; + } + } + return depth == 0; + } + + const char* Position() const { + return current; + } + +private: + void SkipWhitespace() { + while (current != nullptr && current < end) { + if (std::isspace(static_cast(*current))) { + ++current; + continue; + } + if (end - current >= 2 && current[0] == '/' + && current[1] == '/') { + current += 2; + while (current < end && *current != '\n') { + ++current; + } + continue; + } + if (end - current >= 2 && current[0] == '/' + && current[1] == '*') { + current += 2; + while (end - current >= 2 + && !(current[0] == '*' && current[1] == '/')) { + ++current; + } + if (end - current >= 2) { + current += 2; + } + continue; + } + break; + } + } + + const char* current; + const char* end; +}; + +bool ParseProcNodesFromScanner(ProcTokenScanner& scanner) { + if (!scanner.Expect("{")) { + return false; + } + int numNodes = 0; + if (!scanner.ReadInt(numNodes) || numNodes < 0 + || numNodes > 0x1000000) { + return false; + } + cm_procNode_t* nodes = nullptr; + if (numNodes > 0) { + nodes = static_cast(AllocBuildMemory( + sizeof(cm_procNode_t) * static_cast(numNodes), + true)); + if (nodes == nullptr) { + return false; + } + } + for (int index = 0; index < numNodes; ++index) { + if (!scanner.Expect("(") + || !scanner.ReadFloat(nodes[index].plane.a) + || !scanner.ReadFloat(nodes[index].plane.b) + || !scanner.ReadFloat(nodes[index].plane.c) + || !scanner.ReadFloat(nodes[index].plane.d) + || !scanner.Expect(")") + || !scanner.ReadInt(nodes[index].children[0]) + || !scanner.ReadInt(nodes[index].children[1])) { + mem.Free(nodes, ALIGN_16); + return false; + } + } + if (!scanner.Expect("}")) { + mem.Free(nodes, ALIGN_16); + return false; + } + mem.Free(buildData.procNodes, ALIGN_16); + buildData.procNodes = nodes; + buildData.numProcNodes = numNodes; + return true; +} + +} // namespace + +int CM_R_CountChildren(cm_buildNode_t* const node) { + if (node == nullptr || node->planeType == -1) { + return 0; + } + return CM_R_CountChildren(node->children[1]) + + CM_R_CountChildren(node->children[0]) + 2; +} + +void CM_R_TestOptimisation(cm_buildNode_t* node, + int& numSavedPolygonIndices, int& numSavedPolytopeIndices) { + while (node != nullptr && node->planeType != -1) { + int numPolygons = 0; + int numPolytopes = 0; + for (cm_buildPolygonRef_t* reference = node->polygons; + reference != nullptr; reference = reference->next) { + ++numPolygons; + } + for (cm_buildPolytopeRef_t* reference = node->polytopes; + reference != nullptr; reference = reference->next) { + ++numPolytopes; + } + if (numPolygons != 0 || numPolytopes != 0) { + const int savedCopies = + CM_R_CountChildren(node->children[1]) + + CM_R_CountChildren(node->children[0]) + 1; + numSavedPolygonIndices += savedCopies * numPolygons; + numSavedPolytopeIndices += savedCopies * numPolytopes; + } + CM_R_TestOptimisation(node->children[0], + numSavedPolygonIndices, numSavedPolytopeIndices); + node = node->children[1]; + } +} + +bool CM_R_InsideAllChildren(cm_buildNode_t* const node, + const idBounds& bounds) { + if (node == nullptr || node->planeType == -1) { + return true; + } + const int axis = node->planeType; + if (bounds[0][axis] >= node->planeDist + || bounds[1][axis] <= node->planeDist) { + return false; + } + return CM_R_InsideAllChildren(node->children[0], bounds) + && CM_R_InsideAllChildren(node->children[1], bounds); +} + +cm_buildNode_t* idCollisionModelBuilder::AllocNode( + cm_buildModel_t* const model, int blockSize) { + if (model == nullptr) { + return nullptr; + } + blockSize = (std::max)(1, blockSize); + if (model->nodeBlocks == nullptr + || model->nodeBlocks->nextNode == nullptr) { + const std::size_t allocationSize = sizeof(cm_buildNodeBlock_t) + + sizeof(cm_buildNode_t) * static_cast(blockSize); + cm_buildNodeBlock_t* const block = + static_cast( + AllocBuildMemory(allocationSize, true)); + if (block == nullptr) { + return nullptr; + } + block->size = static_cast(allocationSize); + block->nextNode = reinterpret_cast(block + 1); + block->next = model->nodeBlocks; + model->nodeBlocks = block; + for (int index = 0; index + 1 < blockSize; ++index) { + block->nextNode[index].parent = &block->nextNode[index + 1]; + } + block->nextNode[blockSize - 1].parent = nullptr; + } + cm_buildNode_t* const node = model->nodeBlocks->nextNode; + model->nodeBlocks->nextNode = node->parent; + std::memset(node, 0, sizeof(*node)); + node->planeType = -1; + ++model->numNodes; + return node; +} + +cm_buildPolygonRef_t* idCollisionModelBuilder::AllocPolygonReference( + cm_buildModel_t* const model, int blockSize) { + if (model == nullptr) { + return nullptr; + } + blockSize = (std::max)(1, blockSize); + if (model->polygonRefBlocks == nullptr + || model->polygonRefBlocks->nextRef == nullptr) { + const std::size_t allocationSize = + sizeof(cm_buildPolygonRefBlock_t) + + sizeof(cm_buildPolygonRef_t) + * static_cast(blockSize); + cm_buildPolygonRefBlock_t* const block = + static_cast( + AllocBuildMemory(allocationSize, true)); + if (block == nullptr) { + return nullptr; + } + block->size = static_cast(allocationSize); + block->nextRef = reinterpret_cast(block + 1); + block->next = model->polygonRefBlocks; + model->polygonRefBlocks = block; + for (int index = 0; index + 1 < blockSize; ++index) { + block->nextRef[index].next = &block->nextRef[index + 1]; + } + block->nextRef[blockSize - 1].next = nullptr; + } + cm_buildPolygonRef_t* const reference = + model->polygonRefBlocks->nextRef; + model->polygonRefBlocks->nextRef = reference->next; + reference->next = nullptr; + return reference; +} + +cm_buildPolytopeRef_t* idCollisionModelBuilder::AllocPolytopeReference( + cm_buildModel_t* const model, int blockSize) { + if (model == nullptr) { + return nullptr; + } + blockSize = (std::max)(1, blockSize); + if (model->polytopeRefBlocks == nullptr + || model->polytopeRefBlocks->nextRef == nullptr) { + const std::size_t allocationSize = + sizeof(cm_buildPolytopeRefBlock_t) + + sizeof(cm_buildPolytopeRef_t) + * static_cast(blockSize); + cm_buildPolytopeRefBlock_t* const block = + static_cast( + AllocBuildMemory(allocationSize, true)); + if (block == nullptr) { + return nullptr; + } + block->size = static_cast(allocationSize); + block->nextRef = reinterpret_cast(block + 1); + block->next = model->polytopeRefBlocks; + model->polytopeRefBlocks = block; + for (int index = 0; index + 1 < blockSize; ++index) { + block->nextRef[index].next = &block->nextRef[index + 1]; + } + block->nextRef[blockSize - 1].next = nullptr; + } + cm_buildPolytopeRef_t* const reference = + model->polytopeRefBlocks->nextRef; + model->polytopeRefBlocks->nextRef = reference->next; + reference->next = nullptr; + return reference; +} + +cm_buildPolygon_t* idCollisionModelBuilder::AllocPolygon( + cm_buildModel_t* const model, const int numEdges) { + if (model == nullptr || numEdges < 0) { + return nullptr; + } + if (model->numPolygons + 1 > model->maxPolygons) { + const int capacity = model->maxPolygons + 1024; + cm_buildPolygon_t* const array = GrowBuildArray( + model->polygons, model->numPolygons, capacity); + if (array == nullptr) { + return nullptr; + } + model->polygons = array; + model->maxPolygons = capacity; + } + if (model->numPolygonEdges + numEdges > model->maxPolygonEdges) { + int capacity = model->maxPolygonEdges; + do { + capacity += 1024; + } while (capacity < model->numPolygonEdges + numEdges); + int* const array = GrowBuildArray(model->polygonEdges, + model->numPolygonEdges, capacity); + if (array == nullptr) { + return nullptr; + } + model->polygonEdges = array; + model->maxPolygonEdges = capacity; + } + cm_buildPolygon_t* const polygon = + &model->polygons[model->numPolygons++]; + std::memset(polygon, 0, sizeof(*polygon)); + polygon->numEdges = numEdges; + polygon->firstEdge = model->numPolygonEdges; + model->numPolygonEdges += numEdges; + return polygon; +} + +cm_buildPolytope_t* idCollisionModelBuilder::AllocPolytope( + cm_buildModel_t* const model, const int numPlanes) { + if (model == nullptr || numPlanes < 0) { + return nullptr; + } + if (model->numPolytopes + 1 > model->maxPolytopes) { + int growth = model->maxPolytopes > 256 + ? 256 : model->maxPolytopes; + if (growth == 0) { + growth = 4; + } + const int capacity = model->maxPolytopes + growth; + cm_buildPolytope_t* const array = GrowBuildArray( + model->polytopes, model->numPolytopes, capacity); + if (array == nullptr) { + return nullptr; + } + model->polytopes = array; + model->maxPolytopes = capacity; + } + if (model->numPolytopePlanes + numPlanes + > model->maxPolytopePlanes) { + int growth = model->maxPolytopePlanes > 256 + ? 256 : model->maxPolytopePlanes; + if (growth == 0) { + growth = 8; + } + int capacity = model->maxPolytopePlanes; + do { + capacity += growth; + growth = (std::min)(growth * 2, 256); + } while (capacity < model->numPolytopePlanes + numPlanes); + idPlane* const array = GrowBuildArray(model->polytopePlanes, + model->numPolytopePlanes, capacity); + if (array == nullptr) { + return nullptr; + } + model->polytopePlanes = array; + model->maxPolytopePlanes = capacity; + } + cm_buildPolytope_t* const polytope = + &model->polytopes[model->numPolytopes++]; + std::memset(polytope, 0, sizeof(*polytope)); + polytope->numPlanes = numPlanes; + polytope->firstPlane = model->numPolytopePlanes; + model->numPolytopePlanes += numPlanes; + return polytope; +} + +void idCollisionModelBuilder::AddPolygonToNode(cm_buildModel_t* const model, + cm_buildNode_t* const node, cm_buildPolygon_t* const polygon) { + if (model == nullptr || node == nullptr || polygon == nullptr) { + return; + } + const int blockSize = model->numPolygonRefs < 8 ? 8 : 256; + cm_buildPolygonRef_t* const reference = + AllocPolygonReference(model, blockSize); + if (reference != nullptr) { + reference->polygonNum = static_cast(polygon - model->polygons); + reference->next = node->polygons; + node->polygons = reference; + ++model->numPolygonRefs; + } +} + +void idCollisionModelBuilder::AddPolytopeToNode( + cm_buildModel_t* const model, cm_buildNode_t* const node, + cm_buildPolytope_t* const polytope) { + if (model == nullptr || node == nullptr || polytope == nullptr) { + return; + } + const int blockSize = model->numPolytopeRefs < 8 ? 8 : 256; + cm_buildPolytopeRef_t* const reference = + AllocPolytopeReference(model, blockSize); + if (reference != nullptr) { + reference->polytopeNum = static_cast(polytope - model->polytopes); + reference->next = node->polytopes; + node->polytopes = reference; + ++model->numPolytopeRefs; + } +} + +void idCollisionModelBuilder::GetPrimitiveCounts( + const cm_buildNode_t* const node, int& polygonCount, + int& polytopeCount) { + polygonCount = 0; + polytopeCount = 0; + if (node == nullptr) { + return; + } + for (const cm_buildPolygonRef_t* reference = node->polygons; + reference != nullptr; reference = reference->next) { + ++polygonCount; + } + for (const cm_buildPolytopeRef_t* reference = node->polytopes; + reference != nullptr; reference = reference->next) { + ++polytopeCount; + } +} + +int idCollisionModelBuilder::GetNodeContents( + const cm_buildModel_t* const model, const cm_buildNode_t* node) { + if (model == nullptr || node == nullptr) { + return 0; + } + int contents = 0; + while (node != nullptr) { + for (const cm_buildPolygonRef_t* reference = node->polygons; + reference != nullptr; reference = reference->next) { + contents |= model->polygons[reference->polygonNum].contents; + } + for (const cm_buildPolytopeRef_t* reference = node->polytopes; + reference != nullptr; reference = reference->next) { + contents |= model->polytopes[reference->polytopeNum].contents; + } + if (node->planeType == -1) { + break; + } + contents |= GetNodeContents(model, node->children[1]); + node = node->children[0]; + } + return contents; +} + +void idCollisionModelBuilder::FindSubModels_r( + const cm_buildModel_t*, cm_buildNode_t* buildNode, + int& numModelTreeNodes, int& numSubModels) { + while (buildNode != nullptr) { + if (buildNode->stats.canCreateSubModel) { + ++numSubModels; + return; + } + ++numModelTreeNodes; + if (buildNode->planeType == -1) { + return; + } + FindSubModels_r(nullptr, buildNode->children[0], + numModelTreeNodes, numSubModels); + buildNode = buildNode->children[1]; + } +} + +void idCollisionModelBuilder::FreeModelMemory(cm_buildModel_t* const model) { + if (model == nullptr) { + return; + } + while (model->polygonRefBlocks != nullptr) { + cm_buildPolygonRefBlock_t* const block = model->polygonRefBlocks; + model->polygonRefBlocks = block->next; + mem.Free(block, ALIGN_16); + } + while (model->polytopeRefBlocks != nullptr) { + cm_buildPolytopeRefBlock_t* const block = model->polytopeRefBlocks; + model->polytopeRefBlocks = block->next; + mem.Free(block, ALIGN_16); + } + while (model->nodeBlocks != nullptr) { + cm_buildNodeBlock_t* const block = model->nodeBlocks; + model->nodeBlocks = block->next; + mem.Free(block, ALIGN_16); + } + mem.Free(model->polygonEdges, ALIGN_16); + mem.Free(model->polygons, ALIGN_16); + mem.Free(model->polytopePlanes, ALIGN_16); + mem.Free(model->polytopes, ALIGN_16); + mem.Free(model->edges, ALIGN_16); + mem.Free(model->vertices, ALIGN_16); + model->maxVertices = model->numVertices = 0; + model->vertices = nullptr; + model->maxEdges = model->numEdges = 0; + model->edges = nullptr; + model->maxPolygonEdges = model->numPolygonEdges = 0; + model->polygonEdges = nullptr; + model->maxPolygons = model->numPolygons = 0; + model->polygons = nullptr; + model->maxPolytopePlanes = model->numPolytopePlanes = 0; + model->polytopePlanes = nullptr; + model->maxPolytopes = model->numPolytopes = 0; + model->polytopes = nullptr; + model->numNodes = 0; + model->node = nullptr; + model->nodeBlocks = nullptr; + model->polygonRefBlocks = nullptr; + model->polytopeRefBlocks = nullptr; + model->checkCount = 0; + model->isWorldModel = false; + model->numPolytopeRefs = model->numPolygonRefs = 0; + model->numInternalEdges = model->numSharpEdges = 0; + model->numRemovedPolys = model->numMergedPolys = 0; +} + +void idCollisionModelBuilder::RemovePolygon(cm_buildModel_t* const model, + cm_buildNode_t* node, const int polygonNum) { + if (model == nullptr || node == nullptr || polygonNum < 0 + || polygonNum >= model->numPolygons) { + return; + } + while (node != nullptr) { + cm_buildPolygonRef_t* previous = nullptr; + cm_buildPolygonRef_t* reference = node->polygons; + while (reference != nullptr) { + cm_buildPolygonRef_t* const next = reference->next; + if (reference->polygonNum == polygonNum) { + if (previous != nullptr) { + previous->next = next; + } else { + node->polygons = next; + } + --model->numPolygonRefs; + } else { + previous = reference; + } + reference = next; + } + if (node->planeType == -1) { + return; + } + const cm_buildPolygon_t& polygon = model->polygons[polygonNum]; + const int axis = node->planeType; + if (polygon.bounds[0][axis] <= node->planeDist) { + if (polygon.bounds[1][axis] >= node->planeDist) { + RemovePolygon(model, node->children[1], polygonNum); + node = node->children[0]; + } else { + node = node->children[1]; + } + } else { + node = node->children[0]; + } + } +} + +bool idCollisionModelBuilder::PointInsidePolygon( + cm_buildModel_t* const model, cm_buildPolygon_t* const polygon, + const idVec3& point) { + if (model == nullptr || polygon == nullptr) { + return false; + } + const idVec3 normal(polygon->plane.a, polygon->plane.b, + polygon->plane.c); + for (int edgeIndex = 0; edgeIndex < polygon->numEdges; ++edgeIndex) { + const int reference = + model->polygonEdges[polygon->firstEdge + edgeIndex]; + const cm_buildEdge_t& edge = model->edges[std::abs(reference)]; + const int startVertex = reference < 0 + ? edge.vertexNum[1] : edge.vertexNum[0]; + const int endVertex = reference < 0 + ? edge.vertexNum[0] : edge.vertexNum[1]; + const idVec3& start = model->vertices[startVertex].p; + const idVec3& end = model->vertices[endVertex].p; + const idVec3 delta = end - start; + const idVec3 relative = point - start; + const float side = + (normal.x * delta.z - normal.z * delta.x) * relative.y + + (normal.z * delta.y - normal.y * delta.z) * relative.x + + (normal.y * delta.x - normal.x * delta.y) * relative.z; + if (side > 0.1f) { + return false; + } + } + return true; +} + +bool idCollisionModelBuilder::SplitterDividesPrimitives( + cm_buildModel_t* const model, const cm_buildNode_t* node, + const int planeType, const float planeDist) { + if (model == nullptr || node == nullptr || planeType < 0 + || planeType >= 3) { + return false; + } + bool front = false; + bool back = false; + while (node != nullptr) { + for (const cm_buildPolygonRef_t* reference = node->polygons; + reference != nullptr; reference = reference->next) { + const idBounds& bounds = + model->polygons[reference->polygonNum].bounds; + if (bounds[0][planeType] < planeDist) { + if (bounds[1][planeType] <= planeDist) { + back = true; + } + } else { + front = true; + } + } + for (const cm_buildPolytopeRef_t* reference = node->polytopes; + reference != nullptr; reference = reference->next) { + const idBounds& bounds = + model->polytopes[reference->polytopeNum].bounds; + if (bounds[0][planeType] < planeDist) { + if (bounds[1][planeType] <= planeDist) { + back = true; + } + } else { + front = true; + } + } + if (front && back) { + return true; + } + node = node->parent; + } + return false; +} + +void idCollisionModelBuilder::GetNodeBounds_r( + const cm_buildModel_t* const model, const cm_buildNode_t* node, + idBounds& bounds) { + if (model == nullptr || node == nullptr) { + return; + } + while (node != nullptr) { + for (const cm_buildPolygonRef_t* reference = node->polygons; + reference != nullptr; reference = reference->next) { + AddBounds(bounds, + model->polygons[reference->polygonNum].bounds); + } + for (const cm_buildPolytopeRef_t* reference = node->polytopes; + reference != nullptr; reference = reference->next) { + AddBounds(bounds, + model->polytopes[reference->polytopeNum].bounds); + } + if (node->planeType == -1) { + return; + } + GetNodeBounds_r(model, node->children[1], bounds); + node = node->children[0]; + } +} + +void idCollisionModelBuilder::GetNodeBounds( + const cm_buildModel_t* const model, const cm_buildNode_t* const node, + idBounds& bounds) { + bounds[0].Set(1.0e30f, 1.0e30f, 1.0e30f); + bounds[1].Set(-1.0e30f, -1.0e30f, -1.0e30f); + GetNodeBounds_r(model, node, bounds); + if (bounds[0].x > bounds[1].x) { + bounds[0].Zero(); + bounds[1].Zero(); + } +} + +void idCollisionModelBuilder::GetStatsFromNode( + const cm_buildModel_t* const buildModel, + const cm_buildNode_t* const buildNode, cm_buildNodeStats_t& stats) { + if (buildModel == nullptr || buildNode == nullptr) { + return; + } + cm_buildModel_t* const writable = const_cast( + buildModel); + for (const cm_buildPolygonRef_t* reference = buildNode->polygons; + reference != nullptr; reference = reference->next) { + ++stats.numPrimitiveIndices; + cm_buildPolygon_t& polygon = + writable->polygons[reference->polygonNum]; + if (polygon.checkCount == writable->checkCount) { + continue; + } + polygon.checkCount = writable->checkCount; + ++stats.numPolygons; + stats.numPolygonEdges += polygon.numEdges; + stats.lastNumPolygonEdges = polygon.numEdges; + for (int index = 0; index < polygon.numEdges; ++index) { + cm_buildEdge_t& edge = writable->edges[std::abs( + writable->polygonEdges[polygon.firstEdge + index])]; + if (edge.checkCount == writable->checkCount) { + continue; + } + edge.checkCount = writable->checkCount; + ++stats.numEdges; + for (int endpoint = 0; endpoint < 2; ++endpoint) { + cm_buildVertex_t& vertex = + writable->vertices[edge.vertexNum[endpoint]]; + if (vertex.checkCount != writable->checkCount) { + vertex.checkCount = writable->checkCount; + ++stats.numVertices; + } + } + } + cm_buildMaterial_t& material = + writable->materials[polygon.material]; + if (material.checkCount != writable->checkCount) { + material.checkCount = writable->checkCount; + ++stats.numMaterials; + } + } + for (const cm_buildPolytopeRef_t* reference = buildNode->polytopes; + reference != nullptr; reference = reference->next) { + ++stats.numPrimitiveIndices; + cm_buildPolytope_t& polytope = + writable->polytopes[reference->polytopeNum]; + if (polytope.checkCount == writable->checkCount) { + continue; + } + polytope.checkCount = writable->checkCount; + ++stats.numPolytopes; + stats.numPolytopePlanes += polytope.numPlanes; + cm_buildMaterial_t& material = + writable->materials[polytope.material]; + if (material.checkCount != writable->checkCount) { + material.checkCount = writable->checkCount; + ++stats.numMaterials; + } + } +} + +void idCollisionModelBuilder::CreateStatsForSubTree_r( + const cm_buildModel_t* const buildModel, + const cm_buildNode_t* buildNode, cm_buildNodeStats_t& stats) { + while (buildNode != nullptr) { + ++stats.numNodes; + GetStatsFromNode(buildModel, buildNode, stats); + if (buildNode->planeType == -1) { + return; + } + CreateStatsForSubTree_r(buildModel, buildNode->children[0], stats); + buildNode = buildNode->children[1]; + } +} + +bool idCollisionModelBuilder::TestBoundsRange(const char* const modelName, + const idBounds& bounds) { + for (int axis = 0; axis < 3; ++axis) { + if (bounds[0][axis] < -32768.0f + || bounds[1][axis] > 32767.0f) { + idLibPrint::Warning( + "model '%s' [%1.0f, %1.0f, %1.0f] - " + "[%1.0f, %1.0f, %1.0f] out of range", + modelName != nullptr ? modelName : "", + bounds[0].x, bounds[0].y, bounds[0].z, + bounds[1].x, bounds[1].y, bounds[1].z); + return true; + } + } + return false; +} + +void idCollisionModelBuilder::ParseProcNodes(idLexer* const source) { + if (source == nullptr || source->script_p == nullptr + || source->end_p == nullptr || source->script_p > source->end_p) { + idLibPrint::Warning("ParseProcNodes: invalid lexer source"); + return; + } + ProcTokenScanner scanner(source->script_p, source->end_p); + if (!ParseProcNodesFromScanner(scanner)) { + idLibPrint::Warning("ParseProcNodes: malformed proc node block"); + return; + } + source->script_p = scanner.Position(); +} + +void idCollisionModelBuilder::LoadProcBSP(const char* const name) { + mem.Free(buildData.procNodes, ALIGN_16); + buildData.procNodes = nullptr; + buildData.numProcNodes = 0; + if (name == nullptr || *name == '\0') { + return; + } + std::string fileName(name); + const std::size_t slash = fileName.find_last_of("/\\"); + const std::size_t dot = fileName.find_last_of('.'); + if (dot == std::string::npos + || (slash != std::string::npos && dot < slash)) { + fileName += ".proc"; + } else { + fileName.replace(dot, std::string::npos, ".proc"); + } + std::ifstream input(fileName, std::ios::binary); + if (!input) { + idLibPrint::Warning( + "idCollisionModelBuilder::LoadProcBSP: couldn't load %s", + fileName.c_str()); + return; + } + std::string text((std::istreambuf_iterator(input)), + std::istreambuf_iterator()); + ProcTokenScanner scanner(text.data(), text.data() + text.size()); + std::string token; + if (!scanner.Read(token) + || (token != "mapProcFile006" && token != "mapProcFile005")) { + idLibPrint::Warning( + "idCollisionModelBuilder::LoadProcBSP: bad proc file id"); + return; + } + while (scanner.Read(token)) { + if (token == "nodes") { + if (!ParseProcNodesFromScanner(scanner)) { + idLibPrint::Warning( + "idCollisionModelBuilder::LoadProcBSP: malformed nodes"); + } + return; + } + if (token == "model" || token == "shadowModel" + || token == "interAreaPortals" || token == "areas") { + if (!scanner.SkipBracedSection()) { + break; + } + } + } + idLibPrint::Warning( + "idCollisionModelBuilder::LoadProcBSP: nodes section not found"); +} + +void idCollisionModelBuilder::SetupHash() { + if (buildData.cm_vertexHash == nullptr) { + buildData.cm_vertexHash = new idHashIndex(4096, 1024, + TAG_COLLISION); + } + if (buildData.cm_edgeHash == nullptr) { + buildData.cm_edgeHash = new idHashIndex(0x4000, 1024, + TAG_COLLISION); + } + if (buildData.cm_windingList == nullptr) { + buildData.cm_windingList = new cm_windingList_t{}; + } + if (buildData.cm_outList == nullptr) { + buildData.cm_outList = new cm_windingList_t{}; + } + if (buildData.cm_tmpList == nullptr) { + buildData.cm_tmpList = new cm_windingList_t{}; + } +} + +void idCollisionModelBuilder::ClearHash(const idBounds& bounds) { + SetupHash(); + buildData.cm_vertexHash->Clear(); + buildData.cm_edgeHash->Clear(); + buildData.cm_modelBounds = bounds; + const float maximumExtent = (std::max)(bounds[1].x - bounds[0].x, + (std::max)(bounds[1].y - bounds[0].y, + bounds[1].z - bounds[0].z)); + const int target = static_cast(maximumExtent * (1.0f / 64.0f)); + int shift = 1; + int power = 2; + while (power < target) { + power <<= 1; + ++shift; + } + buildData.cm_vertexShift = shift; +} + +void idCollisionModelBuilder::ShutdownHash() { + delete buildData.cm_vertexHash; + buildData.cm_vertexHash = nullptr; + delete buildData.cm_edgeHash; + buildData.cm_edgeHash = nullptr; + delete buildData.cm_tmpList; + buildData.cm_tmpList = nullptr; + delete buildData.cm_outList; + buildData.cm_outList = nullptr; + delete buildData.cm_windingList; + buildData.cm_windingList = nullptr; + mem.Free(buildData.procNodes, ALIGN_16); + buildData.procNodes = nullptr; + buildData.numProcNodes = 0; +} + +bool idCollisionModelBuilder::GetVertex(cm_buildModel_t* const model, + const idVec3& inputVertex, int& vertexNum) { + if (model == nullptr) { + vertexNum = -1; + return false; + } + if (buildData.cm_vertexHash == nullptr) { + SetupHash(); + } + idVec3 vertex = inputVertex; + for (int axis = 0; axis < 3; ++axis) { + const float rounded = std::floor(vertex[axis] + 0.5f); + if (std::fabs(vertex[axis] - rounded) < 0.01f) { + vertex[axis] = rounded; + } + } + const int x = static_cast( + vertex.x - buildData.cm_modelBounds[0].x + 0.5f); + const int y = static_cast( + vertex.y - buildData.cm_modelBounds[0].y + 0.5f); + const int z = static_cast( + vertex.z - buildData.cm_modelBounds[0].z + 0.5f); + const int key = ((((y + 2) >> 2) << 6) + + ((z + 2) >> 2) + ((x + 2) >> 2)) & 0xFFF; + for (int index = buildData.cm_vertexHash->First(key); + index >= 0; index = buildData.cm_vertexHash->Next(index)) { + const idVec3& existing = model->vertices[index].p; + if (std::fabs(vertex.x - existing.x) < 0.1f + && std::fabs(vertex.y - existing.y) < 0.1f + && std::fabs(vertex.z - existing.z) < 0.1f) { + vertexNum = index; + return true; + } + } + if (model->numVertices >= model->maxVertices) { + const int capacity = static_cast( + model->maxVertices * 1.5f + 1.0f); + cm_buildVertex_t* const array = GrowBuildArray(model->vertices, + model->numVertices, capacity); + if (array == nullptr) { + vertexNum = -1; + return false; + } + model->vertices = array; + model->maxVertices = capacity; + buildData.cm_vertexHash->ResizeIndex(capacity); + } + vertexNum = model->numVertices; + cm_buildVertex_t& destination = model->vertices[model->numVertices++]; + std::memset(&destination, 0, sizeof(destination)); + destination.p = vertex; + buildData.cm_vertexHash->Add(key, vertexNum); + return false; +} + +bool idCollisionModelBuilder::GetEdge(cm_buildModel_t* const model, + const idVec3& vertex1, const idVec3& vertex2, int& edgeNum, + int vertex1Num) { + if (model == nullptr) { + edgeNum = 0; + return false; + } + if (buildData.cm_edgeHash == nullptr) { + SetupHash(); + } + if (model->numEdges == 0) { + model->numEdges = 1; + } + bool firstExisted = true; + if (vertex1Num == -1) { + firstExisted = GetVertex(model, vertex1, vertex1Num); + } + int vertex2Num = -1; + const bool secondExisted = GetVertex(model, vertex2, vertex2Num); + if (vertex1Num == vertex2Num) { + edgeNum = 0; + return true; + } + const int key = (vertex1Num + vertex2Num) + & buildData.cm_edgeHash->hashMask; + if (firstExisted && secondExisted) { + for (int index = buildData.cm_edgeHash->First(key); + index >= 0; index = buildData.cm_edgeHash->Next(index)) { + cm_buildEdge_t& edge = model->edges[index]; + if (edge.numUsers == 1 && edge.vertexNum[0] == vertex2Num + && edge.vertexNum[1] == vertex1Num) { + edgeNum = -index; + ++edge.numUsers; + return true; + } + } + } + if (model->numEdges >= model->maxEdges) { + const int capacity = (std::max)(model->numEdges + 1, + static_cast(model->maxEdges * 1.5f + 1.0f)); + cm_buildEdge_t* const array = GrowBuildArray(model->edges, + model->numEdges, capacity); + if (array == nullptr) { + edgeNum = 0; + return false; + } + model->edges = array; + model->maxEdges = capacity; + buildData.cm_edgeHash->ResizeIndex(capacity); + } + edgeNum = model->numEdges; + cm_buildEdge_t& edge = model->edges[model->numEdges++]; + std::memset(&edge, 0, sizeof(edge)); + edge.vertexNum[0] = vertex1Num; + edge.vertexNum[1] = vertex2Num; + edge.numUsers = 1; + buildData.cm_edgeHash->Add(key, edgeNum); + return false; +} + +cm_buildModel_t* idCollisionModelBuilder::AllocBuildModel() { + cm_buildModel_t* const model = new cm_buildModel_t{}; + model->isWorldModel = false; + model->checkCount = 0; + model->maxVertices = model->numVertices = 0; + model->vertices = nullptr; + model->maxEdges = model->numEdges = 0; + model->edges = nullptr; + model->maxPolygonEdges = model->numPolygonEdges = 0; + model->polygonEdges = nullptr; + model->maxPolygons = model->numPolygons = 0; + model->polygons = nullptr; + model->maxPolytopePlanes = model->numPolytopePlanes = 0; + model->polytopePlanes = nullptr; + model->maxPolytopes = model->numPolytopes = 0; + model->polytopes = nullptr; + model->numNodes = 0; + model->node = nullptr; + model->nodeBlocks = nullptr; + model->polygonRefBlocks = nullptr; + model->polytopeRefBlocks = nullptr; + model->numPolytopeRefs = model->numPolygonRefs = 0; + model->numInternalEdges = model->numSharpEdges = 0; + model->numRemovedPolys = model->numMergedPolys = 0; + return model; +} + +int idCollisionModelBuilder::FindMaterial(cm_buildModel_t* const model, + const int contentFlags, const int surfaceFlags, + const int surfaceType) { + if (model == nullptr) { + return -1; + } + for (int index = 0; index < model->materials.Num(); ++index) { + const cm_buildMaterial_t& material = model->materials[index]; + if (material.contentFlags == contentFlags + && material.surfaceFlags == surfaceFlags + && material.surfaceType == surfaceType) { + return index; + } + } + cm_buildMaterial_t material{}; + material.contentFlags = contentFlags; + material.surfaceFlags = surfaceFlags; + material.surfaceType = surfaceType; + return model->materials.Append(material); +} + +bool idCollisionModelBuilder::IsStaticRenderModel( + const char* const fileName) { + if (fileName == nullptr) { + return false; + } + const char* extension = std::strrchr(fileName, '.'); + if (extension == nullptr) { + return false; + } + ++extension; + return _stricmp(extension, "ase") == 0 + || _stricmp(extension, "lwo") == 0 + || _stricmp(extension, "obj") == 0 + || _stricmp(extension, "model") == 0 + || _stricmp(extension, "bmodel") == 0; +} + +void idCollisionModelBuilder::FilterPolygonIntoTree_r( + cm_buildModel_t* const model, cm_buildNode_t* node, + cm_buildPolygonRef_t* const reference, + cm_buildPolygon_t* const polygon) { + if (model == nullptr || node == nullptr || polygon == nullptr) { + return; + } + while (node->planeType != -1) { + if (CM_R_InsideAllChildren(node, polygon->bounds)) { + break; + } + const int axis = node->planeType; + if (polygon->bounds[0][axis] >= node->planeDist) { + node = node->children[0]; + } else if (polygon->bounds[1][axis] <= node->planeDist) { + node = node->children[1]; + } else { + FilterPolygonIntoTree_r(model, node->children[1], nullptr, + polygon); + node = node->children[0]; + } + if (node == nullptr) { + return; + } + } + if (reference != nullptr) { + reference->next = node->polygons; + node->polygons = reference; + } else { + AddPolygonToNode(model, node, polygon); + } +} + +void idCollisionModelBuilder::FilterPolytopeIntoTree_r( + cm_buildModel_t* const model, cm_buildNode_t* node, + cm_buildPolytopeRef_t* const reference, + cm_buildPolytope_t* const polytope) { + if (model == nullptr || node == nullptr || polytope == nullptr) { + return; + } + while (node->planeType != -1) { + if (CM_R_InsideAllChildren(node, polytope->bounds)) { + break; + } + const int axis = node->planeType; + if (polytope->bounds[0][axis] >= node->planeDist) { + node = node->children[0]; + } else if (polytope->bounds[1][axis] <= node->planeDist) { + node = node->children[1]; + } else { + FilterPolytopeIntoTree_r(model, node->children[1], nullptr, + polytope); + node = node->children[0]; + } + if (node == nullptr) { + return; + } + } + if (reference != nullptr) { + reference->next = node->polytopes; + node->polytopes = reference; + } else { + AddPolytopeToNode(model, node, polytope); + } +} + +bool idCollisionModelBuilder::FindSplitter(cm_buildModel_t* const model, + const cm_buildNode_t* const node, const idBounds& bounds, + int& planeType, float& planeDist) { + if (model == nullptr || node == nullptr) { + return false; + } + int polygonCount = 0; + int polytopeCount = 0; + GetPrimitiveCounts(node, polygonCount, polytopeCount); + if (polygonCount <= 4 && polytopeCount <= 4) { + return false; + } + + int axes[3] = {0, 1, 2}; + std::sort(axes, axes + 3, [&bounds](const int left, const int right) { + return bounds[1][left] - bounds[0][left] + > bounds[1][right] - bounds[0][right]; + }); + const bool dense = polygonCount >= 32 || polytopeCount >= 32; + float bestBalance = FLT_MAX; + bool found = false; + for (const int axis : axes) { + const float extent = bounds[1][axis] - bounds[0][axis]; + if (!dense && extent < 128.0f) { + continue; + } + for (const cm_buildNode_t* source = node; source != nullptr; + source = source->parent) { + for (const cm_buildPolytopeRef_t* reference = source->polytopes; + reference != nullptr; reference = reference->next) { + const idBounds& primitiveBounds = + model->polytopes[reference->polytopeNum].bounds; + for (int side = 0; side < 2; ++side) { + const float candidate = primitiveBounds[side][axis]; + const float balance = std::fabs( + (bounds[1][axis] - candidate) + - (candidate - bounds[0][axis])); + if (candidate > bounds[0][axis] + && candidate < bounds[1][axis] + && balance < bestBalance + && SplitterDividesPrimitives(model, node, axis, + candidate)) { + planeType = axis; + planeDist = candidate; + bestBalance = balance; + found = true; + } + } + } + for (const cm_buildPolygonRef_t* reference = source->polygons; + reference != nullptr; reference = reference->next) { + const idBounds& primitiveBounds = + model->polygons[reference->polygonNum].bounds; + for (int side = 0; side < 2; ++side) { + const float candidate = primitiveBounds[side][axis]; + const float balance = std::fabs( + (bounds[1][axis] - candidate) + - (candidate - bounds[0][axis])); + if (candidate > bounds[0][axis] + && candidate < bounds[1][axis] + && balance < bestBalance + && SplitterDividesPrimitives(model, node, axis, + candidate)) { + planeType = axis; + planeDist = candidate; + bestBalance = balance; + found = true; + } + } + } + } + if (found && (dense + || (planeDist - bounds[0][axis] >= 32.0f + && bounds[1][axis] - planeDist >= 32.0f))) { + return true; + } + } + return false; +} + +cm_buildNode_t* idCollisionModelBuilder::CreateAxialBSPTree_r( + cm_buildModel_t* const model, cm_buildNode_t* const node) { + if (model == nullptr || node == nullptr) { + return node; + } + int planeType = -1; + float planeDist = 0.0f; + if (!FindSplitter(model, node, node->bounds, planeType, planeDist)) { + int polygonCount = 0; + int polytopeCount = 0; + GetPrimitiveCounts(node, polygonCount, polytopeCount); + if (polygonCount > 255) { + idLibPrint::Warning("node has %d polygons", polygonCount); + } + if (polytopeCount > 255) { + idLibPrint::Warning("node has %d polytopes", polytopeCount); + } + node->planeType = -1; + return node; + } + + cm_buildNode_t* const front = AllocNode(model, 256); + cm_buildNode_t* const back = AllocNode(model, 256); + if (front == nullptr || back == nullptr) { + node->planeType = -1; + return node; + } + front->parent = node; + back->parent = node; + front->bounds = node->bounds; + back->bounds = node->bounds; + front->bounds[0][planeType] = planeDist; + back->bounds[1][planeType] = planeDist; + node->planeType = planeType; + node->planeDist = planeDist; + node->children[0] = front; + node->children[1] = back; + + cm_buildPolygonRef_t* polygonReference = node->polygons; + node->polygons = nullptr; + while (polygonReference != nullptr) { + cm_buildPolygonRef_t* const next = polygonReference->next; + polygonReference->next = nullptr; + FilterPolygonIntoTree_r(model, node, polygonReference, + &model->polygons[polygonReference->polygonNum]); + polygonReference = next; + } + cm_buildPolytopeRef_t* polytopeReference = node->polytopes; + node->polytopes = nullptr; + while (polytopeReference != nullptr) { + cm_buildPolytopeRef_t* const next = polytopeReference->next; + polytopeReference->next = nullptr; + FilterPolytopeIntoTree_r(model, node, polytopeReference, + &model->polytopes[polytopeReference->polytopeNum]); + polytopeReference = next; + } + CreateAxialBSPTree_r(model, front); + CreateAxialBSPTree_r(model, back); + return node; +} + +cm_buildNode_t* idCollisionModelBuilder::CreateAxialBSPTree( + cm_buildModel_t* const model) { + if (model == nullptr) { + return nullptr; + } + while (model->polygonRefBlocks != nullptr) { + cm_buildPolygonRefBlock_t* const block = model->polygonRefBlocks; + model->polygonRefBlocks = block->next; + mem.Free(block, ALIGN_16); + } + while (model->polytopeRefBlocks != nullptr) { + cm_buildPolytopeRefBlock_t* const block = model->polytopeRefBlocks; + model->polytopeRefBlocks = block->next; + mem.Free(block, ALIGN_16); + } + while (model->nodeBlocks != nullptr) { + cm_buildNodeBlock_t* const block = model->nodeBlocks; + model->nodeBlocks = block->next; + mem.Free(block, ALIGN_16); + } + model->numNodes = 0; + model->numPolygonRefs = 0; + model->numPolytopeRefs = 0; + model->node = AllocNode(model, 256); + if (model->node == nullptr) { + return nullptr; + } + model->node->bounds[0].Set(1.0e30f, 1.0e30f, 1.0e30f); + model->node->bounds[1].Set(-1.0e30f, -1.0e30f, -1.0e30f); + for (int index = 0; index < model->numPolygons; ++index) { + if (model->polygons[index].numEdges != 0) { + AddBounds(model->node->bounds, model->polygons[index].bounds); + FilterPolygonIntoTree_r(model, model->node, nullptr, + &model->polygons[index]); + } + } + for (int index = 0; index < model->numPolytopes; ++index) { + if (model->polytopes[index].numPlanes != 0) { + AddBounds(model->node->bounds, model->polytopes[index].bounds); + FilterPolytopeIntoTree_r(model, model->node, nullptr, + &model->polytopes[index]); + } + } + if (model->node->bounds[0].x > model->node->bounds[1].x) { + model->node->bounds[0].Zero(); + model->node->bounds[1].Zero(); + } + model->node = CreateAxialBSPTree_r(model, model->node); + int savedPolygons = 0; + int savedPolytopes = 0; + CM_R_TestOptimisation(model->node, savedPolygons, savedPolytopes); + return model->node; +} + +void idCollisionModelBuilder::CreatePolygon(cm_buildModel_t* const model, + idFixedWinding* const winding, const idPlane& plane, + const idMaterial* const material, const int primitiveNum) { + if (model == nullptr || winding == nullptr + || winding->GetNumPoints() < 3) { + return; + } + std::vector edgeNumbers; + edgeNumbers.reserve(static_cast(winding->GetNumPoints())); + int previousVertex = -1; + for (int pointIndex = 0; pointIndex < winding->GetNumPoints(); + ++pointIndex) { + const int nextIndex = (pointIndex + 1) % winding->GetNumPoints(); + int edgeNumber = 0; + GetEdge(model, + idVec3((*winding)[pointIndex].x, (*winding)[pointIndex].y, + (*winding)[pointIndex].z), + idVec3((*winding)[nextIndex].x, (*winding)[nextIndex].y, + (*winding)[nextIndex].z), edgeNumber, previousVertex); + if (edgeNumber == 0) { + continue; + } + bool duplicate = false; + for (const int existing : edgeNumbers) { + duplicate |= std::abs(existing) == std::abs(edgeNumber); + } + if (duplicate) { + return; + } + edgeNumbers.push_back(edgeNumber); + const cm_buildEdge_t& edge = model->edges[std::abs(edgeNumber)]; + previousVertex = edge.vertexNum[edgeNumber >= 0 ? 1 : 0]; + } + if (edgeNumbers.size() < 3) { + return; + } + cm_buildPolygon_t* const polygon = AllocPolygon(model, + static_cast(edgeNumbers.size())); + if (polygon == nullptr) { + return; + } + winding->GetBounds(polygon->bounds); + cm_materialBuildInfo_t materialInfo{}; + CM_GetMaterialBuildInfo(material, materialInfo); + polygon->contents = materialInfo.contents; + polygon->material = FindMaterial(model, materialInfo.contents, + materialInfo.surfaceFlags, materialInfo.surfaceType); + polygon->plane = plane; + polygon->primitiveNum = std::abs(primitiveNum); + polygon->checkCount = 0; + for (int index = 0; index < polygon->numEdges; ++index) { + model->polygonEdges[polygon->firstEdge + index] = + edgeNumbers[static_cast(index)]; + } + if (model->node == nullptr) { + model->node = AllocNode(model, 8); + model->node->bounds = polygon->bounds; + } + FilterPolygonIntoTree_r(model, model->node, nullptr, polygon); +} + +void idCollisionModelBuilder::PolygonFromWinding( + cm_buildModel_t* const model, idFixedWinding* const winding, + const idPlane& plane, const idMaterial* const material, + const int primitiveNum) { + if (model == nullptr || winding == nullptr) { + return; + } + if (winding->IsHuge(131072.0f)) { + idLibPrint::Warning( + "PolygonFromWinding: model %s primitive %d is degenerate", + model->name.c_str(), std::abs(primitiveNum)); + return; + } + CreatePolygon(model, winding, plane, material, primitiveNum); +} + +void idCollisionModelBuilder::AddBuildNodePrimitivesToSubModelNode( + const cm_buildModel_t* const buildModel, cm_buildNode_t* const buildNode, + cm_subModelPtrs_t& subModelPtrs, cm_subModelData_t& counts, + cm_node_t& node) { + if (buildModel == nullptr || buildNode == nullptr) { + return; + } + cm_buildModel_t* const writable = const_cast( + buildModel); + const auto packMaterial = [&](cm_buildMaterial_t& source) { + if (source.checkCount == writable->checkCount) { + return; + } + source.checkCount = writable->checkCount; + source.index = counts.numMaterials; + cm_material_t& destination = + subModelPtrs.materials[counts.numMaterials++]; + destination.contentFlags = source.contentFlags; + destination.surfaceFlags = source.surfaceFlags; + destination.surfaceType = source.surfaceType; + destination.surfaceColor[0] = 0xFF; + destination.surfaceColor[1] = 0xFF; + destination.surfaceColor[2] = 0xFF; + destination.pad = 0; + }; + + for (cm_buildPolygonRef_t* reference = buildNode->polygons; + reference != nullptr; reference = reference->next) { + cm_buildPolygon_t& source = + writable->polygons[reference->polygonNum]; + if (source.checkCount != writable->checkCount) { + source.checkCount = writable->checkCount; + cm_buildMaterial_t& sourceMaterial = + writable->materials[source.material]; + packMaterial(sourceMaterial); + source.index = counts.numPolygons; + cm_polygon_t& destination = + subModelPtrs.polygons[counts.numPolygons++]; + destination.bounds.SetBounds(source.bounds); + destination.material = static_cast( + sourceMaterial.index); + destination.firstEdge = static_cast( + counts.numPolygonEdges); + destination.numEdges = static_cast( + source.numEdges); + for (int polygonEdge = 0; polygonEdge < source.numEdges; + ++polygonEdge) { + const int sourceReference = writable->polygonEdges[ + source.firstEdge + polygonEdge]; + cm_buildEdge_t& sourceEdge = + writable->edges[std::abs(sourceReference)]; + if (sourceEdge.checkCount != writable->checkCount) { + sourceEdge.checkCount = writable->checkCount; + for (int endpoint = 0; endpoint < 2; ++endpoint) { + cm_buildVertex_t& sourceVertex = + writable->vertices[ + sourceEdge.vertexNum[endpoint]]; + if (sourceVertex.checkCount + != writable->checkCount) { + sourceVertex.checkCount = writable->checkCount; + sourceVertex.index = counts.numVertices; + cm_vertex_t& destinationVertex = + subModelPtrs.vertices[ + counts.numVertices++]; + destinationVertex.p = sourceVertex.p; + destinationVertex.st[0] = sourceVertex.st[0]; + destinationVertex.st[1] = sourceVertex.st[1]; + } + } + sourceEdge.index = counts.numEdges; + cm_edge_t& destinationEdge = + subModelPtrs.edges[counts.numEdges++]; + destinationEdge.vertexNum[0] = + static_cast(writable->vertices[ + sourceEdge.vertexNum[0]].index); + destinationEdge.vertexNum[1] = + static_cast(writable->vertices[ + sourceEdge.vertexNum[1]].index); + } + std::uint16_t packedReference = + static_cast(sourceEdge.index); + if (sourceReference <= 0) { + packedReference |= 0x8000u; + } + if (sourceEdge.internal != 0) { + packedReference |= 0x4000u; + } + subModelPtrs.polygonEdges[counts.numPolygonEdges++] = + packedReference; + } + } + for (int index = node.numPolytopes; index > 0; --index) { + subModelPtrs.primitiveIndices[node.firstPrimitive + + node.numPolygons + index] = + subModelPtrs.primitiveIndices[node.firstPrimitive + + node.numPolygons + index - 1]; + } + subModelPtrs.primitiveIndices[node.firstPrimitive + + node.numPolygons++] = static_cast(source.index); + ++counts.numPrimitiveIndices; + } + + for (cm_buildPolytopeRef_t* reference = buildNode->polytopes; + reference != nullptr; reference = reference->next) { + cm_buildPolytope_t& source = + writable->polytopes[reference->polytopeNum]; + if (source.checkCount != writable->checkCount) { + source.checkCount = writable->checkCount; + cm_buildMaterial_t& sourceMaterial = + writable->materials[source.material]; + packMaterial(sourceMaterial); + source.index = counts.numPolytopes; + cm_polytope_t& destination = + subModelPtrs.polytopes[counts.numPolytopes++]; + destination.bounds.SetBounds(source.bounds); + destination.material = static_cast( + sourceMaterial.index); + destination.firstPlane = static_cast( + counts.numPolytopePlanes); + destination.numPlanes = static_cast( + source.numPlanes); + for (int plane = 0; plane < source.numPlanes; ++plane) { + subModelPtrs.polytopePlanes[ + counts.numPolytopePlanes++] = + writable->polytopePlanes[source.firstPlane + plane]; + } + } + subModelPtrs.primitiveIndices[node.firstPrimitive + + node.numPolygons + node.numPolytopes++] = + static_cast(source.index); + ++counts.numPrimitiveIndices; + } +} + +void idCollisionModelBuilder::CreateSingleSubModel_r( + const cm_buildModel_t* const buildModel, cm_buildNode_t* buildNode, + cm_subModelPtrs_t& subModelPtrs, cm_subModelData_t& counts, + cm_node_t* parent) { + while (buildNode != nullptr) { + const int nodeIndex = counts.numNodes++; + cm_node_t& node = subModelPtrs.nodes[nodeIndex]; + node.planeType = buildNode->planeType; + node.planeDist = buildNode->planeDist; + node.children[0] = node.children[1] = 0; + node.firstPrimitive = static_cast( + counts.numPrimitiveIndices); + node.numPolygons = 0; + node.numPolytopes = 0; + if (parent != nullptr && buildNode->parent != nullptr) { + const int side = buildNode == buildNode->parent->children[1] + ? 1 : 0; + parent->children[side] = static_cast(nodeIndex); + } else { + for (cm_buildNode_t* ancestor = buildNode->parent; + ancestor != nullptr; ancestor = ancestor->parent) { + AddBuildNodePrimitivesToSubModelNode(buildModel, ancestor, + subModelPtrs, counts, node); + } + } + AddBuildNodePrimitivesToSubModelNode(buildModel, buildNode, + subModelPtrs, counts, node); + if (buildNode->planeType == -1) { + return; + } + parent = &node; + CreateSingleSubModel_r(buildModel, buildNode->children[0], + subModelPtrs, counts, parent); + buildNode = buildNode->children[1]; + } +} + +void idCollisionModelBuilder::CreateNodeStats_r( + const cm_buildModel_t* const buildModel, cm_buildNode_t* buildNode) { + if (buildModel == nullptr) { + return; + } + cm_buildModel_t* const writable = const_cast( + buildModel); + while (buildNode != nullptr) { + ++writable->checkCount; + std::memset(&buildNode->stats, 0, sizeof(buildNode->stats)); + for (cm_buildNode_t* ancestor = buildNode->parent; + ancestor != nullptr; ancestor = ancestor->parent) { + GetStatsFromNode(buildModel, ancestor, buildNode->stats); + } + CreateStatsForSubTree_r(buildModel, buildNode, buildNode->stats); + if (buildNode->stats.lastNumPolygonEdges > 0) { + buildNode->stats.numPolygonEdges = + buildNode->stats.numPolygonEdges + - (buildNode->stats.lastNumPolygonEdges & 3) + 4; + } + CalculateSubModelDataSize(buildNode->stats); + if (buildNode->planeType == -1) { + return; + } + CreateNodeStats_r(buildModel, buildNode->children[0]); + buildNode = buildNode->children[1]; + } +} + +void idCollisionModelBuilder::CreateSubModels_r( + const cm_buildModel_t* const buildModel, cm_buildNode_t* buildNode, + idCollisionModelLocal* const model, cm_modelTreeNode_t* parent) { + if (buildModel == nullptr || model == nullptr) { + return; + } + while (buildNode != nullptr) { + if (buildNode->stats.canCreateSubModel) { + const int subModelIndex = model->polygonModel.numSubModels; + cm_subModel_t& subModel = + model->polygonModel.subModels[subModelIndex]; + cm_subModelPtrs_t pointers{}; + if (!AllocSubModelData(buildNode->stats, buildNode->bounds, + subModel, pointers)) { + return; + } + subModel.fileOffset = -1; + subModel.numUsers = 0; + subModel.state = &model->polygonModel.subModelState[ + subModelIndex]; + *subModel.state = SUBMODEL_STATE_LOADED; + cm_subModelData_t counts{}; + ++const_cast(buildModel)->checkCount; + CreateSingleSubModel_r(buildModel, buildNode, pointers, counts, + nullptr); + if (counts.numPolygons > 0) { + const int padding = 4 - (pointers.polygons[ + counts.numPolygons - 1].numEdges & 3); + for (int index = 0; index < padding; ++index) { + pointers.polygonEdges[counts.numPolygonEdges] = + pointers.polygonEdges[counts.numPolygonEdges - 1]; + ++counts.numPolygonEdges; + } + } + subModel.data->header = subModel.header; + subModel.data->header.loadedSize = + subModel.data->header.totalSize; + ++model->polygonModel.numSubModels; + if (parent != nullptr && buildNode->parent != nullptr) { + const int side = + buildNode == buildNode->parent->children[1] ? 1 : 0; + parent->children[side] = + -model->polygonModel.numSubModels; + } + return; + } + + const int treeNodeIndex = model->polygonModel.numModelTreeNodes++; + cm_modelTreeNode_t& node = + model->polygonModel.modelTreeNodes[treeNodeIndex]; + node.planeType = buildNode->planeType; + node.planeDist = buildNode->planeDist; + node.children[0] = node.children[1] = 0; + if (parent != nullptr && buildNode->parent != nullptr) { + const int side = buildNode == buildNode->parent->children[1] + ? 1 : 0; + parent->children[side] = treeNodeIndex; + } + if (buildNode->planeType == -1) { + return; + } + parent = &node; + CreateSubModels_r(buildModel, buildNode->children[0], model, + parent); + buildNode = buildNode->children[1]; + } +} + +void idCollisionModelBuilder::AddSubModelsToCollisionModel( + idCollisionModelLocal* const model, + const cm_buildModel_t* const buildModel) { + if (model == nullptr || buildModel == nullptr + || buildModel->node == nullptr) { + return; + } + idBounds nodeBounds; + GetNodeBounds(buildModel, buildModel->node, nodeBounds); + if (model->bounds[0].x > model->bounds[1].x) { + model->bounds = nodeBounds; + } else { + AddBounds(model->bounds, nodeBounds); + } + model->contents |= GetNodeContents(buildModel, buildModel->node); + CreateNodeStats_r(buildModel, buildModel->node); + int addTreeNodes = 0; + int addSubModels = 0; + FindSubModels_r(buildModel, buildModel->node, addTreeNodes, + addSubModels); + const int oldTreeNodes = model->polygonModel.numModelTreeNodes; + const int oldSubModels = model->polygonModel.numSubModels; + const int totalTreeNodes = oldTreeNodes + addTreeNodes; + const int totalSubModels = oldSubModels + addSubModels; + if (totalTreeNodes > 0x8000 || totalSubModels > 0x4000) { + idLibPrint::Warning("collision model tree exceeds recovered limits"); + return; + } + + cm_modelTreeNode_t* newTree = nullptr; + if (totalTreeNodes > 0) { + newTree = static_cast(_aligned_malloc( + sizeof(cm_modelTreeNode_t) + * static_cast(totalTreeNodes), 16)); + if (newTree == nullptr) { + return; + } + if (oldTreeNodes > 0) { + std::memcpy(newTree, model->polygonModel.modelTreeNodes, + sizeof(cm_modelTreeNode_t) + * static_cast(oldTreeNodes)); + } + } + cm_subModel_t* const newSubModels = + static_cast(_aligned_malloc( + sizeof(cm_subModel_t) + * static_cast(totalSubModels), 16)); + volatile std::uint8_t* const newStates = + static_cast(_aligned_malloc( + static_cast(totalSubModels), 16)); + if (newSubModels == nullptr || newStates == nullptr) { + _aligned_free(newTree); + _aligned_free(newSubModels); + _aligned_free(const_cast(newStates)); + return; + } + std::memset(newSubModels, 0, + sizeof(cm_subModel_t) * static_cast(totalSubModels)); + if (oldSubModels > 0) { + std::memcpy(newSubModels, model->polygonModel.subModels, + sizeof(cm_subModel_t) * static_cast(oldSubModels)); + } + for (int index = 0; index < totalSubModels; ++index) { + newStates[index] = SUBMODEL_STATE_LOADED; + newSubModels[index].state = &newStates[index]; + } + _aligned_free(model->polygonModel.modelTreeNodes); + _aligned_free(model->polygonModel.subModels); + _aligned_free(const_cast( + model->polygonModel.subModelState)); + model->polygonModel.modelTreeNodes = newTree; + model->polygonModel.subModels = newSubModels; + model->polygonModel.subModelState = newStates; + model->polygonModel.numModelTreeNodes = oldTreeNodes; + model->polygonModel.numSubModels = oldSubModels; + CreateSubModels_r(buildModel, buildModel->node, model, nullptr); +} + +int idCollisionModelBuilder::CountModelTreeNodes_r( + idCollisionModelLocal* const model, const int nodeNum, + idBounds& bounds) { + if (model == nullptr) { + return 0; + } + if (nodeNum < 0) { + const int subModelIndex = -nodeNum - 1; + if (subModelIndex >= 0 + && subModelIndex < model->polygonModel.numSubModels) { + AddBounds(bounds, model->polygonModel.subModels[ + subModelIndex].header.bounds); + } + return 0; + } + if (nodeNum >= model->polygonModel.numModelTreeNodes) { + return 0; + } + const cm_modelTreeNode_t& node = + model->polygonModel.modelTreeNodes[nodeNum]; + if (node.planeType == -1) { + return 1; + } + return CountModelTreeNodes_r(model, node.children[1], bounds) + + CountModelTreeNodes_r(model, node.children[0], bounds) + 1; +} + +void idCollisionModelBuilder::MergeModelTrees( + idCollisionModelLocal* const model) { + if (model == nullptr || model->polygonModel.numModelTreeNodes <= 0 + || model->polygonModel.modelTreeNodes == nullptr) { + return; + } + // The recovered retail function inventories each independently appended + // root and its bounds; it does not rewrite the serialized node array. + // Keeping the roots sequential is significant because stream-area + // submodels refer to the original negative child indices. + int root = 0; + while (root < model->polygonModel.numModelTreeNodes) { + idBounds bounds; + bounds[0].Set(1.0e30f, 1.0e30f, 1.0e30f); + bounds[1].Set(-1.0e30f, -1.0e30f, -1.0e30f); + const int descendants = CountModelTreeNodes_r(model, root, bounds); + const int nextRoot = root + descendants + 1; + if (nextRoot <= root) { + break; + } + root = nextRoot; + } +} + +void idCollisionModelBuilder::GenerateEdgeNormals_r( + cm_buildModel_t* const model, cm_buildNode_t* node) { + if (model == nullptr) { + return; + } + while (node != nullptr) { + for (cm_buildPolygonRef_t* reference = node->polygons; + reference != nullptr; reference = reference->next) { + cm_buildPolygon_t& polygon = + model->polygons[reference->polygonNum]; + if (polygon.checkCount == model->checkCount) { + continue; + } + polygon.checkCount = model->checkCount; + const idVec3 planeNormal(polygon.plane.a, polygon.plane.b, + polygon.plane.c); + for (int polygonEdge = 0; polygonEdge < polygon.numEdges; + ++polygonEdge) { + const int edgeReference = model->polygonEdges[ + polygon.firstEdge + polygonEdge]; + cm_buildEdge_t& edge = + model->edges[std::abs(edgeReference)]; + if (edge.normal.LengthSqr() != 0.0f) { + if (edge.normal.Dot(planeNormal) >= -0.7f) { + edge.normal = edge.normal + planeNormal; + edge.normal.NormalizeFast(); + } else { + const int startIndex = edgeReference < 0 + ? edge.vertexNum[1] : edge.vertexNum[0]; + const int endIndex = edgeReference < 0 + ? edge.vertexNum[0] : edge.vertexNum[1]; + idVec3 direction = model->vertices[endIndex].p + - model->vertices[startIndex].p; + direction.NormalizeFast(); + idVec3 sharp = direction.Cross(edge.normal) + + planeNormal.Cross(direction); + sharp.NormalizeFast(); + edge.normal = sharp * 3.3333333f; + ++model->numSharpEdges; + } + } else if (edge.numUsers == 1) { + const int startIndex = edgeReference < 0 + ? edge.vertexNum[1] : edge.vertexNum[0]; + const int endIndex = edgeReference < 0 + ? edge.vertexNum[0] : edge.vertexNum[1]; + idVec3 direction = model->vertices[endIndex].p + - model->vertices[startIndex].p; + direction.NormalizeFast(); + edge.normal = direction.Cross(planeNormal); + edge.normal.NormalizeFast(); + edge.normal = edge.normal + planeNormal; + edge.normal.NormalizeFast(); + } else { + edge.normal = planeNormal; + } + } + } + if (node->planeType == -1) { + return; + } + GenerateEdgeNormals_r(model, node->children[1]); + node = node->children[0]; + } +} + +bool idCollisionModelBuilder::ChoppedAwayByProcBSP_r( + int nodeNum, idFixedWinding* const winding, const idVec3& normal, + const idVec3& origin, const float radius) { + if (winding == nullptr || buildData.procNodes == nullptr + || buildData.numProcNodes <= 0) { + return false; + } + std::function walk; + walk = [&](int currentNode, idFixedWinding* fragment, + bool rootNode) -> bool { + while (rootNode || currentNode > 0) { + rootNode = false; + if (currentNode < 0 || currentNode >= buildData.numProcNodes) { + return false; + } + const cm_procNode_t& node = buildData.procNodes[currentNode]; + const float distance = node.plane.Distance(origin); + if (distance > radius) { + currentNode = node.children[0]; + continue; + } + if (distance < -radius) { + currentNode = node.children[1]; + continue; + } + idFixedWinding back; + const int side = fragment->SplitInPlace(node.plane, 0.1f, + &back); + if (side == 0) { + currentNode = node.children[0]; + } else if (side == 1) { + currentNode = node.children[1]; + } else if (side == 2) { + currentNode = node.plane.Normal().Dot(normal) > 0.0f + ? node.children[0] : node.children[1]; + } else { + if (!walk(node.children[1], &back, false)) { + return false; + } + currentNode = node.children[0]; + } + } + return currentNode == 0; + }; + return walk(nodeNum, winding, nodeNum == 0); +} + +bool idCollisionModelBuilder::ChoppedAwayByProcBSP( + const idFixedWinding& winding, const idPlane& plane, + const int contents) { + if (buildData.procNodes == nullptr || buildData.numProcNodes == 0 + || (contents & 1) == 0) { + return false; + } + idFixedWinding clipped; + clipped = winding; + idBounds bounds; + clipped.GetBounds(bounds); + const idVec3 origin = (bounds[0] + bounds[1]) * 0.5f; + const float radius = (bounds[1] - origin).Length() + 0.1f; + return ChoppedAwayByProcBSP_r(0, &clipped, plane.Normal(), origin, + radius); +} + +void idCollisionModelBuilder::ReplacePolygons(cm_buildModel_t* const model, + cm_buildNode_t* node, const int polygonNum1, const int polygonNum2, + const int newPolygonNum) { + if (model == nullptr || node == nullptr) { + return; + } + while (node != nullptr) { + bool replaced = false; + cm_buildPolygonRef_t* previous = nullptr; + cm_buildPolygonRef_t* reference = node->polygons; + while (reference != nullptr) { + cm_buildPolygonRef_t* const next = reference->next; + if (reference->polygonNum == polygonNum1 + || reference->polygonNum == polygonNum2) { + if (!replaced) { + reference->polygonNum = newPolygonNum; + replaced = true; + previous = reference; + } else { + if (previous != nullptr) { + previous->next = next; + } else { + node->polygons = next; + } + --model->numPolygonRefs; + } + } else { + previous = reference; + } + reference = next; + } + if (node->planeType == -1) { + return; + } + const int axis = node->planeType; + const idBounds& first = model->polygons[polygonNum1].bounds; + const idBounds& second = model->polygons[polygonNum2].bounds; + if (first[0][axis] <= node->planeDist + || second[0][axis] <= node->planeDist) { + if (first[1][axis] >= node->planeDist + || second[1][axis] >= node->planeDist) { + ReplacePolygons(model, node->children[1], polygonNum1, + polygonNum2, newPolygonNum); + node = node->children[0]; + } else { + node = node->children[1]; + } + } else { + node = node->children[0]; + } + } +} + +void idCollisionModelBuilder::FindInternalEdgesOnPolygon( + cm_buildModel_t* const model, cm_buildPolygon_t* const polygon1, + cm_buildPolygon_t* const polygon2) { + if (model == nullptr || polygon1 == nullptr || polygon2 == nullptr) { + return; + } + for (int axis = 0; axis < 3; ++axis) { + if (polygon1->bounds[0][axis] > polygon2->bounds[1][axis] + || polygon1->bounds[1][axis] < polygon2->bounds[0][axis]) { + return; + } + } + const idVec3 firstNormal(polygon1->plane.a, polygon1->plane.b, + polygon1->plane.c); + const idVec3 secondNormal(polygon2->plane.a, polygon2->plane.b, + polygon2->plane.c); + for (int polygonEdge = 0; polygonEdge < polygon1->numEdges; + ++polygonEdge) { + const int edgeReference = model->polygonEdges[ + polygon1->firstEdge + polygonEdge]; + cm_buildEdge_t& edge = model->edges[std::abs(edgeReference)]; + if (edge.internal != 0) { + continue; + } + const int startIndex = edgeReference < 0 + ? edge.vertexNum[1] : edge.vertexNum[0]; + const int endIndex = edgeReference < 0 + ? edge.vertexNum[0] : edge.vertexNum[1]; + const idVec3& start = model->vertices[startIndex].p; + const idVec3& end = model->vertices[endIndex].p; + bool insideBounds = true; + for (int axis = 0; axis < 3; ++axis) { + insideBounds &= start[axis] <= polygon2->bounds[1][axis] + 0.1f + && end[axis] <= polygon2->bounds[1][axis] + 0.1f + && start[axis] >= polygon2->bounds[0][axis] - 0.1f + && end[axis] >= polygon2->bounds[0][axis] - 0.1f; + } + if (!insideBounds) { + continue; + } + int matchingEdge = -1; + for (int otherEdge = 0; otherEdge < polygon2->numEdges; + ++otherEdge) { + if (std::abs(model->polygonEdges[polygon2->firstEdge + + otherEdge]) == std::abs(edgeReference)) { + matchingEdge = otherEdge; + break; + } + } + if (matchingEdge < 0 + && (std::fabs(polygon2->plane.Distance(start)) > 0.1f + || std::fabs(polygon2->plane.Distance(end)) > 0.1f)) { + continue; + } + if (matchingEdge >= 0 + && (edge.numUsers > 2 + || edgeReference == model->polygonEdges[ + polygon2->firstEdge + matchingEdge])) { + continue; + } + if (secondNormal.Dot(firstNormal.Cross(end - start)) < 0.0f) { + return; + } + if (matchingEdge >= 0 + || (PointInsidePolygon(model, polygon2, start) + && PointInsidePolygon(model, polygon2, end))) { + edge.internal = 1; + ++model->numInternalEdges; + } + } +} + +void idCollisionModelBuilder::FindInternalPolygonEdges( + cm_buildModel_t* const model, cm_buildNode_t* node, + cm_buildPolygon_t* const polygon) { + if (model == nullptr || polygon == nullptr) { + return; + } + while (node != nullptr) { + for (cm_buildPolygonRef_t* reference = node->polygons; + reference != nullptr; reference = reference->next) { + cm_buildPolygon_t& candidate = + model->polygons[reference->polygonNum]; + if (&candidate != polygon + && candidate.material == polygon->material + && candidate.contents == polygon->contents) { + FindInternalEdgesOnPolygon(model, polygon, &candidate); + } + } + if (node->planeType == -1) { + return; + } + const int axis = node->planeType; + if (polygon->bounds[0][axis] <= node->planeDist) { + if (polygon->bounds[1][axis] >= node->planeDist) { + FindInternalPolygonEdges(model, node->children[1], polygon); + node = node->children[0]; + } else { + node = node->children[1]; + } + } else { + node = node->children[0]; + } + } +} + +void idCollisionModelBuilder::FindInternalEdges(cm_buildModel_t* const model, + cm_buildNode_t* node) { + if (model == nullptr) { + return; + } + while (node != nullptr) { + for (cm_buildPolygonRef_t* reference = node->polygons; + reference != nullptr; reference = reference->next) { + cm_buildPolygon_t& polygon = + model->polygons[reference->polygonNum]; + if (polygon.checkCount != model->checkCount) { + polygon.checkCount = model->checkCount; + FindInternalPolygonEdges(model, model->node, &polygon); + } + } + if (node->planeType == -1) { + return; + } + FindInternalEdges(model, node->children[1]); + node = node->children[0]; + } +} + +void idCollisionModelBuilder::OffsetPolygonEdges( + cm_buildModel_t* const model, cm_buildPolygon_t* const polygon) { + if (model == nullptr || polygon == nullptr || polygon->numEdges <= 1) { + return; + } + int bestStart = 0; + float lowestAlignment = 1.0f; + for (int edgeIndex = 0; edgeIndex < polygon->numEdges; ++edgeIndex) { + const int previousIndex = (edgeIndex + polygon->numEdges - 1) + % polygon->numEdges; + const int currentReference = model->polygonEdges[ + polygon->firstEdge + edgeIndex]; + const int previousReference = model->polygonEdges[ + polygon->firstEdge + previousIndex]; + const cm_buildEdge_t& current = + model->edges[std::abs(currentReference)]; + const cm_buildEdge_t& previous = + model->edges[std::abs(previousReference)]; + const int cornerIndex = currentReference < 0 + ? current.vertexNum[1] : current.vertexNum[0]; + const int currentEnd = currentReference < 0 + ? current.vertexNum[0] : current.vertexNum[1]; + const int previousStart = previousReference < 0 + ? previous.vertexNum[1] : previous.vertexNum[0]; + idVec3 outgoing = model->vertices[currentEnd].p + - model->vertices[cornerIndex].p; + idVec3 incoming = model->vertices[previousStart].p + - model->vertices[cornerIndex].p; + outgoing.NormalizeFast(); + incoming.NormalizeFast(); + const float alignment = std::fabs(outgoing.Dot(incoming)); + if (alignment < lowestAlignment) { + lowestAlignment = alignment; + bestStart = edgeIndex; + } + } + std::vector rotated(static_cast(polygon->numEdges)); + for (int index = 0; index < polygon->numEdges; ++index) { + rotated[static_cast(index)] = model->polygonEdges[ + polygon->firstEdge + + (bestStart + index) % polygon->numEdges]; + } + std::copy(rotated.begin(), rotated.end(), + model->polygonEdges + polygon->firstEdge); +} + +void idCollisionModelBuilder::OffsetPolygonEdges_r( + cm_buildModel_t* const model, cm_buildNode_t* node) { + if (model == nullptr) { + return; + } + while (node != nullptr) { + for (cm_buildPolygonRef_t* reference = node->polygons; + reference != nullptr; reference = reference->next) { + cm_buildPolygon_t& polygon = + model->polygons[reference->polygonNum]; + if (polygon.checkCount != model->checkCount) { + polygon.checkCount = model->checkCount; + OffsetPolygonEdges(model, &polygon); + } + } + if (node->planeType == -1) { + return; + } + OffsetPolygonEdges_r(model, node->children[1]); + node = node->children[0]; + } +} + +void idCollisionModelBuilder::ChopWindingListWithPolytope( + cm_windingList_t* const list, const cm_buildModel_t* const model, + const cm_buildPolytope_t* const polytope) { + if (list == nullptr || model == nullptr || polytope == nullptr + || list->numWindings <= 0 || polytope->numPlanes <= 0 + || polytope->numPlanes > 64) { + return; + } + + // A polytope plane points out of the solid. Partition every source + // winding by the inward-facing planes: front fragments are outside and + // survive, while the final back fragment lies inside all planes and is + // removed. This is the scalar PC spelling of the recovered list ping- + // pong implementation. + std::vector surviving; + surviving.reserve(static_cast(list->numWindings)); + for (int sourceIndex = 0; sourceIndex < list->numWindings; + ++sourceIndex) { + std::vector candidates; + candidates.push_back(list->w[sourceIndex]); + std::vector outside; + for (int planeIndex = 0; planeIndex < polytope->numPlanes + && !candidates.empty(); ++planeIndex) { + const idPlane& outward = model->polytopePlanes[ + polytope->firstPlane + planeIndex]; + idPlane inward(-outward.a, -outward.b, -outward.c, + -outward.d); + std::vector nextCandidates; + for (idFixedWinding& candidate : candidates) { + idFixedWinding back; + const int side = candidate.SplitInPlace(inward, 0.1f, + &back); + if (side == 0) { + nextCandidates.push_back(candidate); + } else if (side == 1) { + outside.push_back(candidate); + } else if (side == 2) { + // Coplanar primitive faces are only discarded when they + // face into the clipping solid. This preserves the + // recovered world/non-world coplanar convention. + const idVec3 planeNormal(outward.a, outward.b, + outward.c); + if (list->primitiveNum >= 0 + && planeNormal.Dot(list->normal) > 0.0f) { + outside.push_back(candidate); + } else { + nextCandidates.push_back(candidate); + } + } else { + nextCandidates.push_back(candidate); + outside.push_back(back); + } + if (outside.size() + nextCandidates.size() >= 256u) { + break; + } + } + candidates.swap(nextCandidates); + } + // Anything left in candidates is inside every plane and is chopped. + for (const idFixedWinding& fragment : outside) { + if (surviving.size() >= 256u) { + break; + } + surviving.push_back(fragment); + } + } + list->numWindings = static_cast((std::min)(surviving.size(), + static_cast(256))); + for (int index = 0; index < list->numWindings; ++index) { + list->w[index] = surviving[static_cast(index)]; + } +} + +void idCollisionModelBuilder::ChopWindingListWithTreePolytopes_r( + cm_windingList_t* const list, const cm_buildModel_t* const model, + const cm_buildNode_t* node) { + if (list == nullptr || model == nullptr || node == nullptr + || list->numWindings <= 0) { + return; + } + while (node != nullptr) { + for (cm_buildPolytopeRef_t* reference = node->polytopes; + reference != nullptr; reference = reference->next) { + cm_buildPolytope_t& polytope = const_cast( + model->polytopes[reference->polytopeNum]); + if (polytope.checkCount == model->checkCount) { + continue; + } + polytope.checkCount = model->checkCount; + if (polytope.primitiveNum != list->primitiveNum + && polytope.contents == list->contents + && BoundsOverlap(polytope.bounds, list->bounds)) { + ChopWindingListWithPolytope(list, model, &polytope); + if (list->numWindings == 0) { + return; + } + } + } + if (node->planeType == -1) { + return; + } + const int axis = node->planeType; + if (list->bounds[0][axis] <= node->planeDist) { + if (list->bounds[1][axis] >= node->planeDist) { + ChopWindingListWithTreePolytopes_r(list, model, + node->children[1]); + if (list->numWindings == 0) { + return; + } + node = node->children[0]; + } else { + node = node->children[1]; + } + } else { + node = node->children[0]; + } + } +} + +cm_buildPolygon_t* idCollisionModelBuilder::TryMergePolygons( + cm_buildModel_t* const model, const int polygonNum1, + const int polygonNum2) { + if (model == nullptr || polygonNum1 < 0 || polygonNum2 < 0 + || polygonNum1 >= model->numPolygons + || polygonNum2 >= model->numPolygons + || polygonNum1 == polygonNum2) { + return nullptr; + } + const cm_buildPolygon_t& first = model->polygons[polygonNum1]; + const cm_buildPolygon_t& second = model->polygons[polygonNum2]; + if (first.numEdges < 3 || second.numEdges < 3 + || first.material != second.material + || first.contents != second.contents + || std::fabs(first.plane.a - second.plane.a) > 0.0001f + || std::fabs(first.plane.b - second.plane.b) > 0.0001f + || std::fabs(first.plane.c - second.plane.c) > 0.0001f + || std::fabs(first.plane.d - second.plane.d) > 0.01f + || !BoundsOverlap(first.bounds, second.bounds)) { + return nullptr; + } + for (int edgeIndex = 0; edgeIndex < second.numEdges; ++edgeIndex) { + const int reference = model->polygonEdges[ + second.firstEdge + edgeIndex]; + const idVec3& point = model->vertices[ + DirectedEdgeStart(model, reference)].p; + if (std::fabs(first.plane.Distance(point)) > 0.1f) { + return nullptr; + } + } + + std::vector boundary; + boundary.reserve(static_cast( + first.numEdges + second.numEdges)); + int sharedEdges = 0; + const auto appendUnshared = [&](const cm_buildPolygon_t& polygon, + const cm_buildPolygon_t& other) { + for (int edgeIndex = 0; edgeIndex < polygon.numEdges; ++edgeIndex) { + const int reference = model->polygonEdges[ + polygon.firstEdge + edgeIndex]; + bool shared = false; + for (int otherIndex = 0; otherIndex < other.numEdges; + ++otherIndex) { + const int otherReference = model->polygonEdges[ + other.firstEdge + otherIndex]; + if (reference == -otherReference) { + shared = true; + break; + } + } + if (!shared) { + boundary.push_back(reference); + } else if (&polygon == &first) { + ++sharedEdges; + } + } + }; + appendUnshared(first, second); + appendUnshared(second, first); + if (sharedEdges == 0 || boundary.size() < 3u || boundary.size() > 64u) { + return nullptr; + } + + // Reorder the surviving directed edges into one closed boundary. This + // also rejects point-touching or disconnected polygon unions. + std::vector ordered; + ordered.reserve(boundary.size()); + ordered.push_back(boundary.front()); + boundary.erase(boundary.begin()); + while (!boundary.empty()) { + const int endVertex = DirectedEdgeEnd(model, ordered.back()); + const auto next = std::find_if(boundary.begin(), boundary.end(), + [&](const int reference) { + return DirectedEdgeStart(model, reference) == endVertex; + }); + if (next == boundary.end()) { + return nullptr; + } + ordered.push_back(*next); + boundary.erase(next); + } + if (DirectedEdgeEnd(model, ordered.back()) + != DirectedEdgeStart(model, ordered.front())) { + return nullptr; + } + + const idVec3 normal(first.plane.a, first.plane.b, first.plane.c); + for (std::size_t index = 0; index < ordered.size(); ++index) { + const int previous = ordered[(index + ordered.size() - 1) + % ordered.size()]; + const int current = ordered[index]; + const idVec3& corner = model->vertices[ + DirectedEdgeStart(model, current)].p; + idVec3 incoming = corner - model->vertices[ + DirectedEdgeStart(model, previous)].p; + idVec3 outgoing = model->vertices[ + DirectedEdgeEnd(model, current)].p - corner; + if (incoming.NormalizeFast() == 0.0f + || outgoing.NormalizeFast() == 0.0f + || normal.Dot(incoming.Cross(outgoing)) < -0.005f) { + return nullptr; + } + } + + cm_buildPolygon_t* const merged = AllocPolygon(model, + static_cast(ordered.size())); + if (merged == nullptr) { + return nullptr; + } + // AllocPolygon may grow the polygon array, so reacquire the source. + const cm_buildPolygon_t& currentFirst = model->polygons[polygonNum1]; + const cm_buildPolygon_t& currentSecond = model->polygons[polygonNum2]; + CopyPolygonFields(*merged, currentFirst); + AddBounds(merged->bounds, currentSecond.bounds); + for (int index = 0; index < merged->numEdges; ++index) { + const int reference = ordered[static_cast(index)]; + model->polygonEdges[merged->firstEdge + index] = reference; + ++model->edges[std::abs(reference)].numUsers; + } + return merged; +} + +bool idCollisionModelBuilder::MergePolygonWithTreePolygons( + cm_buildModel_t* const model, cm_buildNode_t* node, + const int polygonNum, const bool mergePrimitives) { + if (model == nullptr || node == nullptr || polygonNum < 0 + || polygonNum >= model->numPolygons) { + return false; + } + cm_buildPolygon_t* source = &model->polygons[polygonNum]; + while (node != nullptr) { + for (cm_buildPolygonRef_t* reference = node->polygons; + reference != nullptr; reference = reference->next) { + const int otherNum = reference->polygonNum; + if (otherNum == polygonNum + || (!mergePrimitives + && model->polygons[otherNum].primitiveNum + != source->primitiveNum)) { + continue; + } + cm_buildPolygon_t* const merged = TryMergePolygons(model, + polygonNum, otherNum); + if (merged == nullptr) { + continue; + } + source = &model->polygons[polygonNum]; + cm_buildPolygon_t* const other = &model->polygons[otherNum]; + const int mergedNum = static_cast( + merged - model->polygons); + ReplacePolygons(model, model->node, polygonNum, otherNum, + mergedNum); + for (int edge = 0; edge < source->numEdges; ++edge) { + --model->edges[std::abs(model->polygonEdges[ + source->firstEdge + edge])].numUsers; + } + for (int edge = 0; edge < other->numEdges; ++edge) { + --model->edges[std::abs(model->polygonEdges[ + other->firstEdge + edge])].numUsers; + } + source->numEdges = 0; + other->numEdges = 0; + ++model->numMergedPolys; + return true; + } + if (node->planeType == -1) { + return false; + } + const int axis = node->planeType; + if (source->bounds[0][axis] <= node->planeDist) { + if (source->bounds[1][axis] >= node->planeDist) { + if (MergePolygonWithTreePolygons(model, node->children[1], + polygonNum, mergePrimitives)) { + return true; + } + node = node->children[0]; + } else { + node = node->children[1]; + } + } else { + node = node->children[0]; + } + } + return false; +} + +void idCollisionModelBuilder::MergeTreePolygons( + cm_buildModel_t* const model, cm_buildNode_t* node, + const bool mergePrimitives) { + if (model == nullptr || node == nullptr) { + return; + } + while (node != nullptr) { + bool merged; + do { + merged = false; + ++model->checkCount; + for (cm_buildPolygonRef_t* reference = node->polygons; + reference != nullptr; reference = reference->next) { + cm_buildPolygon_t& polygon = + model->polygons[reference->polygonNum]; + if (polygon.numEdges == 0 + || polygon.checkCount == model->checkCount) { + continue; + } + polygon.checkCount = model->checkCount; + if (MergePolygonWithTreePolygons(model, model->node, + reference->polygonNum, mergePrimitives)) { + merged = true; + break; + } + } + } while (merged); + if (node->planeType == -1) { + return; + } + MergeTreePolygons(model, node->children[1], mergePrimitives); + node = node->children[0]; + } +} + +void idCollisionModelBuilder::SplitPolygon(cm_buildModel_t* const model, + const int polygonNum) { + if (model == nullptr || polygonNum < 0 + || polygonNum >= model->numPolygons) { + return; + } + cm_buildPolygon_t* source = &model->polygons[polygonNum]; + if (source->numEdges <= 16) { + return; + } + const int originalEdges = source->numEdges; + const int splitStart = 0; + const int splitEnd = originalEdges / 2; + const int startReference = model->polygonEdges[ + source->firstEdge + splitStart]; + const int endReference = model->polygonEdges[ + source->firstEdge + splitEnd]; + const int startVertex = DirectedEdgeStart(model, startReference); + const int endVertex = DirectedEdgeStart(model, endReference); + if (startVertex == endVertex) { + return; + } + + int diagonal = 0; + GetEdge(model, model->vertices[startVertex].p, + model->vertices[endVertex].p, diagonal, startVertex); + if (diagonal == 0) { + return; + } + // GetEdge accounts for one user. Both split polygons use the diagonal. + ++model->edges[std::abs(diagonal)].numUsers; + + const std::vector original(model->polygonEdges + source->firstEdge, + model->polygonEdges + source->firstEdge + originalEdges); + const cm_buildPolygon_t originalFields = *source; + for (int side = 0; side < 2; ++side) { + const int begin = side == 0 ? splitStart : splitEnd; + const int end = side == 0 ? splitEnd : originalEdges; + const int count = end - begin + 1; + cm_buildPolygon_t* const split = AllocPolygon(model, count); + if (split == nullptr) { + return; + } + CopyPolygonFields(*split, originalFields); + split->bounds[0].Set(FLT_MAX, FLT_MAX, FLT_MAX); + split->bounds[1].Set(-FLT_MAX, -FLT_MAX, -FLT_MAX); + int cursor = 0; + split->numEdges = count; + const int closing = side == 0 ? -diagonal : diagonal; + model->polygonEdges[split->firstEdge + cursor++] = closing; + for (int edge = begin; edge < end; ++edge) { + const int reference = original[static_cast(edge)]; + model->polygonEdges[split->firstEdge + cursor++] = reference; + ++model->edges[std::abs(reference)].numUsers; + } + for (int edge = 0; edge < split->numEdges; ++edge) { + const int vertex = DirectedEdgeStart(model, + model->polygonEdges[split->firstEdge + edge]); + const idVec3& point = model->vertices[vertex].p; + for (int axis = 0; axis < 3; ++axis) { + split->bounds[0][axis] = (std::min)( + split->bounds[0][axis], point[axis]); + split->bounds[1][axis] = (std::max)( + split->bounds[1][axis], point[axis]); + } + } + FilterPolygonIntoTree_r(model, model->node, nullptr, split); + } + source = &model->polygons[polygonNum]; + RemovePolygon(model, model->node, polygonNum); + for (int edge = 0; edge < source->numEdges; ++edge) { + --model->edges[std::abs(model->polygonEdges[ + source->firstEdge + edge])].numUsers; + } + source->numEdges = 0; +} + +void idCollisionModelBuilder::SplitPolygons(cm_buildModel_t* const model) { + if (model == nullptr) { + return; + } + for (int polygonNum = 0; polygonNum < model->numPolygons; + ++polygonNum) { + if (model->polygons[polygonNum].numEdges > 16) { + SplitPolygon(model, polygonNum); + } + } +} + +idFixedWinding* idCollisionModelBuilder::WindingOutsidePolytopes( + cm_buildModel_t* const model, idFixedWinding* const winding, + const idPlane& plane, const int contents, const int primitiveNum) { + if (model == nullptr || winding == nullptr || model->node == nullptr + || winding->GetNumPoints() < 3) { + return winding; + } + if (buildData.cm_windingList == nullptr) { + SetupHash(); + } + cm_windingList_t& list = *buildData.cm_windingList; + list.numWindings = 1; + list.w[0] = *winding; + list.normal.Set(plane.a, plane.b, plane.c); + winding->GetBounds(list.bounds); + list.origin = (list.bounds[0] + list.bounds[1]) * 0.5f; + list.radius = (list.bounds[1] - list.origin).Length() + 0.1f; + for (int axis = 0; axis < 3; ++axis) { + list.bounds[0][axis] -= 0.1f; + list.bounds[1][axis] += 0.1f; + } + list.contents = contents; + list.primitiveNum = primitiveNum; + ++model->checkCount; + ChopWindingListWithTreePolytopes_r(&list, model, model->node); + if (list.numWindings == 0) { + return nullptr; + } + if (list.numWindings == 1) { + return &list.w[0]; + } + if (!model->isWorldModel) { + return winding; + } + int outsideFragment = -1; + for (int index = 0; index < list.numWindings; ++index) { + if (!ChoppedAwayByProcBSP(list.w[index], plane, contents)) { + if (outsideFragment >= 0) { + return winding; + } + outsideFragment = index; + } + } + return outsideFragment >= 0 ? &list.w[outsideFragment] : nullptr; +} + +int idCollisionModelBuilder::SetupSubModelData(cm_subModelData_t& data, + const cm_buildNodeStats_t& stats) { + data.isConvex = 0; + data.numNodes = stats.numNodes; + data.numPrimitiveIndices = stats.numPrimitiveIndices; + data.numMaterials = stats.numMaterials; + data.numPolygons = stats.numPolygons; + data.numPolygonEdges = stats.numPolygonEdges; + data.numEdges = stats.numEdges; + data.numVertices = stats.numVertices; + data.numPolytopes = stats.numPolytopes; + data.numPolytopePlanes = stats.numPolytopePlanes; + data.pad = 0; + + data.nodeOffset = 112; + data.primitiveIndexOffset = Align( + data.nodeOffset + 16 * data.numNodes, 2); + data.materialOffset = Align(data.primitiveIndexOffset + + 2 * data.numPrimitiveIndices, 16); + data.polygonOffset = Align(data.materialOffset + + 16 * data.numMaterials, 16); + data.polygonEdgeOffset = Align(data.polygonOffset + + 16 * data.numPolygons, 2); + data.edgeOffset = Align(data.polygonEdgeOffset + + 2 * data.numPolygonEdges, 4); + data.vertexOffset = Align(data.edgeOffset + 4 * data.numEdges, 16); + data.polytopeOffset = Align(data.vertexOffset + + 16 * data.numVertices, 16); + data.polytopePlaneOffset = Align(data.polytopeOffset + + 16 * data.numPolytopes, 16); + return data.polytopePlaneOffset + 16 * data.numPolytopePlanes; +} + +void idCollisionModelBuilder::CalculateSubModelDataSize( + cm_buildNodeStats_t& stats) { + cm_subModelData_t data{}; + stats.totalMemory = SetupSubModelData(data, stats); + stats.canCreateSubModel = stats.numNodes <= 0x10000 && + stats.numPrimitiveIndices <= 0x10000 && + stats.numMaterials <= 256 && stats.numPolygons <= 0x10000 && + stats.numPolygonEdges <= 0x10000 && stats.numEdges <= 0x4000 && + stats.numVertices <= 0x10000 && stats.numPolytopes <= 0x10000 && + stats.numPolytopePlanes <= 0x10000 && + stats.totalMemory <= 0x10000; +} + +bool idCollisionModelBuilder::AllocSubModelData( + const cm_buildNodeStats_t& stats, const idBounds& bounds, + cm_subModel_t& subModel, cm_subModelPtrs_t& pointers) { + cm_buildNodeStats_t checked = stats; + CalculateSubModelDataSize(checked); + if (!checked.canCreateSubModel) { + return false; + } + std::memset(&subModel, 0, sizeof(subModel)); + subModel.header.totalSize = checked.totalMemory; + subModel.header.loadedSize = checked.totalMemory; + subModel.header.bounds = bounds; + subModel.data = static_cast(_aligned_malloc( + static_cast(checked.totalMemory), 16)); + if (subModel.data == nullptr) { + return false; + } + std::memset(subModel.data, 0, + static_cast(checked.totalMemory)); + SetupSubModelData(*subModel.data, checked); + subModel.data->header = subModel.header; + idPolygonModelCollisionDetection::SetupSubModelPtrsFromData( + pointers, subModel.data); + return true; +} + +bool idCollisionModelBuilder::BuildForTrm(idCollisionModelLocal* const model, + const char* const modelName, const idTraceModel& traceModel, + const idMaterial*) { + if (model == nullptr) { + return false; + } + + model->SetName(modelName); + model->bounds = traceModel.bounds; + model->contents = 1; + model->isWorldModel = false; + model->isTraceModel = true; + model->isConvex = traceModel.isConvex; + model->modelType = CM_POLYGONMODEL; + model->polygonModel.numModelTreeNodes = 0; + model->polygonModel.modelTreeNodes = nullptr; + model->polygonModel.numSubModels = 1; + + cm_buildNodeStats_t stats{}; + stats.numNodes = 1; + stats.numPrimitiveIndices = static_cast(traceModel.numPolys) + + (traceModel.isConvex ? 1 : 0); + stats.numMaterials = 1; + stats.numPolygons = static_cast(traceModel.numPolys); + stats.numEdges = static_cast(traceModel.numEdges); + stats.numVertices = static_cast(traceModel.numVerts); + stats.numPolytopes = traceModel.isConvex ? 1 : 0; + stats.numPolytopePlanes = traceModel.isConvex + ? static_cast(traceModel.numPolys) + : 0; + for (unsigned int polygon = 0; polygon < traceModel.numPolys; + ++polygon) { + stats.numPolygonEdges += + static_cast(traceModel.numPolyEdges[polygon]); + } + const int polygonEdgePadding = traceModel.numPolys == 0 + ? 4 + : 4 - (static_cast( + traceModel.numPolyEdges[traceModel.numPolys - 1]) & 3); + stats.numPolygonEdges += polygonEdgePadding; + CalculateSubModelDataSize(stats); + if (!stats.canCreateSubModel) { + return false; + } + + model->polygonModel.subModels = static_cast( + _aligned_malloc(sizeof(cm_subModel_t), 16)); + model->polygonModel.subModelState = + static_cast(_aligned_malloc(1, 16)); + if (model->polygonModel.subModels == nullptr || + model->polygonModel.subModelState == nullptr) { + model->FreeData(); + return false; + } + std::memset(model->polygonModel.subModels, 0, sizeof(cm_subModel_t)); + + cm_subModel_t& subModel = model->polygonModel.subModels[0]; + subModel.header.totalSize = stats.totalMemory; + subModel.header.loadedSize = 32; + subModel.header.bounds = traceModel.bounds; + subModel.data = static_cast( + _aligned_malloc(static_cast(stats.totalMemory), 16)); + subModel.fileOffset = -1; + subModel.numUsers = 0; + subModel.state = model->polygonModel.subModelState; + *subModel.state = SUBMODEL_STATE_LOADED; + if (subModel.data == nullptr) { + model->FreeData(); + return false; + } + std::memset(subModel.data, 0, + static_cast(stats.totalMemory)); + SetupSubModelData(*subModel.data, stats); + subModel.data->header = subModel.header; + subModel.data->header.loadedSize = subModel.data->header.totalSize; + subModel.data->isConvex = traceModel.isConvex ? 1 : 0; + + cm_subModelPtrs_t pointers{}; + idPolygonModelCollisionDetection::SetupSubModelPtrsFromData( + pointers, subModel.data); + cm_node_t& node = pointers.nodes[0]; + node.planeType = -1; + node.planeDist = 0.0f; + node.children[0] = 0; + node.children[1] = 0; + node.firstPrimitive = 0; + node.numPolygons = static_cast(traceModel.numPolys); + node.numPolytopes = traceModel.isConvex ? 1 : 0; + + if (traceModel.type == TRM_INVALID || traceModel.numPolys == 0) { + return false; + } + + cm_material_t& material = pointers.materials[0]; + material.contentFlags = 1; + material.surfaceFlags = 0; + material.surfaceType = 0; + material.surfaceColor[0] = 0xFF; + material.surfaceColor[1] = 0xFF; + material.surfaceColor[2] = 0xFF; + material.pad = 0; + + for (unsigned int vertex = 0; vertex < traceModel.numVerts; ++vertex) { + pointers.vertices[vertex].p.Set(traceModel.vertsX[vertex], + traceModel.vertsY[vertex], traceModel.vertsZ[vertex]); + pointers.vertices[vertex].st[0] = 0; + pointers.vertices[vertex].st[1] = 0; + } + for (unsigned int edge = 0; edge < traceModel.numEdges; ++edge) { + pointers.edges[edge].vertexNum[0] = traceModel.edges[edge].v[0]; + pointers.edges[edge].vertexNum[1] = traceModel.edges[edge].v[1]; + } + + int polygonEdgeCursor = 0; + for (unsigned int polygonIndex = 0; + polygonIndex < traceModel.numPolys; ++polygonIndex) { + cm_polygon_t& polygon = pointers.polygons[polygonIndex]; + polygon.material = 0; + polygon.firstEdge = + static_cast(polygonEdgeCursor); + polygon.numEdges = static_cast( + traceModel.numPolyEdges[polygonIndex]); + + idBounds polygonBounds; + polygonBounds[0].Set(FLT_MAX, FLT_MAX, FLT_MAX); + polygonBounds[1].Set(-FLT_MAX, -FLT_MAX, -FLT_MAX); + for (unsigned int edge = 0; + edge < traceModel.numPolyEdges[polygonIndex]; ++edge) { + const std::uint8_t traceReference = + traceModel.polyEdges[polygonIndex][edge]; + const std::uint16_t modelReference = + static_cast(traceReference & 0x7F) | + ((traceReference & 0x80) != 0 ? 0x8000 : 0); + pointers.polygonEdges[polygonEdgeCursor++] = modelReference; + const cm_edge_t& modelEdge = + pointers.edges[CM_EdgeIndex(modelReference)]; + const idVec3& point = pointers.vertices[ + CM_EdgeStartVertex(modelEdge, modelReference)].p; + for (int axis = 0; axis < 3; ++axis) { + polygonBounds[0][axis] = (std::min)( + polygonBounds[0][axis], point[axis]); + polygonBounds[1][axis] = (std::max)( + polygonBounds[1][axis], point[axis]); + } + } + polygon.bounds.SetBounds(polygonBounds); + pointers.primitiveIndices[polygonIndex] = + static_cast(polygonIndex); + } + + const std::uint16_t paddingValue = polygonEdgeCursor > 0 + ? pointers.polygonEdges[polygonEdgeCursor - 1] + : 0; + for (int padding = 0; padding < polygonEdgePadding; ++padding) { + pointers.polygonEdges[polygonEdgeCursor++] = paddingValue; + } + + if (traceModel.isConvex) { + pointers.primitiveIndices[traceModel.numPolys] = 0; + cm_polytope_t& polytope = pointers.polytopes[0]; + polytope.bounds.SetBounds(traceModel.bounds); + polytope.material = 0; + polytope.numPlanes = + static_cast(traceModel.numPolys); + polytope.firstPlane = 0; + for (unsigned int plane = 0; plane < traceModel.numPolys; ++plane) { + pointers.polytopePlanes[plane].a = traceModel.polyPlaneX[plane]; + pointers.polytopePlanes[plane].b = traceModel.polyPlaneY[plane]; + pointers.polytopePlanes[plane].c = traceModel.polyPlaneZ[plane]; + pointers.polytopePlanes[plane].d = traceModel.polyPlaneW[plane]; + } + } + return true; +} + +bool idCollisionModelBuilder::BuildForGrid( + idCollisionModelLocal* const model, const char* const modelName, + const idGenGridModel& grid, const idCollisionGridState& state, + const idMaterial*) { + if (model == nullptr || modelName == nullptr || state.numActive <= 0) { + return false; + } + + std::vector selectedParts; + std::vector nodePolygonCounts((std::max)(1, grid.nodes.Num()), 0); + int sourcePolygonCount = 0; + int sourcePolygonEdges = 0; + idBounds modelBounds; + modelBounds[0].Set(FLT_MAX, FLT_MAX, FLT_MAX); + modelBounds[1].Set(-FLT_MAX, -FLT_MAX, -FLT_MAX); + for (int active = state.FirstActive(); active >= 0; + active = state.NextActive(active)) { + if (active >= grid.indices.Num()) { + continue; + } + const int partIndex = grid.indices[active]; + if (partIndex == idGenGridModel::INVALID_INDEX + || partIndex < 0 || partIndex >= grid.parts.Num()) { + continue; + } + const cm_gridPart_t& part = grid.parts[partIndex]; + if (part.nodeIndex >= nodePolygonCounts.size() + || part.firstPolygonIndex + part.numPolygons + > grid.polygons.Num()) { + return false; + } + selectedParts.push_back(partIndex); + sourcePolygonCount += part.numPolygons; + nodePolygonCounts[part.nodeIndex] += 2 * part.numPolygons; + const idBounds partBounds = part.bounds.ToBounds(); + for (int axis = 0; axis < 3; ++axis) { + modelBounds[0][axis] = (std::min)(modelBounds[0][axis], + partBounds[0][axis]); + modelBounds[1][axis] = (std::max)(modelBounds[1][axis], + partBounds[1][axis]); + } + for (int polygon = 0; polygon < part.numPolygons; ++polygon) { + sourcePolygonEdges += grid.polygons[ + part.firstPolygonIndex + polygon].numEdges; + } + } + if (sourcePolygonCount <= 0) { + return false; + } + for (int count : nodePolygonCounts) { + if (count > 255) { + return false; + } + } + + const int edgePadding = 4 - ((2 * sourcePolygonEdges) & 3); + cm_buildNodeStats_t stats{}; + stats.numNodes = (std::max)(1, grid.nodes.Num()); + stats.numPrimitiveIndices = 2 * sourcePolygonCount; + stats.numMaterials = 1; + stats.numPolygons = 2 * sourcePolygonCount; + stats.numPolygonEdges = 2 * sourcePolygonEdges + edgePadding; + stats.numEdges = grid.edges.Num(); + stats.numVertices = grid.vertices.Num(); + stats.numPolytopes = 0; + stats.numPolytopePlanes = 0; + + model->FreeData(); + model->SetName(modelName); + model->modelType = CM_POLYGONMODEL; + model->bounds = modelBounds; + model->contents = 1; + model->isWorldModel = false; + model->isTraceModel = false; + model->isConvex = false; + model->isStreamed = false; + model->polygonModel.numModelTreeNodes = 0; + model->polygonModel.modelTreeNodes = nullptr; + model->polygonModel.numSubModels = 1; + model->polygonModel.subModels = static_cast( + _aligned_malloc(sizeof(cm_subModel_t), 16)); + model->polygonModel.subModelState = + static_cast(_aligned_malloc(1, 16)); + if (model->polygonModel.subModels == nullptr + || model->polygonModel.subModelState == nullptr) { + model->FreeData(); + return false; + } + cm_subModel_t& subModel = model->polygonModel.subModels[0]; + cm_subModelPtrs_t pointers{}; + if (!AllocSubModelData(stats, modelBounds, subModel, pointers)) { + model->FreeData(); + return false; + } + subModel.fileOffset = -1; + subModel.numUsers = 0; + subModel.state = model->polygonModel.subModelState; + *subModel.state = SUBMODEL_STATE_LOADED; + subModel.data->isConvex = 0; + + cm_material_t& material = pointers.materials[0]; + material.contentFlags = 1; + material.surfaceFlags = 0; + material.surfaceType = 0; + material.surfaceColor[0] = material.surfaceColor[1] + = material.surfaceColor[2] = 0xFF; + material.pad = 0; + for (int index = 0; index < grid.vertices.Num(); ++index) { + pointers.vertices[index].p = grid.vertices[index]; + pointers.vertices[index].st[0] = 0; + pointers.vertices[index].st[1] = 0; + } + for (int index = 0; index < grid.edges.Num(); ++index) { + pointers.edges[index] = grid.edges[index]; + } + + std::vector nodeCursors(nodePolygonCounts.size(), 0); + int primitiveOffset = 0; + for (int node = 0; node < stats.numNodes; ++node) { + cm_node_t& destination = pointers.nodes[node]; + if (node < grid.nodes.Num()) { + const cm_gridNodeBSP_t& source = grid.nodes[node]; + destination.planeType = source.planeType; + destination.planeDist = source.planeDist; + destination.children[0] = source.children[0]; + destination.children[1] = source.children[1]; + } else { + destination.planeType = -1; + destination.planeDist = 0.0f; + destination.children[0] = destination.children[1] = 0; + } + destination.firstPrimitive = + static_cast(primitiveOffset); + destination.numPolygons = static_cast( + nodePolygonCounts[node]); + destination.numPolytopes = 0; + nodeCursors[node] = primitiveOffset; + primitiveOffset += nodePolygonCounts[node]; + } + + int polygonCursor = 0; + int edgeCursor = 0; + for (int partIndex : selectedParts) { + const cm_gridPart_t& part = grid.parts[partIndex]; + for (int partPolygon = 0; partPolygon < part.numPolygons; + ++partPolygon) { + const cm_polygon_t& source = grid.polygons[ + part.firstPolygonIndex + partPolygon]; + for (int side = 0; side < 2; ++side) { + cm_polygon_t& destination = pointers.polygons[polygonCursor]; + destination = source; + destination.material = 0; + destination.firstEdge = static_cast( + edgeCursor); + for (int edge = 0; edge < source.numEdges; ++edge) { + const int sourceEdge = side == 0 ? edge + : source.numEdges - edge - 1; + std::uint16_t reference = grid.polygonEdges[ + source.firstEdge + sourceEdge]; + if (side != 0) { + reference ^= 0x8000; + } + pointers.polygonEdges[edgeCursor++] = reference; + } + pointers.primitiveIndices[ + nodeCursors[part.nodeIndex]++] = + static_cast(polygonCursor++); + } + } + } + const std::uint16_t padding = edgeCursor > 0 + ? pointers.polygonEdges[edgeCursor - 1] : 0; + while (edgeCursor < stats.numPolygonEdges) { + pointers.polygonEdges[edgeCursor++] = padding; + } + return true; +} diff --git a/source/engine/cm/collisionmodelbuilder.h b/source/engine/cm/collisionmodelbuilder.h index f02e4e7..2fc0fc7 100644 --- a/source/engine/cm/collisionmodelbuilder.h +++ b/source/engine/cm/collisionmodelbuilder.h @@ -1,43 +1,490 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\cm\collisionmodelbuilder.h -// Recovered logical types: 3 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "cm/collisiontypes.h" +#include "idlib/containers/list.h" +#include "idlib/geometry/winding.h" +#include "idlib/text/str.h" +class idCollisionModelLocal; +class idCollisionGridState; +class idDeclMD6; +class idDrawVert; +class idGenGridModel; +class idMapBrush; +class idMapFile; +class idMapModel; +class idMapPatch; +class idMaterial; +class idRenderModel; +class idStaticModel; +class idSurface_Patch; +class idTraceModel; +class idLexer; -// IDA Local Type ordinal 23795; PDB kind: class. -class idCollisionModelBuilder::idStaticModelGeometry -{ -public: - const idStaticModel *staticModel; - const idMapModel *mapModel; - const idRenderModel *renderModel; - idList primitiveGroupNumbers; - idVec3 origin; - idMat3 axis; - idVec3 scale; - const idMaterial *overrideClipMaterial; +struct cm_buildPolygonRef_t; +struct cm_buildPolytopeRef_t; + +struct cm_buildVertex_t { + idVec3 p; + std::uint16_t st[2]; + int checkCount; + int index; }; -// IDA Local Type ordinal 23797; PDB kind: class. -class idCollisionModelBuilder -{ -public: +struct cm_buildEdge_t { + int vertexNum[2]; + idVec3 normal; + std::uint8_t internal; + std::uint8_t numUsers; + std::uint16_t pad; + int checkCount; + int index; }; -// IDA Local Type ordinal 23798; PDB kind: class. -class idCollisionModelBuilder::idCollisionModelGeometry -{ -public: - idCollisionModelLocal *collisionModel; - idStr modelName; - unsigned int fileTime; - bool isWorldEntity; - bool isStreamed; - bool isStreamArea; - bool allowDiscrete; - idVec3 streamVolumeOrigin; - idMat3 streamVolumeAxis; - idList models; +struct cm_buildMaterial_t { + int contentFlags; + int surfaceFlags; + int surfaceType; + int checkCount; + int index; }; + +struct cm_collisionSphereDesc_t { + std::uint8_t joint; + std::uint8_t surfaceType; + std::uint16_t pad; + idVec3 offset; + float radius; +}; + +struct cm_sphereBuildSource_t { + const char* name; + std::uint32_t timeStamp; + idBounds bounds; + int contents; + int numModelJoints; + const cm_collisionSphereDesc_t* spheres; + int numSpheres; +}; + +using cm_declMD6SphereExtractor_t = bool (*)(const idDeclMD6*, + cm_sphereBuildSource_t&); + +struct cm_materialBuildInfo_t { + int contents; + int surfaceFlags; + int surfaceType; + bool discrete; +}; + +struct cm_modelSurfaceBuildSource_t { + const idDrawVert* vertices; + int numVertices; + const std::uint16_t* indices; + int numIndices; + const idMaterial* material; +}; + +struct cm_modelBuildSource_t { + const char* name; + std::uint32_t timeStamp; + const cm_modelSurfaceBuildSource_t* surfaces; + int numSurfaces; +}; + +using cm_materialBuildInfoExtractor_t = bool (*)(const idMaterial*, + cm_materialBuildInfo_t&); +using cm_materialResolver_t = const idMaterial* (*)(const char*); +using cm_renderModelBuildExtractor_t = bool (*)(const idRenderModel*, + cm_modelBuildSource_t&); +using cm_staticModelBuildExtractor_t = bool (*)(const idStaticModel*, + cm_modelBuildSource_t&); +using cm_mapFileBuildCallback_t = void (*)(const idMapFile*, bool, bool); + +bool CM_GetMaterialBuildInfo(const idMaterial* material, + cm_materialBuildInfo_t& info); + +struct cm_buildNodeStats_t { + int numNodes; + int numPrimitiveIndices; + int numMaterials; + int numPolygons; + int numPolygonEdges; + int numEdges; + int numVertices; + int numPolytopes; + int numPolytopePlanes; + int lastNumPolygonEdges; + int totalMemory; + bool canCreateSubModel; + std::uint8_t pad[3]; +}; + +struct cm_buildNode_t { + int planeType; + float planeDist; + idBounds bounds; + cm_buildNode_t* parent; + cm_buildNode_t* children[2]; + cm_buildPolygonRef_t* polygons; + cm_buildPolytopeRef_t* polytopes; + cm_buildNodeStats_t stats; +}; + +struct cm_buildPolygonRef_t { + int polygonNum; + cm_buildPolygonRef_t* next; +}; + +struct cm_buildPolytopeRef_t { + int polytopeNum; + cm_buildPolytopeRef_t* next; +}; + +struct cm_buildNodeBlock_t { + int size; + cm_buildNode_t* nextNode; + cm_buildNodeBlock_t* next; +}; + +struct cm_buildPolygonRefBlock_t { + int size; + cm_buildPolygonRef_t* nextRef; + cm_buildPolygonRefBlock_t* next; +}; + +struct cm_buildPolytopeRefBlock_t { + int size; + cm_buildPolytopeRef_t* nextRef; + cm_buildPolytopeRefBlock_t* next; +}; + +struct cm_buildPolygon_t { + idPlane plane; + idBounds bounds; + int contents; + int material; + int primitiveNum; + int numEdges; + int firstEdge; + int checkCount; + int index; +}; + +struct cm_buildPolytope_t { + idBounds bounds; + int contents; + int material; + int primitiveNum; + int numPlanes; + int firstPlane; + int checkCount; + int index; +}; + +struct cm_buildModel_t { + idStr name; + int maxVertices; + int numVertices; + cm_buildVertex_t* vertices; + int maxEdges; + int numEdges; + cm_buildEdge_t* edges; + int maxPolygonEdges; + int numPolygonEdges; + int* polygonEdges; + int maxPolygons; + int numPolygons; + cm_buildPolygon_t* polygons; + int maxPolytopePlanes; + int numPolytopePlanes; + idPlane* polytopePlanes; + int maxPolytopes; + int numPolytopes; + cm_buildPolytope_t* polytopes; + int numNodes; + cm_buildNode_t* node; + idList materials; + cm_buildNodeBlock_t* nodeBlocks; + cm_buildPolygonRefBlock_t* polygonRefBlocks; + cm_buildPolytopeRefBlock_t* polytopeRefBlocks; + int checkCount; + bool isWorldModel; + std::uint8_t pad[3]; + int numPrimitives; + int numPolytopeRefs; + int numPolygonRefs; + int numInternalEdges; + int numSharpEdges; + int numRemovedPolys; + int numMergedPolys; +}; + +struct cm_windingList_t { + int numWindings; + idFixedWinding w[256]; + idVec3 normal; + idBounds bounds; + idVec3 origin; + float radius; + int contents; + int primitiveNum; +}; + +int CM_R_CountChildren(cm_buildNode_t* node); +void CM_R_TestOptimisation(cm_buildNode_t* node, + int& numSavedPolygonIndices, int& numSavedPolytopeIndices); +bool CM_R_InsideAllChildren(cm_buildNode_t* node, + const idBounds& bounds); + +class idCollisionModelBuilder { +public: + class idStaticModelGeometry { + public: + const idStaticModel* staticModel = nullptr; + const idMapModel* mapModel = nullptr; + const idRenderModel* renderModel = nullptr; + idList primitiveGroupNumbers; + idVec3 origin; + idMat3 axis; + idVec3 scale; + const idMaterial* overrideClipMaterial = nullptr; + }; + + class idCollisionModelGeometry { + public: + idCollisionModelLocal* collisionModel = nullptr; + idStr modelName; + unsigned int fileTime = 0; + bool isWorldEntity = false; + bool isStreamed = false; + bool isStreamArea = false; + bool allowDiscrete = false; + idVec3 streamVolumeOrigin; + idMat3 streamVolumeAxis; + idList models; + }; + + static cm_buildNode_t* AllocNode(cm_buildModel_t* model, + int blockSize); + static cm_buildPolygonRef_t* AllocPolygonReference( + cm_buildModel_t* model, int blockSize); + static cm_buildPolytopeRef_t* AllocPolytopeReference( + cm_buildModel_t* model, int blockSize); + static cm_buildPolygon_t* AllocPolygon(cm_buildModel_t* model, + int numEdges); + static cm_buildPolytope_t* AllocPolytope(cm_buildModel_t* model, + int numPlanes); + static void AddPolygonToNode(cm_buildModel_t* model, + cm_buildNode_t* node, cm_buildPolygon_t* polygon); + static void AddPolytopeToNode(cm_buildModel_t* model, + cm_buildNode_t* node, cm_buildPolytope_t* polytope); + static void GetPrimitiveCounts(const cm_buildNode_t* node, + int& polygonCount, int& polytopeCount); + static int GetNodeContents(const cm_buildModel_t* model, + const cm_buildNode_t* node); + static void FindSubModels_r(const cm_buildModel_t* buildModel, + cm_buildNode_t* buildNode, int& numModelTreeNodes, + int& numSubModels); + static void FreeModelMemory(cm_buildModel_t* model); + static bool PointInsidePolygon(cm_buildModel_t* model, + cm_buildPolygon_t* polygon, const idVec3& point); + static void RemovePolygon(cm_buildModel_t* model, + cm_buildNode_t* node, int polygonNum); + static bool SplitterDividesPrimitives(cm_buildModel_t* model, + const cm_buildNode_t* node, int planeType, float planeDist); + static void GetNodeBounds_r(const cm_buildModel_t* model, + const cm_buildNode_t* node, idBounds& bounds); + static void GetNodeBounds(const cm_buildModel_t* model, + const cm_buildNode_t* node, idBounds& bounds); + static void GetStatsFromNode(const cm_buildModel_t* buildModel, + const cm_buildNode_t* buildNode, cm_buildNodeStats_t& stats); + static void CreateStatsForSubTree_r(const cm_buildModel_t* buildModel, + const cm_buildNode_t* buildNode, cm_buildNodeStats_t& stats); + static bool TestBoundsRange(const char* modelName, + const idBounds& bounds); + static void SetupHash(); + static void ClearHash(const idBounds& bounds); + static void ShutdownHash(); + static bool GetVertex(cm_buildModel_t* model, const idVec3& vertex, + int& vertexNum); + static bool GetEdge(cm_buildModel_t* model, const idVec3& vertex1, + const idVec3& vertex2, int& edgeNum, int vertex1Num = -1); + static cm_buildModel_t* AllocBuildModel(); + static int FindMaterial(cm_buildModel_t* model, int contentFlags, + int surfaceFlags, int surfaceType); + static bool IsStaticRenderModel(const char* fileName); + static void CreatePolygon(cm_buildModel_t* model, idFixedWinding* winding, + const idPlane& plane, const idMaterial* material, + int primitiveNum); + static void PolygonFromWinding(cm_buildModel_t* model, + idFixedWinding* winding, const idPlane& plane, + const idMaterial* material, int primitiveNum); + static void FilterPolygonIntoTree_r(cm_buildModel_t* model, + cm_buildNode_t* node, cm_buildPolygonRef_t* reference, + cm_buildPolygon_t* polygon); + static void FilterPolytopeIntoTree_r(cm_buildModel_t* model, + cm_buildNode_t* node, cm_buildPolytopeRef_t* reference, + cm_buildPolytope_t* polytope); + static bool FindSplitter(cm_buildModel_t* model, + const cm_buildNode_t* node, const idBounds& bounds, + int& planeType, float& planeDist); + static cm_buildNode_t* CreateAxialBSPTree_r(cm_buildModel_t* model, + cm_buildNode_t* node); + static cm_buildNode_t* CreateAxialBSPTree(cm_buildModel_t* model); + static void AddBuildNodePrimitivesToSubModelNode( + const cm_buildModel_t* buildModel, cm_buildNode_t* buildNode, + cm_subModelPtrs_t& subModelPtrs, cm_subModelData_t& counts, + cm_node_t& node); + static void CreateSingleSubModel_r(const cm_buildModel_t* buildModel, + cm_buildNode_t* buildNode, cm_subModelPtrs_t& subModelPtrs, + cm_subModelData_t& counts, cm_node_t* parent); + static void CreateNodeStats_r(const cm_buildModel_t* buildModel, + cm_buildNode_t* buildNode); + static void CreateSubModels_r(const cm_buildModel_t* buildModel, + cm_buildNode_t* buildNode, idCollisionModelLocal* model, + cm_modelTreeNode_t* parent); + static void AddSubModelsToCollisionModel(idCollisionModelLocal* model, + const cm_buildModel_t* buildModel); + static int CountModelTreeNodes_r(idCollisionModelLocal* model, + int nodeNum, idBounds& bounds); + static void GenerateEdgeNormals_r(cm_buildModel_t* model, + cm_buildNode_t* node); + static bool ChoppedAwayByProcBSP_r(int nodeNum, + idFixedWinding* winding, const idVec3& normal, + const idVec3& origin, float radius); + static bool ChoppedAwayByProcBSP(const idFixedWinding& winding, + const idPlane& plane, int contents); + static void ReplacePolygons(cm_buildModel_t* model, + cm_buildNode_t* node, int polygonNum1, int polygonNum2, + int newPolygonNum); + static void FindInternalEdgesOnPolygon(cm_buildModel_t* model, + cm_buildPolygon_t* polygon1, cm_buildPolygon_t* polygon2); + static void FindInternalPolygonEdges(cm_buildModel_t* model, + cm_buildNode_t* node, cm_buildPolygon_t* polygon); + static void FindInternalEdges(cm_buildModel_t* model, + cm_buildNode_t* node); + static void OffsetPolygonEdges(cm_buildModel_t* model, + cm_buildPolygon_t* polygon); + static void OffsetPolygonEdges_r(cm_buildModel_t* model, + cm_buildNode_t* node); + static void ChopWindingListWithPolytope(cm_windingList_t* list, + const cm_buildModel_t* model, const cm_buildPolytope_t* polytope); + static void ChopWindingListWithTreePolytopes_r( + cm_windingList_t* list, const cm_buildModel_t* model, + const cm_buildNode_t* node); + static cm_buildPolygon_t* TryMergePolygons(cm_buildModel_t* model, + int polygonNum1, int polygonNum2); + static bool MergePolygonWithTreePolygons(cm_buildModel_t* model, + cm_buildNode_t* node, int polygonNum, bool mergePrimitives); + static void MergeTreePolygons(cm_buildModel_t* model, + cm_buildNode_t* node, bool mergePrimitives); + static void SplitPolygon(cm_buildModel_t* model, int polygonNum); + static void SplitPolygons(cm_buildModel_t* model); + static idFixedWinding* WindingOutsidePolytopes( + cm_buildModel_t* model, idFixedWinding* winding, + const idPlane& plane, int contents, int primitiveNum); + static void MergeModelTrees(idCollisionModelLocal* model); + static void ParseProcNodes(idLexer* source); + static void LoadProcBSP(const char* name); + static int SetupBuildGroups(idCollisionModelGeometry& geometry); + static void AddMapModelEstimates( + const idStaticModelGeometry& geometry, int groupNum, + int primitiveNum, int& numVertices, int& numEdges, + int& numPolygons, idBounds& bounds); + static void AddRenderModelEstimates( + const idStaticModelGeometry& geometry, int groupNum, + int primitiveNum, int& numVertices, int& numEdges, + int& numPolygons, idBounds& bounds); + static void GetMapModelBrushBounds( + const idStaticModelGeometry& geometry, int groupNum, + idBounds& bounds); + static void ConvertBrushSides(cm_buildModel_t* model, + const idMapBrush* brush, const idVec3& origin, + const idMat3& axis, const idVec3& scale, + const idMaterial* overrideClipMaterial, int primitiveNum); + static void ConvertBrush(cm_buildModel_t* model, + const idMapBrush* brush, const idVec3& origin, + const idMat3& axis, const idVec3& scale, + const idMaterial* overrideClipMaterial, int primitiveNum); + static void CreatePatchPolygons(cm_buildModel_t* model, + const idSurface_Patch* mesh, const idVec3& origin, + const idMat3& axis, const idVec3& scale, + const idMaterial* material, int primitiveNum); + static void ConvertPatch(cm_buildModel_t* model, + const idMapPatch* patch, const idVec3& origin, + const idMat3& axis, const idVec3& scale, + const idMaterial* overrideClipMaterial, int primitiveNum); + static void ConvertMapModelPolytopes(cm_buildModel_t* model, + const idStaticModelGeometry& geometry, int groupNum, + int primitiveNum); + static void ConvertMapModelPrimitives(cm_buildModel_t* model, + const idStaticModelGeometry& geometry, int groupNum, + int primitiveNum); + static void ConvertRenderModelSurfaces(cm_buildModel_t* model, + const idStaticModelGeometry& geometry, int groupNum, + int primitiveNum); + static void AddCollisionModelGeometry(idCollisionModelLocal* model, + const idCollisionModelGeometry& geometry, int groupNum); + static void BuildCollisionModelForGeometry(idCollisionModelLocal* model, + idCollisionModelGeometry& geometry, const int* subModelIndices, + int numSubModelIndices, const char* optionalModelName); + static bool BuildForRenderModel(idCollisionModelLocal* model, + const idRenderModel* renderModel); + static bool BuildForStaticModel(idCollisionModelLocal* model, + const idStaticModel* staticModel, const int* subModelIndices, + int numSubModelIndices, const char* optionalModelName); + static void BuildForMapFile(const idMapFile* mapFile, + bool inlineStatic, bool mapModelOnly); + static void CreateStreamAreas(idCollisionModelLocal* model, + const idList& geometries); + static void SetMaterialBuildInfoExtractor( + cm_materialBuildInfoExtractor_t extractor); + static void SetMaterialResolver(cm_materialResolver_t resolver); + static void SetRenderModelBuildExtractor( + cm_renderModelBuildExtractor_t extractor); + static void SetStaticModelBuildExtractor( + cm_staticModelBuildExtractor_t extractor); + static void SetMapFileBuildCallback(cm_mapFileBuildCallback_t callback); + + static bool IsAnimatedRenderModel(const char* fileName); + static bool BuildForDeclMD6(idCollisionModelLocal* model, + const idDeclMD6* md6Decl); + static bool BuildForSpheres(idCollisionModelLocal* model, + const cm_sphereBuildSource_t& source); + static void SetDeclMD6SphereExtractor( + cm_declMD6SphereExtractor_t extractor); + + static int SetupSubModelData(cm_subModelData_t& data, + const cm_buildNodeStats_t& stats); + static void CalculateSubModelDataSize(cm_buildNodeStats_t& stats); + static bool BuildForTrm(idCollisionModelLocal* model, + const char* modelName, const idTraceModel& traceModel, + const idMaterial* material); + static bool BuildForGrid(idCollisionModelLocal* model, + const char* modelName, const idGenGridModel& grid, + const idCollisionGridState& state, const idMaterial* material); + static bool AllocSubModelData(const cm_buildNodeStats_t& stats, + const idBounds& bounds, cm_subModel_t& subModel, + cm_subModelPtrs_t& pointers); +}; + +static_assert(sizeof(cm_buildNodeStats_t) == 48, + "Recovered cm_buildNodeStats_t ABI changed"); +static_assert(sizeof(cm_collisionSphereDesc_t) == 20, + "Recovered collision sphere descriptor layout changed"); + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(cm_buildVertex_t) == 24, + "Recovered cm_buildVertex_t ABI changed"); +static_assert(sizeof(cm_buildEdge_t) == 32, + "Recovered cm_buildEdge_t ABI changed"); +static_assert(sizeof(cm_buildMaterial_t) == 20, + "Recovered cm_buildMaterial_t ABI changed"); +static_assert(sizeof(cm_buildNode_t) == 100, + "Recovered cm_buildNode_t ABI changed"); +static_assert(sizeof(cm_buildPolygon_t) == 68, + "Recovered cm_buildPolygon_t ABI changed"); +static_assert(sizeof(cm_buildPolytope_t) == 52, + "Recovered cm_buildPolytope_t ABI changed"); +#endif diff --git a/source/engine/cm/collisionmodelbuilder_geometry.cpp b/source/engine/cm/collisionmodelbuilder_geometry.cpp new file mode 100644 index 0000000..1762ff5 --- /dev/null +++ b/source/engine/cm/collisionmodelbuilder_geometry.cpp @@ -0,0 +1,901 @@ +#include "cm/collisionmodelbuilder.h" + +#include "cm/collisionmodel.h" +#include "framework/resourcelist.h" +#include "idlib/geometry/drawvert.h" +#include "idlib/geometry/surface_patch.h" +#include "idlib/lib_print.h" +#include "idlib/sys/sys_alloc.h" +#include "mapfile/mapfile.h" + +#include +#include +#include +#include +#include +#include + +namespace { + +cm_materialBuildInfoExtractor_t materialInfoExtractor = nullptr; +cm_materialResolver_t materialResolver = nullptr; +cm_renderModelBuildExtractor_t renderModelExtractor = nullptr; +cm_staticModelBuildExtractor_t staticModelExtractor = nullptr; +cm_mapFileBuildCallback_t mapFileBuildCallback = nullptr; + +idVec3 TransformPoint(const idVec3& point, const idVec3& origin, + const idMat3& axis, const idVec3& scale) { + const idVec3 scaled(point.x * scale.x, point.y * scale.y, + point.z * scale.z); + return origin + idVec3( + axis[0].x * scaled.x + axis[1].x * scaled.y + + axis[2].x * scaled.z, + axis[0].y * scaled.x + axis[1].y * scaled.y + + axis[2].y * scaled.z, + axis[0].z * scaled.x + axis[1].z * scaled.y + + axis[2].z * scaled.z); +} + +bool PlaneFromPoints(const idVec3& first, const idVec3& second, + const idVec3& third, idPlane& plane) { + idVec3 normal = (second - first).Cross(third - first); + if (normal.NormalizeFast() == 0.0f) { + return false; + } + plane = idPlane(normal.x, normal.y, normal.z, -normal.Dot(first)); + return true; +} + +idFixedWinding BasePlaneWinding(const idPlane& plane) { + idVec3 normal(plane.a, plane.b, plane.c); + normal.NormalizeFast(); + idVec3 reference = std::fabs(normal.z) < 0.9f + ? idVec3(0.0f, 0.0f, 1.0f) + : idVec3(0.0f, 1.0f, 0.0f); + idVec3 right = reference.Cross(normal); + right.NormalizeFast(); + idVec3 up = normal.Cross(right); + up.NormalizeFast(); + const idVec3 center = normal * -plane.d; + constexpr float radius = 131072.0f; + right = right * radius; + up = up * radius; + idFixedWinding winding; + winding.AddPoint(center - right - up); + winding.AddPoint(center + right - up); + winding.AddPoint(center + right + up); + winding.AddPoint(center - right + up); + return winding; +} + +bool ClipWindingToBrush(const idMapBrush& brush, const int sideIndex, + idFixedWinding& winding) { + for (int otherIndex = 0; otherIndex < brush.sides.Num(); ++otherIndex) { + if (otherIndex == sideIndex || brush.sides[otherIndex] == nullptr) { + continue; + } + idFixedWinding back; + const int side = winding.SplitInPlace( + brush.sides[otherIndex]->plane, 0.1f, &back); + if (side == 0) { + winding.Clear(); + return false; + } + if (side == 3) { + winding = back; + } + if (winding.GetNumPoints() < 3) { + return false; + } + } + return winding.GetNumPoints() >= 3; +} + +void TransformWinding(idFixedWinding& winding, const idVec3& origin, + const idMat3& axis, const idVec3& scale) { + for (int index = 0; index < winding.GetNumPoints(); ++index) { + const idVec3 transformed = TransformPoint(idVec3( + winding[index].x, winding[index].y, winding[index].z), + origin, axis, scale); + winding[index].x = transformed.x; + winding[index].y = transformed.y; + winding[index].z = transformed.z; + } +} + +void AddPointToBounds(idBounds& bounds, const idVec3& point) { + for (int axis = 0; axis < 3; ++axis) { + bounds[0][axis] = (std::min)(bounds[0][axis], point[axis]); + bounds[1][axis] = (std::max)(bounds[1][axis], point[axis]); + } +} + +void AddBoundsToBounds(idBounds& destination, const idBounds& source) { + for (int axis = 0; axis < 3; ++axis) { + destination[0][axis] = (std::min)(destination[0][axis], + source[0][axis]); + destination[1][axis] = (std::max)(destination[1][axis], + source[1][axis]); + } +} + +const idMaterial* ResolveMaterial(const idStr& name, + const idMaterial* const overrideMaterial) { + if (overrideMaterial != nullptr) { + return overrideMaterial; + } + return materialResolver != nullptr ? materialResolver(name.c_str()) + : nullptr; +} + +bool ExtractModel(const idCollisionModelBuilder::idStaticModelGeometry& g, + cm_modelBuildSource_t& source) { + std::memset(&source, 0, sizeof(source)); + if (g.staticModel != nullptr && staticModelExtractor != nullptr) { + return staticModelExtractor(g.staticModel, source); + } + if (g.renderModel != nullptr && renderModelExtractor != nullptr) { + return renderModelExtractor(g.renderModel, source); + } + return false; +} + +int PrimitiveGroup( + const idCollisionModelBuilder::idStaticModelGeometry& geometry, + const int primitiveIndex) { + return primitiveIndex >= 0 + && primitiveIndex < geometry.primitiveGroupNumbers.Num() + ? geometry.primitiveGroupNumbers[primitiveIndex] + : 0; +} + +const idMaterial* MapPrimitiveMaterial(const idMapPrimitive* primitive, + const idMaterial* overrideMaterial) { + if (overrideMaterial != nullptr) { + return overrideMaterial; + } + if (primitive == nullptr) { + return nullptr; + } + if (primitive->type == MAP_PRIMITIVE_PATCH) { + return ResolveMaterial(static_cast(primitive) + ->material, nullptr); + } + const idMapBrush* const brush = + static_cast(primitive); + return brush->sides.Num() > 0 && brush->sides[0] != nullptr + ? ResolveMaterial(brush->sides[0]->material, nullptr) : nullptr; +} + +} // namespace + +bool CM_GetMaterialBuildInfo(const idMaterial* const material, + cm_materialBuildInfo_t& info) { + info.contents = 1; + info.surfaceFlags = 0; + info.surfaceType = 0; + info.discrete = false; + return materialInfoExtractor != nullptr + ? materialInfoExtractor(material, info) : material == nullptr; +} + +void idCollisionModelBuilder::SetMaterialBuildInfoExtractor( + const cm_materialBuildInfoExtractor_t extractor) { + materialInfoExtractor = extractor; +} + +void idCollisionModelBuilder::SetMaterialResolver( + const cm_materialResolver_t resolver) { + materialResolver = resolver; +} + +void idCollisionModelBuilder::SetRenderModelBuildExtractor( + const cm_renderModelBuildExtractor_t extractor) { + renderModelExtractor = extractor; +} + +void idCollisionModelBuilder::SetStaticModelBuildExtractor( + const cm_staticModelBuildExtractor_t extractor) { + staticModelExtractor = extractor; +} + +void idCollisionModelBuilder::SetMapFileBuildCallback( + const cm_mapFileBuildCallback_t callback) { + mapFileBuildCallback = callback; +} + +int idCollisionModelBuilder::SetupBuildGroups( + idCollisionModelGeometry& geometry) { + int nextGroup = 1; + for (int modelIndex = 0; modelIndex < geometry.models.Num(); + ++modelIndex) { + idStaticModelGeometry& model = geometry.models[modelIndex]; + if (model.mapModel != nullptr) { + model.primitiveGroupNumbers.SetNum( + model.mapModel->primitives.Num()); + for (int index = 0; index < model.primitiveGroupNumbers.Num(); + ++index) { + model.primitiveGroupNumbers[index] = 0; + } + continue; + } + cm_modelBuildSource_t source{}; + if (!ExtractModel(model, source) || source.numSurfaces < 0) { + model.primitiveGroupNumbers.Clear(); + continue; + } + model.primitiveGroupNumbers.SetNum(source.numSurfaces); + bool hasCollisionSurface = false; + for (int index = 0; index < source.numSurfaces; ++index) { + cm_materialBuildInfo_t info{}; + CM_GetMaterialBuildInfo(source.surfaces[index].material, info); + hasCollisionSurface |= (info.surfaceFlags & 0x40) != 0; + } + for (int index = 0; index < source.numSurfaces; ++index) { + cm_materialBuildInfo_t info{}; + CM_GetMaterialBuildInfo(source.surfaces[index].material, info); + const bool enabled = (info.contents & 0xEBFFFFFF) != 0 + && (!hasCollisionSurface + || (info.surfaceFlags & 0x40) != 0); + model.primitiveGroupNumbers[index] = !enabled ? -1 + : geometry.allowDiscrete && info.discrete + ? nextGroup++ : 0; + } + } + return nextGroup; +} + +void idCollisionModelBuilder::AddRenderModelEstimates( + const idStaticModelGeometry& geometry, const int groupNum, + const int, int& numVertices, int& numEdges, int& numPolygons, + idBounds& bounds) { + cm_modelBuildSource_t source{}; + if (!ExtractModel(geometry, source)) { + return; + } + for (int surfaceIndex = 0; surfaceIndex < source.numSurfaces; + ++surfaceIndex) { + if (PrimitiveGroup(geometry, surfaceIndex) != groupNum) { + continue; + } + const cm_modelSurfaceBuildSource_t& surface = + source.surfaces[surfaceIndex]; + if (surface.vertices == nullptr || surface.indices == nullptr + || surface.numIndices < 3) { + continue; + } + numVertices += surface.numVertices; + numEdges += surface.numIndices; + numPolygons += surface.numIndices / 3; + for (int vertex = 0; vertex < surface.numVertices; ++vertex) { + AddPointToBounds(bounds, TransformPoint( + surface.vertices[vertex].xyz, geometry.origin, + geometry.axis, geometry.scale)); + } + } +} + +void idCollisionModelBuilder::GetMapModelBrushBounds( + const idStaticModelGeometry& geometry, const int groupNum, + idBounds& bounds) { + if (geometry.mapModel == nullptr) { + return; + } + for (int primitiveIndex = 0; + primitiveIndex < geometry.mapModel->primitives.Num(); + ++primitiveIndex) { + const idMapPrimitive* const primitive = + geometry.mapModel->primitives[primitiveIndex]; + if (primitive == nullptr || primitive->type != MAP_PRIMITIVE_BRUSH + || PrimitiveGroup(geometry, primitiveIndex) != groupNum) { + continue; + } + const idMapBrush& brush = *static_cast(primitive); + for (int sideIndex = 0; sideIndex < brush.sides.Num(); ++sideIndex) { + if (brush.sides[sideIndex] == nullptr) { + continue; + } + idFixedWinding side = BasePlaneWinding( + brush.sides[sideIndex]->plane); + if (!ClipWindingToBrush(brush, sideIndex, side)) { + continue; + } + TransformWinding(side, geometry.origin, geometry.axis, + geometry.scale); + idBounds sideBounds; + side.GetBounds(sideBounds); + AddBoundsToBounds(bounds, sideBounds); + } + } +} + +void idCollisionModelBuilder::AddMapModelEstimates( + const idStaticModelGeometry& geometry, const int groupNum, + const int, int& numVertices, int& numEdges, int& numPolygons, + idBounds& bounds) { + if (geometry.mapModel == nullptr) { + return; + } + GetMapModelBrushBounds(geometry, groupNum, bounds); + for (int primitiveIndex = 0; + primitiveIndex < geometry.mapModel->primitives.Num(); + ++primitiveIndex) { + const idMapPrimitive* const primitive = + geometry.mapModel->primitives[primitiveIndex]; + if (primitive == nullptr + || PrimitiveGroup(geometry, primitiveIndex) != groupNum) { + continue; + } + if (primitive->type == MAP_PRIMITIVE_BRUSH) { + const int sides = static_cast(primitive) + ->sides.Num(); + numVertices += sides * 8; + numEdges += sides * 8; + numPolygons += sides; + } else if (primitive->type == MAP_PRIMITIVE_PATCH) { + const idMapPatch* const patch = + static_cast(primitive); + numVertices += patch->verts.Num(); + numEdges += patch->indexes.Num(); + numPolygons += patch->indexes.Num() / 3; + for (int vertex = 0; vertex < patch->verts.Num(); ++vertex) { + AddPointToBounds(bounds, TransformPoint( + patch->verts[vertex].xyz, geometry.origin, + geometry.axis, geometry.scale)); + } + } + } +} + +void idCollisionModelBuilder::ConvertBrush(cm_buildModel_t* const model, + const idMapBrush* const brush, const idVec3& origin, + const idMat3& axis, const idVec3& scale, + const idMaterial* const overrideMaterial, const int primitiveNum) { + if (model == nullptr || brush == nullptr || brush->sides.Num() < 4) { + return; + } + cm_buildPolytope_t* const polytope = AllocPolytope(model, + brush->sides.Num()); + if (polytope == nullptr) { + return; + } + polytope->bounds[0].Set(FLT_MAX, FLT_MAX, FLT_MAX); + polytope->bounds[1].Set(-FLT_MAX, -FLT_MAX, -FLT_MAX); + polytope->primitiveNum = primitiveNum; + const idMaterial* material = overrideMaterial; + for (int sideIndex = 0; sideIndex < brush->sides.Num(); ++sideIndex) { + const idMapBrushSide* const side = brush->sides[sideIndex]; + if (side == nullptr) { + polytope->numPlanes = 0; + return; + } + if (material == nullptr) { + material = ResolveMaterial(side->material, nullptr); + } + idFixedWinding winding = BasePlaneWinding(side->plane); + if (!ClipWindingToBrush(*brush, sideIndex, winding)) { + polytope->numPlanes = 0; + return; + } + TransformWinding(winding, origin, axis, scale); + idBounds windingBounds; + winding.GetBounds(windingBounds); + AddBoundsToBounds(polytope->bounds, windingBounds); + idPlane transformedPlane; + if (!PlaneFromPoints(idVec3(winding[0].x, winding[0].y, + winding[0].z), idVec3(winding[1].x, winding[1].y, + winding[1].z), idVec3(winding[2].x, winding[2].y, + winding[2].z), transformedPlane)) { + polytope->numPlanes = 0; + return; + } + model->polytopePlanes[polytope->firstPlane + sideIndex] = + transformedPlane; + } + cm_materialBuildInfo_t info{}; + CM_GetMaterialBuildInfo(material, info); + polytope->contents = info.contents; + polytope->material = FindMaterial(model, info.contents, + info.surfaceFlags, info.surfaceType); + if (model->node == nullptr) { + model->node = AllocNode(model, 8); + } + AddBoundsToBounds(model->node->bounds, polytope->bounds); + AddPolytopeToNode(model, model->node, polytope); +} + +void idCollisionModelBuilder::ConvertBrushSides( + cm_buildModel_t* const model, const idMapBrush* const brush, + const idVec3& origin, const idMat3& axis, const idVec3& scale, + const idMaterial* const overrideMaterial, const int primitiveNum) { + if (model == nullptr || brush == nullptr) { + return; + } + for (int sideIndex = 0; sideIndex < brush->sides.Num(); ++sideIndex) { + const idMapBrushSide* const side = brush->sides[sideIndex]; + if (side == nullptr) { + continue; + } + idFixedWinding winding = BasePlaneWinding(side->plane); + if (!ClipWindingToBrush(*brush, sideIndex, winding)) { + continue; + } + TransformWinding(winding, origin, axis, scale); + idPlane plane; + if (!PlaneFromPoints(idVec3(winding[0].x, winding[0].y, + winding[0].z), idVec3(winding[1].x, winding[1].y, + winding[1].z), idVec3(winding[2].x, winding[2].y, + winding[2].z), plane)) { + continue; + } + const idMaterial* const material = ResolveMaterial(side->material, + overrideMaterial); + cm_materialBuildInfo_t info{}; + CM_GetMaterialBuildInfo(material, info); + idFixedWinding* const outside = WindingOutsidePolytopes(model, + &winding, plane, info.contents, primitiveNum); + if (outside != nullptr) { + PolygonFromWinding(model, outside, plane, material, + primitiveNum); + } + } +} + +void idCollisionModelBuilder::CreatePatchPolygons( + cm_buildModel_t* const model, const idSurface_Patch* const mesh, + const idVec3& origin, const idMat3& axis, const idVec3& scale, + const idMaterial* const material, const int primitiveNum) { + if (model == nullptr || mesh == nullptr) { + return; + } + std::vector generatedIndices; + const int* indices = mesh->indexes.Num() > 0 + ? mesh->indexes.Ptr() : nullptr; + int numIndices = mesh->indexes.Num(); + if (indices == nullptr && mesh->width > 1 && mesh->height > 1) { + for (int row = 0; row < mesh->height - 1; ++row) { + for (int column = 0; column < mesh->width - 1; ++column) { + const int first = row * mesh->width + column; + generatedIndices.push_back(first); + generatedIndices.push_back(first + 1); + generatedIndices.push_back(first + mesh->width + 1); + generatedIndices.push_back(first); + generatedIndices.push_back(first + mesh->width + 1); + generatedIndices.push_back(first + mesh->width); + } + } + indices = generatedIndices.data(); + numIndices = static_cast(generatedIndices.size()); + } + for (int index = 0; index + 2 < numIndices; index += 3) { + const int i0 = indices[index + 0]; + const int i1 = indices[index + 1]; + const int i2 = indices[index + 2]; + if (i0 < 0 || i1 < 0 || i2 < 0 || i0 >= mesh->verts.Num() + || i1 >= mesh->verts.Num() || i2 >= mesh->verts.Num()) { + continue; + } + const idVec3 p0 = TransformPoint(mesh->verts[i0].xyz, origin, + axis, scale); + const idVec3 p1 = TransformPoint(mesh->verts[i1].xyz, origin, + axis, scale); + const idVec3 p2 = TransformPoint(mesh->verts[i2].xyz, origin, + axis, scale); + idPlane plane; + if (!PlaneFromPoints(p0, p1, p2, plane)) { + continue; + } + idFixedWinding winding; + winding.AddPoint(p0); + winding.AddPoint(p1); + winding.AddPoint(p2); + cm_materialBuildInfo_t info{}; + CM_GetMaterialBuildInfo(material, info); + idFixedWinding* const outside = WindingOutsidePolytopes(model, + &winding, plane, info.contents, primitiveNum); + if (outside != nullptr) { + PolygonFromWinding(model, outside, plane, material, + primitiveNum); + } + } +} + +void idCollisionModelBuilder::ConvertPatch(cm_buildModel_t* const model, + const idMapPatch* const patch, const idVec3& origin, + const idMat3& axis, const idVec3& scale, + const idMaterial* const overrideMaterial, const int primitiveNum) { + if (patch == nullptr) { + return; + } + CreatePatchPolygons(model, patch, origin, axis, scale, + ResolveMaterial(patch->material, overrideMaterial), primitiveNum); +} + +void idCollisionModelBuilder::ConvertMapModelPolytopes( + cm_buildModel_t* const model, const idStaticModelGeometry& geometry, + const int groupNum, int primitiveNum) { + if (model == nullptr || geometry.mapModel == nullptr) { + return; + } + for (int index = 0; index < geometry.mapModel->primitives.Num(); + ++index, ++primitiveNum) { + const idMapPrimitive* const primitive = + geometry.mapModel->primitives[index]; + if (primitive != nullptr && primitive->type == MAP_PRIMITIVE_BRUSH + && PrimitiveGroup(geometry, index) == groupNum) { + ConvertBrush(model, static_cast(primitive), + geometry.origin, geometry.axis, geometry.scale, + geometry.overrideClipMaterial, primitiveNum); + } + } +} + +void idCollisionModelBuilder::ConvertMapModelPrimitives( + cm_buildModel_t* const model, const idStaticModelGeometry& geometry, + const int groupNum, int primitiveNum) { + if (model == nullptr || geometry.mapModel == nullptr) { + return; + } + for (int index = 0; index < geometry.mapModel->primitives.Num(); + ++index, ++primitiveNum) { + const idMapPrimitive* const primitive = + geometry.mapModel->primitives[index]; + if (primitive == nullptr || PrimitiveGroup(geometry, index) + != groupNum) { + continue; + } + if (primitive->type == MAP_PRIMITIVE_PATCH) { + ConvertPatch(model, static_cast(primitive), + geometry.origin, geometry.axis, geometry.scale, + geometry.overrideClipMaterial, primitiveNum); + } else if (primitive->type == MAP_PRIMITIVE_BRUSH) { + ConvertBrushSides(model, + static_cast(primitive), geometry.origin, + geometry.axis, geometry.scale, + geometry.overrideClipMaterial, primitiveNum); + } + } +} + +void idCollisionModelBuilder::ConvertRenderModelSurfaces( + cm_buildModel_t* const model, const idStaticModelGeometry& geometry, + const int groupNum, int primitiveNum) { + cm_modelBuildSource_t source{}; + if (model == nullptr || !ExtractModel(geometry, source)) { + return; + } + for (int surfaceIndex = 0; surfaceIndex < source.numSurfaces; + ++surfaceIndex, ++primitiveNum) { + if (PrimitiveGroup(geometry, surfaceIndex) != groupNum) { + continue; + } + const cm_modelSurfaceBuildSource_t& surface = + source.surfaces[surfaceIndex]; + if (surface.vertices == nullptr || surface.indices == nullptr) { + continue; + } + cm_materialBuildInfo_t info{}; + CM_GetMaterialBuildInfo(surface.material, info); + for (int index = 0; index + 2 < surface.numIndices; index += 3) { + const int i0 = surface.indices[index + 0]; + const int i1 = surface.indices[index + 1]; + const int i2 = surface.indices[index + 2]; + if (i0 < 0 || i1 < 0 || i2 < 0 + || i0 >= surface.numVertices || i1 >= surface.numVertices + || i2 >= surface.numVertices) { + continue; + } + const idVec3 p0 = TransformPoint(surface.vertices[i0].xyz, + geometry.origin, geometry.axis, geometry.scale); + const idVec3 p1 = TransformPoint(surface.vertices[i1].xyz, + geometry.origin, geometry.axis, geometry.scale); + const idVec3 p2 = TransformPoint(surface.vertices[i2].xyz, + geometry.origin, geometry.axis, geometry.scale); + idPlane plane; + if (!PlaneFromPoints(p0, p1, p2, plane)) { + continue; + } + idFixedWinding winding; + winding.AddPoint(p0); + winding.AddPoint(p1); + winding.AddPoint(p2); + idFixedWinding* const outside = WindingOutsidePolytopes(model, + &winding, plane, info.contents, primitiveNum); + if (outside != nullptr) { + PolygonFromWinding(model, outside, plane, surface.material, + primitiveNum); + } + } + } +} + +void idCollisionModelBuilder::AddCollisionModelGeometry( + idCollisionModelLocal* const collisionModel, + const idCollisionModelGeometry& geometry, const int groupNum) { + if (collisionModel == nullptr) { + return; + } + int numVertices = 0; + int numEdges = 0; + int numPolygons = 0; + int primitiveNum = 0; + idBounds bounds; + bounds[0].Set(FLT_MAX, FLT_MAX, FLT_MAX); + bounds[1].Set(-FLT_MAX, -FLT_MAX, -FLT_MAX); + for (int index = 0; index < geometry.models.Num(); ++index) { + const idStaticModelGeometry& modelGeometry = geometry.models[index]; + if (modelGeometry.mapModel != nullptr) { + AddMapModelEstimates(modelGeometry, groupNum, primitiveNum, + numVertices, numEdges, numPolygons, bounds); + primitiveNum += modelGeometry.mapModel->primitives.Num(); + } else { + AddRenderModelEstimates(modelGeometry, groupNum, primitiveNum, + numVertices, numEdges, numPolygons, bounds); + cm_modelBuildSource_t source{}; + if (ExtractModel(modelGeometry, source)) { + primitiveNum += source.numSurfaces; + } + } + } + if (numVertices == 0 || numPolygons == 0) { + return; + } + cm_buildModel_t* const buildModel = AllocBuildModel(); + buildModel->name = collisionModel->GetName(); + buildModel->isWorldModel = geometry.isWorldEntity; + buildModel->node = AllocNode(buildModel, 8); + buildModel->node->bounds = bounds; + ClearHash(bounds); + + primitiveNum = 0; + for (int index = 0; index < geometry.models.Num(); ++index) { + const idStaticModelGeometry& modelGeometry = geometry.models[index]; + if (modelGeometry.mapModel != nullptr) { + ConvertMapModelPolytopes(buildModel, modelGeometry, groupNum, + primitiveNum); + primitiveNum += modelGeometry.mapModel->primitives.Num(); + } else { + cm_modelBuildSource_t source{}; + if (ExtractModel(modelGeometry, source)) { + primitiveNum += source.numSurfaces; + } + } + } + if (buildModel->numPolytopes > 1) { + CreateAxialBSPTree(buildModel); + } + ClearHash(bounds); + primitiveNum = 0; + for (int index = 0; index < geometry.models.Num(); ++index) { + const idStaticModelGeometry& modelGeometry = geometry.models[index]; + if (modelGeometry.mapModel != nullptr) { + ConvertMapModelPrimitives(buildModel, modelGeometry, groupNum, + primitiveNum); + primitiveNum += modelGeometry.mapModel->primitives.Num(); + } else { + ConvertRenderModelSurfaces(buildModel, modelGeometry, groupNum, + primitiveNum); + cm_modelBuildSource_t source{}; + if (ExtractModel(modelGeometry, source)) { + primitiveNum += source.numSurfaces; + } + } + } + CreateAxialBSPTree(buildModel); + ++buildModel->checkCount; + MergeTreePolygons(buildModel, buildModel->node, true); + SplitPolygons(buildModel); + ++buildModel->checkCount; + FindInternalEdges(buildModel, buildModel->node); + ++buildModel->checkCount; + GenerateEdgeNormals_r(buildModel, buildModel->node); + ++buildModel->checkCount; + OffsetPolygonEdges_r(buildModel, buildModel->node); + CreateAxialBSPTree(buildModel); + TestBoundsRange(collisionModel->GetName(), bounds); + AddSubModelsToCollisionModel(collisionModel, buildModel); + FreeModelMemory(buildModel); + delete buildModel; +} + +void idCollisionModelBuilder::BuildCollisionModelForGeometry( + idCollisionModelLocal* const model, idCollisionModelGeometry& geometry, + const int* const subModelIndices, const int numSubModelIndices, + const char* const optionalModelName) { + if (model == nullptr) { + return; + } + model->FreeData(); + model->SetName(optionalModelName != nullptr ? optionalModelName + : geometry.modelName.c_str()); + model->modelType = CM_POLYGONMODEL; + model->bounds[0].Set(FLT_MAX, FLT_MAX, FLT_MAX); + model->bounds[1].Set(-FLT_MAX, -FLT_MAX, -FLT_MAX); + model->contents = 0; + model->sourceFileTime = geometry.fileTime; + model->isWorldModel = geometry.isWorldEntity; + model->isTraceModel = false; + model->isConvex = false; + model->isStreamed = geometry.isStreamed; + const int numGroups = SetupBuildGroups(geometry); + if (subModelIndices != nullptr && numSubModelIndices > 0) { + for (int index = 0; index < numSubModelIndices; ++index) { + AddCollisionModelGeometry(model, geometry, + subModelIndices[index]); + } + } else { + for (int group = 0; group < numGroups; ++group) { + AddCollisionModelGeometry(model, geometry, group); + } + } + MergeModelTrees(model); +} + +bool idCollisionModelBuilder::BuildForRenderModel( + idCollisionModelLocal* const model, + const idRenderModel* const renderModel) { + cm_modelBuildSource_t source{}; + if (model == nullptr || renderModel == nullptr + || renderModelExtractor == nullptr + || !renderModelExtractor(renderModel, source)) { + return false; + } + idStaticModelGeometry modelGeometry; + modelGeometry.renderModel = renderModel; + modelGeometry.origin.Zero(); + modelGeometry.axis = idMat3(1.0f); + modelGeometry.scale.Set(1.0f, 1.0f, 1.0f); + idCollisionModelGeometry geometry; + geometry.modelName = source.name != nullptr ? source.name + : "_renderModel"; + geometry.fileTime = source.timeStamp; + geometry.allowDiscrete = true; + geometry.models.Append(modelGeometry); + SetupHash(); + BuildCollisionModelForGeometry(model, geometry, nullptr, 0, nullptr); + ShutdownHash(); + return model->polygonModel.numSubModels > 0; +} + +bool idCollisionModelBuilder::BuildForStaticModel( + idCollisionModelLocal* const model, + const idStaticModel* const staticModel, + const int* const subModelIndices, const int numSubModelIndices, + const char* const optionalModelName) { + cm_modelBuildSource_t source{}; + if (model == nullptr || staticModel == nullptr + || staticModelExtractor == nullptr + || !staticModelExtractor(staticModel, source)) { + return false; + } + idStaticModelGeometry modelGeometry; + modelGeometry.staticModel = staticModel; + modelGeometry.origin.Zero(); + modelGeometry.axis = idMat3(1.0f); + modelGeometry.scale.Set(1.0f, 1.0f, 1.0f); + idCollisionModelGeometry geometry; + geometry.modelName = source.name != nullptr ? source.name + : "_staticModel"; + geometry.fileTime = source.timeStamp; + geometry.allowDiscrete = true; + geometry.models.Append(modelGeometry); + SetupHash(); + BuildCollisionModelForGeometry(model, geometry, subModelIndices, + numSubModelIndices, optionalModelName); + ShutdownHash(); + return model->polygonModel.numSubModels > 0; +} + +void idCollisionModelBuilder::BuildForMapFile( + const idMapFile* const mapFile, const bool inlineStatic, + const bool mapModelOnly) { + if (mapFileBuildCallback != nullptr) { + mapFileBuildCallback(mapFile, inlineStatic, mapModelOnly); + return; + } + if (mapFile == nullptr) { + return; + } + (void)inlineStatic; + (void)mapModelOnly; + SetupHash(); + for (int entityIndex = 0; entityIndex < mapFile->entities.Num(); + ++entityIndex) { + const idMapEntity* const entity = mapFile->entities[entityIndex]; + if (entity == nullptr || entity->model.primitives.Num() == 0) { + continue; + } + idStaticModelGeometry modelGeometry; + modelGeometry.mapModel = &entity->model; + modelGeometry.origin.Zero(); + modelGeometry.axis = idMat3(1.0f); + modelGeometry.scale.Set(1.0f, 1.0f, 1.0f); + idCollisionModelGeometry geometry; + if (entityIndex == 0) { + geometry.modelName = mapFile->name; + geometry.isWorldEntity = true; + } else if (entity->model.name.Length() > 0) { + geometry.modelName = entity->model.name; + } else { + char generatedName[64]; + _snprintf_s(generatedName, sizeof(generatedName), _TRUNCATE, + "%s_entity_%d", mapFile->name.c_str(), entityIndex); + geometry.modelName = generatedName; + } + geometry.fileTime = mapFile->fileTime; + geometry.models.Append(modelGeometry); + idCollisionModelLocal* const collisionModel = + new idCollisionModelLocal(); + BuildCollisionModelForGeometry(collisionModel, geometry, nullptr, + 0, nullptr); + if (collisionModel->polygonModel.numSubModels == 0) { + delete collisionModel; + continue; + } + idCollisionModelLocal::resourceList.Add(collisionModel); + collisionModel->Write_Binary(); + } + ShutdownHash(); +} + +void idCollisionModelBuilder::CreateStreamAreas( + idCollisionModelLocal* const model, + const idList& geometries) { + if (model == nullptr) { + return; + } + int numAreas = 0; + int nameBytes = 0; + for (int index = 0; index < geometries.Num(); ++index) { + if (geometries[index].isStreamArea) { + ++numAreas; + nameBytes += geometries[index].modelName.Length() + 1; + } + } + if (numAreas == 0) { + return; + } + const int numAreaSubModels = numAreas + * model->polygonModel.numSubModels; + const int totalSize = sizeof(streamAreasHeader_t) + + numAreas * sizeof(streamArea_t) + + numAreaSubModels * sizeof(std::uint16_t) + nameBytes; + streamAreasHeader_t* const header = + static_cast(_aligned_malloc(totalSize, 16)); + if (header == nullptr) { + return; + } + std::memset(header, 0, totalSize); + header->totalSize = totalSize; + header->numStreamAreas = numAreas; + header->numStreamAreaSubModels = numAreaSubModels; + header->numStreamAreaNameBytes = nameBytes; + streamAreasPtrs_t pointers{}; + SetupStreamAreaPtrs(header, pointers); + int areaIndex = 0; + int subModelOffset = 0; + int nameOffset = 0; + for (int index = 0; index < geometries.Num(); ++index) { + const idCollisionModelGeometry& geometry = geometries[index]; + if (!geometry.isStreamArea) { + continue; + } + streamArea_t& area = pointers.streamAreas[areaIndex++]; + area.volumeNameOffset = nameOffset; + area.volumeOrigin = geometry.streamVolumeOrigin; + area.volumeAxis = geometry.streamVolumeAxis; + area.numSubModels = model->polygonModel.numSubModels; + area.subModelsOffset = subModelOffset; + for (int subModel = 0; + subModel < model->polygonModel.numSubModels; ++subModel) { + pointers.streamAreaSubModels[subModelOffset++] = + static_cast(subModel); + } + const int length = geometry.modelName.Length() + 1; + std::memcpy(pointers.streamAreaNameBytes + nameOffset, + geometry.modelName.c_str(), length); + nameOffset += length; + } + _aligned_free(model->streamAreas); + model->streamAreas = header; +} diff --git a/source/engine/cm/collisionmodelbuilder_sphere.cpp b/source/engine/cm/collisionmodelbuilder_sphere.cpp new file mode 100644 index 0000000..adbd85b --- /dev/null +++ b/source/engine/cm/collisionmodelbuilder_sphere.cpp @@ -0,0 +1,120 @@ +#include "cm/collisionmodelbuilder.h" + +#include "cm/collisionmodel.h" +#include "cm/jobs/spheremodel/spheremodel.h" + +#include +#include +#include +#include + +namespace { + +cm_declMD6SphereExtractor_t declMD6SphereExtractor = nullptr; + +int Align16(const int value) { + return (value + 15) & ~15; +} + +} // namespace + +bool idCollisionModelBuilder::IsAnimatedRenderModel( + const char* const fileName) { + if (fileName == nullptr) { + return false; + } + const char* const extension = std::strrchr(fileName, '.'); + if (extension == nullptr || extension[1] == '\0') { + return false; + } + return _stricmp(extension + 1, "md6") == 0; +} + +void idCollisionModelBuilder::SetDeclMD6SphereExtractor( + const cm_declMD6SphereExtractor_t extractor) { + declMD6SphereExtractor = extractor; +} + +bool idCollisionModelBuilder::BuildForDeclMD6( + idCollisionModelLocal* const model, const idDeclMD6* const md6Decl) { + if (model == nullptr || md6Decl == nullptr + || declMD6SphereExtractor == nullptr) { + return false; + } + cm_sphereBuildSource_t source{}; + return declMD6SphereExtractor(md6Decl, source) + && BuildForSpheres(model, source); +} + +bool idCollisionModelBuilder::BuildForSpheres( + idCollisionModelLocal* const model, + const cm_sphereBuildSource_t& source) { + if (model == nullptr || source.name == nullptr || source.spheres == nullptr + || source.numSpheres <= 0 || source.numModelJoints <= 0 + || source.numModelJoints > 256 || source.numSpheres > 0xFFFF) { + return false; + } + + const int paddedSpheres = (source.numSpheres + 3) & ~3; + const int jointOffset = 64; + const int offsetXOffset = Align16(jointOffset + paddedSpheres); + const int offsetYOffset = Align16(offsetXOffset + 4 * paddedSpheres); + const int offsetZOffset = Align16(offsetYOffset + 4 * paddedSpheres); + const int radiusOffset = Align16(offsetZOffset + 4 * paddedSpheres); + const int surfaceTypeOffset = Align16(radiusOffset + 4 * paddedSpheres); + const int totalSize = surfaceTypeOffset + paddedSpheres; + if (totalSize > 0xFFFF || surfaceTypeOffset > 0xFFFF) { + return false; + } + + cm_sphereModel_t* const packed = static_cast( + _aligned_malloc(static_cast(totalSize), 16)); + if (packed == nullptr) { + return false; + } + std::memset(packed, 0, static_cast(totalSize)); + packed->totalSize = static_cast(totalSize); + packed->timeStamp = source.timeStamp; + packed->bounds = source.bounds; + packed->contents = static_cast(source.contents); + packed->numModelJoints = static_cast( + source.numModelJoints); + packed->numSpheres = static_cast(source.numSpheres); + packed->jointOffset = static_cast(jointOffset); + packed->offsetXOffset = static_cast(offsetXOffset); + packed->offsetYOffset = static_cast(offsetYOffset); + packed->offsetZOffset = static_cast(offsetZOffset); + packed->radiusOffset = static_cast(radiusOffset); + packed->surfTypeOffset = static_cast(surfaceTypeOffset); + + cm_sphereModelPtrs_t pointers{}; + idSphereModelCollisionDetection::SetupCollisionSpherePtrs( + packed, pointers); + for (int index = 0; index < paddedSpheres; ++index) { + const int sourceIndex = (std::min)(index, source.numSpheres - 1); + const cm_collisionSphereDesc_t& sphere = source.spheres[sourceIndex]; + if (sphere.joint >= source.numModelJoints || sphere.radius < 0.0f) { + _aligned_free(packed); + return false; + } + pointers.joint[index] = sphere.joint; + pointers.offsetX[index] = sphere.offset.x; + pointers.offsetY[index] = sphere.offset.y; + pointers.offsetZ[index] = sphere.offset.z; + pointers.radius[index] = sphere.radius; + pointers.surfType[index] = sphere.surfaceType; + } + + model->FreeData(); + model->SetName(source.name); + model->sourceFileTime = source.timeStamp; + model->modelType = CM_SPHEREMODEL; + model->bounds = source.bounds; + model->contents = source.contents; + model->isWorldModel = false; + model->isTraceModel = false; + model->isConvex = false; + model->isStreamed = false; + model->sphereModel = packed; + return true; +} diff --git a/source/engine/cm/collisionmodelmanager.cpp b/source/engine/cm/collisionmodelmanager.cpp new file mode 100644 index 0000000..c676fac --- /dev/null +++ b/source/engine/cm/collisionmodelmanager.cpp @@ -0,0 +1,944 @@ +#include "cm/collisionmodelmanager.h" + +#include "cm/collisiongrid.h" +#include "cm/collisionmodel.h" +#include "cm/collisionmodelbuilder.h" +#include "cm/jobs/polygonmodel/polygonmodel.h" +#include "cm/jobs/polygonmodel/polygonmodeldata.h" + +#include "idlib/sys/sys_alloc.h" +#include "idlib/filesystem/file.h" +#include "idlib/filesystem/filesystem.h" +#include "idlib/geometry/tracemodel.h" +#include "idlib/lib_print.h" +#include "idlib/text/cmdargs.h" +#include "framework/resourcelist.h" + +#include +#include +#include +#include +#include + +idCollisionModelManager collisionModelManager; + +namespace { + +idCollisionQueryJobManager cdQuery; +idTraceWork* baseTraceWork = nullptr; + +void EnsureQueryManager() { + if (cdQuery.queryData == nullptr) { + cdQuery.Init(); + } + if (baseTraceWork == nullptr) { + baseTraceWork = idPolygonModelCollisionDetection::AllocTraceWork(); + } +} + +} // namespace + +idCollisionGridState::idCollisionGridState() + : material(nullptr), numActive(0), firstActive(-1), + active(TAG_BITARRAY) { +} + +void idCollisionGridState::Create(const int num, + const idMaterial* const material_) { + active.Alloc(static_cast(num)); + for (int index = 0; index < num; ++index) { + active.Set(static_cast(index)); + } + numActive = num; + material = material_; + firstActive = num > 0 ? 0 : -1; +} + +void idCollisionGridState::Activate(const unsigned int id) { + if (active.Get(id)) { + return; + } + if (firstActive < 0 || static_cast(id) < firstActive) { + firstActive = static_cast(id); + } + active.Set(id); + ++numActive; +} + +int idCollisionGridState::NextActive(const int id) const { + for (unsigned int candidate = static_cast(id + 1); + candidate < active.Num(); ++candidate) { + if (active.Get(candidate)) { + return static_cast(candidate); + } + } + return -1; +} + +void idCollisionGridState::Inactivate(const unsigned int id) { + if (!active.Get(id)) { + return; + } + active.Clear(id); + --numActive; + if (numActive == 0) { + firstActive = -1; + } else if (firstActive == static_cast(id)) { + firstActive = NextActive(static_cast(id)); + } +} + +int idCollisionGridState::FirstActive() const { + return numActive > 0 ? firstActive : -1; +} + +idCollisionModel* idCollisionModelManager::ModelFromTrm( + const char* const modelName, const idTraceModel& traceModel, + const idMaterial* const material) { + idCollisionModelLocal* const model = new idCollisionModelLocal(); + if (model == nullptr) { + return nullptr; + } + if (!idCollisionModelBuilder::BuildForTrm( + model, modelName, traceModel, material)) { + delete model; + return nullptr; + } + return model; +} + +idCollisionModel* idCollisionModelManager::BuildModel( + const char* const modelName, const idStaticModel* const staticModel, + const int* const subModelIndices, const int numSubModelIndices) { + if (staticModel == nullptr) { + return nullptr; + } + idCollisionModelLocal* const model = new idCollisionModelLocal(); + if (model == nullptr || !idCollisionModelBuilder::BuildForStaticModel( + model, staticModel, subModelIndices, numSubModelIndices, + modelName)) { + delete model; + return nullptr; + } + idCollisionModelLocal::resourceList.Add(model); + return model; +} + +void idCollisionModelManager::BuildModelsForMapFile( + const idMapFile* const mapFile, const bool mapModelOnly) { + idCollisionModelBuilder::BuildForMapFile(mapFile, true, + mapModelOnly); +} + +idCollisionModel* idCollisionModelManager::ModelFromRender( + const char*, const idRenderModel* const renderModel) { + if (renderModel == nullptr) { + return nullptr; + } + idCollisionModelLocal* const model = new idCollisionModelLocal(); + if (model == nullptr + || !idCollisionModelBuilder::BuildForRenderModel(model, + renderModel)) { + delete model; + return nullptr; + } + return model; +} + +idCollisionModel* idCollisionModelManager::ModelFromGrid( + const char* const modelName, const idCollisionGrid* const grid, + const idCollisionGridState& state, const idMaterial* const material) { + const idCollisionGridLocal* const localGrid = + dynamic_cast(grid); + if (localGrid == nullptr) { + return nullptr; + } + idCollisionModelLocal* const model = new idCollisionModelLocal(); + if (!idCollisionModelBuilder::BuildForGrid(model, modelName, + localGrid->grid, state, material)) { + delete model; + return nullptr; + } + return model; +} + +void idCollisionModelManager::Init() { + EnsureQueryManager(); +} + +void idCollisionModelManager::Shutdown() { + if (baseTraceWork != nullptr) { + baseTraceWork->~idTraceWork(); + mem.Free(baseTraceWork, ALIGN_16); + baseTraceWork = nullptr; + } + cdQuery.Shutdown(); +} + +void idCollisionModelManager::StartQueryFrame() { + EnsureQueryManager(); + cdQuery.StartFrame(); +} + +void idCollisionModelManager::EndQueryFrame() { + EnsureQueryManager(); + cdQuery.EndFrame(); +} + +void idCollisionModelManager::SubmitQueries() { + EnsureQueryManager(); + cdQuery.SubmitQueries(); +} + +void idCollisionModelManager::WaitForAllQueries() { + EnsureQueryManager(); + cdQuery.WaitForAllQueries(); +} + +idCollisionModel* idCollisionModelManager::LoadModel( + const char* const modelName) { + if (modelName == nullptr || *modelName == '\0') { + return nullptr; + } + idCollisionModelLocal* const model = new idCollisionModelLocal(); + model->SetName(modelName); + model->LoadResource(); + return model; +} + +idCollisionGrid* idCollisionModelManager::LoadGrid( + const char* const modelName) { + if (modelName == nullptr || *modelName == '\0') { + return nullptr; + } + idCollisionGridLocal* const grid = new idCollisionGridLocal(); + grid->SetName(modelName); + grid->LoadResource(); + return grid; +} + +int idCollisionModelManager::FindStreamArea(idCollisionModel* const model, + const char* const areaName) { + idCollisionModelLocal* const local = + dynamic_cast(model); + if (local == nullptr || local->streamAreas == nullptr + || areaName == nullptr) { + return -1; + } + streamAreasPtrs_t pointers{}; + SetupStreamAreaPtrs(local->streamAreas, pointers); + for (int index = 0; index < local->streamAreas->numStreamAreas; + ++index) { + const char* const name = pointers.streamAreaNameBytes + + pointers.streamAreas[index].volumeNameOffset; + if (_stricmp(name, areaName) == 0) { + return index; + } + } + return -1; +} + +void idCollisionModelManager::StreamModel(idCollisionModel* const model, + const int* const areaIndices, const int numAreaIndices, + const bool wait) { + idCollisionModelLocal* const local = + dynamic_cast(model); + if (local == nullptr || !local->isStreamed + || local->streamAreas == nullptr + || (numAreaIndices != -1 + && (numAreaIndices < 0 + || (numAreaIndices > 0 && areaIndices == nullptr)))) { + return; + } + + // The recovered interface describes the complete desired-area set. The + // final flag only controls whether the Xenon implementation waits for its + // asynchronous I/O passes; the PC port performs those reads synchronously. + (void)wait; + std::vector desired( + static_cast(local->polygonModel.numSubModels), false); + streamAreasPtrs_t areas{}; + SetupStreamAreaPtrs(local->streamAreas, areas); + if (numAreaIndices == -1) { + std::fill(desired.begin(), desired.end(), true); + } else { + for (int areaListIndex = 0; areaListIndex < numAreaIndices; + ++areaListIndex) { + const int areaIndex = areaIndices[areaListIndex]; + if (areaIndex < 0 + || areaIndex >= local->streamAreas->numStreamAreas) { + continue; + } + const streamArea_t& area = areas.streamAreas[areaIndex]; + for (int index = 0; index < area.numSubModels; ++index) { + const int subModelIndex = areas.streamAreaSubModels[ + area.subModelsOffset + index]; + if (subModelIndex >= 0 + && subModelIndex < local->polygonModel.numSubModels) { + desired[static_cast(subModelIndex)] = true; + } + } + } + } + + for (int subModelIndex = 0; + subModelIndex < local->polygonModel.numSubModels; + ++subModelIndex) { + cm_subModel_t& subModel = + local->polygonModel.subModels[subModelIndex]; + if (!desired[static_cast(subModelIndex)]) { + if (*subModel.state == SUBMODEL_STATE_LOADED + && subModel.numUsers == 0 + && local->memoryMappedFile == nullptr) { + *subModel.state = SUBMODEL_STATE_UNLOADED; + _aligned_free(subModel.data); + subModel.data = nullptr; + } + continue; + } + if (*subModel.state == SUBMODEL_STATE_LOADED + || local->streamFilePtr == nullptr + || subModel.fileOffset < 0) { + continue; + } + cm_subModelData_t* const data = + static_cast(_aligned_malloc( + subModel.header.totalSize, 16)); + if (data != nullptr && local->streamFilePtr->ReadOfs( + subModel.fileOffset, data, + subModel.header.totalSize) + == static_cast(subModel.header.totalSize)) { + subModel.data = data; + *subModel.state = SUBMODEL_STATE_LOADED; + } else { + _aligned_free(data); + } + } +} + +bool idCollisionModelManager::IsResident(const idBounds&, + const idPositionedCollisionModel* const models, + const int numModels) { + if (models == nullptr || numModels <= 0) { + return true; + } + for (int modelIndex = 0; modelIndex < numModels; ++modelIndex) { + const idCollisionModelLocal* const model = + dynamic_cast( + models[modelIndex].model); + if (model == nullptr || model->modelType != CM_POLYGONMODEL) { + continue; + } + for (int subModelIndex = 0; + subModelIndex < model->polygonModel.numSubModels; + ++subModelIndex) { + if (*model->polygonModel.subModels[subModelIndex].state + != SUBMODEL_STATE_LOADED) { + return false; + } + } + } + return true; +} + +void PrintSubModelInfo(const cm_subModelData_t* const data) { + if (data == nullptr) { + return; + } + idLibPrint::Printf(" %6i nodes (%i kB)\n", data->numNodes, + data->numNodes * 16 / 1024); + idLibPrint::Printf(" %6i primitive indices (%i kB)\n", + data->numPrimitiveIndices, data->numPrimitiveIndices * 2 / 1024); + idLibPrint::Printf(" %6i materials (%i kB)\n", data->numMaterials, + data->numMaterials * 16 / 1024); + idLibPrint::Printf(" %6i polygons (%i kB)\n", data->numPolygons, + data->numPolygons * 16 / 1024); + idLibPrint::Printf(" %6i polygon edges (%i kB)\n", + data->numPolygonEdges, data->numPolygonEdges * 2 / 1024); + idLibPrint::Printf(" %6i edges (%i kB)\n", data->numEdges, + data->numEdges * 4 / 1024); + idLibPrint::Printf(" %6i vertices (%i kB)\n", data->numVertices, + data->numVertices * 16 / 1024); + idLibPrint::Printf(" %6i polytopes (%i kB)\n", data->numPolytopes, + data->numPolytopes * 16 / 1024); + idLibPrint::Printf(" %6i polytope planes (%i kB)\n", + data->numPolytopePlanes, data->numPolytopePlanes * 16 / 1024); +} + +void AddCollisionModelInfo(const idCollisionModelLocal* const model, + cm_subModelData_t* const stats) { + if (model == nullptr || stats == nullptr + || model->modelType != CM_POLYGONMODEL) { + return; + } + for (int index = 0; index < model->polygonModel.numSubModels; ++index) { + const cm_subModel_t& subModel = model->polygonModel.subModels[index]; + const cm_subModelData_t* const data = AcquireSubModelData(subModel); + if (data != nullptr && data->header.loadedSize != 32) { + stats->numNodes += data->numNodes; + stats->numPrimitiveIndices += data->numPrimitiveIndices; + stats->numMaterials += data->numMaterials; + stats->numPolygons += data->numPolygons; + stats->numPolygonEdges += data->numPolygonEdges; + stats->numEdges += data->numEdges; + stats->numVertices += data->numVertices; + stats->numPolytopes += data->numPolytopes; + stats->numPolytopePlanes += data->numPolytopePlanes; + } + ReleaseSubModelData(subModel, data); + } +} + +void PrintCollisionModelInfo(const idCollisionModelLocal* const model) { + if (model == nullptr) { + return; + } + idLibPrint::Printf("collision model %s: %i total / %i loaded bytes\n", + model->GetName(), model->GetTotalMemory(), model->GetLoadedMemory()); + if (model->modelType != CM_POLYGONMODEL) { + return; + } + for (int index = 0; index < model->polygonModel.numSubModels; ++index) { + const cm_subModel_t& subModel = model->polygonModel.subModels[index]; + const cm_subModelData_t* const data = AcquireSubModelData(subModel); + if (data != nullptr && data->header.loadedSize != 32) { + PrintSubModelInfo(data); + } + ReleaseSubModelData(subModel, data); + } +} + +bool idTrmFromSubModel::TrmFromSubModel( + const idCollisionModelLocal* const model, + const cm_subModelData_t* const data, idTraceModel& trm) { + if (model == nullptr || data == nullptr || data->header.loadedSize == 32 + || data->numVertices > 32 || data->numEdges > 32 + || data->numPolygons > 16 || data->numVertices <= 3) { + return false; + } + std::memset(&trm, 0, sizeof(trm)); + trm.type = TRM_CUSTOM; + trm.bounds[0].Set(FLT_MAX, FLT_MAX, FLT_MAX); + trm.bounds[1].Set(-FLT_MAX, -FLT_MAX, -FLT_MAX); + cm_subModelPtrs_t pointers{}; + idPolygonModelCollisionDetection::SetupSubModelPtrsFromData( + pointers, data); + trm.numPolys = data->numPolygons; + trm.maxPolyEdges = 0; + for (int polygonIndex = 0; polygonIndex < data->numPolygons; + ++polygonIndex) { + const cm_polygon_t& polygon = pointers.polygons[polygonIndex]; + idPlane plane; + CM_GetPolygonPlane(pointers, polygon, plane); + trm.polyPlaneX[polygonIndex] = plane.a; + trm.polyPlaneY[polygonIndex] = plane.b; + trm.polyPlaneZ[polygonIndex] = plane.c; + trm.polyPlaneW[polygonIndex] = plane.d; + trm.numPolyEdges[polygonIndex] = polygon.numEdges; + trm.maxPolyEdges = (std::max)(trm.maxPolyEdges, + static_cast(polygon.numEdges)); + for (int edge = 0; edge < polygon.numEdges; ++edge) { + const std::uint16_t reference = pointers.polygonEdges[ + polygon.firstEdge + edge]; + trm.polyEdges[polygonIndex][edge] = static_cast( + CM_EdgeIndex(reference) + | ((reference & 0x8000) != 0 ? 0x80 : 0)); + } + } + trm.numVerts = data->numVertices; + for (int index = 0; index < data->numVertices; ++index) { + const idVec3& vertex = pointers.vertices[index].p; + trm.vertsX[index] = vertex.x; + trm.vertsY[index] = vertex.y; + trm.vertsZ[index] = vertex.z; + for (int axis = 0; axis < 3; ++axis) { + trm.bounds[0][axis] = (std::min)(trm.bounds[0][axis], + vertex[axis]); + trm.bounds[1][axis] = (std::max)(trm.bounds[1][axis], + vertex[axis]); + } + } + trm.numEdges = data->numEdges; + for (int index = 0; index < data->numEdges; ++index) { + trm.edges[index].v[0] = pointers.edges[index].vertexNum[0]; + trm.edges[index].v[1] = pointers.edges[index].vertexNum[1]; + } + if (!trm.IsClosedSurface()) { + return false; + } + trm.CalculateInsetSphereRadius(); + trm.offset = (trm.bounds[0] + trm.bounds[1]) * 0.5f; + trm.GenerateEdgeNormals(); + trm.TestConvexity(); + trm.ClearUnused(); + return true; +} + +bool TrmFromModel(idCollisionModelLocal* const model, idTraceModel* const trm) { + if (model == nullptr || trm == nullptr + || model->modelType != CM_POLYGONMODEL + || model->polygonModel.numSubModels != 1) { + return false; + } + const cm_subModel_t& subModel = model->polygonModel.subModels[0]; + const cm_subModelData_t* const data = AcquireSubModelData(subModel); + const bool result = idTrmFromSubModel::TrmFromSubModel( + model, data, *trm); + ReleaseSubModelData(subModel, data); + return result; +} + +bool idCollisionModelManager::TrmFromModel(const char* const modelName, + idTraceModel& trm) { + idCollisionModelLocal* const model = dynamic_cast( + LoadModel(modelName)); + if (model == nullptr) { + return false; + } + const bool result = ::TrmFromModel(model, &trm); + delete model; + return result; +} + +int idCollisionModelManager::CompoundTrmFromModel( + const char* const modelName, idTraceModel* const trms, + const int maxTrms, int* const invalidSubmodelIndices, + int* const numInvalidIndices) { + idCollisionModelLocal* const model = dynamic_cast( + LoadModel(modelName)); + if (model == nullptr || trms == nullptr || maxTrms <= 0 + || model->modelType != CM_POLYGONMODEL + || model->polygonModel.numSubModels > maxTrms) { + delete model; + return 0; + } + const int invalidCapacity = numInvalidIndices != nullptr + ? *numInvalidIndices : 0; + if (numInvalidIndices != nullptr) { + *numInvalidIndices = 0; + } + int count = 0; + for (int index = 0; index < model->polygonModel.numSubModels; ++index) { + const cm_subModel_t& subModel = model->polygonModel.subModels[index]; + const cm_subModelData_t* const data = AcquireSubModelData(subModel); + const bool valid = idTrmFromSubModel::TrmFromSubModel( + model, data, trms[count]); + ReleaseSubModelData(subModel, data); + if (valid) { + ++count; + } else if (invalidSubmodelIndices != nullptr + && numInvalidIndices != nullptr + && *numInvalidIndices < invalidCapacity) { + invalidSubmodelIndices[(*numInvalidIndices)++] = index + 1; + } else { + delete model; + return 0; + } + } + delete model; + return count; +} + +idCollisionQuery idCollisionModelManager::Translation(trace_t* const result, + const idVec3& start, const idVec3& end, const idBounds& globalBounds, + const idTraceModel** const trms, const int numTrms, + const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* const models, const int numModels, + const char* const userName) { + EnsureQueryManager(); + idCollisionQuery query = cdQuery.SubmitTranslationQuery(start, end, + globalBounds, trms, numTrms, trmAxis, contentMask, models, + numModels, userName); + if (result != nullptr) { + cdQuery.GetTraceResult(result, query, false); + } + return query; +} + +idCollisionQuery idCollisionModelManager::LocalTranslation( + const idCollisionQuery localSpace, const idVec3& start, + const idVec3& end, const idBounds& globalBounds, + const idTraceModel** const trms, const int numTrms, + const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* const models, const int numModels, + const char* const userName) { + EnsureQueryManager(); + return cdQuery.SubmitLocalTranslationQuery(localSpace, start, end, + globalBounds, trms, numTrms, trmAxis, contentMask, models, + numModels, userName); +} + +idCollisionQuery idCollisionModelManager::Rotation(trace_t* const result, + const idVec3& start, const idRotation& rotation, + const idBounds& globalBounds, const idTraceModel** const trms, + const int numTrms, const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* const models, const int numModels, + const char* const userName) { + EnsureQueryManager(); + idCollisionQuery query = cdQuery.SubmitRotationQuery(start, rotation, + globalBounds, trms, numTrms, trmAxis, contentMask, models, + numModels, userName); + if (result != nullptr) { + cdQuery.GetRotationResult(baseTraceWork, result, query, false); + } + return query; +} + +idCollisionQuery idCollisionModelManager::Motion(trace_t* const result, + const idVec3& start, const idVec3& end, const idRotation& rotation, + const idBounds& globalBounds, const idTraceModel** const trms, + const int numTrms, const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* const models, const int numModels, + const char* const userName) { + EnsureQueryManager(); + idCollisionQuery query = cdQuery.SubmitMotionQuery(start, end, rotation, + globalBounds, trms, numTrms, trmAxis, contentMask, models, + numModels, userName); + if (result != nullptr) { + cdQuery.GetTraceResult(result, query, false); + } + return query; +} + +idCollisionQuery idCollisionModelManager::MotionContacts( + trace_t* const result, contactsResult_t* const contacts, + const idVec3& start, const idVec3& end, const idRotation& rotation, + const float depth, const idBounds& globalBounds, + const idTraceModel** const trms, const int numTrms, + const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* const models, const int numModels, + const char* const userName) { + EnsureQueryManager(); + idCollisionQuery query = cdQuery.SubmitMotionContactsQuery(start, end, + rotation, depth, globalBounds, trms, numTrms, trmAxis, + contentMask, models, numModels, userName); + if (result != nullptr || contacts != nullptr) { + cdQuery.GetMotionContactsResult(baseTraceWork, result, contacts, + query, false); + } + return query; +} + +idCollisionQuery idCollisionModelManager::Contents(trace_t* const result, + const idVec3& start, const idBounds& globalBounds, + const idTraceModel** const trms, const int numTrms, + const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* const models, const int numModels, + const char* const userName) { + EnsureQueryManager(); + idCollisionQuery query = cdQuery.SubmitContentsQuery(start, globalBounds, + trms, numTrms, trmAxis, contentMask, models, numModels, userName); + if (result != nullptr) { + cdQuery.GetTraceResult(result, query, false); + } + return query; +} + +idCollisionQuery idCollisionModelManager::LocalContents( + const idCollisionQuery localSpace, const idVec3& start, + const idBounds& globalBounds, const idTraceModel** const trms, + const int numTrms, const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* const models, const int numModels, + const char* const userName) { + EnsureQueryManager(); + return cdQuery.SubmitLocalContentsQuery(localSpace, start, globalBounds, + trms, numTrms, trmAxis, contentMask, models, numModels, userName); +} + +idCollisionQuery idCollisionModelManager::Contacts( + contactsResult_t* const result, const idVec3& start, + const idVec3& direction, const float depth, + const idBounds& globalBounds, const idTraceModel** const trms, + const int numTrms, const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* const models, const int numModels, + const char* const userName) { + EnsureQueryManager(); + idCollisionQuery query = cdQuery.SubmitContactsQuery(start, direction, + depth, globalBounds, trms, numTrms, trmAxis, contentMask, models, + numModels, userName); + if (result != nullptr) { + cdQuery.GetContactsResult(result, query, false); + } + return query; +} + +idCollisionQuery idCollisionModelManager::Clip(clipResult_t* const result, + const idVec3& start, const idBounds& globalBounds, + const idTraceModel** const trms, const int numTrms, + const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* const models, const int numModels, + const char* const userName) { + EnsureQueryManager(); + idCollisionQuery query = cdQuery.SubmitClipQuery(start, globalBounds, + trms, numTrms, trmAxis, contentMask, models, numModels, userName); + if (result != nullptr) { + cdQuery.GetClipResult(baseTraceWork, result, query, false); + } + return query; +} + +idCollisionQuery idCollisionModelManager::StepMove(trace_t* const result, + const idVec3& start, const idVec3& end, const idVec3& downNormal, + const float stepUp, const float stepDown, + const idBounds& globalBounds, const idTraceModel** const trms, + const int numTrms, const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* const models, const int numModels, + const char* const userName) { + EnsureQueryManager(); + idCollisionQuery query = cdQuery.SubmitStepMoveQuery(start, end, + downNormal, stepUp, stepDown, globalBounds, trms, numTrms, + trmAxis, contentMask, models, numModels, userName); + if (result != nullptr) { + cdQuery.GetTraceResult(result, query, false); + } + return query; +} + +idCollisionQuery idCollisionModelManager::StepMoveContacts( + trace_t* const result, contactsResult_t* const contacts, + const idVec3& start, const idVec3& end, const idVec3& downNormal, + const float stepUp, const float stepDown, + const idBounds& globalBounds, const idTraceModel** const trms, + const int numTrms, const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* const models, const int numModels, + const char* const userName) { + EnsureQueryManager(); + idCollisionQuery query = cdQuery.SubmitStepMoveContactsQuery(start, end, + downNormal, stepUp, stepDown, globalBounds, trms, numTrms, + trmAxis, contentMask, models, numModels, userName); + if (result != nullptr || contacts != nullptr) { + cdQuery.GetSlideMoveContactsResult(baseTraceWork, result, contacts, + query, false); + } + return query; +} + +idCollisionQuery idCollisionModelManager::SlideMove(trace_t* const result, + const idVec3& start, const idVec3& velocity, + const idVec3& gravityVector, const float stepUp, + const float stepDown, const idBounds& globalBounds, + const idTraceModel** const trms, const int numTrms, + const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* const models, const int numModels, + const char* const userName) { + EnsureQueryManager(); + idCollisionQuery query = cdQuery.SubmitSlideMoveQuery(start, velocity, + gravityVector, stepUp, stepDown, globalBounds, trms, numTrms, + trmAxis, contentMask, models, numModels, userName); + if (result != nullptr) { + cdQuery.GetTraceResult(result, query, false); + } + return query; +} + +idCollisionQuery idCollisionModelManager::SlideMoveContacts( + trace_t* const result, contactsResult_t* const contacts, + const idVec3& start, const idVec3& velocity, + const idVec3& gravityVector, const float stepUp, + const float stepDown, const idBounds& globalBounds, + const idTraceModel** const trms, const int numTrms, + const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* const models, const int numModels, + const char* const userName) { + EnsureQueryManager(); + idCollisionQuery query = cdQuery.SubmitSlideMoveContactsQuery(start, + velocity, gravityVector, stepUp, stepDown, globalBounds, trms, + numTrms, trmAxis, contentMask, models, numModels, userName); + if (result != nullptr || contacts != nullptr) { + cdQuery.GetSlideMoveContactsResult(baseTraceWork, result, contacts, + query, false); + } + return query; +} + +bool idCollisionModelManager::GetContentsResult(trace_t* const result, + idCollisionQuery& query, const bool peek) { + EnsureQueryManager(); + return cdQuery.GetTraceResult(result, query, peek); +} + +bool idCollisionModelManager::GetMotionContactsResult(trace_t* const result, + contactsResult_t* const contacts, idCollisionQuery& query, + const bool peek) { + EnsureQueryManager(); + return cdQuery.GetMotionContactsResult(baseTraceWork, result, contacts, + query, peek); +} + +bool idCollisionModelManager::GetClipResult(clipResult_t* const result, + idCollisionQuery& query, const bool peek) { + EnsureQueryManager(); + return cdQuery.GetClipResult(baseTraceWork, result, query, peek); +} + +bool idCollisionModelManager::GetStepMoveContactsResult( + trace_t* const result, contactsResult_t* const contacts, + idCollisionQuery& query, const bool peek) { + EnsureQueryManager(); + return cdQuery.GetSlideMoveContactsResult(baseTraceWork, result, + contacts, query, peek); +} + +void StepMoveInternal(trace_t* const result, const idVec3& start, + const idVec3& end, const idVec3& downNormal, const float stepUp, + const float stepDown, const idBounds& globalBounds, + const idTraceModel** const traceModels, const int numTraceModels, + const idMat3& traceModelAxis, const int contentMask, + const idPositionedCollisionModel* const models, const int numModels, + const char* const userName) { + collisionModelManager.StepMove(result, start, end, downNormal, stepUp, + stepDown, globalBounds, traceModels, numTraceModels, + traceModelAxis, contentMask, models, numModels, userName); +} + +void ListCollisionModels() { + int totalMemory = 0; + int loadedMemory = 0; + int maxResidentMemory = 0; + for (int index = 0; index < idCollisionModelLocal::resourceList.num; + ++index) { + const idCollisionModelLocal* const model = + static_cast( + idCollisionModelLocal::resourceList.Index(index)); + if (model != nullptr) { + const int modelTotal = model->GetTotalMemory(); + const int modelLoaded = model->GetLoadedMemory(); + const int modelResident = model->GetMaxResidentMemory(nullptr); + idLibPrint::Printf( + "%4d: %6d kB / %6d kB / %6d kB / %4d / %s\n", + index, modelTotal >> 10, modelLoaded >> 10, + modelResident >> 10, + model->modelType == CM_POLYGONMODEL + ? model->polygonModel.numSubModels : 0, + model->GetName()); + totalMemory += modelTotal; + loadedMemory += modelLoaded; + maxResidentMemory += modelResident; + } + } + idLibPrint::Printf( + "%d kB in %d models (%d kB loaded, %d kB max resident)\n", + totalMemory >> 10, idCollisionModelLocal::resourceList.num, + loadedMemory >> 10, maxResidentMemory >> 10); +} + +void ListCollisionModels_f(const idCmdArgs*) { + ListCollisionModels(); +} + +void CollisionModelInfo_f(const idCmdArgs* const args) { + if (args == nullptr || args->Argc() < 2) { + idLibPrint::Printf( + "usage: collisionModelInfo ; use -1 for totals\n"); + return; + } + const int index = std::atoi(args->Argv(1)); + if (index >= 0) { + PrintCollisionModelInfo(static_cast( + idCollisionModelLocal::resourceList.Index(index))); + return; + } + cm_subModelData_t totals{}; + for (int modelIndex = 0; + modelIndex < idCollisionModelLocal::resourceList.num; + ++modelIndex) { + AddCollisionModelInfo(static_cast( + idCollisionModelLocal::resourceList.Index(modelIndex)), + &totals); + } + PrintSubModelInfo(&totals); +} + +void ExportCollisionModel_f(const idCmdArgs* const args) { + if (args == nullptr || args->Argc() < 2) { + idLibPrint::Printf( + "usage: exportCollisionModel [output.obj]\n"); + return; + } + idCollisionModelLocal* const model = + dynamic_cast( + collisionModelManager.LoadModel(args->Argv(1))); + if (model == nullptr || model->modelType != CM_POLYGONMODEL) { + delete model; + idLibPrint::Warning("could not load collision model %s", + args->Argv(1)); + return; + } + idStr outputName(args->Argc() > 2 ? args->Argv(2) : args->Argv(1)); + if (args->Argc() <= 2) { + outputName.SetFileExtension("obj"); + } else if (std::strrchr(outputName.c_str(), '.') == nullptr) { + outputName.Append(".obj"); + } + idFileLocal output(fileSystem->OpenFileWrite(outputName.c_str(), + FSPATH_BASE)); + if (output.file == nullptr) { + delete model; + idLibPrint::Warning("could not create %s", outputName.c_str()); + return; + } + output->Printf("# recovered idTech 5 collision model %s\n", + model->GetName()); + int vertexBase = 1; + for (int subModelIndex = 0; + subModelIndex < model->polygonModel.numSubModels; + ++subModelIndex) { + const cm_subModel_t& subModel = + model->polygonModel.subModels[subModelIndex]; + const cm_subModelData_t* const data = AcquireSubModelData(subModel); + if (data == nullptr || data->header.loadedSize == 32) { + ReleaseSubModelData(subModel, data); + continue; + } + cm_subModelPtrs_t pointers{}; + idPolygonModelCollisionDetection::SetupSubModelPtrsFromData( + pointers, data); + for (int vertex = 0; vertex < data->numVertices; ++vertex) { + const idVec3& point = pointers.vertices[vertex].p; + output->Printf("v %.9g %.9g %.9g\n", point.x, point.y, + point.z); + } + output->Printf("g submodel_%d\n", subModelIndex); + for (int polygonIndex = 0; polygonIndex < data->numPolygons; + ++polygonIndex) { + const cm_polygon_t& polygon = pointers.polygons[polygonIndex]; + if (polygon.numEdges < 3) { + continue; + } + output->Printf("f"); + for (int edgeIndex = 0; edgeIndex < polygon.numEdges; + ++edgeIndex) { + const std::uint16_t reference = pointers.polygonEdges[ + polygon.firstEdge + edgeIndex]; + const cm_edge_t& edge = pointers.edges[ + CM_EdgeIndex(reference)]; + output->Printf(" %d", vertexBase + + CM_EdgeStartVertex(edge, reference)); + } + output->Printf("\n"); + } + vertexBase += data->numVertices; + ReleaseSubModelData(subModel, data); + } + delete model; + idLibPrint::Printf("wrote %s\n", outputName.c_str()); +} + +void BuildCollisionModelsForMap_f(const idCmdArgs* const args) { + if (args == nullptr || args->Argc() < 2) { + idLibPrint::Printf("usage: bcm [-entityOnly]\n"); + return; + } + idLibPrint::Warning( + "bcm command requires the framework map-file loader for %s", + args->Argv(1)); +} + +void StripBCM_f(const idCmdArgs* const args) { + if (args == nullptr || args->Argc() < 2) { + idLibPrint::Printf("usage: stripBCM \n"); + return; + } + idLibPrint::Warning( + "stripBCM requires the recovered map-specific strip volume set"); +} diff --git a/source/engine/cm/collisionmodelmanager.h b/source/engine/cm/collisionmodelmanager.h index 9c7c4b5..9472dfc 100644 --- a/source/engine/cm/collisionmodelmanager.h +++ b/source/engine/cm/collisionmodelmanager.h @@ -1,13 +1,170 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\cm\collisionmodelmanager.h -// Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "cm/collisionqueryjobmanager.h" +class idCollisionModel; +class idCollisionGrid; +class idCollisionGridState; +class idJointMat; +class idMapFile; +class idMaterial; +class idRenderModel; +class idStaticModel; +class idTraceModel; +class idVec3; +class idMat3; -// IDA Local Type ordinal 14007; PDB kind: class. -class idCollisionModelManager -{ +class idCollisionDebugDrawSink { public: + virtual ~idCollisionDebugDrawSink() = default; + virtual void DrawLine(const idVec3& start, const idVec3& end, + bool internalEdge, int lifeTime) = 0; + virtual void DrawPolygon(const idVec3* points, int numPoints, + int contents, int surfaceFlags, int surfaceType, + int lifeTime) = 0; + virtual void DrawSphere(const idVec3& center, float radius, + int surfaceType, int lifeTime) = 0; }; + +int ContentsFromString(const char* string); + +class idTrmFromSubModel { +public: + static bool TrmFromSubModel(const idCollisionModelLocal* model, + const cm_subModelData_t* subModelData, idTraceModel& trm); +}; + +class idCollisionModelManager { +public: + static void SetDebugDrawSink(idCollisionDebugDrawSink* sink); + + idCollisionModel* ModelFromTrm(const char* modelName, + const idTraceModel& traceModel, const idMaterial* material); + idCollisionModel* BuildModel(const char* modelName, + const idStaticModel* staticModel, const int* subModelIndices, + int numSubModelIndices); + void BuildModelsForMapFile(const idMapFile* mapFile, + bool mapModelOnly); + idCollisionModel* ModelFromRender(const char* modelName, + const idRenderModel* renderModel); + idCollisionModel* ModelFromGrid(const char* modelName, + const idCollisionGrid* grid, const idCollisionGridState& state, + const idMaterial* material); + void DrawCollisionModel(idCollisionModel* model, + const idJointMat* modelJoints, const idVec3& modelOrigin, + const idMat3& modelAxis, const idVec3& viewOrigin, + const idMat3& viewAxis, float radius, int lifeTime); + void DebugOutput(const idVec3& viewOrigin, const idMat3& viewAxis); + + void Init(); + void Shutdown(); + void StartQueryFrame(); + void EndQueryFrame(); + void SubmitQueries(); + void WaitForAllQueries(); + + idCollisionModel* LoadModel(const char* modelName); + idCollisionGrid* LoadGrid(const char* modelName); + int FindStreamArea(idCollisionModel* model, const char* areaName); + void StreamModel(idCollisionModel* model, const int* areaIndices, + int numAreaIndices, bool wait); + bool IsResident(const idBounds& bounds, + const idPositionedCollisionModel* models, int numModels); + bool TrmFromModel(const char* modelName, idTraceModel& trm); + int CompoundTrmFromModel(const char* modelName, idTraceModel* trms, + int maxTrms, int* invalidSubmodelIndices, + int* numInvalidIndices); + + idCollisionQuery Translation(trace_t* result, const idVec3& start, + const idVec3& end, const idBounds& globalBounds, + const idTraceModel** trms, int numTrms, const idMat3& trmAxis, + int contentMask, const idPositionedCollisionModel* models, + int numModels, const char* userName); + idCollisionQuery LocalTranslation(idCollisionQuery localSpace, + const idVec3& start, const idVec3& end, + const idBounds& globalBounds, const idTraceModel** trms, + int numTrms, const idMat3& trmAxis, int contentMask, + const idPositionedCollisionModel* models, int numModels, + const char* userName); + idCollisionQuery Rotation(trace_t* result, const idVec3& start, + const idRotation& rotation, const idBounds& globalBounds, + const idTraceModel** trms, int numTrms, const idMat3& trmAxis, + int contentMask, const idPositionedCollisionModel* models, + int numModels, const char* userName); + idCollisionQuery Motion(trace_t* result, const idVec3& start, + const idVec3& end, const idRotation& rotation, + const idBounds& globalBounds, const idTraceModel** trms, + int numTrms, const idMat3& trmAxis, int contentMask, + const idPositionedCollisionModel* models, int numModels, + const char* userName); + idCollisionQuery MotionContacts(trace_t* result, + contactsResult_t* contacts, const idVec3& start, + const idVec3& end, const idRotation& rotation, float depth, + const idBounds& globalBounds, const idTraceModel** trms, + int numTrms, const idMat3& trmAxis, int contentMask, + const idPositionedCollisionModel* models, int numModels, + const char* userName); + idCollisionQuery Contents(trace_t* result, const idVec3& start, + const idBounds& globalBounds, const idTraceModel** trms, + int numTrms, const idMat3& trmAxis, int contentMask, + const idPositionedCollisionModel* models, int numModels, + const char* userName); + idCollisionQuery LocalContents(idCollisionQuery localSpace, + const idVec3& start, const idBounds& globalBounds, + const idTraceModel** trms, int numTrms, const idMat3& trmAxis, + int contentMask, const idPositionedCollisionModel* models, + int numModels, const char* userName); + idCollisionQuery Contacts(contactsResult_t* result, + const idVec3& start, const idVec3& direction, float depth, + const idBounds& globalBounds, const idTraceModel** trms, + int numTrms, const idMat3& trmAxis, int contentMask, + const idPositionedCollisionModel* models, int numModels, + const char* userName); + idCollisionQuery Clip(clipResult_t* result, const idVec3& start, + const idBounds& globalBounds, const idTraceModel** trms, + int numTrms, const idMat3& trmAxis, int contentMask, + const idPositionedCollisionModel* models, int numModels, + const char* userName); + idCollisionQuery StepMove(trace_t* result, const idVec3& start, + const idVec3& end, const idVec3& downNormal, float stepUp, + float stepDown, const idBounds& globalBounds, + const idTraceModel** trms, int numTrms, const idMat3& trmAxis, + int contentMask, const idPositionedCollisionModel* models, + int numModels, const char* userName); + idCollisionQuery StepMoveContacts(trace_t* result, + contactsResult_t* contacts, const idVec3& start, + const idVec3& end, const idVec3& downNormal, float stepUp, + float stepDown, const idBounds& globalBounds, + const idTraceModel** trms, int numTrms, const idMat3& trmAxis, + int contentMask, const idPositionedCollisionModel* models, + int numModels, const char* userName); + idCollisionQuery SlideMove(trace_t* result, const idVec3& start, + const idVec3& velocity, const idVec3& gravityVector, + float stepUp, float stepDown, const idBounds& globalBounds, + const idTraceModel** trms, int numTrms, const idMat3& trmAxis, + int contentMask, const idPositionedCollisionModel* models, + int numModels, const char* userName); + idCollisionQuery SlideMoveContacts(trace_t* result, + contactsResult_t* contacts, const idVec3& start, + const idVec3& velocity, const idVec3& gravityVector, + float stepUp, float stepDown, const idBounds& globalBounds, + const idTraceModel** trms, int numTrms, const idMat3& trmAxis, + int contentMask, const idPositionedCollisionModel* models, + int numModels, const char* userName); + + bool GetContentsResult(trace_t* result, idCollisionQuery& query, + bool peek); + bool GetMotionContactsResult(trace_t* result, + contactsResult_t* contacts, idCollisionQuery& query, bool peek); + bool GetClipResult(clipResult_t* result, idCollisionQuery& query, + bool peek); + bool GetStepMoveContactsResult(trace_t* result, + contactsResult_t* contacts, idCollisionQuery& query, bool peek); +}; + +extern idCollisionModelManager collisionModelManager; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idCollisionModelManager) == 1, + "Recovered idCollisionModelManager ABI changed"); +#endif diff --git a/source/engine/cm/collisionmodelmanager_debug.cpp b/source/engine/cm/collisionmodelmanager_debug.cpp new file mode 100644 index 0000000..14ff1b1 --- /dev/null +++ b/source/engine/cm/collisionmodelmanager_debug.cpp @@ -0,0 +1,235 @@ +#include "cm/collisionmodelmanager.h" + +#include "cm/collisionmodel.h" +#include "cm/jobs/polygonmodel/polygonmodel.h" +#include "cm/jobs/polygonmodel/polygonmodeldata.h" +#include "cm/jobs/spheremodel/spheremodel.h" +#include "idlib/geometry/jointtransform.h" + +#include +#include +#include +#include +#include +#include + +namespace { + +idCollisionDebugDrawSink* debugDrawSink = nullptr; + +struct contentsName_t { + const char* name; + int flag; +}; + +const contentsName_t contentsNames[] = { + { "solid", 0x00000001 }, { "opaque", 0x00000002 }, + { "water", 0x00000004 }, { "playerclip", 0x00000008 }, + { "monsterclip", 0x00000010 }, { "vehicleclip", 0x00000020 }, + { "moveableclip", 0x00000040 }, { "shotclip", 0x00000080 }, + { "ikclip", 0x00000100 }, { "aiaware", 0x00000200 }, + { "ai", 0x00000400 }, { "projectile", 0x00000800 }, + { "corpse", 0x00001000 }, { "breakable", 0x00002000 }, + { "trigger", 0x00004000 }, { "player", 0x00008000 }, + { "vehicle", 0x00010000 }, { "obstacle", 0x00020000 }, + { "contextualcover_clip", 0x00040000 }, + { "playercoverclip", 0x00080000 }, + { "monstercoverclip", 0x00100000 }, + { "playerfocus", 0x00200000 }, { "pushable", 0x00400000 }, + { "shield", 0x00800000 }, { "tickclip", 0x01000000 }, + { "aas_fly", 0x02000000 }, { "aas_solid", 0x04000000 }, + { "aas_obstacle", 0x08000000 }, + { "aas_cluster_portal", 0x10000000 }, + { "aas_walkable_wall", 0x20000000 }, + { "nocover", 0x40000000 }, + { "do_not_use", static_cast(0x80000000u) } +}; + +idVec3 TransformPoint(const idVec3& point, const idVec3& origin, + const idMat3& axis) { + return origin + idVec3( + axis[0].x * point.x + axis[1].x * point.y + axis[2].x * point.z, + axis[0].y * point.x + axis[1].y * point.y + axis[2].y * point.z, + axis[0].z * point.x + axis[1].z * point.y + axis[2].z * point.z); +} + +bool InRadius(const idVec3& point, const idVec3& viewOrigin, + const float radius) { + return radius <= 0.0f + || (point - viewOrigin).LengthSqr() <= radius * radius; +} + +void DrawEdge(const cm_subModelPtrs_t* const pointers, + const std::uint16_t edgeReference, const idVec3& origin, + const idMat3& axis, const idVec3& viewOrigin, const float radius, + const int lifeTime) { + if (debugDrawSink == nullptr) { + return; + } + const cm_edge_t& edge = pointers->edges[CM_EdgeIndex(edgeReference)]; + const idVec3 start = TransformPoint(pointers->vertices[ + CM_EdgeStartVertex(edge, edgeReference)].p, origin, axis); + const idVec3 end = TransformPoint(pointers->vertices[ + CM_EdgeEndVertex(edge, edgeReference)].p, origin, axis); + if (InRadius(start, viewOrigin, radius) + || InRadius(end, viewOrigin, radius)) { + debugDrawSink->DrawLine(start, end, + (edgeReference & 0x4000) != 0, lifeTime); + } +} + +void DrawPolygon(const cm_subModelPtrs_t* const pointers, + const cm_polygon_t& polygon, const idVec3& origin, + const idMat3& axis, const idVec3& viewOrigin, const float radius, + const int lifeTime) { + if (debugDrawSink == nullptr || polygon.numEdges == 0) { + return; + } + std::vector points; + points.reserve(polygon.numEdges); + bool visible = radius <= 0.0f; + for (int index = 0; index < polygon.numEdges; ++index) { + const std::uint16_t edgeReference = pointers->polygonEdges[ + polygon.firstEdge + index]; + const cm_edge_t& edge = pointers->edges[CM_EdgeIndex(edgeReference)]; + points.push_back(TransformPoint(pointers->vertices[ + CM_EdgeStartVertex(edge, edgeReference)].p, origin, axis)); + visible = visible || InRadius(points.back(), viewOrigin, radius); + } + if (!visible) { + return; + } + const cm_material_t& material = pointers->materials[polygon.material]; + debugDrawSink->DrawPolygon(points.data(), + static_cast(points.size()), material.contentFlags, + material.surfaceFlags, material.surfaceType, lifeTime); + for (int index = 0; index < polygon.numEdges; ++index) { + DrawEdge(pointers, pointers->polygonEdges[polygon.firstEdge + index], + origin, axis, viewOrigin, radius, lifeTime); + } +} + +void DrawNodePolygons(const cm_subModelPtrs_t* const pointers, + const cm_subModelData_t& data, const idVec3& origin, + const idMat3& axis, const idVec3& viewOrigin, const float radius, + const int lifeTime) { + for (int polygon = 0; polygon < data.numPolygons; ++polygon) { + DrawPolygon(pointers, pointers->polygons[polygon], origin, axis, + viewOrigin, radius, lifeTime); + } +} + +void SpeedTest(const idVec3*) { + // The recovered benchmark is command/CVar driven. Query timing belongs + // to the console integration layer; collision drawing itself is live. +} + +void DebugTranslationFailure(const idVec3*, const idMat3*) {} +void DebugRotationFailure(const idVec3*, const idMat3*) {} +void DebugFailedQuery(const idVec3*, const idMat3*) {} + +} // namespace + +int ContentsFromString(const char* const string) { + if (string == nullptr) { + return 0; + } + int flags = 0; + const char* cursor = string; + while (*cursor != '\0') { + while (*cursor != '\0' + && (std::isspace(static_cast(*cursor)) + || *cursor == ',')) { + ++cursor; + } + const char* const begin = cursor; + while (*cursor != '\0' + && !std::isspace(static_cast(*cursor)) + && *cursor != ',') { + ++cursor; + } + if (cursor == begin) { + continue; + } + std::string token(begin, cursor); + if (token.compare(0, 9, "CONTENTS_") == 0 + || token.compare(0, 9, "contents_") == 0) { + token.erase(0, 9); + } + for (const contentsName_t& entry : contentsNames) { + if (_stricmp(token.c_str(), entry.name) == 0) { + flags |= entry.flag; + break; + } + } + } + return flags; +} + +void idCollisionModelManager::SetDebugDrawSink( + idCollisionDebugDrawSink* const sink) { + debugDrawSink = sink; +} + +void idCollisionModelManager::DrawCollisionModel( + idCollisionModel* const model, const idJointMat* const modelJoints, + const idVec3& modelOrigin, const idMat3& modelAxis, + const idVec3& viewOrigin, const idMat3&, const float radius, + const int lifeTime) { + if (debugDrawSink == nullptr || model == nullptr) { + return; + } + idCollisionModelLocal* const local = + dynamic_cast(model); + if (local == nullptr) { + return; + } + if (local->modelType == CM_SPHEREMODEL && local->sphereModel != nullptr) { + cm_sphereModelPtrs_t spheres{}; + const int count = idSphereModelCollisionDetection:: + SetupCollisionSpherePtrs(local->sphereModel, spheres); + for (int index = 0; index < count; ++index) { + idVec3 center(spheres.offsetX[index], spheres.offsetY[index], + spheres.offsetZ[index]); + if (modelJoints != nullptr) { + const idJointMat& joint = modelJoints[spheres.joint[index]]; + center.Set( + joint.mat[0] * center.x + joint.mat[1] * center.y + + joint.mat[2] * center.z + joint.mat[3], + joint.mat[4] * center.x + joint.mat[5] * center.y + + joint.mat[6] * center.z + joint.mat[7], + joint.mat[8] * center.x + joint.mat[9] * center.y + + joint.mat[10] * center.z + joint.mat[11]); + } + center = TransformPoint(center, modelOrigin, modelAxis); + if (InRadius(center, viewOrigin, radius)) { + debugDrawSink->DrawSphere(center, spheres.radius[index], + spheres.surfType[index], lifeTime); + } + } + return; + } + if (local->modelType != CM_POLYGONMODEL) { + return; + } + for (int index = 0; index < local->polygonModel.numSubModels; ++index) { + const cm_subModel_t& subModel = local->polygonModel.subModels[index]; + const cm_subModelData_t* const data = AcquireSubModelData(subModel); + if (data != nullptr && data->header.loadedSize != 32) { + cm_subModelPtrs_t pointers{}; + idPolygonModelCollisionDetection::SetupSubModelPtrsFromData( + pointers, data); + DrawNodePolygons(&pointers, *data, modelOrigin, modelAxis, + viewOrigin, radius, lifeTime); + } + ReleaseSubModelData(subModel, data); + } +} + +void idCollisionModelManager::DebugOutput(const idVec3& viewOrigin, + const idMat3& viewAxis) { + SpeedTest(&viewOrigin); + DebugTranslationFailure(&viewOrigin, &viewAxis); + DebugRotationFailure(&viewOrigin, &viewAxis); + DebugFailedQuery(&viewOrigin, &viewAxis); +} diff --git a/source/engine/cm/collisionqueryjobmanager.cpp b/source/engine/cm/collisionqueryjobmanager.cpp new file mode 100644 index 0000000..710dbd5 --- /dev/null +++ b/source/engine/cm/collisionqueryjobmanager.cpp @@ -0,0 +1,1051 @@ +#include "cm/collisionqueryjobmanager.h" + +#include "cm/jobs/polygonmodel/polygonmodel.h" + +#include +#include +#include +#include +#include +#include + +namespace { + +struct pcQuerySlot_t { + queryResults_t primary; + queryResults_t secondary; + bool hasSecondary; +}; + +struct pcQueryMemory_t { + void* data; + std::size_t size; +}; + +} // namespace + +struct idQueryData { + idQueryData(); + ~idQueryData(); + + void AdvanceSubModelQueryFirstSubmittedIndex(std::uint64_t newIndex); + void StartFrame(); + int UpdateQueryDataStats(queryDataStats_t* stats, int& numStats, + int maxStats, queryDataStats_t& totalStats); + void EndFrame(); + void Clear(); + void ReleaseQueryOnlyData(); + + modelQuery_t* AllocModelQuery(); + subModelQuery_t* AllocSubModelQuery(); + queryParms_t* AllocQueryParms(); + slideMoveState_t* AllocSlideMoveState(); + queryResults_t* AllocIntermediateResults(unsigned int numResults, + unsigned int resultSize); + queryResults_t* AllocFinalResult(unsigned int totalSize); + + std::vector outstanding; + std::vector modelQueries; + std::vector subModelQueries; + std::vector queryParms; + std::vector slideMoveStates; + std::vector intermediateResults; + std::vector finalResults; + bool started; + int failedModelQuery; + int failedSubModelQuery; + int failedQueryParms; + int failedIntermediateResults; + int failedFinalResults; + int failedSlideMoveState; + int failedMergeResults; +}; + +timings_t::timings_t() + : min(UINT_MAX), max(0), total(0), count(0) { +} + +idQueryData::idQueryData() + : started(false), failedModelQuery(0), failedSubModelQuery(0), + failedQueryParms(0), failedIntermediateResults(0), + failedFinalResults(0), failedSlideMoveState(0), + failedMergeResults(0) { +} + +idQueryData::~idQueryData() { + Clear(); +} + +void idQueryData::AdvanceSubModelQueryFirstSubmittedIndex( + const std::uint64_t newIndex) { + const std::size_t count = newIndex >= subModelQueries.size() + ? subModelQueries.size() : static_cast(newIndex); + for (std::size_t index = 0; index < count; ++index) { + subModelQuery_t* const query = subModelQueries[index]; + if (query->subModel != nullptr && query->subModelData != nullptr) { + ReleaseSubModelData(*query->subModel, query->subModelData); + query->subModelData = nullptr; + } + delete query; + } + subModelQueries.erase(subModelQueries.begin(), + subModelQueries.begin() + count); +} + +void idQueryData::StartFrame() { + started = true; +} + +int idQueryData::UpdateQueryDataStats(queryDataStats_t* const stats, + int& numStats, const int maxStats, queryDataStats_t& totalStats) { + totalStats.numCollisionQueries = 0; + totalStats.numModelQueries = 0; + totalStats.numSubModelQueries = 0; + totalStats.queryDataSize = 0; + totalStats.finalResultSize = 0; + + for (int index = 0; index < numStats; ++index) { + stats[index].numCollisionQueries = 0; + stats[index].numModelQueries = 0; + stats[index].numSubModelQueries = 0; + stats[index].queryDataSize = 0; + stats[index].finalResultSize = 0; + } + + for (const modelQuery_t* const query : modelQueries) { + const char* const userName = query->userName != nullptr + ? query->userName : "*Unknown*"; + int statIndex = 0; + while (statIndex < numStats + && stats[statIndex].userName != userName) { + ++statIndex; + } + if (statIndex == numStats) { + if (numStats >= maxStats || stats == nullptr) { + continue; + } + std::memset(&stats[statIndex], 0, sizeof(stats[statIndex])); + stats[statIndex].userName = userName; + ++numStats; + } + + queryDataStats_t& entry = stats[statIndex]; + const int resultSize = query->type >= TRACE_CONTACTS_UNI_DIR + ? 992 : 192; + entry.numCollisionQueries += query->nextOnQuery == nullptr ? 1 : 0; + ++entry.numModelQueries; + entry.numSubModelQueries += query->numSubModelQueries; + entry.queryDataSize += (std::max)(1, + query->numSubModelQueries) * resultSize; + entry.finalResultSize += query->finalResultsPtr != nullptr + ? resultSize : 0; + } + + for (int index = 0; index < numStats; ++index) { + queryDataStats_t& entry = stats[index]; + entry.maxCollisionQueries = (std::max)(entry.maxCollisionQueries, + entry.numCollisionQueries); + entry.maxModelQueries = (std::max)(entry.maxModelQueries, + entry.numModelQueries); + entry.maxSubModelQueries = (std::max)(entry.maxSubModelQueries, + entry.numSubModelQueries); + entry.maxQueryDataSize = (std::max)(entry.maxQueryDataSize, + entry.queryDataSize); + entry.maxFinalResultSize = (std::max)(entry.maxFinalResultSize, + entry.finalResultSize); + totalStats.numCollisionQueries += entry.numCollisionQueries; + totalStats.numModelQueries += entry.numModelQueries; + totalStats.numSubModelQueries += entry.numSubModelQueries; + totalStats.queryDataSize += entry.queryDataSize; + totalStats.finalResultSize += entry.finalResultSize; + } + + totalStats.maxCollisionQueries = (std::max)( + totalStats.maxCollisionQueries, totalStats.numCollisionQueries); + totalStats.maxModelQueries = (std::max)(totalStats.maxModelQueries, + totalStats.numModelQueries); + totalStats.maxSubModelQueries = (std::max)( + totalStats.maxSubModelQueries, totalStats.numSubModelQueries); + totalStats.maxQueryDataSize = (std::max)(totalStats.maxQueryDataSize, + totalStats.queryDataSize); + totalStats.maxFinalResultSize = (std::max)( + totalStats.maxFinalResultSize, totalStats.finalResultSize); + return numStats; +} + +void idQueryData::EndFrame() { + started = false; +} + +void idQueryData::Clear() { + ReleaseQueryOnlyData(); + for (pcQuerySlot_t* const slot : outstanding) { + delete slot; + } + outstanding.clear(); + for (const pcQueryMemory_t& block : finalResults) { + _aligned_free(block.data); + } + finalResults.clear(); + started = false; + failedModelQuery = 0; + failedSubModelQuery = 0; + failedQueryParms = 0; + failedIntermediateResults = 0; + failedFinalResults = 0; + failedSlideMoveState = 0; + failedMergeResults = 0; +} + +void idQueryData::ReleaseQueryOnlyData() { + AdvanceSubModelQueryFirstSubmittedIndex(subModelQueries.size()); + for (modelQuery_t* const query : modelQueries) { + delete query; + } + modelQueries.clear(); + for (queryParms_t* const parms : queryParms) { + delete parms; + } + queryParms.clear(); + for (slideMoveState_t* const state : slideMoveStates) { + delete state; + } + slideMoveStates.clear(); + for (const pcQueryMemory_t& block : intermediateResults) { + _aligned_free(block.data); + } + intermediateResults.clear(); +} + +modelQuery_t* idQueryData::AllocModelQuery() { + if (modelQueries.size() >= 2048) { + ++failedModelQuery; + return nullptr; + } + modelQuery_t* const query = new (std::nothrow) modelQuery_t{}; + if (query == nullptr) { + ++failedModelQuery; + return nullptr; + } + modelQueries.push_back(query); + return query; +} + +subModelQuery_t* idQueryData::AllocSubModelQuery() { + if (subModelQueries.size() >= 4096) { + ++failedSubModelQuery; + return nullptr; + } + subModelQuery_t* const query = new (std::nothrow) subModelQuery_t{}; + if (query == nullptr) { + ++failedSubModelQuery; + return nullptr; + } + subModelQueries.push_back(query); + return query; +} + +queryParms_t* idQueryData::AllocQueryParms() { + if (queryParms.size() >= 2048) { + ++failedQueryParms; + return nullptr; + } + queryParms_t* const parms = new (std::nothrow) queryParms_t{}; + if (parms == nullptr) { + ++failedQueryParms; + return nullptr; + } + queryParms.push_back(parms); + return parms; +} + +slideMoveState_t* idQueryData::AllocSlideMoveState() { + if (slideMoveStates.size() >= 128) { + ++failedSlideMoveState; + return nullptr; + } + slideMoveState_t* const state = + new (std::nothrow) slideMoveState_t{}; + if (state == nullptr) { + ++failedSlideMoveState; + return nullptr; + } + slideMoveStates.push_back(state); + return state; +} + +queryResults_t* idQueryData::AllocIntermediateResults( + const unsigned int numResults, const unsigned int resultSize) { + constexpr std::size_t capacity = 1024 * 1024; + if (numResults == 0 || resultSize == 0 + || numResults > capacity / resultSize) { + const std::uint64_t requested = + static_cast(numResults) * resultSize; + failedIntermediateResults += static_cast((std::min)(requested, + static_cast(INT_MAX))); + return nullptr; + } + const std::size_t size = static_cast(numResults) + * resultSize; + std::size_t allocated = 0; + for (const pcQueryMemory_t& block : intermediateResults) { + allocated += block.size; + } + if (size > capacity - (std::min)(allocated, capacity)) { + failedIntermediateResults += static_cast((std::min)(size, + static_cast(INT_MAX))); + return nullptr; + } + void* const memory = _aligned_malloc(size, 128); + if (memory == nullptr) { + failedIntermediateResults += static_cast((std::min)(size, + static_cast(INT_MAX))); + return nullptr; + } + std::memset(memory, 0, size); + intermediateResults.push_back({memory, size}); + return static_cast(memory); +} + +queryResults_t* idQueryData::AllocFinalResult( + const unsigned int totalSize) { + std::size_t allocated = 0; + for (const pcQueryMemory_t& block : finalResults) { + allocated += block.size; + } + if (totalSize == 0 || totalSize > 512 * 1024 - (std::min)(allocated, + static_cast(512 * 1024))) { + failedFinalResults += static_cast(totalSize); + return nullptr; + } + void* const memory = _aligned_malloc(totalSize, 128); + if (memory == nullptr) { + failedFinalResults += static_cast(totalSize); + return nullptr; + } + std::memset(memory, 0, totalSize); + finalResults.push_back({memory, totalSize}); + return static_cast(memory); +} + +namespace { + +pcQuerySlot_t* Slot(const idCollisionQuery& query) { + return reinterpret_cast( + static_cast(query.offset)); +} + +pcQuerySlot_t* OwnedSlot(const idQueryData* const data, + const idCollisionQuery& query) { + pcQuerySlot_t* const slot = Slot(query); + if (data == nullptr || slot == nullptr) { + return nullptr; + } + return std::find(data->outstanding.begin(), data->outstanding.end(), + slot) != data->outstanding.end() ? slot : nullptr; +} + +idCollisionQuery MakeQuery(idQueryData* const data, pcQuerySlot_t* slot) { + if (data != nullptr) { + data->outstanding.push_back(slot); + } + idCollisionQuery query; + query.offset = static_cast( + reinterpret_cast(slot)); + return query; +} + +void InitResult(queryResults_t& result, const traceType_t type, + const idVec3& end, const idMat3& axis) { + std::memset(&result, 0, sizeof(result)); + result.query.type = type; + result.query.status = QUERY_STATUS_SUCCESS; + result.query.done = 1; + if (type == TRACE_CONTACTS_UNI_DIR || type == TRACE_CONTACTS_OMNI_DIR) { + reinterpret_cast(result.data)->numContacts = 0; + } else if (type == TRACE_CLIP) { + reinterpret_cast(result.data)->numVerts = 0; + reinterpret_cast(result.data)->numIndices = 0; + } else { + trace_t& trace = *reinterpret_cast(result.data); + std::memset(&trace, 0, sizeof(trace)); + trace.fraction = 1.0f; + trace.endpos = end; + trace.endAxis = axis; + } +} + +idCollisionQuery SubmitSimple(idQueryData* const data, + const traceType_t type, + const idVec3& start, const idVec3& end, const idRotation* rotation, + const idVec3& direction, const float depth, + const idTraceModel** const trms, const int numTrms, + const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* const models, const int numModels) { + pcQuerySlot_t* const slot = new pcQuerySlot_t; + slot->hasSecondary = false; + InitResult(slot->primary, type, end, trmAxis); + std::vector mergeResults; + if (models != nullptr && numModels > 0) { + for (int modelNumber = 0; modelNumber < numModels; ++modelNumber) { + const idPositionedCollisionModel& positioned = models[modelNumber]; + const idCollisionModelLocal* const local = + dynamic_cast(positioned.model); + if (local == nullptr) { + continue; + } + const int traceModelCount = (std::max)(1, numTrms); + for (int trmNumber = 0; trmNumber < traceModelCount; ++trmNumber) { + const idTraceModel* const trm = trms != nullptr && numTrms > 0 + ? trms[trmNumber] : nullptr; + queryParms_t parms{}; + parms.type = type; + parms.autoMerge = true; + parms.testQuery = type == TRACE_TRANSLATION + || type == TRACE_ROTATION; + parms.start = start; + parms.end = end; + if (rotation != nullptr) { + parms.rotationOrigin = rotation->origin; + parms.rotationAxis = rotation->vec; + parms.rotationAngle = rotation->angle; + } + parms.dir = direction; + parms.depth = depth; + parms.trmAxis = trmAxis; + parms.contentMask = contentMask; + parms.modelOrigin = positioned.modelOrigin; + parms.modelAxis = positioned.modelAxis; + parms.modelEntityNum = positioned.modelEntityNum; + parms.modelPhysicsId = positioned.modelPhysicsId; + parms.modelBodyId = positioned.modelBodyId; + parms.modelContentsOverride = positioned.modelContentsOverride; + parms.selfId = 0; + queryResults_t modelResult{}; + modelResult.query.status = QUERY_STATUS_PENDING; + idTraceWork work; + work.Init(); + if (local->modelType == CM_POLYGONMODEL) { + idCollisionQueryExecute::ExecutePolygonModelQuery(&work, + modelResult, &parms, trm, &local->polygonModel); + } else if (local->modelType == CM_SPHEREMODEL + && local->sphereModel != nullptr) { + idCollisionQueryExecute::ExecuteSphereModelQuery( + modelResult, &parms, positioned.modelJoints, + local->sphereModel); + } + mergeResults.push_back(modelResult); + } + } + } + if (!mergeResults.empty()) { + idCollisionDetectionMerge::MergeQueryResults(&slot->primary, + sizeof(queryResults_t), type, mergeResults.data(), + static_cast(mergeResults.size()), nullptr, + DEPENDENCY_NONE, nullptr, nullptr); + slot->primary.query.done = 1; + slot->primary.query.type = type; + if (slot->primary.query.status == QUERY_STATUS_PENDING) { + slot->primary.query.status = QUERY_STATUS_SUCCESS; + } + } + return MakeQuery(data, slot); +} + +void DestroyQuery(idQueryData* const data, idCollisionQuery& query) { + pcQuerySlot_t* const slot = OwnedSlot(data, query); + if (data != nullptr && slot != nullptr) { + const auto found = std::find(data->outstanding.begin(), + data->outstanding.end(), slot); + data->outstanding.erase(found); + delete slot; + } + query.offset = 0; +} + +} // namespace + +idCollisionQueryJobManager::idCollisionQueryJobManager() + : queryData(nullptr), dummyQueryResults(nullptr), queryFrameNumber(0), + stalledFrameNumber(0), firstWaitTime(0), numJobGroups(0), + jobGroups(nullptr), jobList(nullptr) { + std::memset(&failedQuery, 0, sizeof(failedQuery)); +} + +idCollisionQueryJobManager::~idCollisionQueryJobManager() { + Shutdown(); +} + +void idCollisionQueryJobManager::Init() { + Shutdown(); + queryData = new idQueryData; + dummyQueryResults = new queryResults_t; + std::memset(dummyQueryResults, 0, sizeof(*dummyQueryResults)); + dummyQueryResults->query.done = 1; + dummyQueryResults->query.merged = 1; + dummyQueryResults->query.status = QUERY_STATUS_SUCCESS; + queryFrameNumber = 0; + stalledFrameNumber = 0; + firstWaitTime = 0; +} + +void idCollisionQueryJobManager::Shutdown() { + if (queryData != nullptr) { + delete queryData; + queryData = nullptr; + } + delete dummyQueryResults; + dummyQueryResults = nullptr; + delete[] jobGroups; + jobGroups = nullptr; + numJobGroups = 0; + jobList = nullptr; +} + +void idCollisionQueryJobManager::WaitForAllQueries() { + // Queries execute synchronously on PC. This preserves the recovered wait + // contract while avoiding the Xenon SPU/parallel-job dependency. + if (queryData != nullptr) { + queryData->ReleaseQueryOnlyData(); + } +} + +idCollisionQuery idCollisionQueryJobManager::SubmitTranslationQuery( + const idVec3& start, const idVec3& end, const idBounds&, + const idTraceModel** trms, const int numTrms, const idMat3& trmAxis, + const int contentMask, const idPositionedCollisionModel* models, + const int numModels, const char*) { + return SubmitSimple(queryData, TRACE_TRANSLATION, start, end, nullptr, + idVec3(0, 0, 0), 0.0f, trms, numTrms, trmAxis, contentMask, + models, numModels); +} + +idCollisionQuery idCollisionQueryJobManager::SubmitLocalTranslationQuery( + idCollisionQuery localSpace, const idVec3& start, const idVec3& end, + const idBounds& bounds, const idTraceModel** trms, const int numTrms, + const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* models, const int numModels, + const char* userName) { + pcQuerySlot_t* const localSlot = OwnedSlot(queryData, localSpace); + if (localSlot == nullptr) { + return SubmitTranslationQuery(start, end, bounds, trms, numTrms, + trmAxis, contentMask, models, numModels, userName); + } + queryParms_t source{}; + source.start = start; + source.end = end; + source.trmAxis = trmAxis; + queryParms_t transformed; + idCollisionQueryExecute::SetupDependentParms(&transformed, &source, + &localSlot->primary, DEPENDENCY_LOCAL_SPACE, + &localSlot->primary, nullptr); + return SubmitTranslationQuery(transformed.start, transformed.end, bounds, + trms, numTrms, transformed.trmAxis, contentMask, + models, numModels, userName); +} + +idCollisionQuery idCollisionQueryJobManager::SubmitRotationQuery( + const idVec3& start, const idRotation& rotation, const idBounds&, + const idTraceModel** trms, const int numTrms, const idMat3& trmAxis, + const int contentMask, const idPositionedCollisionModel* models, + const int numModels, const char*) { + return SubmitSimple(queryData, TRACE_ROTATION, start, start, &rotation, + idVec3(0, 0, 0), 0.0f, trms, numTrms, trmAxis, + contentMask, models, numModels); +} + +idCollisionQuery idCollisionQueryJobManager::SubmitMotionQuery( + const idVec3& start, const idVec3& end, const idRotation& rotation, + const idBounds& bounds, const idTraceModel** trms, const int numTrms, + const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* models, const int numModels, + const char* userName) { + idCollisionQuery translation = SubmitTranslationQuery(start, end, bounds, + trms, numTrms, trmAxis, contentMask, models, numModels, userName); + idCollisionQuery rotationQuery = SubmitRotationQuery(start, rotation, + bounds, trms, numTrms, trmAxis, contentMask, + models, numModels, userName); + pcQuerySlot_t* const finalSlot = new pcQuerySlot_t; + finalSlot->hasSecondary = false; + InitResult(finalSlot->primary, TRACE_TRANSLATION, end, trmAxis); + idCollisionDetectionMerge::MergeMotionResults( + reinterpret_cast(finalSlot->primary.data), + reinterpret_cast(Slot(rotationQuery)->primary.data), + reinterpret_cast(Slot(translation)->primary.data)); + DestroyQuery(queryData, translation); + DestroyQuery(queryData, rotationQuery); + return MakeQuery(queryData, finalSlot); +} + +idCollisionQuery idCollisionQueryJobManager::SubmitMotionContactsQuery( + const idVec3& start, const idVec3& end, const idRotation& rotation, + const float depth, const idBounds& bounds, const idTraceModel** trms, + const int numTrms, const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* models, const int numModels, + const char* userName) { + idCollisionQuery motion = SubmitMotionQuery(start, end, rotation, bounds, + trms, numTrms, trmAxis, contentMask, models, numModels, userName); + pcQuerySlot_t* const slot = Slot(motion); + const trace_t& trace = *reinterpret_cast(slot->primary.data); + idCollisionQuery contacts = SubmitContactsQuery(trace.endpos, + idVec3(0, 0, 0), depth, bounds, trms, numTrms, trace.endAxis, + contentMask, models, numModels, userName); + slot->secondary = Slot(contacts)->primary; + slot->hasSecondary = true; + DestroyQuery(queryData, contacts); + return motion; +} + +idCollisionQuery idCollisionQueryJobManager::SubmitContentsQuery( + const idVec3& start, const idBounds&, const idTraceModel** trms, + const int numTrms, const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* models, const int numModels, + const char*) { + const traceType_t type = trms == nullptr || numTrms == 0 + ? TRACE_CONTENTS_POINT : TRACE_CONTENTS; + return SubmitSimple(queryData, type, start, start, nullptr, + idVec3(0, 0, 0), + 0.0f, trms, numTrms, trmAxis, contentMask, models, numModels); +} + +idCollisionQuery idCollisionQueryJobManager::SubmitLocalContentsQuery( + idCollisionQuery localSpace, const idVec3& start, + const idBounds& bounds, const idTraceModel** trms, const int numTrms, + const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* models, const int numModels, + const char* userName) { + pcQuerySlot_t* const localSlot = OwnedSlot(queryData, localSpace); + idVec3 transformedStart = start; + idMat3 transformedAxis = trmAxis; + if (localSlot != nullptr) { + queryParms_t source{}; + source.start = source.end = start; + source.trmAxis = trmAxis; + queryParms_t transformed; + idCollisionQueryExecute::SetupDependentParms(&transformed, &source, + &localSlot->primary, DEPENDENCY_LOCAL_SPACE, + &localSlot->primary, nullptr); + transformedStart = transformed.start; + transformedAxis = transformed.trmAxis; + } + return SubmitContentsQuery(transformedStart, bounds, trms, numTrms, + transformedAxis, contentMask, models, numModels, userName); +} + +idCollisionQuery idCollisionQueryJobManager::SubmitContactsQuery( + const idVec3& start, const idVec3& direction, const float depth, + const idBounds&, const idTraceModel** trms, const int numTrms, + const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* models, const int numModels, + const char*) { + const traceType_t type = direction.LengthSqr() > 1.0e-12f + ? TRACE_CONTACTS_UNI_DIR : TRACE_CONTACTS_OMNI_DIR; + return SubmitSimple(queryData, type, start, start, nullptr, + direction, depth, + trms, numTrms, trmAxis, contentMask, models, numModels); +} + +idCollisionQuery idCollisionQueryJobManager::SubmitClipQuery( + const idVec3& start, const idBounds&, const idTraceModel** trms, + const int numTrms, const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* models, const int numModels, + const char*) { + return SubmitSimple(queryData, TRACE_CLIP, start, start, nullptr, + idVec3(0, 0, 0), 0.0f, trms, numTrms, trmAxis, + contentMask, models, numModels); +} + +idCollisionQuery idCollisionQueryJobManager::SubmitStepMoveQuery( + const idVec3& start, const idVec3& end, const idVec3& downNormal, + const float stepUp, const float stepDown, const idBounds& bounds, + const idTraceModel** trms, const int numTrms, const idMat3& trmAxis, + const int contentMask, const idPositionedCollisionModel* models, + const int numModels, const char* userName) { + const auto runTranslation = [&](const idVec3& from, + const idVec3& to) { + idCollisionQuery query = SubmitTranslationQuery(from, to, bounds, + trms, numTrms, trmAxis, contentMask, models, numModels, + userName); + trace_t trace = *reinterpret_cast( + Slot(query)->primary.data); + DestroyQuery(queryData, query); + return trace; + }; + + const trace_t forward1 = runTranslation(start, end); + const idVec3 stepUpEnd = forward1.endpos + - downNormal * stepUp; + const trace_t up = runTranslation(forward1.endpos, stepUpEnd); + const idVec3 remaining = (end - start) + * (1.0f - forward1.fraction); + const trace_t forward2 = runTranslation(up.endpos, + up.endpos + remaining); + const float climbed = stepUp * up.fraction; + const trace_t down = runTranslation(forward2.endpos, + forward2.endpos + downNormal * (climbed + stepDown)); + + pcQuerySlot_t* const slot = new pcQuerySlot_t; + slot->hasSecondary = false; + InitResult(slot->primary, TRACE_TRANSLATION, end, trmAxis); + idCollisionDetectionMerge::MergeStepMoveResults( + reinterpret_cast(slot->primary.data), &down, + &forward2, &forward1, false); + slot->primary.query.merged = 1; + return MakeQuery(queryData, slot); +} + +idCollisionQuery idCollisionQueryJobManager::SubmitStepMoveContactsQuery( + const idVec3& start, const idVec3& end, const idVec3& downNormal, + const float stepUp, const float stepDown, const idBounds& bounds, + const idTraceModel** trms, const int numTrms, const idMat3& trmAxis, + const int contentMask, const idPositionedCollisionModel* models, + const int numModels, const char* userName) { + idCollisionQuery query = SubmitStepMoveQuery(start, end, downNormal, + stepUp, stepDown, bounds, trms, numTrms, trmAxis, contentMask, + models, numModels, userName); + pcQuerySlot_t* const slot = Slot(query); + const trace_t& trace = *reinterpret_cast(slot->primary.data); + idCollisionQuery contacts = SubmitContactsQuery(trace.endpos, + downNormal, 0.5f, bounds, trms, numTrms, trace.endAxis, + contentMask, models, numModels, userName); + slot->secondary = Slot(contacts)->primary; + slot->hasSecondary = true; + DestroyQuery(queryData, contacts); + return query; +} + +idCollisionQuery idCollisionQueryJobManager::SubmitSlideMoveQuery( + const idVec3& start, const idVec3& velocity, + const idVec3& gravityVector, const float stepUp, + const float stepDown, const idBounds& bounds, + const idTraceModel** trms, const int numTrms, const idMat3& trmAxis, + const int contentMask, const idPositionedCollisionModel* models, + const int numModels, const char* userName) { + slideMoveState_t state{}; + idCollisionDetectionMerge::InitSlideMoveState(&state, velocity, + gravityVector); + trace_t trace{}; + trace.fraction = 1.0f; + trace.endpos = start; + trace.endAxis = trmAxis; + idVec3 current = start; + idVec3 downDirection(0.0f, 0.0f, -1.0f); + const float gravityLength = gravityVector.Length(); + if (gravityLength > 1.0e-6f) { + downDirection = gravityVector * (1.0f / gravityLength); + } + for (int iteration = 0; iteration < 4 + && state.fractionRemaining > 0.0f; ++iteration) { + const idVec3 target = current + + state.velocity * state.fractionRemaining; + idCollisionQuery step = SubmitStepMoveQuery(current, target, + downDirection, stepUp, stepDown, bounds, trms, numTrms, + trmAxis, contentMask, models, numModels, userName); + trace = *reinterpret_cast(Slot(step)->primary.data); + DestroyQuery(queryData, step); + current = trace.endpos; + if (idCollisionDetectionMerge::UpdateSlideMoveState(&state, + &trace)) { + break; + } + } + trace.endpos = current; + idCollisionDetectionMerge::FinishSlideMoveState(&state, &trace); + + pcQuerySlot_t* const slot = new pcQuerySlot_t; + slot->hasSecondary = false; + InitResult(slot->primary, TRACE_TRANSLATION, current, trmAxis); + *reinterpret_cast(slot->primary.data) = trace; + slot->primary.query.merged = 1; + return MakeQuery(queryData, slot); +} + +idCollisionQuery idCollisionQueryJobManager::SubmitSlideMoveContactsQuery( + const idVec3& start, const idVec3& velocity, + const idVec3& gravityVector, const float stepUp, const float stepDown, + const idBounds& bounds, const idTraceModel** trms, const int numTrms, + const idMat3& trmAxis, const int contentMask, + const idPositionedCollisionModel* models, const int numModels, + const char* userName) { + idCollisionQuery query = SubmitSlideMoveQuery(start, velocity, + gravityVector, stepUp, stepDown, bounds, trms, numTrms, trmAxis, + contentMask, models, numModels, userName); + pcQuerySlot_t* const slot = Slot(query); + const trace_t& trace = *reinterpret_cast(slot->primary.data); + idVec3 contactDirection(0.0f, 0.0f, -1.0f); + const float gravityLength = gravityVector.Length(); + if (gravityLength > 1.0e-6f) { + contactDirection = gravityVector * (1.0f / gravityLength); + } + idCollisionQuery contacts = SubmitContactsQuery(trace.endpos, + contactDirection, 0.5f, bounds, trms, numTrms, trace.endAxis, + contentMask, models, numModels, userName); + slot->secondary = Slot(contacts)->primary; + slot->hasSecondary = true; + DestroyQuery(queryData, contacts); + return query; +} + +bool idCollisionQueryJobManager::GetRotationResult(idTraceWork*, + trace_t* const result, idCollisionQuery& query, const bool peek) { + pcQuerySlot_t* const slot = OwnedSlot(queryData, query); + if (slot == nullptr || result == nullptr) { + return false; + } + *result = *reinterpret_cast(slot->primary.data); + if (!peek) { + DestroyQuery(queryData, query); + } + return true; +} + +bool idCollisionQueryJobManager::GetTraceResult(trace_t* const result, + idCollisionQuery& query, const bool peek) { + pcQuerySlot_t* const slot = OwnedSlot(queryData, query); + if (slot == nullptr || result == nullptr) { + return false; + } + *result = *reinterpret_cast(slot->primary.data); + if (!peek) { + DestroyQuery(queryData, query); + } + return true; +} + +bool idCollisionQueryJobManager::GetContactsResult( + contactsResult_t* const result, idCollisionQuery& query, + const bool peek) { + pcQuerySlot_t* const slot = OwnedSlot(queryData, query); + if (slot == nullptr || result == nullptr) { + return false; + } + result->CopyFrom(*reinterpret_cast( + slot->primary.data)); + if (!peek) { + DestroyQuery(queryData, query); + } + return true; +} + +bool idCollisionQueryJobManager::GetMotionContactsResult(idTraceWork*, + trace_t* const result, contactsResult_t* const contacts, + idCollisionQuery& query, const bool peek) { + pcQuerySlot_t* const slot = OwnedSlot(queryData, query); + if (slot == nullptr) { + return false; + } + if (result != nullptr) { + *result = *reinterpret_cast(slot->primary.data); + } + if (contacts != nullptr) { + if (slot->hasSecondary) { + contacts->CopyFrom(*reinterpret_cast( + slot->secondary.data)); + } else { + contacts->numContacts = 0; + } + } + if (!peek) { + DestroyQuery(queryData, query); + } + return true; +} + +bool idCollisionQueryJobManager::GetClipResult(idTraceWork*, + clipResult_t* const result, idCollisionQuery& query, const bool peek) { + pcQuerySlot_t* const slot = OwnedSlot(queryData, query); + if (slot == nullptr || result == nullptr) { + return false; + } + *result = *reinterpret_cast(slot->primary.data); + if (!peek) { + DestroyQuery(queryData, query); + } + return true; +} + +bool idCollisionQueryJobManager::GetSlideMoveContactsResult(idTraceWork* tw, + trace_t* result, contactsResult_t* contacts, idCollisionQuery& query, + const bool peek) { + return GetMotionContactsResult(tw, result, contacts, query, peek); +} + +void idCollisionQueryJobManager::ShowDebugInfo() { + // Profiling accumulators are retained in the recovered layout. Console + // presentation belongs to the later renderer/console integration. +} + +void idCollisionQueryJobManager::StartFrame() { + if (queryData != nullptr) { + queryData->StartFrame(); + } +} + +void idCollisionQueryJobManager::EndFrame() { + if (queryData != nullptr) { + queryData->EndFrame(); + } +} + +void idCollisionQueryJobManager::SubmitQueries() { + if (queryData != nullptr) { + queryData->ReleaseQueryOnlyData(); + } + ++queryFrameNumber; + // Synchronous PC submissions are complete before this fence is reached. +} + +void CollisionMergeJob(modelQuery_t* const query) { + if (query == nullptr || query->finalResultsPtr == nullptr) { + return; + } + idCollisionDetectionMerge::MergeQueryResults(query->finalResultsPtr, + query->resultSize, query->type, query->mergeResults, + query->numMergeResults, query->slideMoveState, + query->dependencyType, query->dependency1, query->dependency2); + query->finalResultsPtr->query.mergeThreadId = 0; + query->finalResultsPtr->query.merged = 1; +} + +void CollisionExecuteJob(modelQuery_t* const query) { + if (query == nullptr || query->parms == nullptr + || query->resultsPtr == nullptr) { + return; + } + queryParms_t parms{}; + idCollisionQueryExecute::SetupDependentParms(&parms, query->parms, + query->modelPosition, query->dependencyType, query->dependency1, + query->dependency2); + if (query->modelType == CM_POLYGONMODEL + && query->polygonModel != nullptr) { + idTraceWork work; + work.Init(); + idCollisionQueryExecute::ExecutePolygonModelQuery(&work, + *query->resultsPtr, &parms, query->trm, query->polygonModel); + } else if (query->modelType == CM_SPHEREMODEL + && query->sphereModel != nullptr) { + idCollisionQueryExecute::ExecuteSphereModelQuery(*query->resultsPtr, + &parms, query->modelJoints, query->sphereModel); + } + query->resultsPtr->query.executeThreadId = 0; + query->resultsPtr->query.done = 1; +} + +bool idCollisionQueryJobManager::WaitForQueryResults( + queryResults_t* const results) { + return results != nullptr && results->query.done != 0; +} + +modelQuery_t* idCollisionQueryJobManager::AllocModelQuery( + const idPositionedCollisionModel& model) { + if (queryData == nullptr || model.model == nullptr) { + return nullptr; + } + const idCollisionModelLocal* const local = + dynamic_cast(model.model); + if (local == nullptr) { + return nullptr; + } + modelQuery_t* const query = queryData->AllocModelQuery(); + queryParms_t* const parms = queryData->AllocQueryParms(); + if (query == nullptr || parms == nullptr) { + return nullptr; + } + query->parms = parms; + query->modelType = local->modelType; + query->polygonModel = &local->polygonModel; + query->sphereModel = local->sphereModel; + query->modelJoints = model.modelJoints; + query->resultsPtr = dummyQueryResults; + query->finalResultsPtr = dummyQueryResults; + query->mergeResults = dummyQueryResults; + query->executePriority = 1023; + query->mergePriority = 1023; + query->frameNumber = queryFrameNumber; + query->parms->modelOrigin = model.modelOrigin; + query->parms->modelAxis = model.modelAxis; + query->parms->modelEntityNum = model.modelEntityNum; + query->parms->modelPhysicsId = model.modelPhysicsId; + query->parms->modelBodyId = model.modelBodyId; + query->parms->modelContentsOverride = model.modelContentsOverride; + return query; +} + +idCollisionQuery idCollisionQueryJobManager::AllocFinishedFinalResult( + const traceType_t firstType, const traceType_t, + const idVec3& endPosition, const idMat3& endAxis, const char*) { + pcQuerySlot_t* const slot = new pcQuerySlot_t; + slot->hasSecondary = false; + InitResult(slot->primary, firstType, endPosition, endAxis); + slot->primary.query.merged = 1; + return MakeQuery(queryData, slot); +} + +idCollisionQuery idCollisionQueryJobManager::AddModelQueryToMergeList( + modelQuery_t* const modelQuery, const int mergePriority, + slideMoveState_t* const slideMoveState) { + if (modelQuery == nullptr) { + return idCollisionQuery{0}; + } + pcQuerySlot_t* const slot = new pcQuerySlot_t; + slot->hasSecondary = false; + InitResult(slot->primary, modelQuery->type, + modelQuery->parms != nullptr ? modelQuery->parms->end + : idVec3(0.0f, 0.0f, 0.0f), + modelQuery->parms != nullptr ? modelQuery->parms->trmAxis + : idMat3()); + modelQuery->mergePriority = mergePriority; + modelQuery->slideMoveState = slideMoveState; + modelQuery->finalResultsPtr = &slot->primary; + CollisionMergeJob(modelQuery); + return MakeQuery(queryData, slot); +} + +void idCollisionQueryJobManager::AddModelQueryToExecuteList( + modelQuery_t* const modelQuery, const idCollisionQuery modelPosition, + const idCollisionQuery dependency1, const idCollisionQuery dependency2, + const dependencyType_t dependencyType, const idVec3& globalStart, + const idVec3& globalEnd, const idBounds&, int& executePriority) { + if (modelQuery == nullptr) { + return; + } + pcQuerySlot_t* const modelPositionSlot = + OwnedSlot(queryData, modelPosition); + pcQuerySlot_t* const dependency1Slot = + OwnedSlot(queryData, dependency1); + pcQuerySlot_t* const dependency2Slot = + OwnedSlot(queryData, dependency2); + modelQuery->modelPosition = modelPositionSlot != nullptr + ? &modelPositionSlot->primary : nullptr; + modelQuery->dependency1 = dependency1Slot != nullptr + ? &dependency1Slot->primary : nullptr; + modelQuery->dependency2 = dependency2Slot != nullptr + ? &dependency2Slot->primary : nullptr; + modelQuery->dependencyType = dependencyType; + modelQuery->executePriority = executePriority++; + if (modelQuery->parms != nullptr) { + modelQuery->parms->start = globalStart; + modelQuery->parms->end = globalEnd; + } + CollisionExecuteJob(modelQuery); +} + +void idCollisionQueryJobManager::CreateMergeJobs(idParallelJobList*, + modelQuery_t* modelQuery) { + for (; modelQuery != nullptr; + modelQuery = modelQuery->nextInMergeList) { + if (modelQuery->mergePriority != 1023) { + CollisionMergeJob(modelQuery); + } + } +} + +void idCollisionQueryJobManager::CreateExecuteJobs(idParallelJobList*, + modelQuery_t* modelQuery) { + for (; modelQuery != nullptr; + modelQuery = modelQuery->nextInExecuteList) { + if (modelQuery->executePriority != 1023) { + CollisionExecuteJob(modelQuery); + } + } +} diff --git a/source/engine/cm/collisionqueryjobmanager.h b/source/engine/cm/collisionqueryjobmanager.h index c336b1c..339fa73 100644 --- a/source/engine/cm/collisionqueryjobmanager.h +++ b/source/engine/cm/collisionqueryjobmanager.h @@ -1,28 +1,246 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\cm\collisionqueryjobmanager.h -// Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "cm/collisionmodel.h" +#include "cm/jobs/collisionmerge.h" +#include "idlib/math/rotation.h" +#include -// IDA Local Type ordinal 23766; PDB kind: class. -class idCollisionQueryJobManager -{ -public: - idQueryData *queryData; - queryResults_t *dummyQueryResults; - int queryFrameNumber; - int stalledFrameNumber; - unsigned __int64 firstWaitTime; - int numJobGroups; - jobGroup_t *jobGroups; - idParallelJobList *jobList; - failedQuery_t failedQuery; - profile_t threadProfile[8]; - profile_t translationProfile; - profile_t rotationProfile; - profile_t contentsProfile; - profile_t contactsProfile; - profile_t clipProfile; +class idParallelJobList; +struct idQueryData; +struct subModelQuery_t; + +struct modelQuery_t { + traceType_t type; + queryParms_t* parms; + const idTraceModel* trm; + cmType_t modelType; + const cm_polygonModel_t* polygonModel; + const cm_sphereModel_t* sphereModel; + const idJointMat* modelJoints; + const char* userName; + queryResults_t* resultsPtr; + queryResults_t* finalResultsPtr; + modelQuery_t* nextOnQuery; + subModelQuery_t* subModelQueries; + int numSubModelQueries; + int executePriority; + int mergePriority; + modelQuery_t* nextInExecuteList; + modelQuery_t* nextInMergeList; + slideMoveState_t* slideMoveState; + queryResults_t* mergeResults; + int numMergeResults; + int resultSize; + queryResults_t* modelPosition; + queryResults_t* dependency1; + queryResults_t* dependency2; + dependencyType_t dependencyType; + int frameNumber; + int pad[6]; }; + +struct subModelQuery_t { + queryParms_t* parms; + const idTraceModel* trm; + const cm_subModel_t* subModel; + const cm_subModelData_t* subModelData; + int subModelNum; + queryResults_t* resultsPtr; + modelQuery_t* modelQuery; + subModelQuery_t* nextOnModelQuery; +}; + +struct timings_t { + timings_t(); + + std::uint32_t min; + std::uint32_t max; + std::uint32_t total; + std::uint32_t count; +}; + +struct queryDataStats_t { + const char* userName; + int numCollisionQueries; + int numModelQueries; + int numSubModelQueries; + int queryDataSize; + int finalResultSize; + int maxCollisionQueries; + int maxModelQueries; + int maxSubModelQueries; + int maxQueryDataSize; + int maxFinalResultSize; +}; + +struct profile_t { + timings_t timings[5]; +}; + +struct jobGroup_t { + modelQuery_t* stallingExecuteJobs; + modelQuery_t* notStallingExecuteJobs1; + modelQuery_t* notStallingExecuteJobs2; + modelQuery_t* stallingMergeJobs; + modelQuery_t* notStallingMergeJobs; + int numJobs; +}; + +struct failedQuery_t { + bool valid; + queryResults_t results; + queryParms_t parms; + const idTraceModel* trm; + idCollisionModel* trmModel; + const cm_polygonModel_t* model; + const cm_subModel_t* subModel; + const cm_subModelData_t* subModelData; + idBounds subModelBounds; + int subModelNum; +}; + +class idCollisionQueryJobManager { +public: + idCollisionQueryJobManager(); + ~idCollisionQueryJobManager(); + + void Init(); + void Shutdown(); + void WaitForAllQueries(); + + idCollisionQuery SubmitTranslationQuery(const idVec3& start, + const idVec3& end, const idBounds& globalBounds, + const idTraceModel** trms, int numTrms, const idMat3& trmAxis, + int contentMask, const idPositionedCollisionModel* models, + int numModels, const char* userName); + idCollisionQuery SubmitLocalTranslationQuery(idCollisionQuery localSpace, + const idVec3& start, const idVec3& end, + const idBounds& globalBounds, const idTraceModel** trms, + int numTrms, const idMat3& trmAxis, int contentMask, + const idPositionedCollisionModel* models, int numModels, + const char* userName); + idCollisionQuery SubmitRotationQuery(const idVec3& start, + const idRotation& rotation, const idBounds& globalBounds, + const idTraceModel** trms, int numTrms, const idMat3& trmAxis, + int contentMask, const idPositionedCollisionModel* models, + int numModels, const char* userName); + idCollisionQuery SubmitMotionQuery(const idVec3& start, + const idVec3& end, const idRotation& rotation, + const idBounds& globalBounds, const idTraceModel** trms, + int numTrms, const idMat3& trmAxis, int contentMask, + const idPositionedCollisionModel* models, int numModels, + const char* userName); + idCollisionQuery SubmitMotionContactsQuery(const idVec3& start, + const idVec3& end, const idRotation& rotation, float depth, + const idBounds& globalBounds, const idTraceModel** trms, + int numTrms, const idMat3& trmAxis, int contentMask, + const idPositionedCollisionModel* models, int numModels, + const char* userName); + idCollisionQuery SubmitContentsQuery(const idVec3& start, + const idBounds& globalBounds, const idTraceModel** trms, + int numTrms, const idMat3& trmAxis, int contentMask, + const idPositionedCollisionModel* models, int numModels, + const char* userName); + idCollisionQuery SubmitLocalContentsQuery(idCollisionQuery localSpace, + const idVec3& start, const idBounds& globalBounds, + const idTraceModel** trms, int numTrms, const idMat3& trmAxis, + int contentMask, const idPositionedCollisionModel* models, + int numModels, const char* userName); + idCollisionQuery SubmitContactsQuery(const idVec3& start, + const idVec3& direction, float depth, + const idBounds& globalBounds, const idTraceModel** trms, + int numTrms, const idMat3& trmAxis, int contentMask, + const idPositionedCollisionModel* models, int numModels, + const char* userName); + idCollisionQuery SubmitClipQuery(const idVec3& start, + const idBounds& globalBounds, const idTraceModel** trms, + int numTrms, const idMat3& trmAxis, int contentMask, + const idPositionedCollisionModel* models, int numModels, + const char* userName); + idCollisionQuery SubmitStepMoveQuery(const idVec3& start, + const idVec3& end, const idVec3& downNormal, float stepUp, + float stepDown, const idBounds& globalBounds, + const idTraceModel** trms, int numTrms, const idMat3& trmAxis, + int contentMask, const idPositionedCollisionModel* models, + int numModels, const char* userName); + idCollisionQuery SubmitStepMoveContactsQuery(const idVec3& start, + const idVec3& end, const idVec3& downNormal, float stepUp, + float stepDown, const idBounds& globalBounds, + const idTraceModel** trms, int numTrms, const idMat3& trmAxis, + int contentMask, const idPositionedCollisionModel* models, + int numModels, const char* userName); + idCollisionQuery SubmitSlideMoveQuery(const idVec3& start, + const idVec3& velocity, const idVec3& gravityVector, float stepUp, + float stepDown, const idBounds& globalBounds, + const idTraceModel** trms, int numTrms, const idMat3& trmAxis, + int contentMask, const idPositionedCollisionModel* models, + int numModels, const char* userName); + idCollisionQuery SubmitSlideMoveContactsQuery(const idVec3& start, + const idVec3& velocity, const idVec3& gravityVector, float stepUp, + float stepDown, const idBounds& globalBounds, + const idTraceModel** trms, int numTrms, const idMat3& trmAxis, + int contentMask, const idPositionedCollisionModel* models, + int numModels, const char* userName); + + bool GetRotationResult(idTraceWork* tw, trace_t* result, + idCollisionQuery& query, bool peek); + bool GetTraceResult(trace_t* result, idCollisionQuery& query, + bool peek); + bool GetContactsResult(contactsResult_t* result, + idCollisionQuery& query, bool peek); + bool GetMotionContactsResult(idTraceWork* tw, trace_t* result, + contactsResult_t* contacts, idCollisionQuery& query, bool peek); + bool GetClipResult(idTraceWork* tw, clipResult_t* result, + idCollisionQuery& query, bool peek); + bool GetSlideMoveContactsResult(idTraceWork* tw, trace_t* result, + contactsResult_t* contacts, idCollisionQuery& query, bool peek); + + void ShowDebugInfo(); + void StartFrame(); + void EndFrame(); + void SubmitQueries(); + + bool WaitForQueryResults(queryResults_t* results); + modelQuery_t* AllocModelQuery( + const idPositionedCollisionModel& model); + idCollisionQuery AllocFinishedFinalResult(traceType_t firstType, + traceType_t secondType, const idVec3& endPosition, + const idMat3& endAxis, const char* userName); + idCollisionQuery AddModelQueryToMergeList(modelQuery_t* modelQuery, + int mergePriority, slideMoveState_t* slideMoveState); + void AddModelQueryToExecuteList(modelQuery_t* modelQuery, + idCollisionQuery modelPosition, idCollisionQuery dependency1, + idCollisionQuery dependency2, dependencyType_t dependencyType, + const idVec3& globalStart, const idVec3& globalEnd, + const idBounds& globalBounds, int& executePriority); + void CreateMergeJobs(idParallelJobList* jobList, + modelQuery_t* modelQuery); + void CreateExecuteJobs(idParallelJobList* jobList, + modelQuery_t* modelQuery); + + idQueryData* queryData; + queryResults_t* dummyQueryResults; + int queryFrameNumber; + int stalledFrameNumber; + std::uint64_t firstWaitTime; + int numJobGroups; + jobGroup_t* jobGroups; + idParallelJobList* jobList; + failedQuery_t failedQuery; + profile_t threadProfile[8]; + profile_t translationProfile; + profile_t rotationProfile; + profile_t contentsProfile; + profile_t contactsProfile; + profile_t clipProfile; +}; + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(timings_t) == 16, + "Recovered timings_t ABI changed"); +static_assert(sizeof(queryDataStats_t) == 44, + "Recovered queryDataStats_t ABI changed"); +static_assert(sizeof(idCollisionQueryJobManager) == 2304, + "Recovered idCollisionQueryJobManager ABI changed"); +#endif diff --git a/source/engine/cm/collisiontypes.h b/source/engine/cm/collisiontypes.h new file mode 100644 index 0000000..02b982c --- /dev/null +++ b/source/engine/cm/collisiontypes.h @@ -0,0 +1,211 @@ +#pragma once + +#include "idlib/bv/bounds.h" +#include "idlib/bv/boundsshort.h" +#include "idlib/math/plane.h" +#include "idlib/math/vector.h" + +#include + +enum cmType_t : int { + CM_POLYGONMODEL = 0, + CM_SPHEREMODEL = 1 +}; + +enum subModelState_t : int { + SUBMODEL_STATE_UNLOADED = 0, + SUBMODEL_STATE_LOADED = 1, + SUBMODEL_STATE_LOADING = 2 +}; + +struct cm_node_t { + int planeType; + float planeDist; + std::uint16_t children[2]; + std::uint16_t firstPrimitive; + std::uint8_t numPolygons; + std::uint8_t numPolytopes; +}; + +struct cm_material_t { + int contentFlags; + int surfaceFlags; + int surfaceType; + std::uint8_t surfaceColor[3]; + std::uint8_t pad; +}; + +struct cm_polygon_t { + idBoundsShort bounds; + std::uint8_t material; + std::uint8_t numEdges; + std::uint16_t firstEdge; +}; + +struct cm_edge_t { + std::uint16_t vertexNum[2]; +}; + +struct cm_vertex_t { + idVec3 p; + std::uint16_t st[2]; +}; + +struct cm_polytope_t { + idBoundsShort bounds; + std::uint8_t material; + std::uint8_t numPlanes; + std::uint16_t firstPlane; +}; + +struct cm_subModelPtrs_t { + int isConvex; + cm_node_t* nodes; + std::uint16_t* primitiveIndices; + cm_material_t* materials; + cm_polygon_t* polygons; + std::uint16_t* polygonEdges; + cm_edge_t* edges; + cm_vertex_t* vertices; + cm_polytope_t* polytopes; + idPlane* polytopePlanes; +}; + +struct cm_modelTreeNode_t { + int planeType; + float planeDist; + int children[2]; +}; + +struct cm_subModelHeader_t { + int totalSize; + int loadedSize; + idBounds bounds; +}; + +struct cm_subModelData_t { + cm_subModelHeader_t header; + int isConvex; + int numNodes; + int nodeOffset; + int numPrimitiveIndices; + int primitiveIndexOffset; + int numMaterials; + int materialOffset; + int numPolygons; + int polygonOffset; + int numPolygonEdges; + int polygonEdgeOffset; + int numEdges; + int edgeOffset; + int numVertices; + int vertexOffset; + int numPolytopes; + int polytopeOffset; + int numPolytopePlanes; + int polytopePlaneOffset; + int pad; +}; + +struct cm_subModel_t { + cm_subModelHeader_t header; + cm_subModelData_t* data; + int fileOffset; + int numUsers; + volatile std::uint8_t* state; +}; + +struct cm_polygonModel_t { + int numModelTreeNodes; + int numSubModels; + cm_modelTreeNode_t* modelTreeNodes; + cm_subModel_t* subModels; + volatile std::uint8_t* subModelState; +}; + +struct cm_sphereModel_t { + std::uint32_t totalSize; + std::uint32_t timeStamp; + idBounds bounds; + std::uint32_t contents; + std::uint16_t numModelJoints; + std::uint16_t numSpheres; + std::uint16_t jointOffset; + std::uint16_t offsetXOffset; + std::uint16_t offsetYOffset; + std::uint16_t offsetZOffset; + std::uint16_t radiusOffset; + std::uint16_t surfTypeOffset; +}; + +struct cm_sphereModelPtrs_t { + std::uint8_t* joint; + float* offsetX; + float* offsetY; + float* offsetZ; + float* radius; + std::uint8_t* surfType; +}; + +struct streamAreasHeader_t { + int totalSize; + int numStreamAreas; + int numStreamAreaSubModels; + int numStreamAreaNameBytes; +}; + +struct streamArea_t { + int volumeNameOffset; + idVec3 volumeOrigin; + idMat3 volumeAxis; + int numSubModels; + int subModelsOffset; +}; + +struct streamAreasPtrs_t { + streamArea_t* streamAreas; + std::uint16_t* streamAreaSubModels; + char* streamAreaNameBytes; +}; + +void SetupStreamAreaPtrs(streamAreasHeader_t* header, + streamAreasPtrs_t& pointers); +const cm_subModelData_t* AcquireSubModelData( + const cm_subModel_t& subModel); +void ReleaseSubModelData(const cm_subModel_t& subModel, + const cm_subModelData_t* data); + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(cm_node_t) == 16, "Recovered cm_node_t ABI changed"); +static_assert(sizeof(cm_material_t) == 16, + "Recovered cm_material_t ABI changed"); +static_assert(sizeof(cm_polygon_t) == 16, + "Recovered cm_polygon_t ABI changed"); +static_assert(sizeof(cm_edge_t) == 4, "Recovered cm_edge_t ABI changed"); +static_assert(sizeof(cm_vertex_t) == 16, + "Recovered cm_vertex_t ABI changed"); +static_assert(sizeof(cm_polytope_t) == 16, + "Recovered cm_polytope_t ABI changed"); +static_assert(sizeof(cm_subModelPtrs_t) == 40, + "Recovered cm_subModelPtrs_t ABI changed"); +static_assert(sizeof(cm_modelTreeNode_t) == 16, + "Recovered cm_modelTreeNode_t ABI changed"); +static_assert(sizeof(cm_subModelHeader_t) == 32, + "Recovered cm_subModelHeader_t ABI changed"); +static_assert(sizeof(cm_subModelData_t) == 112, + "Recovered cm_subModelData_t ABI changed"); +static_assert(sizeof(cm_subModel_t) == 48, + "Recovered cm_subModel_t ABI changed"); +static_assert(sizeof(cm_polygonModel_t) == 20, + "Recovered cm_polygonModel_t ABI changed"); +static_assert(sizeof(cm_sphereModel_t) == 52, + "Recovered cm_sphereModel_t ABI changed"); +static_assert(sizeof(cm_sphereModelPtrs_t) == 24, + "Recovered cm_sphereModelPtrs_t ABI changed"); +static_assert(sizeof(streamAreasHeader_t) == 16, + "Recovered streamAreasHeader_t ABI changed"); +static_assert(sizeof(streamArea_t) == 60, + "Recovered streamArea_t ABI changed"); +static_assert(sizeof(streamAreasPtrs_t) == 12, + "Recovered streamAreasPtrs_t ABI changed"); +#endif diff --git a/source/engine/cm/jobs/collisionmerge.cpp b/source/engine/cm/jobs/collisionmerge.cpp new file mode 100644 index 0000000..862ab23 --- /dev/null +++ b/source/engine/cm/jobs/collisionmerge.cpp @@ -0,0 +1,537 @@ +#include "cm/jobs/collisionmerge.h" + +#include +#include +#include + +namespace { + +const queryResults_t* ResultAt(const queryResults_t* results, + const int index, const int resultSize) { + return reinterpret_cast( + reinterpret_cast(results) + + static_cast(index) * resultSize); +} + +bool SameVertex(const idVec3& left, const idVec3& right) { + return std::fabs(left.x - right.x) <= 0.1f && + std::fabs(left.y - right.y) <= 0.1f && + std::fabs(left.z - right.z) <= 0.1f; +} + +bool SameTriangleCyclic(const std::int16_t* left, + const std::int16_t* right) { + return (left[0] == right[0] && left[1] == right[1] && + left[2] == right[2]) || + (left[0] == right[1] && left[1] == right[2] && + left[2] == right[0]) || + (left[0] == right[2] && left[1] == right[0] && + left[2] == right[1]); +} + +} // namespace + +void idCollisionDetectionMerge::MergeContentsResults( + queryResults_t* const finalResult, + const queryResults_t* const mergeResults, const int numMergeResults, + const int resultSize) { + if (numMergeResults <= 0) { + return; + } + + trace_t* const finalTrace = + reinterpret_cast(finalResult->data); + *finalTrace = *reinterpret_cast(mergeResults->data); + + for (int index = 1; index < numMergeResults; ++index) { + const trace_t& candidate = *reinterpret_cast( + ResultAt(mergeResults, index, resultSize)->data); + if (candidate.c.contentFlags != 0 && + finalTrace->c.contentFlags == 0) { + *finalTrace = candidate; + } else { + finalTrace->c.contentFlags |= candidate.c.contentFlags; + } + } +} + +void idCollisionDetectionMerge::MergeMotionResults(trace_t* const result, + const trace_t* const rotation, const trace_t* const translation) { + const idVec3 rotationEndPosition = rotation->endpos; + const idMat3 rotationEndAxis = rotation->endAxis; + const float fraction = + (rotation->fraction + translation->fraction) * 0.5f; + + *result = rotation->fraction < 1.0f ? *rotation : *translation; + result->fraction = fraction; + result->endpos = rotationEndPosition; + result->endAxis = rotationEndAxis; +} + +void idCollisionDetectionMerge::MergeStepMoveResults( + trace_t* const result, const trace_t* const down, + const trace_t* const forward2, const trace_t* const forward1, + const bool slideMove) { + std::uint8_t flags = CONTACT_FLAG_NONE; + if (down->fraction < 1.0f) { + flags = CONTACT_FLAG_STEPMOVE_ONSOLID; + if (down->c.normal.z > 0.70710677f) { + flags |= CONTACT_FLAG_STEPMOVE_ONGROUND; + } + } + + if (slideMove && + (flags & CONTACT_FLAG_STEPMOVE_ONSOLID) != 0 && + (flags & CONTACT_FLAG_STEPMOVE_ONGROUND) == 0) { + *result = *forward1; + result->c.separation = 0.0f; + return; + } + + if (forward1->fraction >= 1.0f || forward2->fraction >= 1.0f) { + result->fraction = 1.0f; + } else { + result->fraction = + (1.0f - forward1->fraction) * forward2->fraction + + forward1->fraction; + } + result->endpos = down->endpos; + result->endAxis = down->endAxis; + result->c = forward2->c; + result->c.flags |= flags; + result->c.separation = down->endpos.z - forward1->endpos.z; +} + +bool ClipVelocity(idVec3& velocity, const idVec3* const planes, + const int numPlanes) { + for (int first = 0; first < numPlanes; ++first) { + float into = velocity.Dot(planes[first]); + if (into >= 0.05f) { + continue; + } + const float firstScale = into >= 0.0f ? 0.99900097f : 1.001f; + velocity = velocity - planes[first] * (into * firstScale); + + for (int second = 0; second < numPlanes; ++second) { + if (second == first) { + continue; + } + into = velocity.Dot(planes[second]); + if (into >= 0.05f) { + continue; + } + const float secondScale = + into >= 0.0f ? 0.99900097f : 1.001f; + velocity = velocity - planes[second] * (into * secondScale); + if (velocity.Dot(planes[first]) >= 0.0f) { + continue; + } + + idVec3 crease = planes[first].Cross(planes[second]); + if (crease.NormalizeFast() == 0.0f) { + velocity.Zero(); + return true; + } + velocity = crease * velocity.Dot(crease); + bool blocked = false; + for (int third = 0; third < numPlanes; ++third) { + if (third != first && third != second && + velocity.Dot(planes[third]) < 0.05f) { + blocked = true; + break; + } + } + if (blocked) { + velocity.Zero(); + return true; + } + } + } + return false; +} + +void idCollisionDetectionMerge::InitSlideMoveState( + slideMoveState_t* const state, const idVec3& velocity, + const idVec3& gravityVector) { + state->velocity = velocity; + state->endVelocity.Zero(); + std::memset(&state->firstContact, 0, sizeof(state->firstContact)); + state->fractionRemaining = 1.0f; + state->steppedUp = 0.0f; + state->startNormal = velocity; + state->startNormal.NormalizeFast(); + state->numPlanes = 0; + state->pad = 0; + + const float gravityLength = gravityVector.Length(); + if (gravityLength > 1.01f) { + const float gravityScale = (gravityLength - 1.0f) / gravityLength; + state->endVelocity = velocity + gravityVector * gravityScale; + state->velocity = (velocity + state->endVelocity) * 0.5f; + } +} + +bool idCollisionDetectionMerge::UpdateSlideMoveState( + slideMoveState_t* const state, trace_t* const trace) { + state->fractionRemaining *= 1.0f - trace->fraction; + state->steppedUp += trace->c.separation; + if (trace->fraction >= 1.0f) { + state->fractionRemaining = 0.0f; + trace->c.flags |= CONTACT_FLAG_SLIDEMOVE_FINISHED; + return true; + } + if (state->firstContact.type == CONTACT_NONE) { + state->firstContact = trace->c; + } + + bool duplicatePlane = false; + for (int index = 0; index < state->numPlanes; ++index) { + if (state->planes[index].Dot(trace->c.normal) > 0.99900001f) { + const float into = state->velocity.Dot(trace->c.normal); + const float scale = into >= 0.0f ? 0.99900097f : 1.001f; + state->velocity = state->velocity - + trace->c.normal * (into * scale); + duplicatePlane = true; + break; + } + } + + if (!duplicatePlane) { + if (state->numPlanes >= 4) { + state->fractionRemaining = 0.0f; + state->velocity.Zero(); + trace->c.flags |= CONTACT_FLAG_SLIDEMOVE_FINISHED; + return true; + } + state->planes[state->numPlanes++] = trace->c.normal; + if (ClipVelocity(state->velocity, state->planes, + state->numPlanes)) { + state->fractionRemaining = 0.0f; + state->velocity.Zero(); + trace->c.flags |= CONTACT_FLAG_SLIDEMOVE_FINISHED; + return true; + } + if (state->startNormal.Dot(state->velocity) < 0.01f) { + state->velocity = state->velocity - state->startNormal * + state->velocity.Dot(state->startNormal); + } + } + + trace->c.normal = state->velocity; + trace->c.dist = state->fractionRemaining; + return false; +} + +void idCollisionDetectionMerge::FinishSlideMoveState( + slideMoveState_t* const state, trace_t* const trace) { + if (state->velocity.LengthSqr() != 0.0f && + state->endVelocity.LengthSqr() != 0.0f) { + ClipVelocity(state->endVelocity, state->planes, + state->numPlanes); + state->velocity = state->endVelocity; + } + trace->c = state->firstContact; + trace->c.normal = state->velocity; + trace->c.dist = state->fractionRemaining; + trace->c.separation = state->steppedUp; +} + +void idCollisionDetectionMerge::MergeTraceResults( + queryResults_t* const finalResult, + const queryResults_t* const mergeResults, const int numMergeResults, + const int resultSize) { + if (numMergeResults <= 0) { + return; + } + + trace_t* const finalTrace = + reinterpret_cast(finalResult->data); + *finalTrace = *reinterpret_cast(mergeResults->data); + for (int index = 1; index < numMergeResults; ++index) { + const trace_t& candidate = *reinterpret_cast( + ResultAt(mergeResults, index, resultSize)->data); + if (candidate.fraction < finalTrace->fraction) { + *finalTrace = candidate; + } + } +} + +void idCollisionDetectionMerge::MergeContactsResults( + queryResults_t* const finalResult, + const queryResults_t* const mergeResults, const int numMergeResults, + const int resultSize) { + struct normalGroup_t { + const contactInfo_t* contacts[12]; + int numContacts; + } groups[12] = {}; + + int numGroups = 0; + for (int resultIndex = 0; resultIndex < numMergeResults; + ++resultIndex) { + const contactsResult_t& source = + *reinterpret_cast( + ResultAt(mergeResults, resultIndex, resultSize)->data); + const int sourceCount = (std::min)(source.numContacts, 12); + for (int contactIndex = 0; contactIndex < sourceCount; + ++contactIndex) { + const contactInfo_t* const contact = + &source.contacts[contactIndex]; + int groupIndex = 0; + for (; groupIndex < numGroups; ++groupIndex) { + if (groups[groupIndex].contacts[0]->normal.Dot( + contact->normal) > 0.90630776f) { + break; + } + } + if (groupIndex == numGroups) { + if (numGroups >= 12) { + continue; + } + groups[numGroups++].numContacts = 0; + } + normalGroup_t& group = groups[groupIndex]; + if (group.numContacts < 12) { + group.contacts[group.numContacts++] = contact; + } + } + } + + contactsResult_t& destination = + *reinterpret_cast(finalResult->data); + destination.numContacts = 0; + for (int groupIndex = 0; groupIndex < numGroups; ++groupIndex) { + const normalGroup_t& group = groups[groupIndex]; + const contactInfo_t* unique[12]; + int numUnique = 0; + for (int contactIndex = 0; contactIndex < group.numContacts; + ++contactIndex) { + const contactInfo_t* const candidate = + group.contacts[contactIndex]; + bool duplicate = false; + for (int uniqueIndex = 0; uniqueIndex < numUnique; + ++uniqueIndex) { + if ((candidate->point - unique[uniqueIndex]->point) + .LengthSqr() < 1.0f) { + duplicate = true; + break; + } + } + if (!duplicate) { + unique[numUnique++] = candidate; + } + } + + int selected[3] = {0, 1, 2}; + int numSelected = numUnique; + if (numUnique > 3) { + float largestAreaSqr = 0.0f; + for (int first = 0; first < numUnique - 2; ++first) { + for (int second = first + 1; second < numUnique - 1; + ++second) { + for (int third = second + 1; third < numUnique; + ++third) { + const idVec3 side1 = + unique[second]->point - unique[first]->point; + const idVec3 side2 = + unique[third]->point - unique[first]->point; + const float areaSqr = + side1.Cross(side2).LengthSqr(); + if (areaSqr > largestAreaSqr) { + largestAreaSqr = areaSqr; + selected[0] = first; + selected[1] = second; + selected[2] = third; + } + } + } + } + numSelected = 3; + } + + for (int uniqueIndex = 0; uniqueIndex < numUnique && + destination.numContacts < 12; ++uniqueIndex) { + bool useContact = numUnique <= 3; + if (numUnique > 3) { + for (int selection = 0; selection < numSelected; + ++selection) { + useContact |= selected[selection] == uniqueIndex; + } + } + if (useContact) { + destination.contacts[destination.numContacts++] = + *unique[uniqueIndex]; + } + } + } +} + +void idCollisionDetectionMerge::MergeClipResults( + queryResults_t* const finalResult, + const queryResults_t* const mergeResults, const int numMergeResults, + const int resultSize) { + if (numMergeResults <= 0) { + return; + } + + clipResult_t* const finalClip = + reinterpret_cast(finalResult->data); + *finalClip = *reinterpret_cast(mergeResults->data); + + for (int resultIndex = 1; resultIndex < numMergeResults; ++resultIndex) { + const clipResult_t& source = + *reinterpret_cast( + ResultAt(mergeResults, resultIndex, resultSize)->data); + std::int16_t vertexRemap[32]; + for (int vertex = 0; vertex < source.numVerts && vertex < 32; + ++vertex) { + vertexRemap[vertex] = -1; + for (int existing = 0; existing < finalClip->numVerts; + ++existing) { + if (SameVertex(source.verts[vertex], + finalClip->verts[existing])) { + vertexRemap[vertex] = static_cast(existing); + break; + } + } + if (vertexRemap[vertex] == -1 && finalClip->numVerts < 32) { + vertexRemap[vertex] = + static_cast(finalClip->numVerts); + finalClip->verts[finalClip->numVerts++] = + source.verts[vertex]; + } + } + + for (int sourceIndex = 0; + sourceIndex + 2 < source.numIndices && sourceIndex + 2 < 264; + sourceIndex += 3) { + const int sourceA = source.indices[sourceIndex]; + const int sourceB = source.indices[sourceIndex + 1]; + const int sourceC = source.indices[sourceIndex + 2]; + if (sourceA < 0 || sourceA >= 32 || sourceB < 0 || + sourceB >= 32 || sourceC < 0 || sourceC >= 32) { + continue; + } + const std::int16_t candidate[3] = { + vertexRemap[sourceA], vertexRemap[sourceB], + vertexRemap[sourceC]}; + if (candidate[0] == -1 || candidate[1] == -1 || + candidate[2] == -1) { + continue; + } + + bool duplicate = false; + for (int existing = 0; existing + 2 < finalClip->numIndices; + existing += 3) { + if (SameTriangleCyclic(candidate, + &finalClip->indices[existing])) { + duplicate = true; + break; + } + } + if (!duplicate && finalClip->numIndices + 3 <= 264) { + finalClip->indices[finalClip->numIndices++] = candidate[0]; + finalClip->indices[finalClip->numIndices++] = candidate[1]; + finalClip->indices[finalClip->numIndices++] = candidate[2]; + } + } + } +} + +void idCollisionDetectionMerge::MergeQueryResults( + queryResults_t* const finalResult, const int resultSize, + const traceType_t type, const queryResults_t* const mergeResults, + const int numMergeResults, slideMoveState_t* const slideMoveState, + const dependencyType_t dependencyType, + const queryResults_t* const dependency1, + const queryResults_t* const dependency2) { + switch (type) { + case TRACE_CONTENTS: + case TRACE_CONTENTS_POINT: + MergeContentsResults(finalResult, mergeResults, numMergeResults, + resultSize); + break; + case TRACE_CONTACTS_UNI_DIR: + case TRACE_CONTACTS_OMNI_DIR: + MergeContactsResults(finalResult, mergeResults, numMergeResults, + resultSize); + break; + case TRACE_TRANSLATION: + case TRACE_TRANSLATION_POINT: + case TRACE_ROTATION: + case TRACE_ROTATION_POINT: + MergeTraceResults(finalResult, mergeResults, numMergeResults, + resultSize); + break; + case TRACE_CLIP: + MergeClipResults(finalResult, mergeResults, numMergeResults, + resultSize); + break; + default: + break; + } + + switch (dependencyType) { + case DEPENDENCY_MOTION_ROTATION: + MergeMotionResults( + reinterpret_cast(finalResult->data), + reinterpret_cast(finalResult->data), + reinterpret_cast(dependency1->data)); + break; + case DEPENDENCY_MOTION_CONTACTS: { + const trace_t& motion = + *reinterpret_cast(dependency1->data); + contactsResult_t& contacts = + *reinterpret_cast(finalResult->data); + if (motion.fraction < 1.0f && contacts.numContacts == 0) { + contacts.contacts[0] = motion.c; + contacts.numContacts = 1; + } + break; + } + case DEPENDENCY_STEPMOVE_STEP_DOWN: + MergeStepMoveResults( + reinterpret_cast(finalResult->data), + reinterpret_cast(finalResult->data), + reinterpret_cast(dependency1->data), + reinterpret_cast(dependency2->data), false); + break; + case DEPENDENCY_SLIDEMOVE_STEP_UP_2: + case DEPENDENCY_SLIDEMOVE_STEP_UP_3: + case DEPENDENCY_SLIDEMOVE_STEP_UP_4: + case DEPENDENCY_SLIDEMOVE_2ND_MOVE_2: + case DEPENDENCY_SLIDEMOVE_2ND_MOVE_3: + case DEPENDENCY_SLIDEMOVE_2ND_MOVE_4: + case DEPENDENCY_SLIDEMOVE_SLIDE: + if (slideMoveState->fractionRemaining <= 0.0f) { + reinterpret_cast(finalResult->data)->c.flags |= + CONTACT_FLAG_SLIDEMOVE_FINISHED; + } + break; + case DEPENDENCY_SLIDEMOVE_STEP_DOWN_1: + case DEPENDENCY_SLIDEMOVE_STEP_DOWN_2: + case DEPENDENCY_SLIDEMOVE_STEP_DOWN_3: + case DEPENDENCY_SLIDEMOVE_STEP_DOWN_4: { + trace_t* const trace = + reinterpret_cast(finalResult->data); + MergeStepMoveResults(trace, trace, + reinterpret_cast(dependency1->data), + reinterpret_cast(dependency2->data), true); + if (slideMoveState->fractionRemaining > 0.0f) { + UpdateSlideMoveState(slideMoveState, trace); + } else { + trace->c.flags |= CONTACT_FLAG_SLIDEMOVE_FINISHED; + } + if (dependencyType == DEPENDENCY_SLIDEMOVE_STEP_DOWN_4) { + FinishSlideMoveState(slideMoveState, trace); + } + break; + } + default: + break; + } + + finalResult->query.type = type; + finalResult->query.done = 1; + finalResult->query.merged = 1; + finalResult->query.status = mergeResults->query.status; +} diff --git a/source/engine/cm/jobs/collisionmerge.h b/source/engine/cm/jobs/collisionmerge.h index 1ae3a66..0433bcb 100644 --- a/source/engine/cm/jobs/collisionmerge.h +++ b/source/engine/cm/jobs/collisionmerge.h @@ -1,30 +1,60 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\cm\jobs\collisionmerge.h -// Recovered logical types: 3 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "cm/jobs/collisionquery.h" - -// IDA Local Type ordinal 3237; PDB kind: enum. -enum collisionFeature_t : __int32 -{ - COLLISION_FEATURE_INVALID = 0x0, - COLLISION_FEATURE_VERTEX = 0x1, - COLLISION_FEATURE_EDGE = 0x2, - COLLISION_FEATURE_POLYGON = 0x3, - COLLISION_FEATURE_POLYTOPE = 0x4, +enum collisionFeature_t : int { + COLLISION_FEATURE_INVALID = 0, + COLLISION_FEATURE_VERTEX = 1, + COLLISION_FEATURE_EDGE = 2, + COLLISION_FEATURE_POLYGON = 3, + COLLISION_FEATURE_POLYTOPE = 4 }; -// IDA Local Type ordinal 23756; PDB kind: class. -class idCollisionDetectionMerge -{ +struct slideMoveState_t { + idVec3 velocity; + idVec3 endVelocity; + contactInfo_t firstContact; + float fractionRemaining; + float steppedUp; + idVec3 startNormal; + idVec3 planes[4]; + int numPlanes; + int pad; +}; + +bool ClipVelocity(idVec3& velocity, const idVec3* planes, int numPlanes); + +class idCollisionDetectionMerge { public: + static void MergeContentsResults(queryResults_t* finalResult, + const queryResults_t* mergeResults, int numMergeResults, + int resultSize); + static void MergeMotionResults(trace_t* result, const trace_t* rotation, + const trace_t* translation); + static void MergeStepMoveResults(trace_t* result, const trace_t* down, + const trace_t* forward2, const trace_t* forward1, bool slideMove); + static void InitSlideMoveState(slideMoveState_t* state, + const idVec3& velocity, const idVec3& gravityVector); + static bool UpdateSlideMoveState(slideMoveState_t* state, + trace_t* trace); + static void FinishSlideMoveState(slideMoveState_t* state, + trace_t* trace); + static void MergeTraceResults(queryResults_t* finalResult, + const queryResults_t* mergeResults, int numMergeResults, + int resultSize); + static void MergeContactsResults(queryResults_t* finalResult, + const queryResults_t* mergeResults, int numMergeResults, + int resultSize); + static void MergeClipResults(queryResults_t* finalResult, + const queryResults_t* mergeResults, int numMergeResults, + int resultSize); + static void MergeQueryResults(queryResults_t* finalResult, + int resultSize, traceType_t type, + const queryResults_t* mergeResults, int numMergeResults, + slideMoveState_t* slideMoveState, dependencyType_t dependencyType, + const queryResults_t* dependency1, + const queryResults_t* dependency2); }; -// IDA Local Type ordinal 23757; PDB kind: struct. -struct idCollisionDetectionMerge::MergeContactsResults::__l2::contactGroup_t -{ - const contactInfo_t *contacts[12]; - int numContacts; -}; +static_assert(sizeof(slideMoveState_t) == 176, + "Recovered slideMoveState_t ABI changed"); diff --git a/source/engine/cm/jobs/collisionquery.cpp b/source/engine/cm/jobs/collisionquery.cpp new file mode 100644 index 0000000..90351e5 --- /dev/null +++ b/source/engine/cm/jobs/collisionquery.cpp @@ -0,0 +1,361 @@ +#include "cm/jobs/collisionquery.h" + +#include "cm/jobs/polygonmodel/polygonmodel.h" +#include "cm/jobs/spheremodel/spheremodel.h" +#include "idlib/geometry/tracemodel.h" + +#include +#include + +namespace { + +idVec3 ModelToWorldVector(const idMat3& axis, const idVec3& value) { + return idVec3( + axis[0].x * value.x + axis[1].x * value.y + axis[2].x * value.z, + axis[0].y * value.x + axis[1].y * value.y + axis[2].y * value.z, + axis[0].z * value.x + axis[1].z * value.y + axis[2].z * value.z); +} + +void FinishQuery(idTraceWork& tw, queryResults_t& results, + const queryParms_t& parms) { + if (results.query.status == QUERY_STATUS_PENDING) { + results.query.status = QUERY_STATUS_SUCCESS; + } + for (int index = 0; index < 5; ++index) { + results.query.profile[index] = static_cast( + tw.profile[index]); + } + results.query.type = parms.type; + results.query.done = 1; + results.query.merged = parms.autoMerge ? 1 : 0; +} + +template +void ExecutePolygonOperation(idTraceWork& tw, queryResults_t& results, + const queryParms_t& parms, const idTraceModel* trm, + TraceFunction&& traceFunction) { + trace_t startContents; + if (parms.testQuery && trm != nullptr + && (parms.type == TRACE_TRANSLATION + || parms.type == TRACE_ROTATION)) { + idPolygonModelCollisionDetection::StartContents(&tw, &startContents, + parms.start, trm, parms.trmAxis, parms.contentMask, + parms.modelOrigin, parms.modelAxis); + traceFunction(tw); + idPolygonModelCollisionDetection::FinishContents(&tw, + parms.modelOrigin, parms.modelAxis, parms.modelEntityNum, + parms.modelPhysicsId, parms.modelBodyId, parms.selfId, + parms.modelContentsOverride); + if (startContents.c.contentFlags != 0) { + results.query.status = static_cast( + results.query.status | QUERY_STATUS_BAD_START); + } + } + + switch (parms.type) { + case TRACE_TRANSLATION: + if (idPolygonModelCollisionDetection::StartTranslation(&tw, + reinterpret_cast(results.data), nullptr, + parms.start, parms.end, trm, parms.trmAxis, + parms.contentMask, parms.modelOrigin, parms.modelAxis)) { + traceFunction(tw); + idPolygonModelCollisionDetection::FinishTranslation(&tw, + parms.start, parms.end, parms.modelOrigin, parms.modelAxis, + parms.modelEntityNum, parms.modelPhysicsId, + parms.modelBodyId, parms.selfId, + parms.modelContentsOverride); + } + break; + case TRACE_TRANSLATION_POINT: + if (idPolygonModelCollisionDetection::StartTranslationPoint(&tw, + reinterpret_cast(results.data), parms.start, + parms.end, parms.contentMask, parms.modelOrigin, + parms.modelAxis)) { + traceFunction(tw); + idPolygonModelCollisionDetection::FinishTranslation(&tw, + parms.start, parms.end, parms.modelOrigin, parms.modelAxis, + parms.modelEntityNum, parms.modelPhysicsId, + parms.modelBodyId, parms.selfId, + parms.modelContentsOverride); + } + break; + case TRACE_ROTATION: + if (idPolygonModelCollisionDetection::StartRotation(&tw, + reinterpret_cast(results.data), + parms.rotationOrigin, parms.rotationAxis, + parms.rotationAngle, parms.start, trm, parms.trmAxis, + parms.contentMask, parms.modelOrigin, parms.modelAxis)) { + traceFunction(tw); + idPolygonModelCollisionDetection::FinishRotation(&tw, + parms.rotationOrigin, parms.rotationAxis, + parms.rotationAngle, parms.start, parms.trmAxis, + parms.modelOrigin, parms.modelAxis, parms.modelEntityNum, + parms.modelPhysicsId, parms.modelBodyId, parms.selfId, + parms.modelContentsOverride); + } + break; + case TRACE_ROTATION_POINT: + if (idPolygonModelCollisionDetection::StartRotationPoint(&tw, + reinterpret_cast(results.data), + parms.rotationOrigin, parms.rotationAxis, + parms.rotationAngle, parms.start, parms.contentMask, + parms.modelOrigin, parms.modelAxis)) { + traceFunction(tw); + idPolygonModelCollisionDetection::FinishRotation(&tw, + parms.rotationOrigin, parms.rotationAxis, + parms.rotationAngle, parms.start, parms.trmAxis, + parms.modelOrigin, parms.modelAxis, parms.modelEntityNum, + parms.modelPhysicsId, parms.modelBodyId, parms.selfId, + parms.modelContentsOverride); + } + break; + case TRACE_CONTENTS: + if (trm != nullptr) { + idPolygonModelCollisionDetection::StartContents(&tw, + reinterpret_cast(results.data), parms.start, + trm, parms.trmAxis, parms.contentMask, parms.modelOrigin, + parms.modelAxis); + traceFunction(tw); + idPolygonModelCollisionDetection::FinishContents(&tw, + parms.modelOrigin, parms.modelAxis, parms.modelEntityNum, + parms.modelPhysicsId, parms.modelBodyId, parms.selfId, + parms.modelContentsOverride); + } + break; + case TRACE_CONTENTS_POINT: + idPolygonModelCollisionDetection::StartContentsPoint(&tw, + reinterpret_cast(results.data), parms.start, + parms.contentMask, parms.modelOrigin, parms.modelAxis); + traceFunction(tw); + idPolygonModelCollisionDetection::FinishContents(&tw, + parms.modelOrigin, parms.modelAxis, parms.modelEntityNum, + parms.modelPhysicsId, parms.modelBodyId, parms.selfId, + parms.modelContentsOverride); + break; + case TRACE_CONTACTS_UNI_DIR: + case TRACE_CONTACTS_OMNI_DIR: + if (trm != nullptr) { + contactsResult_t& contacts = + *reinterpret_cast(results.data); + contacts.numContacts = 0; + idPolygonModelCollisionDetection::StartContacts(&tw, &contacts, + parms.start, parms.dir, parms.depth, *trm, + parms.trmAxis, parms.contentMask, parms.modelOrigin, + parms.modelAxis); + traceFunction(tw); + idPolygonModelCollisionDetection::FinishContacts(&tw, 0, + parms.modelOrigin, parms.modelAxis, parms.modelEntityNum, + parms.modelPhysicsId, parms.modelBodyId, parms.selfId, + parms.modelContentsOverride); + } + break; + case TRACE_CLIP: + if (trm != nullptr) { + clipResult_t& clip = *reinterpret_cast(results.data); + clip.numVerts = 0; + clip.numIndices = 0; + idPolygonModelCollisionDetection::StartClip(&tw, &clip, + parms.start, *trm, parms.trmAxis, parms.contentMask, + parms.modelOrigin, parms.modelAxis); + traceFunction(tw); + idPolygonModelCollisionDetection::FinishClip(&tw, 0, + parms.modelOrigin, parms.modelAxis); + } + break; + default: + results.query.status = QUERY_STATUS_FAILED; + break; + } + + if (parms.testQuery && trm != nullptr + && (parms.type == TRACE_TRANSLATION + || parms.type == TRACE_ROTATION)) { + const trace_t& motion = *reinterpret_cast(results.data); + trace_t endContents; + idPolygonModelCollisionDetection::StartContents(&tw, &endContents, + motion.endpos, trm, motion.endAxis, parms.contentMask, + parms.modelOrigin, parms.modelAxis); + traceFunction(tw); + idPolygonModelCollisionDetection::FinishContents(&tw, + parms.modelOrigin, parms.modelAxis, parms.modelEntityNum, + parms.modelPhysicsId, parms.modelBodyId, parms.selfId, + parms.modelContentsOverride); + if (endContents.c.contentFlags != 0) { + results.query.status = static_cast( + results.query.status | QUERY_STATUS_FAILED); + } + } + FinishQuery(tw, results, parms); +} + +} // namespace + +void idCollisionQueryExecute::ExecuteSubModelQuery(idTraceWork* const tw, + queryResults_t& results, const queryParms_t* const parms, + const idTraceModel* const trm, + const cm_subModelData_t* const subModelData, const int subModelNum) { + if (tw == nullptr || parms == nullptr || subModelData == nullptr) { + results.query.status = QUERY_STATUS_FAILED; + results.query.done = 1; + return; + } + std::memset(tw->profile, 0, sizeof(tw->profile)); + ExecutePolygonOperation(*tw, results, *parms, trm, + [subModelData, subModelNum](idTraceWork& work) { + idPolygonModelCollisionDetection::TraceThroughSubModel(&work, + subModelData, subModelNum); + }); +} + +void idCollisionQueryExecute::ExecutePolygonModelQuery(idTraceWork* const tw, + queryResults_t& results, const queryParms_t* const parms, + const idTraceModel* const trm, + const cm_polygonModel_t* const polygonModel) { + if (tw == nullptr || parms == nullptr || polygonModel == nullptr) { + results.query.status = QUERY_STATUS_FAILED; + results.query.done = 1; + return; + } + std::memset(tw->profile, 0, sizeof(tw->profile)); + ExecutePolygonOperation(*tw, results, *parms, trm, + [polygonModel](idTraceWork& work) { + idPolygonModelCollisionDetection::TraceThroughModel(&work, + *polygonModel); + }); +} + +void idCollisionQueryExecute::ExecuteSphereModelQuery( + queryResults_t& results, const queryParms_t* const parms, + const idJointMat* const modelJoints, + const cm_sphereModel_t* const sphereModel) { + if (parms == nullptr || sphereModel == nullptr) { + results.query.status = QUERY_STATUS_FAILED; + results.query.done = 1; + return; + } + switch (parms->type) { + case TRACE_TRANSLATION: + case TRACE_TRANSLATION_POINT: + idSphereModelCollisionDetection::TraceThroughModel( + *reinterpret_cast(results.data), *sphereModel, + parms->start, parms->end, parms->depth, parms->trmAxis, + modelJoints, parms->modelOrigin, parms->modelAxis, + parms->modelEntityNum, parms->modelPhysicsId, + parms->modelBodyId, parms->selfId, + parms->modelContentsOverride); + break; + case TRACE_CONTACTS_UNI_DIR: + case TRACE_CONTACTS_OMNI_DIR: + reinterpret_cast(results.data)->numContacts = 0; + break; + case TRACE_CLIP: + reinterpret_cast(results.data)->numVerts = 0; + reinterpret_cast(results.data)->numIndices = 0; + break; + default: { + trace_t& trace = *reinterpret_cast(results.data); + std::memset(&trace, 0, sizeof(trace)); + trace.fraction = 1.0f; + trace.endpos = parms->end; + trace.endAxis = parms->trmAxis; + break; + } + } + if (results.query.status == QUERY_STATUS_PENDING) { + results.query.status = QUERY_STATUS_SUCCESS; + } + std::memset(results.query.profile, 0, sizeof(results.query.profile)); + results.query.type = parms->type; + results.query.done = 1; + results.query.merged = parms->autoMerge ? 1 : 0; +} + +void idCollisionQueryExecute::SetupDependentParms( + queryParms_t* const resultParms, const queryParms_t* const sourceParms, + const queryResults_t* const modelPosition, + const dependencyType_t dependencyType, + const queryResults_t* const dependency1, + const queryResults_t* const dependency2) { + if (resultParms == nullptr || sourceParms == nullptr) { + return; + } + *resultParms = *sourceParms; + if (modelPosition != nullptr) { + const trace_t& position = + *reinterpret_cast(modelPosition->data); + resultParms->modelOrigin = position.endpos; + resultParms->modelAxis = position.endAxis; + } + const trace_t* const first = dependency1 != nullptr + ? reinterpret_cast(dependency1->data) : nullptr; + const trace_t* const second = dependency2 != nullptr + ? reinterpret_cast(dependency2->data) : nullptr; + if (first == nullptr && dependencyType != DEPENDENCY_NONE) { + return; + } + switch (dependencyType) { + case DEPENDENCY_NONE: + break; + case DEPENDENCY_MOTION_ROTATION: + resultParms->start = first->endpos; + resultParms->rotationOrigin = first->endpos; + break; + case DEPENDENCY_MOTION_CONTACTS: + resultParms->start = first->endpos; + resultParms->trmAxis = first->endAxis; + break; + case DEPENDENCY_STEPMOVE_STEP_UP: + case DEPENDENCY_STEPMOVE_STEP_DOWN: + case DEPENDENCY_SLIDEMOVE_STEP_UP_1: + case DEPENDENCY_SLIDEMOVE_STEP_UP_2: + case DEPENDENCY_SLIDEMOVE_STEP_UP_3: + case DEPENDENCY_SLIDEMOVE_STEP_UP_4: + case DEPENDENCY_SLIDEMOVE_STEP_DOWN_1: + case DEPENDENCY_SLIDEMOVE_STEP_DOWN_2: + case DEPENDENCY_SLIDEMOVE_STEP_DOWN_3: + case DEPENDENCY_SLIDEMOVE_STEP_DOWN_4: + resultParms->start = first->endpos; + resultParms->end = (first->c.flags & CONTACT_FLAG_SLIDEMOVE_FINISHED) + ? first->endpos + : first->endpos + sourceParms->dir * sourceParms->depth; + break; + case DEPENDENCY_STEPMOVE_2ND_MOVE: + case DEPENDENCY_SLIDEMOVE_2ND_MOVE_1: + case DEPENDENCY_SLIDEMOVE_2ND_MOVE_2: + case DEPENDENCY_SLIDEMOVE_2ND_MOVE_3: + case DEPENDENCY_SLIDEMOVE_2ND_MOVE_4: + resultParms->start = first->endpos; + if (first->c.flags & CONTACT_FLAG_SLIDEMOVE_FINISHED) { + resultParms->end = first->endpos; + } else { + idVec3 base = sourceParms->end; + if (second != nullptr) { + base = second->endpos + + second->c.normal * second->c.dist; + } + resultParms->end = base + sourceParms->dir + * (sourceParms->depth * first->fraction); + } + break; + case DEPENDENCY_STEPMOVE_CONTACTS: + case DEPENDENCY_SLIDEMOVE_CONTACTS: + resultParms->start = first->endpos; + resultParms->end = first->endpos; + break; + case DEPENDENCY_SLIDEMOVE_SLIDE: + resultParms->start = first->endpos; + resultParms->end = (first->c.flags & CONTACT_FLAG_SLIDEMOVE_FINISHED) + ? first->endpos + : first->endpos + first->c.normal * first->c.dist; + break; + case DEPENDENCY_LOCAL_SPACE: + resultParms->start = first->endpos + + ModelToWorldVector(first->endAxis, sourceParms->start); + resultParms->end = first->endpos + + ModelToWorldVector(first->endAxis, sourceParms->end); + resultParms->trmAxis = first->endAxis * sourceParms->trmAxis; + break; + default: + break; + } +} diff --git a/source/engine/cm/jobs/collisionquery.h b/source/engine/cm/jobs/collisionquery.h index 4ad2710..a5506e0 100644 --- a/source/engine/cm/jobs/collisionquery.h +++ b/source/engine/cm/jobs/collisionquery.h @@ -1,20 +1,127 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\cm\jobs\collisionquery.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "cm/jobs/collisionresults.h" +#include "cm/collisiontypes.h" +#include -// IDA Local Type ordinal 14071; PDB kind: class. -class idCollisionQuery -{ -public: - unsigned __int64 offset; +enum traceType_t : int { + TRACE_INVALID = 0, + TRACE_TRANSLATION = 1, + TRACE_TRANSLATION_POINT = 2, + TRACE_ROTATION = 3, + TRACE_ROTATION_POINT = 4, + TRACE_CONTENTS = 5, + TRACE_CONTENTS_POINT = 6, + TRACE_CONTACTS_UNI_DIR = 7, + TRACE_CONTACTS_OMNI_DIR = 8, + TRACE_CLIP = 9 }; -// IDA Local Type ordinal 23754; PDB kind: class. -class idCollisionQueryExecute -{ -public: +enum queryStatus_t : int { + QUERY_STATUS_PENDING = 0, + QUERY_STATUS_SUCCESS = 1, + QUERY_STATUS_BAD_START = 2, + QUERY_STATUS_FAILED = 4 }; + +enum dependencyType_t : int { + DEPENDENCY_NONE = 0, + DEPENDENCY_MOTION_ROTATION = 1, + DEPENDENCY_MOTION_CONTACTS = 2, + DEPENDENCY_STEPMOVE_STEP_UP = 3, + DEPENDENCY_STEPMOVE_2ND_MOVE = 4, + DEPENDENCY_STEPMOVE_STEP_DOWN = 5, + DEPENDENCY_STEPMOVE_CONTACTS = 6, + DEPENDENCY_SLIDEMOVE_STEP_UP_1 = 7, + DEPENDENCY_SLIDEMOVE_STEP_UP_2 = 8, + DEPENDENCY_SLIDEMOVE_STEP_UP_3 = 9, + DEPENDENCY_SLIDEMOVE_STEP_UP_4 = 10, + DEPENDENCY_SLIDEMOVE_2ND_MOVE_1 = 11, + DEPENDENCY_SLIDEMOVE_2ND_MOVE_2 = 12, + DEPENDENCY_SLIDEMOVE_2ND_MOVE_3 = 13, + DEPENDENCY_SLIDEMOVE_2ND_MOVE_4 = 14, + DEPENDENCY_SLIDEMOVE_STEP_DOWN_1 = 15, + DEPENDENCY_SLIDEMOVE_STEP_DOWN_2 = 16, + DEPENDENCY_SLIDEMOVE_STEP_DOWN_3 = 17, + DEPENDENCY_SLIDEMOVE_STEP_DOWN_4 = 18, + DEPENDENCY_SLIDEMOVE_SLIDE = 19, + DEPENDENCY_SLIDEMOVE_CONTACTS = 20, + DEPENDENCY_LOCAL_SPACE = 21 +}; + +class idCollisionQuery { +public: + std::uint64_t offset; +}; + +class idTraceModel; +struct idTraceWork; +class idJointMat; +struct queryResults_t; + +struct queryParms_t { + traceType_t type; + bool autoMerge; + bool testQuery; + std::uint8_t pad[10]; + idVec3 start; + idVec3 end; + idVec3 rotationOrigin; + idVec3 rotationAxis; + float rotationAngle; + idVec3 dir; + float depth; + idMat3 trmAxis; + int contentMask; + idVec3 modelOrigin; + idMat3 modelAxis; + int modelEntityNum; + int modelPhysicsId; + int modelBodyId; + int modelContentsOverride; + int selfId; +}; + +class idCollisionQueryExecute { +public: + static void ExecuteSubModelQuery(idTraceWork* tw, + queryResults_t& results, const queryParms_t* parms, + const idTraceModel* trm, const cm_subModelData_t* subModelData, + int subModelNum); + static void ExecutePolygonModelQuery(idTraceWork* tw, + queryResults_t& results, const queryParms_t* parms, + const idTraceModel* trm, const cm_polygonModel_t* polygonModel); + static void ExecuteSphereModelQuery(queryResults_t& results, + const queryParms_t* parms, const idJointMat* modelJoints, + const cm_sphereModel_t* sphereModel); + static void SetupDependentParms(queryParms_t* resultParms, + const queryParms_t* sourceParms, + const queryResults_t* modelPosition, + dependencyType_t dependencyType, + const queryResults_t* dependency1, + const queryResults_t* dependency2); +}; + +struct queryResults_t { + struct query_t { + traceType_t type; + int done; + int merged; + queryStatus_t status; + std::uint16_t executeThreadId; + std::uint16_t mergeThreadId; + std::uint32_t profile[5]; + std::uint64_t modelQueryIndex; + } query; + std::uint8_t data[928]; +}; + +static_assert(sizeof(idCollisionQuery) == 8, + "Recovered idCollisionQuery ABI changed"); +static_assert(sizeof(queryResults_t::query_t) == 48, + "Recovered queryResults_t::query_t ABI changed"); +static_assert(sizeof(queryResults_t) == 976, + "Recovered queryResults_t ABI changed"); +static_assert(sizeof(queryParms_t) == 192, + "Recovered queryParms_t ABI changed"); diff --git a/source/engine/cm/jobs/collisionresults.h b/source/engine/cm/jobs/collisionresults.h new file mode 100644 index 0000000..4b3c5aa --- /dev/null +++ b/source/engine/cm/jobs/collisionresults.h @@ -0,0 +1,87 @@ +#pragma once + +#include "idlib/math/matrix.h" +#include "idlib/math/vector.h" + +#include +#include + +// Recovered from tungsten.exe type information. These are the common result +// records shared by CM and GameLib; keeping one definition prevents the two +// libraries from drifting at their ABI boundary. +enum contactType_t : int { + CONTACT_NONE = 0, + CONTACT_EDGE = 1, + CONTACT_MODELVERTEX = 2, + CONTACT_TRMVERTEX = 3, + CONTACT_SPHERE = 4 +}; + +enum contactFlag_t : int { + CONTACT_FLAG_NONE = 0, + CONTACT_FLAG_SUBMODEL_NOT_RESIDENT = 1, + CONTACT_FLAG_STEPMOVE_ONSOLID = 2, + CONTACT_FLAG_STEPMOVE_ONGROUND = 4, + CONTACT_FLAG_CAR_TO_CAR_COLLISION = 8, + CONTACT_FLAG_OUTSIDE_LARGEST_SUPPORT_TRIANGLE = 16, + CONTACT_FLAG_SLIDEMOVE_FINISHED = 32 +}; + +struct contactInfo_t { + contactType_t type; + idVec3 point; + idVec3 normal; + float dist; + float separation; + int contentFlags; + int surfaceFlags; + int surfaceType; + int modelFeature; + int trmFeature; + int entityNum; + int physicsId; + int bodyId; + int selfId; + std::uint8_t flags; + std::uint8_t surfaceColor[3]; +}; + +struct trace_t { + float fraction; + idVec3 endpos; + idMat3 endAxis; + contactInfo_t c; +}; + +struct contactsResult_t { + int numContacts; + int pad[3]; + contactInfo_t contacts[12]; + + // Recovered from engine/cm/jobs/collisionresults.h. The dump copies each + // 76-byte contact; memcpy is the exact scalar PC spelling of that loop. + void CopyFrom(const contactsResult_t& other) { + numContacts = other.numContacts; + const int copyCount = numContacts < 12 ? numContacts : 12; + if (copyCount > 0) { + std::memcpy(contacts, other.contacts, + static_cast(copyCount) * sizeof(contactInfo_t)); + } + } +}; + +struct clipResult_t { + int numVerts; + int numIndices; + int pad[2]; + idVec3 verts[32]; + std::int16_t indices[264]; +}; + +static_assert(sizeof(contactInfo_t) == 76, + "Recovered contactInfo_t ABI changed"); +static_assert(sizeof(trace_t) == 128, "Recovered trace_t ABI changed"); +static_assert(sizeof(contactsResult_t) == 928, + "Recovered contactsResult_t ABI changed"); +static_assert(sizeof(clipResult_t) == 928, + "Recovered clipResult_t ABI changed"); diff --git a/source/engine/cm/jobs/polygonmodel/polygonmodel.h b/source/engine/cm/jobs/polygonmodel/polygonmodel.h index f13b154..00e2ac3 100644 --- a/source/engine/cm/jobs/polygonmodel/polygonmodel.h +++ b/source/engine/cm/jobs/polygonmodel/polygonmodel.h @@ -1,17 +1,309 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\cm\jobs\polygonmodel\polygonmodel.h -// Recovered logical types: 1 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "cm/collisiontypes.h" +#include "cm/jobs/collisionquery.h" +#include "idlib/math/mat3x4.h" +#include "idlib/math/pluecker.h" +#include +#include -// IDA Local Type ordinal 20007; PDB kind: struct. -struct cm_polygonModel_t -{ - int numModelTreeNodes; - int numSubModels; - cm_modelTreeNode_t *modelTreeNodes; - cm_subModel_t *subModels; - volatile char *subModelState; +class idTraceModel; + +struct cm_sideCache_t { + std::uint32_t side; }; + +struct cm_trmVertex_t { + idBoundsShort bounds; + int pad; +}; + +struct cm_trmEdge_t { + idBoundsShort bounds; + std::uint16_t vertexNum[2]; +}; + +struct cm_trmPolygon_t { + idPlane plane; + idBoundsShort bounds; + std::uint32_t numEdges; + std::uint8_t edges[16]; + std::uint32_t vertexSideMask; + std::uint32_t vertexSideBits; + std::uint32_t pad[2]; +}; + +struct idModelCheckCounts { + std::uint8_t baseCheckCounts[928]; + std::uint8_t checkCount; + std::uint8_t* vertexCheckCounts; + std::uint8_t* edgeCheckCounts; + std::uint8_t* polygonCheckCounts; + std::uint8_t* polytopeCheckCounts; + + void SetupForSubModel(const cm_subModelData_t* subModelData); +}; + +struct idTraceWork { + cm_trmVertex_t verts[32]; + cm_trmEdge_t edges[32]; + cm_trmPolygon_t polys[16]; + idVec4 vertexPosition[32]; + idVec4 vertexEndPosition[32]; + idPluecker vertexPluecker[32]; + idPluecker edgePluecker[32]; + idPluecker edgeZAxisPluecker[32]; + idVec4 edgeNormal[32]; + std::uint8_t vertIsUsed[32]; + std::uint8_t edgeIsUsed[32]; + std::uint8_t polyIsUsed[16]; + std::uint32_t numVerts; + std::uint32_t numEdges; + std::uint32_t numPolys; + int contents; + idVec4 start; + idVec4 end; + idVec4 dir; + idVec4 negDir; + idMat3x4 trmTransform; + idVec4 trmBoundsMin; + idVec4 trmBoundsMax; + idVec4 trmExtents; + idVec4 traceBoundsMin; + idVec4 traceBoundsMax; + idBoundsShort traceBoundsShort; + int pad; + idPlane heartPlane1; + idPlane heartPlane2; + float maxDistFromHeartPlane1; + float maxDistFromHeartPlane2; + float fraction; + int subModelNum; + float angle; + float negAngle; + float maxTan; + float initialTan; + idVec4 origin; + idVec4 axis; + idMat3x4 ZAxisTransform; + idMat3x4 endTransform; + float contactDepth; + traceType_t traceType; + bool isConvex; + bool quickExit; + cm_sideCache_t polygonSideCache; + cm_sideCache_t polygonEdgeSideCache[20]; + cm_sideCache_t polygonVertexSideCache[20]; + idPluecker polygonEdgePlueckerCache[16]; + idPluecker polygonVertexPlueckerCache[16]; + std::uint8_t subModelDataForBounds[768]; + idModelCheckCounts modelCheckCounts; + cm_subModelPtrs_t subModelPtrs; + trace_t* traceResult; + contactsResult_t* contactsResult; + clipResult_t* clipResult; + trace_t tempTraceResult; + int profile[5]; + + void Init(); +}; + +class idPolygonModelCollisionDetection { +public: + static void SetupSubModelPtrsFromData(cm_subModelPtrs_t& pointers, + const cm_subModelData_t* data); + static idTraceWork* AllocTraceWork(); + static int GetTraceWorkSPUSize(); + + static cm_subModelData_t* SetupSubModelForBounds(cm_subModelData_t* data, + int size, const idBounds& bounds); + static bool TestStuckInSubModelBounds(idTraceWork* tw, + const idBounds& subModelBounds); + static idVec3 LocalExtentsFromUnTransformedBounds( + const idBounds& globalBounds, const idVec3& globalStart, + const idVec3& globalEnd, const idMat3& modelAxis); + static void TraceThroughSubModelTree(idTraceWork* tw); + static void TraceThroughSubModel(idTraceWork* tw, + const cm_subModelData_t* subModelData, int subModelNum); + static unsigned int GetSubModelsForTrace(const cm_polygonModel_t& model, + const idVec3& start, const idVec3& end, const idVec3& extents, + int* subModelNums); + static void TraceThroughModel(idTraceWork* tw, + const cm_polygonModel_t& model); + + static bool TestTrmVertsInPolytope(idTraceWork* tw, int polytopeNum); + static bool TestTrmInPolygon(idTraceWork* tw, int polygonNum); + static void StartContents(idTraceWork* tw, trace_t* result, + const idVec3& start, const idTraceModel* trm, + const idMat3& trmAxis, int contentMask, + const idVec3& modelOrigin, const idMat3& modelAxis); + static void StartContentsPoint(idTraceWork* tw, trace_t* result, + const idVec3& start, int contentMask, + const idVec3& modelOrigin, const idMat3& modelAxis); + static void FinishContents(idTraceWork* tw, const idVec3& modelOrigin, + const idMat3& modelAxis, int modelEntityNum, int modelPhysicsId, + int modelBodyId, int selfId, int modelContentsOverride); + + static void TranslationSetup(idTraceWork* tw, const idVec3& start, + const idVec3& end, const idVec3& offset, const idMat3& trmAxis, + const idVec3& modelOrigin, const idMat3& modelAxis); + static void TranslationUsedPrimitives(idTraceWork* tw, + const idVec3& start, const idVec3& end, const idTraceModel& trm, + const idMat3& trmAxis); + static void TranslationHeartPlanes(idTraceWork* tw); + static void TranslationVerts(idTraceWork* tw, const idTraceModel& trm); + static void TranslationEdges(idTraceWork* tw, const idTraceModel& trm); + static void TranslationPolys(idTraceWork* tw, const idTraceModel& trm); + static void TranslationBounds(idTraceWork* tw); + static void TranslationUpdateBounds(idTraceWork* tw); + static void TranslationPlueckerCache(idTraceWork* tw, + const cm_polygon_t& polygon); + static void TranslationEdgePlueckerCache(idTraceWork* tw, + const cm_polygon_t& polygon); + static void TranslationSideCache(const idPluecker* pluecker, + const std::uint8_t* used, unsigned int count, + const idPluecker* plueckerCache, cm_sideCache_t* sideCache, + unsigned int cacheSize); + static void TranslationPolygonSideCache(idTraceWork* tw, + const cm_polygon_t& polygon); + static void AddContact(idTraceWork* tw); + static float TranslateEdgeThroughEdge(const idPluecker& first, + const idPluecker& second, const idVec3& direction); + static int TranslateTrmEdgesThroughPolygon(idTraceWork* tw, + const cm_polygon_t& polygon); + static float TranslatePointThroughPlane(const idPlane& plane, + const idVec3& start, const idVec3& end); + static int TranslateTrmVertsThroughPolygon(idTraceWork* tw, + const cm_polygon_t& polygon, const idPlane& polygonPlane); + static int TranslatePolygonVertsThroughTrm(idTraceWork* tw, + const cm_polygon_t& polygon); + static int TranslateTrmThroughPolygon(idTraceWork* tw, int polygonNum); + static int TranslatePointThroughPolygon(idTraceWork* tw, int polygonNum); + static int StartTranslation(idTraceWork* tw, trace_t* result, + contactsResult_t* contacts, const idVec3& start, const idVec3& end, + const idTraceModel* trm, const idMat3& trmAxis, int contentMask, + const idVec3& modelOrigin, const idMat3& modelAxis); + static int StartTranslationPoint(idTraceWork* tw, trace_t* result, + const idVec3& start, const idVec3& end, int contentMask, + const idVec3& modelOrigin, const idMat3& modelAxis); + static void FinishTranslation(idTraceWork* tw, const idVec3& start, + const idVec3& end, const idVec3& modelOrigin, + const idMat3& modelAxis, int modelEntityNum, int modelPhysicsId, + int modelBodyId, int selfId, int modelContentsOverride); + + static int ClipInPlace(idVec5* points, int numPoints, + const idPlane& plane, float epsilon, bool keepOn); + static bool ClipPolygonWithTrm(idTraceWork* tw, int polygonNum); + static void StartClip(idTraceWork* tw, clipResult_t* result, + const idVec3& start, const idTraceModel& trm, + const idMat3& trmAxis, int contentMask, + const idVec3& modelOrigin, const idMat3& modelAxis); + static void FinishClip(idTraceWork* tw, int firstClipVert, + const idVec3& modelOrigin, const idMat3& modelAxis); + + static void TestTrmEdgeInContactWithPolygon(idTraceWork* tw, + const cm_polygon_t& polygon, int trmEdgeNum); + static void TestTrmVertexInContactWithPolygon(idTraceWork* tw, + const cm_polygon_t& polygon, const idPlane& polygonPlane, + int trmVertNum); + static void TestVertexInContactWithTrmPolygon(idTraceWork* tw, + const cm_trmPolygon_t& trmPolygon, const cm_polygon_t& polygon, + const cm_vertex_t& vertex); + static bool TestTrmInContactWithPolygon(idTraceWork* tw, + int polygonNum); + static void StartContacts(idTraceWork* tw, contactsResult_t* result, + const idVec3& start, const idVec3& direction, float depth, + const idTraceModel& trm, const idMat3& trmAxis, int contentMask, + const idVec3& modelOrigin, const idMat3& modelAxis); + static void FinishContacts(idTraceWork* tw, int firstContact, + const idVec3& modelOrigin, const idMat3& modelAxis, + int modelEntityNum, int modelPhysicsId, int modelBodyId, int selfId, + int modelContentsOverride); + + static bool EdgeIntersectsBoundsShort(const idBoundsShort& bounds, + const idVec3& start, const idVec3& end); + static void RotationSetup(idTraceWork* tw, const idVec3& rotationOrigin, + const idVec3& rotationAxis, float angle, const idVec3& start, + const idVec3& offset, const idMat3& trmAxis, + const idVec3& modelOrigin, const idMat3& modelAxis); + static void TransformFromOriginAxisAngle(idMat3x4& transform, + const idVec3& origin, const idVec3& axis, float angle); + static void TransformAxisToZAxis(idMat3x4& transform, + const idVec3& origin, const idVec3& axis); + static void RotationVerts(idTraceWork* tw, const idTraceModel& trm, + idVec4* zverts); + static void RotationEdges(idTraceWork* tw, const idTraceModel& trm, + const idVec4* zverts); + static void RotationPolys(idTraceWork* tw, const idTraceModel& trm); + static void RotationBounds(idTraceWork* tw); + static void RotationEdgePlueckerCache(idTraceWork* tw, + const cm_polygon_t& polygon); + static void RotationCullPolygonEdges(idTraceWork* tw, + const cm_polygon_t& polygon); + static int CollisionBetweenEdgeBounds(const idTraceWork& tw, + const idVec3& firstStart, const idVec3& firstEnd, + const idVec3& secondStart, const idVec3& secondEnd, + float tanHalfAngle, idVec3& collisionPoint, + idVec3& collisionNormal); + static int RotateEdgeThroughEdge(const idPluecker& first, + const idPluecker& second, float angle, float minTan, float maxTan, + float& tanHalfAngle); + static int EdgeFurthestFromEdge(const idPluecker& first, + const idPluecker& second, float angle, float& tanHalfAngle, + float& direction); + static int RotateTrmEdgesThroughPolygon(idTraceWork* tw, + const cm_polygon_t& polygon); + static int RotatePointThroughPlane(const idVec3& point, + const idPlane& plane, float angle, float minTan, float maxTan, + float& tanHalfAngle); + static int PointFurthestFromPlane(const idVec3& point, + const idPlane& plane, float angle, float& tanHalfAngle, + float& direction); + static int RotatePointThroughEpsilonPlane(const idTraceWork& tw, + const idVec3& point, const idVec3& endPoint, const idPlane& plane, + float angle, const idVec3& rotationOrigin, float& tanHalfAngle, + idVec3& collisionPoint, idVec3& endDirection); + static int RotateTrmVertsThroughPolygon(idTraceWork* tw, + const cm_polygon_t& polygon, const idPlane& polygonPlane); + static int RotatePolygonVertsThroughTrm(idTraceWork* tw, + const cm_polygon_t& polygon); + static bool RotateTrmThroughPolygon(idTraceWork* tw, int polygonNum); + static int StartRotation(idTraceWork* tw, trace_t* result, + const idVec3& rotationOrigin, const idVec3& rotationAxis, + float angle, const idVec3& start, const idTraceModel* trm, + const idMat3& trmAxis, int contentMask, + const idVec3& modelOrigin, const idMat3& modelAxis); + static int StartRotationPoint(idTraceWork* tw, trace_t* result, + const idVec3& rotationOrigin, const idVec3& rotationAxis, + float angle, const idVec3& start, int contentMask, + const idVec3& modelOrigin, const idMat3& modelAxis); + static void FinishRotation(idTraceWork* tw, + const idVec3& rotationOrigin, const idVec3& rotationAxis, + float angle, const idVec3& start, const idMat3& trmAxis, + const idVec3& modelOrigin, const idMat3& modelAxis, + int modelEntityNum, int modelPhysicsId, int modelBodyId, int selfId, + int modelContentsOverride); +}; + +float CM_TanZeroHalfPI(float angle); +float CM_ArcTanPositive(float value); +void CM_PointRotationBounds(const idVec3& origin, const idVec3& axis, + const idVec3& start, const idVec3& end, + idVec4& boundsMin, idVec4& boundsMax); + +static_assert(sizeof(cm_sideCache_t) == 4, + "Recovered cm_sideCache_t ABI changed"); +static_assert(sizeof(cm_trmVertex_t) == 16, + "Recovered cm_trmVertex_t ABI changed"); +static_assert(sizeof(cm_trmEdge_t) == 16, + "Recovered cm_trmEdge_t ABI changed"); +static_assert(sizeof(cm_trmPolygon_t) == 64, + "Recovered cm_trmPolygon_t ABI changed"); + +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(idModelCheckCounts) == 948, + "Recovered idModelCheckCounts ABI changed"); +static_assert(sizeof(idTraceWork) == 9244, + "Recovered idTraceWork ABI changed"); +#endif diff --git a/source/engine/cm/jobs/polygonmodel/polygonmodel_cache.cpp b/source/engine/cm/jobs/polygonmodel/polygonmodel_cache.cpp new file mode 100644 index 0000000..558380f --- /dev/null +++ b/source/engine/cm/jobs/polygonmodel/polygonmodel_cache.cpp @@ -0,0 +1,135 @@ +#include "cm/jobs/polygonmodel/polygonmodel.h" + +#include "idlib/sys/sys_alloc.h" + +#include +#include +#include + +namespace { + +std::size_t CheckCountBytes(const int count) { + return static_cast((count + 7) & ~7) >> 3; +} + +template +void ClearArray(T (&values)[Count]) { + std::memset(values, 0, sizeof(values)); +} + +void ClearMatrix(idMat3x4& matrix) { + std::fill(matrix.mat, matrix.mat + 12, 0.0f); +} + +void ClearVector(idVec4& vector) { + vector.Set(0.0f, 0.0f, 0.0f, 0.0f); +} + +void ClearPlane(idPlane& plane) { + plane.a = 0.0f; + plane.b = 0.0f; + plane.c = 0.0f; + plane.d = 0.0f; +} + +} // namespace + +void idModelCheckCounts::SetupForSubModel( + const cm_subModelData_t* const subModelData) { + vertexCheckCounts = baseCheckCounts; + edgeCheckCounts = vertexCheckCounts + + CheckCountBytes(subModelData->numVertices); + polygonCheckCounts = edgeCheckCounts + + CheckCountBytes(subModelData->numEdges); + polytopeCheckCounts = polygonCheckCounts + + CheckCountBytes(subModelData->numPolygons); + + const std::size_t usedBytes = CheckCountBytes(subModelData->numVertices) + + CheckCountBytes(subModelData->numEdges) + + CheckCountBytes(subModelData->numPolygons) + + CheckCountBytes(subModelData->numPolytopes); + const std::size_t alignedBytes = (usedBytes + 15u) & ~std::size_t(15u); + std::memset(baseCheckCounts, 0, alignedBytes); +} + +void idTraceWork::Init() { + numVerts = 0; + numEdges = 0; + numPolys = 0; + contents = 0; + ClearVector(start); + ClearVector(end); + ClearVector(dir); + ClearVector(negDir); + ClearMatrix(trmTransform); + ClearVector(trmBoundsMin); + ClearVector(trmBoundsMax); + ClearVector(trmExtents); + ClearVector(traceBoundsMin); + ClearVector(traceBoundsMax); + std::memset(&traceBoundsShort, 0, sizeof(traceBoundsShort)); + pad = 0; + ClearPlane(heartPlane1); + ClearPlane(heartPlane2); + maxDistFromHeartPlane1 = 0.0f; + maxDistFromHeartPlane2 = 0.0f; + fraction = 0.0f; + subModelNum = 0; + angle = 0.0f; + negAngle = 0.0f; + maxTan = 0.0f; + initialTan = 0.0f; + ClearVector(origin); + ClearVector(axis); + ClearMatrix(ZAxisTransform); + ClearMatrix(endTransform); + contactDepth = 0.0f; + traceType = TRACE_TRANSLATION; + isConvex = false; + quickExit = false; + polygonSideCache.side = 0; + + std::memset(modelCheckCounts.baseCheckCounts, 0, + sizeof(modelCheckCounts.baseCheckCounts)); + modelCheckCounts.checkCount = 0; + modelCheckCounts.vertexCheckCounts = nullptr; + modelCheckCounts.edgeCheckCounts = nullptr; + modelCheckCounts.polygonCheckCounts = nullptr; + modelCheckCounts.polytopeCheckCounts = nullptr; + traceResult = nullptr; + contactsResult = nullptr; + clipResult = nullptr; + + ClearArray(verts); + ClearArray(edges); + ClearArray(polys); + ClearArray(vertexPosition); + ClearArray(vertexEndPosition); + ClearArray(vertexPluecker); + ClearArray(edgePluecker); + ClearArray(edgeZAxisPluecker); + ClearArray(edgeNormal); + ClearArray(vertIsUsed); + ClearArray(edgeIsUsed); + ClearArray(polyIsUsed); + ClearArray(polygonEdgeSideCache); + ClearArray(polygonVertexSideCache); + ClearArray(polygonEdgePlueckerCache); + ClearArray(polygonVertexPlueckerCache); + std::memset(&subModelPtrs, 0, sizeof(subModelPtrs)); + ClearArray(profile); +} + +idTraceWork* idPolygonModelCollisionDetection::AllocTraceWork() { + void* const memory = mem.AllocWithLocation( + "engine/cm/jobs/polygonmodel/polygonmodel_cache.cpp : TAG_COLLISION", + static_cast(sizeof(idTraceWork)), TAG_COLLISION, false, + ALIGN_16, HEAP_DEFAULTHEAP); + idTraceWork* const traceWork = new (memory) idTraceWork; + traceWork->Init(); + return traceWork; +} + +int idPolygonModelCollisionDetection::GetTraceWorkSPUSize() { + return 9244; +} diff --git a/source/engine/cm/jobs/polygonmodel/polygonmodel_clip.cpp b/source/engine/cm/jobs/polygonmodel/polygonmodel_clip.cpp new file mode 100644 index 0000000..79b93c5 --- /dev/null +++ b/source/engine/cm/jobs/polygonmodel/polygonmodel_clip.cpp @@ -0,0 +1,157 @@ +#include "cm/jobs/polygonmodel/polygonmodel.h" + +#include "cm/jobs/polygonmodel/polygonmodeldata.h" +#include "idlib/geometry/tracemodel.h" + +#include +#include +#include + +namespace { + +idVec3 ModelToWorldVector(const idMat3& axis, const idVec3& value) { + return idVec3( + axis[0].x * value.x + axis[1].x * value.y + axis[2].x * value.z, + axis[0].y * value.x + axis[1].y * value.y + axis[2].y * value.z, + axis[0].z * value.x + axis[1].z * value.y + axis[2].z * value.z); +} + +bool TestAndSet(std::uint8_t* bits, const int index) { + if (bits == nullptr) { + return false; + } + const std::uint8_t mask = static_cast(1u << (index & 7)); + std::uint8_t& value = bits[index >> 3]; + const bool old = (value & mask) != 0; + value = static_cast(value | mask); + return old; +} + +} // namespace + +int idPolygonModelCollisionDetection::ClipInPlace(idVec5* const points, + const int numPoints, const idPlane& plane, const float epsilon, + const bool keepOn) { + if (points == nullptr || numPoints <= 0) { + return 0; + } + idVec5 clipped[64]; + int clippedCount = 0; + idVec5 previous = points[numPoints - 1]; + float previousDistance = plane.a * previous.x + plane.b * previous.y + + plane.c * previous.z + plane.d; + bool previousInside = keepOn + ? previousDistance <= epsilon : previousDistance < -epsilon; + for (int index = 0; index < numPoints; ++index) { + const idVec5 current = points[index]; + const float currentDistance = plane.a * current.x + + plane.b * current.y + plane.c * current.z + plane.d; + const bool currentInside = keepOn + ? currentDistance <= epsilon : currentDistance < -epsilon; + if (currentInside != previousInside && clippedCount < 64) { + const float denominator = previousDistance - currentDistance; + const float fraction = std::fabs(denominator) > 1.0e-20f + ? previousDistance / denominator : 0.0f; + idVec5& intersection = clipped[clippedCount++]; + for (int component = 0; component < 5; ++component) { + intersection[component] = previous[component] + + (current[component] - previous[component]) * fraction; + } + } + if (currentInside && clippedCount < 64) { + clipped[clippedCount++] = current; + } + previous = current; + previousDistance = currentDistance; + previousInside = currentInside; + } + std::memcpy(points, clipped, + static_cast(clippedCount) * sizeof(idVec5)); + return clippedCount; +} + +bool idPolygonModelCollisionDetection::ClipPolygonWithTrm( + idTraceWork* const tw, const int polygonNum) { + if (tw->clipResult == nullptr + || TestAndSet(tw->modelCheckCounts.polygonCheckCounts, polygonNum)) { + return false; + } + const cm_polygon_t& polygon = tw->subModelPtrs.polygons[polygonNum]; + const cm_material_t& material = tw->subModelPtrs.materials[polygon.material]; + if ((material.contentFlags & tw->contents) == 0 + || !tw->traceBoundsShort.IntersectsBounds(polygon.bounds)) { + return false; + } + + idVec5 points[64]; + int numPoints = 0; + for (int edgeNumber = 0; + edgeNumber < polygon.numEdges && numPoints < 64; ++edgeNumber) { + const std::uint16_t reference = tw->subModelPtrs.polygonEdges[ + polygon.firstEdge + edgeNumber]; + const cm_edge_t& edge = tw->subModelPtrs.edges[CM_EdgeIndex(reference)]; + const cm_vertex_t& vertex = tw->subModelPtrs.vertices[ + CM_EdgeStartVertex(edge, reference)]; + points[numPoints++] = idVec5(vertex.p.x, vertex.p.y, vertex.p.z, + static_cast(vertex.st[0]), + static_cast(vertex.st[1])); + } + for (unsigned int planeNumber = 0; + planeNumber < tw->numPolys && numPoints >= 3; ++planeNumber) { + numPoints = ClipInPlace(points, numPoints, + tw->polys[planeNumber].plane, 0.01f, true); + } + if (numPoints < 3) { + return false; + } + + clipResult_t& result = *tw->clipResult; + const int availableVerts = 32 - result.numVerts; + numPoints = (std::min)(numPoints, availableVerts); + if (numPoints < 3) { + return true; + } + const int firstVertex = result.numVerts; + for (int index = 0; index < numPoints; ++index) { + result.verts[result.numVerts++].Set( + points[index].x, points[index].y, points[index].z); + } + for (int index = 1; index + 1 < numPoints + && result.numIndices + 3 <= 264; ++index) { + result.indices[result.numIndices++] = + static_cast(firstVertex); + result.indices[result.numIndices++] = + static_cast(firstVertex + index); + result.indices[result.numIndices++] = + static_cast(firstVertex + index + 1); + } + return result.numVerts >= 32 || result.numIndices >= 264; +} + +void idPolygonModelCollisionDetection::StartClip(idTraceWork* const tw, + clipResult_t* const result, const idVec3& start, + const idTraceModel& trm, const idMat3& trmAxis, const int contentMask, + const idVec3& modelOrigin, const idMat3& modelAxis) { + StartContents(tw, &tw->tempTraceResult, start, &trm, trmAxis, + contentMask, modelOrigin, modelAxis); + tw->traceType = TRACE_CLIP; + tw->traceResult = nullptr; + tw->clipResult = result; + tw->contactsResult = nullptr; + result->numVerts = 0; + result->numIndices = 0; +} + +void idPolygonModelCollisionDetection::FinishClip(idTraceWork* const tw, + const int firstClipVert, const idVec3& modelOrigin, + const idMat3& modelAxis) { + if (tw->clipResult == nullptr) { + return; + } + clipResult_t& result = *tw->clipResult; + for (int index = (std::max)(0, firstClipVert); + index < result.numVerts; ++index) { + result.verts[index] = ModelToWorldVector(modelAxis, + result.verts[index]) + modelOrigin; + } +} diff --git a/source/engine/cm/jobs/polygonmodel/polygonmodel_contacts.cpp b/source/engine/cm/jobs/polygonmodel/polygonmodel_contacts.cpp new file mode 100644 index 0000000..05ba455 --- /dev/null +++ b/source/engine/cm/jobs/polygonmodel/polygonmodel_contacts.cpp @@ -0,0 +1,334 @@ +#include "cm/jobs/polygonmodel/polygonmodel.h" + +#include "cm/jobs/polygonmodel/polygonmodeldata.h" +#include "idlib/geometry/tracemodel.h" + +#include +#include +#include +#include + +namespace { + +idVec3 Vec3(const idVec4& value) { + return idVec3(value.x, value.y, value.z); +} + +idVec3 ModelToWorldVector(const idMat3& axis, const idVec3& value) { + return idVec3( + axis[0].x * value.x + axis[1].x * value.y + axis[2].x * value.z, + axis[0].y * value.x + axis[1].y * value.y + axis[2].y * value.z, + axis[0].z * value.x + axis[1].z * value.y + axis[2].z * value.z); +} + +bool TestAndSet(std::uint8_t* bits, const int index) { + if (bits == nullptr) { + return false; + } + const std::uint8_t mask = static_cast(1u << (index & 7)); + std::uint8_t& value = bits[index >> 3]; + const bool old = (value & mask) != 0; + value = static_cast(value | mask); + return old; +} + +bool PointInsidePolygon(const cm_subModelPtrs_t& model, + const cm_polygon_t& polygon, const idPlane& plane, const idVec3& point) { + bool positive = false; + bool negative = false; + for (int edgeNumber = 0; edgeNumber < polygon.numEdges; ++edgeNumber) { + const std::uint16_t reference = model.polygonEdges[ + polygon.firstEdge + edgeNumber]; + const cm_edge_t& edge = model.edges[CM_EdgeIndex(reference)]; + const idVec3& start = model.vertices[ + CM_EdgeStartVertex(edge, reference)].p; + const idVec3& end = model.vertices[ + CM_EdgeEndVertex(edge, reference)].p; + const float side = (end - start).Cross(point - start).Dot( + plane.Normal()); + positive |= side > 0.01f; + negative |= side < -0.01f; + if (positive && negative) { + return false; + } + } + return true; +} + +void ClosestSegmentPoints(const idVec3& p1, const idVec3& q1, + const idVec3& p2, const idVec3& q2, idVec3& first, idVec3& second) { + const idVec3 d1 = q1 - p1; + const idVec3 d2 = q2 - p2; + const idVec3 r = p1 - p2; + const float a = d1.Dot(d1); + const float e = d2.Dot(d2); + const float f = d2.Dot(r); + float s = 0.0f; + float t = 0.0f; + if (a <= 1.0e-12f && e <= 1.0e-12f) { + first = p1; + second = p2; + return; + } + if (a <= 1.0e-12f) { + t = (std::max)(0.0f, (std::min)(1.0f, f / e)); + } else { + const float c = d1.Dot(r); + if (e <= 1.0e-12f) { + s = (std::max)(0.0f, (std::min)(1.0f, -c / a)); + } else { + const float b = d1.Dot(d2); + const float denominator = a * e - b * b; + if (denominator != 0.0f) { + s = (std::max)(0.0f, (std::min)(1.0f, + (b * f - c * e) / denominator)); + } + t = (b * s + f) / e; + if (t < 0.0f) { + t = 0.0f; + s = (std::max)(0.0f, (std::min)(1.0f, -c / a)); + } else if (t > 1.0f) { + t = 1.0f; + s = (std::max)(0.0f, + (std::min)(1.0f, (b - c) / a)); + } + } + } + first = p1 + d1 * s; + second = p2 + d2 * t; +} + +void SetMaterial(contactInfo_t& contact, const cm_material_t& material) { + contact.contentFlags = material.contentFlags; + contact.surfaceFlags = material.surfaceFlags; + contact.surfaceType = material.surfaceType; + contact.surfaceColor[0] = material.surfaceColor[0]; + contact.surfaceColor[1] = material.surfaceColor[1]; + contact.surfaceColor[2] = material.surfaceColor[2]; +} + +void AppendContact(idTraceWork& tw, const contactType_t type, + const idVec3& point, idVec3 normal, const float separation, + const cm_material_t& material, const int modelFeature, + const int trmFeature) { + if (tw.contactsResult == nullptr || tw.contactsResult->numContacts >= 12) { + return; + } + if (normal.NormalizeFast() == 0.0f) { + normal.Set(0.0f, 0.0f, 1.0f); + } + contactInfo_t& contact = + tw.contactsResult->contacts[tw.contactsResult->numContacts++]; + std::memset(&contact, 0, sizeof(contact)); + contact.type = type; + contact.point = point; + contact.normal = normal; + contact.dist = normal.Dot(point); + contact.separation = separation; + SetMaterial(contact, material); + contact.modelFeature = modelFeature; + contact.trmFeature = trmFeature; +} + +} // namespace + +void idPolygonModelCollisionDetection::TestTrmEdgeInContactWithPolygon( + idTraceWork* const tw, const cm_polygon_t& polygon, + const int trmEdgeNum) { + if (trmEdgeNum < 0 || trmEdgeNum >= static_cast(tw->numEdges)) { + return; + } + const idVec3 trmStart = Vec3(tw->vertexPosition[ + tw->edges[trmEdgeNum].vertexNum[0]]); + const idVec3 trmEnd = Vec3(tw->vertexPosition[ + tw->edges[trmEdgeNum].vertexNum[1]]); + const cm_material_t& material = tw->subModelPtrs.materials[polygon.material]; + for (int edgeNumber = 0; edgeNumber < polygon.numEdges; ++edgeNumber) { + const std::uint16_t reference = tw->subModelPtrs.polygonEdges[ + polygon.firstEdge + edgeNumber]; + const int modelEdgeNum = CM_EdgeIndex(reference); + const cm_edge_t& edge = tw->subModelPtrs.edges[modelEdgeNum]; + const idVec3& modelStart = tw->subModelPtrs.vertices[ + CM_EdgeStartVertex(edge, reference)].p; + const idVec3& modelEnd = tw->subModelPtrs.vertices[ + CM_EdgeEndVertex(edge, reference)].p; + idVec3 trmPoint; + idVec3 modelPoint; + ClosestSegmentPoints(trmStart, trmEnd, modelStart, modelEnd, + trmPoint, modelPoint); + idVec3 delta = trmPoint - modelPoint; + const float distance = delta.Length(); + if (distance > tw->contactDepth) { + continue; + } + if (distance <= 1.0e-6f) { + delta = (trmEnd - trmStart).Cross(modelEnd - modelStart); + } + AppendContact(*tw, CONTACT_EDGE, (trmPoint + modelPoint) * 0.5f, + delta, distance - tw->contactDepth, material, + ((tw->subModelNum << 16) & 0x1FFF0000) + | 0x40000000 | modelEdgeNum, + 0x40000000 | trmEdgeNum); + } +} + +void idPolygonModelCollisionDetection::TestTrmVertexInContactWithPolygon( + idTraceWork* const tw, const cm_polygon_t& polygon, + const idPlane& polygonPlane, const int trmVertNum) { + if (trmVertNum < 0 || trmVertNum >= static_cast(tw->numVerts)) { + return; + } + const idVec3 point = Vec3(tw->vertexPosition[trmVertNum]); + const float distance = polygonPlane.Distance(point); + if (std::fabs(distance) > tw->contactDepth) { + return; + } + const idVec3 projected = point - polygonPlane.Normal() * distance; + if (!PointInsidePolygon(tw->subModelPtrs, polygon, + polygonPlane, projected)) { + return; + } + const cm_material_t& material = tw->subModelPtrs.materials[polygon.material]; + AppendContact(*tw, CONTACT_TRMVERTEX, projected, polygonPlane.Normal(), + std::fabs(distance) - tw->contactDepth, material, + ((tw->subModelNum << 16) & 0x1FFF0000) + | 0x60000000 + | static_cast(&polygon - tw->subModelPtrs.polygons), + trmVertNum); +} + +void idPolygonModelCollisionDetection::TestVertexInContactWithTrmPolygon( + idTraceWork* const tw, const cm_trmPolygon_t& trmPolygon, + const cm_polygon_t& polygon, const cm_vertex_t& vertex) { + const float distance = trmPolygon.plane.Distance(vertex.p); + if (std::fabs(distance) > tw->contactDepth) { + return; + } + const idVec3 projected = vertex.p - trmPolygon.plane.Normal() * distance; + bool positive = false; + bool negative = false; + for (unsigned int edgeNumber = 0; + edgeNumber < trmPolygon.numEdges; ++edgeNumber) { + const int edgeIndex = trmPolygon.edges[edgeNumber] & 0x7F; + const cm_trmEdge_t& edge = tw->edges[edgeIndex]; + const int direction = trmPolygon.edges[edgeNumber] >> 7; + const idVec3 start = Vec3(tw->vertexPosition[edge.vertexNum[direction]]); + const idVec3 end = Vec3(tw->vertexPosition[edge.vertexNum[direction ^ 1]]); + const float side = (end - start).Cross(projected - start).Dot( + trmPolygon.plane.Normal()); + positive |= side > 0.01f; + negative |= side < -0.01f; + } + if (positive && negative) { + return; + } + const cm_material_t& material = tw->subModelPtrs.materials[polygon.material]; + const int vertexNumber = static_cast( + &vertex - tw->subModelPtrs.vertices); + AppendContact(*tw, CONTACT_MODELVERTEX, projected, + -trmPolygon.plane.Normal(), std::fabs(distance) - tw->contactDepth, + material, ((tw->subModelNum << 16) & 0x1FFF0000) + | 0x20000000 | vertexNumber, + 0x60000000 + | static_cast(&trmPolygon - tw->polys)); +} + +bool idPolygonModelCollisionDetection::TestTrmInContactWithPolygon( + idTraceWork* const tw, const int polygonNum) { + if (TestAndSet(tw->modelCheckCounts.polygonCheckCounts, polygonNum)) { + return false; + } + const cm_polygon_t& polygon = tw->subModelPtrs.polygons[polygonNum]; + const cm_material_t& material = tw->subModelPtrs.materials[polygon.material]; + if ((material.contentFlags & tw->contents) == 0 + || !tw->traceBoundsShort.IntersectsBounds(polygon.bounds)) { + return false; + } + const int firstContact = tw->contactsResult != nullptr + ? tw->contactsResult->numContacts : 0; + idPlane plane; + CM_GetPolygonPlane(tw->subModelPtrs, polygon, plane); + for (unsigned int vertex = 0; vertex < tw->numVerts; ++vertex) { + TestTrmVertexInContactWithPolygon(tw, polygon, plane, + static_cast(vertex)); + } + for (unsigned int edge = 0; edge < tw->numEdges; ++edge) { + TestTrmEdgeInContactWithPolygon(tw, polygon, + static_cast(edge)); + } + for (int edgeNumber = 0; edgeNumber < polygon.numEdges; ++edgeNumber) { + const std::uint16_t reference = tw->subModelPtrs.polygonEdges[ + polygon.firstEdge + edgeNumber]; + const cm_edge_t& edge = tw->subModelPtrs.edges[CM_EdgeIndex(reference)]; + const cm_vertex_t& vertex = tw->subModelPtrs.vertices[ + CM_EdgeStartVertex(edge, reference)]; + for (unsigned int trmPolygon = 0; + trmPolygon < tw->numPolys; ++trmPolygon) { + TestVertexInContactWithTrmPolygon(tw, tw->polys[trmPolygon], + polygon, vertex); + } + } + return tw->contactsResult != nullptr + && (tw->contactsResult->numContacts >= 12 + || tw->contactsResult->numContacts > firstContact && tw->quickExit); +} + +void idPolygonModelCollisionDetection::StartContacts(idTraceWork* const tw, + contactsResult_t* const result, const idVec3& start, + const idVec3& direction, const float depth, const idTraceModel& trm, + const idMat3& trmAxis, const int contentMask, + const idVec3& modelOrigin, const idMat3& modelAxis) { + tw->contactDepth = (std::max)(0.0f, depth); + if (direction.LengthSqr() > 1.0e-12f) { + idVec3 normalizedDirection = direction; + normalizedDirection.NormalizeFast(); + StartTranslation(tw, &tw->tempTraceResult, result, start, + start + normalizedDirection * depth, &trm, trmAxis, + contentMask, modelOrigin, modelAxis); + tw->traceType = TRACE_CONTACTS_UNI_DIR; + tw->contactDepth = depth; + return; + } + StartContents(tw, &tw->tempTraceResult, start, &trm, trmAxis, + contentMask, modelOrigin, modelAxis); + tw->traceType = TRACE_CONTACTS_OMNI_DIR; + tw->traceResult = &tw->tempTraceResult; + tw->contactsResult = result; + tw->contactDepth = depth; + for (int axis = 0; axis < 3; ++axis) { + tw->traceBoundsMin[axis] -= depth; + tw->traceBoundsMax[axis] += depth; + } + idBounds bounds; + bounds[0].Set(tw->traceBoundsMin.x, tw->traceBoundsMin.y, + tw->traceBoundsMin.z); + bounds[1].Set(tw->traceBoundsMax.x, tw->traceBoundsMax.y, + tw->traceBoundsMax.z); + tw->traceBoundsShort.SetBounds(bounds); +} + +void idPolygonModelCollisionDetection::FinishContacts(idTraceWork* const tw, + const int firstContact, const idVec3& modelOrigin, + const idMat3& modelAxis, const int modelEntityNum, + const int modelPhysicsId, const int modelBodyId, const int selfId, + const int modelContentsOverride) { + if (tw->contactsResult == nullptr) { + return; + } + contactsResult_t& result = *tw->contactsResult; + for (int index = (std::max)(0, firstContact); + index < result.numContacts && index < 12; ++index) { + contactInfo_t& contact = result.contacts[index]; + contact.normal = ModelToWorldVector(modelAxis, contact.normal); + contact.point = ModelToWorldVector(modelAxis, contact.point) + + modelOrigin; + contact.dist += modelOrigin.Dot(contact.normal); + contact.entityNum = modelEntityNum; + contact.physicsId = modelPhysicsId; + contact.bodyId = modelBodyId; + contact.selfId = selfId; + if (modelContentsOverride != 0 && contact.contentFlags != 0) { + contact.contentFlags = modelContentsOverride; + } + } +} diff --git a/source/engine/cm/jobs/polygonmodel/polygonmodel_contents.cpp b/source/engine/cm/jobs/polygonmodel/polygonmodel_contents.cpp new file mode 100644 index 0000000..12cfe6f --- /dev/null +++ b/source/engine/cm/jobs/polygonmodel/polygonmodel_contents.cpp @@ -0,0 +1,447 @@ +#include "cm/jobs/polygonmodel/polygonmodel.h" + +#include "cm/jobs/polygonmodel/polygonmodeldata.h" +#include "idlib/geometry/tracemodel.h" + +#include +#include +#include +#include + +namespace { + +constexpr float CM_BOUNDS_EPSILON = 1.0f; +constexpr float CM_PLANE_EPSILON = 0.0001f; + +idVec3 Vec3(const idVec4& value) { + return idVec3(value.x, value.y, value.z); +} + +void SetVec4(idVec4& target, const idVec3& value, const float w = 0.0f) { + target.Set(value.x, value.y, value.z, w); +} + +idVec3 ModelToWorldVector(const idMat3& axis, const idVec3& value) { + return idVec3( + axis[0].x * value.x + axis[1].x * value.y + axis[2].x * value.z, + axis[0].y * value.x + axis[1].y * value.y + axis[2].y * value.z, + axis[0].z * value.x + axis[1].z * value.y + axis[2].z * value.z); +} + +idVec3 WorldToModelVector(const idMat3& axis, const idVec3& value) { + return idVec3(axis[0].Dot(value), axis[1].Dot(value), + axis[2].Dot(value)); +} + +idVec3 TraceModelToWorldVector(const idMat3& axis, const idVec3& value) { + return ModelToWorldVector(axis, value); +} + +bool IsIdentity(const idMat3& axis) { + return axis[0].x == 1.0f && axis[1].y == 1.0f + && axis[2].z == 1.0f + && axis[0].y == 0.0f && axis[0].z == 0.0f + && axis[1].x == 0.0f && axis[1].z == 0.0f + && axis[2].x == 0.0f && axis[2].y == 0.0f; +} + +bool BoundsIntersect(const idBoundsShort& lhs, const idBoundsShort& rhs) { + return lhs.IntersectsBounds(rhs); +} + +bool TestAndSet(std::uint8_t* bits, const int index) { + if (bits == nullptr || index < 0) { + return false; + } + const std::uint8_t mask = static_cast(1u << (index & 7)); + std::uint8_t& value = bits[index >> 3]; + const bool wasSet = (value & mask) != 0; + value = static_cast(value | mask); + return wasSet; +} + +void SetMaterial(contactInfo_t& contact, const cm_material_t& material) { + contact.contentFlags = material.contentFlags; + contact.surfaceFlags = material.surfaceFlags; + contact.surfaceType = material.surfaceType; + contact.surfaceColor[0] = material.surfaceColor[0]; + contact.surfaceColor[1] = material.surfaceColor[1]; + contact.surfaceColor[2] = material.surfaceColor[2]; +} + +bool PointInsidePolygon(const cm_subModelPtrs_t& model, + const cm_polygon_t& polygon, const idPlane& plane, const idVec3& point) { + bool hasPositive = false; + bool hasNegative = false; + const idVec3 normal = plane.Normal(); + for (int edgeNumber = 0; edgeNumber < polygon.numEdges; ++edgeNumber) { + const std::uint16_t edgeReference = + model.polygonEdges[polygon.firstEdge + edgeNumber]; + const cm_edge_t& edge = model.edges[CM_EdgeIndex(edgeReference)]; + const idVec3& start = model.vertices[ + CM_EdgeStartVertex(edge, edgeReference)].p; + const idVec3& end = model.vertices[ + CM_EdgeEndVertex(edge, edgeReference)].p; + const float side = (end - start).Cross(point - start).Dot(normal); + hasPositive |= side > CM_PLANE_EPSILON; + hasNegative |= side < -CM_PLANE_EPSILON; + if (hasPositive && hasNegative) { + return false; + } + } + return true; +} + +bool PointInsideTraceModel(const idTraceWork& tw, const idVec3& point, + int& nearestPolygon) { + float nearestDistance = -std::numeric_limits::max(); + nearestPolygon = 0; + for (unsigned int polygonNumber = 0; + polygonNumber < tw.numPolys; ++polygonNumber) { + const float distance = tw.polys[polygonNumber].plane.Distance(point); + if (distance >= 0.0f) { + return false; + } + if (distance > nearestDistance) { + nearestDistance = distance; + nearestPolygon = static_cast(polygonNumber); + } + } + return tw.numPolys != 0; +} + +void MakeTraceBounds(idTraceWork& tw, const idVec3& minimum, + const idVec3& maximum, const bool padBounds) { + const float padding = padBounds ? CM_BOUNDS_EPSILON : 0.0f; + tw.traceBoundsMin.Set(minimum.x - padding, minimum.y - padding, + minimum.z - padding, 0.0f); + tw.traceBoundsMax.Set(maximum.x + padding, maximum.y + padding, + maximum.z + padding, 0.0f); + idBounds quantized; + quantized[0].Set(std::floor(tw.traceBoundsMin.x) - 1.0f, + std::floor(tw.traceBoundsMin.y) - 1.0f, + std::floor(tw.traceBoundsMin.z) - 1.0f); + quantized[1].Set(std::ceil(tw.traceBoundsMax.x) + 1.0f, + std::ceil(tw.traceBoundsMax.y) + 1.0f, + std::ceil(tw.traceBoundsMax.z) + 1.0f); + tw.traceBoundsShort.SetBounds(quantized); +} + +void SetPolygonContact(idTraceWork& tw, const cm_polygon_t& polygon, + const int polygonNum, const int trmVertex, const idVec3& point, + const idPlane& plane) { + trace_t& trace = *tw.traceResult; + trace.fraction = 0.0f; + trace.c.type = CONTACT_TRMVERTEX; + trace.c.point = point; + trace.c.normal = plane.Normal(); + trace.c.dist = plane.Dist(); + trace.c.separation = 0.0f; + SetMaterial(trace.c, tw.subModelPtrs.materials[polygon.material]); + trace.c.modelFeature = ((tw.subModelNum << 16) & 0x1FFF0000) + | 0x60000000 | polygonNum; + trace.c.trmFeature = trmVertex; + trace.c.flags = 0; +} + +} // namespace + +bool idPolygonModelCollisionDetection::TestTrmVertsInPolytope( + idTraceWork* const tw, const int polytopeNum) { + if (TestAndSet(tw->modelCheckCounts.polytopeCheckCounts, polytopeNum)) { + return false; + } + + const cm_polytope_t& polytope = tw->subModelPtrs.polytopes[polytopeNum]; + const cm_material_t& material = + tw->subModelPtrs.materials[polytope.material]; + if ((material.contentFlags & tw->contents) == 0 + || !BoundsIntersect(tw->traceBoundsShort, polytope.bounds)) { + return false; + } + + for (unsigned int vertexNumber = 0; + vertexNumber < tw->numVerts; ++vertexNumber) { + const idVec3 vertex = Vec3(tw->vertexPosition[vertexNumber]); + float nearestDistance = -std::numeric_limits::max(); + int nearestPlane = 0; + bool inside = true; + for (int planeNumber = 0; + planeNumber < polytope.numPlanes; ++planeNumber) { + const idPlane& plane = tw->subModelPtrs.polytopePlanes[ + polytope.firstPlane + planeNumber]; + const float distance = plane.Distance(vertex); + if (distance >= 0.0f) { + inside = false; + break; + } + if (distance > nearestDistance) { + nearestDistance = distance; + nearestPlane = planeNumber; + } + } + if (!inside) { + continue; + } + + trace_t& trace = *tw->traceResult; + const idPlane& plane = tw->subModelPtrs.polytopePlanes[ + polytope.firstPlane + nearestPlane]; + trace.fraction = 0.0f; + trace.c.type = CONTACT_TRMVERTEX; + trace.c.normal = plane.Normal(); + trace.c.dist = plane.Dist(); + trace.c.separation = 0.0f; + trace.c.point = vertex; + SetMaterial(trace.c, material); + trace.c.modelFeature = 0x80000000 + | ((tw->subModelNum << 16) & 0x1FFF0000) | polytopeNum; + trace.c.trmFeature = static_cast(vertexNumber); + trace.c.flags = 0; + return true; + } + return false; +} + +bool idPolygonModelCollisionDetection::TestTrmInPolygon( + idTraceWork* const tw, const int polygonNum) { + if (TestAndSet(tw->modelCheckCounts.polygonCheckCounts, polygonNum)) { + return false; + } + + const cm_polygon_t& polygon = tw->subModelPtrs.polygons[polygonNum]; + const cm_material_t& material = + tw->subModelPtrs.materials[polygon.material]; + if ((material.contentFlags & tw->contents) == 0 + || !BoundsIntersect(tw->traceBoundsShort, polygon.bounds)) { + return false; + } + + idPlane polygonPlane; + CM_GetPolygonPlane(tw->subModelPtrs, polygon, polygonPlane); + bool hasFront = false; + bool hasBack = false; + for (unsigned int vertexNumber = 0; + vertexNumber < tw->numVerts; ++vertexNumber) { + const float distance = polygonPlane.Distance( + Vec3(tw->vertexPosition[vertexNumber])); + hasFront |= distance > CM_PLANE_EPSILON; + hasBack |= distance < -CM_PLANE_EPSILON; + } + if (!hasBack) { + if (tw->subModelPtrs.isConvex != 0) { + // The recovered convex early-out terminates the whole submodel as + // soon as one separating polygon plane is found. + tw->quickExit = true; + return true; + } + return false; + } + + // The authoritative Pluecker tests find the same geometric event: a trace + // edge crosses the model polygon. The PC scalar path spells it directly. + for (unsigned int edgeNumber = 0; edgeNumber < tw->numEdges; ++edgeNumber) { + const int first = tw->edges[edgeNumber].vertexNum[0]; + const int second = tw->edges[edgeNumber].vertexNum[1]; + const idVec3 firstPoint = Vec3(tw->vertexPosition[first]); + const idVec3 secondPoint = Vec3(tw->vertexPosition[second]); + const float firstDistance = polygonPlane.Distance(firstPoint); + const float secondDistance = polygonPlane.Distance(secondPoint); + if ((firstDistance < 0.0f) == (secondDistance < 0.0f)) { + continue; + } + const float denominator = firstDistance - secondDistance; + if (std::fabs(denominator) <= CM_PLANE_EPSILON) { + continue; + } + const float fraction = firstDistance / denominator; + const idVec3 point = firstPoint + (secondPoint - firstPoint) * fraction; + if (PointInsidePolygon(tw->subModelPtrs, polygon, + polygonPlane, point)) { + const int featureVertex = firstDistance < 0.0f ? first : second; + SetPolygonContact(*tw, polygon, polygonNum, featureVertex, + Vec3(tw->vertexPosition[featureVertex]), polygonPlane); + return true; + } + } + + // A model vertex can be embedded in the trace model without an edge-plane + // crossing (the second recovered contents case). + for (int edgeNumber = 0; edgeNumber < polygon.numEdges; ++edgeNumber) { + const std::uint16_t edgeReference = + tw->subModelPtrs.polygonEdges[polygon.firstEdge + edgeNumber]; + const cm_edge_t& edge = + tw->subModelPtrs.edges[CM_EdgeIndex(edgeReference)]; + const int vertexNumber = CM_EdgeStartVertex(edge, edgeReference); + if (TestAndSet(tw->modelCheckCounts.vertexCheckCounts, vertexNumber)) { + continue; + } + const idVec3& point = tw->subModelPtrs.vertices[vertexNumber].p; + int tracePolygon = 0; + if (!PointInsideTraceModel(*tw, point, tracePolygon)) { + continue; + } + + trace_t& trace = *tw->traceResult; + const idPlane& plane = tw->polys[tracePolygon].plane; + trace.fraction = 0.0f; + trace.c.type = CONTACT_MODELVERTEX; + trace.c.point = point; + trace.c.normal = -plane.Normal(); + trace.c.dist = plane.d; + trace.c.separation = 0.0f; + SetMaterial(trace.c, material); + trace.c.modelFeature = ((tw->subModelNum << 16) & 0x1FFF0000) + | 0x20000000 | vertexNumber; + trace.c.trmFeature = tracePolygon; + trace.c.flags = 0; + return true; + } + return false; +} + +void idPolygonModelCollisionDetection::StartContents(idTraceWork* const tw, + trace_t* const result, const idVec3& start, const idTraceModel* const trm, + const idMat3& trmAxis, const int contentMask, + const idVec3& modelOrigin, const idMat3& modelAxis) { + std::memset(result, 0, sizeof(*result)); + result->fraction = 1.0f; + result->endpos = start; + result->endAxis = trmAxis; + tw->traceResult = result; + tw->contactsResult = nullptr; + tw->clipResult = nullptr; + tw->fraction = 1.0f; + tw->contents = contentMask; + tw->isConvex = trm->isConvex; + tw->traceType = TRACE_CONTENTS; + tw->quickExit = false; + + const idVec3 localStart = WorldToModelVector(modelAxis, + start + TraceModelToWorldVector(trmAxis, trm->offset) - modelOrigin); + SetVec4(tw->start, localStart); + tw->end = tw->start; + + idVec3 minimum(std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max()); + idVec3 maximum(-std::numeric_limits::max(), + -std::numeric_limits::max(), + -std::numeric_limits::max()); + tw->numVerts = (std::min)(trm->numVerts, 32u); + for (unsigned int vertexNumber = 0; + vertexNumber < tw->numVerts; ++vertexNumber) { + const idVec3 source(trm->vertsX[vertexNumber], + trm->vertsY[vertexNumber], trm->vertsZ[vertexNumber]); + const idVec3 world = start + TraceModelToWorldVector(trmAxis, source); + const idVec3 local = WorldToModelVector(modelAxis, + world - modelOrigin); + SetVec4(tw->vertexPosition[vertexNumber], local); + minimum.x = (std::min)(minimum.x, local.x); + minimum.y = (std::min)(minimum.y, local.y); + minimum.z = (std::min)(minimum.z, local.z); + maximum.x = (std::max)(maximum.x, local.x); + maximum.y = (std::max)(maximum.y, local.y); + maximum.z = (std::max)(maximum.z, local.z); + } + SetVec4(tw->trmBoundsMin, minimum - localStart); + SetVec4(tw->trmBoundsMax, maximum - localStart); + tw->trmExtents.Set( + (std::max)(std::fabs(tw->trmBoundsMin.x), + std::fabs(tw->trmBoundsMax.x)), + (std::max)(std::fabs(tw->trmBoundsMin.y), + std::fabs(tw->trmBoundsMax.y)), + (std::max)(std::fabs(tw->trmBoundsMin.z), + std::fabs(tw->trmBoundsMax.z)), 0.0f); + + tw->numEdges = (std::min)(trm->numEdges, 32u); + for (unsigned int edgeNumber = 0; + edgeNumber < tw->numEdges; ++edgeNumber) { + tw->edges[edgeNumber].vertexNum[0] = trm->edges[edgeNumber].v[0]; + tw->edges[edgeNumber].vertexNum[1] = trm->edges[edgeNumber].v[1]; + const idVec3 first = Vec3(tw->vertexPosition[ + tw->edges[edgeNumber].vertexNum[0]]); + const idVec3 second = Vec3(tw->vertexPosition[ + tw->edges[edgeNumber].vertexNum[1]]); + tw->edgePluecker[edgeNumber].FromLine(first, second); + } + + tw->numPolys = (std::min)(trm->numPolys, 16u); + for (unsigned int polygonNumber = 0; + polygonNumber < tw->numPolys; ++polygonNumber) { + cm_trmPolygon_t& polygon = tw->polys[polygonNumber]; + polygon.numEdges = (std::min)(trm->numPolyEdges[polygonNumber], 16u); + std::memcpy(polygon.edges, trm->polyEdges[polygonNumber], + polygon.numEdges); + const idVec3 sourceNormal(trm->polyPlaneX[polygonNumber], + trm->polyPlaneY[polygonNumber], trm->polyPlaneZ[polygonNumber]); + idVec3 localNormal = WorldToModelVector(modelAxis, + TraceModelToWorldVector(trmAxis, sourceNormal)); + localNormal.NormalizeFast(); + polygon.plane.Normal() = localNormal; + if (polygon.numEdges != 0) { + const int edgeIndex = polygon.edges[0] & 0x7F; + const int vertexIndex = tw->edges[edgeIndex].vertexNum[ + polygon.edges[0] >> 7]; + polygon.plane.d = -localNormal.Dot( + Vec3(tw->vertexPosition[vertexIndex])); + } else { + polygon.plane.d = 0.0f; + } + } + MakeTraceBounds(*tw, minimum, maximum, true); +} + +void idPolygonModelCollisionDetection::StartContentsPoint( + idTraceWork* const tw, trace_t* const result, const idVec3& start, + const int contentMask, const idVec3& modelOrigin, + const idMat3& modelAxis) { + std::memset(result, 0, sizeof(*result)); + result->fraction = 1.0f; + result->endpos = start; + result->endAxis = idMat3(1.0f); + tw->traceResult = result; + tw->contactsResult = nullptr; + tw->clipResult = nullptr; + tw->fraction = 1.0f; + tw->contents = contentMask; + tw->isConvex = true; + tw->traceType = TRACE_CONTENTS_POINT; + tw->quickExit = false; + + const idVec3 local = WorldToModelVector(modelAxis, start - modelOrigin); + SetVec4(tw->start, local); + tw->end = tw->start; + tw->numVerts = 1; + tw->numEdges = 0; + tw->numPolys = 0; + tw->vertexPosition[0] = tw->start; + tw->trmBoundsMin.Set(0.0f, 0.0f, 0.0f, 0.0f); + tw->trmBoundsMax.Set(0.0f, 0.0f, 0.0f, 0.0f); + tw->trmExtents.Set(0.0f, 0.0f, 0.0f, 0.0f); + MakeTraceBounds(*tw, local, local, false); +} + +void idPolygonModelCollisionDetection::FinishContents( + idTraceWork* const tw, const idVec3& modelOrigin, + const idMat3& modelAxis, const int modelEntityNum, + const int modelPhysicsId, const int modelBodyId, const int selfId, + const int modelContentsOverride) { + if (tw->traceResult == nullptr || tw->traceResult->fraction >= 1.0f) { + return; + } + contactInfo_t& contact = tw->traceResult->c; + if (!IsIdentity(modelAxis)) { + contact.normal = ModelToWorldVector(modelAxis, contact.normal); + contact.point = ModelToWorldVector(modelAxis, contact.point); + } + contact.point = contact.point + modelOrigin; + contact.dist += modelOrigin.Dot(contact.normal); + contact.entityNum = modelEntityNum; + contact.physicsId = modelPhysicsId; + contact.bodyId = modelBodyId; + contact.selfId = selfId; + if (modelContentsOverride != 0 && contact.contentFlags != 0) { + contact.contentFlags = modelContentsOverride; + } +} diff --git a/source/engine/cm/jobs/polygonmodel/polygonmodel_inline.h b/source/engine/cm/jobs/polygonmodel/polygonmodel_inline.h new file mode 100644 index 0000000..d84a542 --- /dev/null +++ b/source/engine/cm/jobs/polygonmodel/polygonmodel_inline.h @@ -0,0 +1,39 @@ +#pragma once + +#include "cm/collisiontypes.h" + +#include + +inline int CM_BoundsPlaneSide(const idVec4& boundsMin, + const idVec4& boundsMax, const idPlane& plane) { + const idVec3 center( + (boundsMin.x + boundsMax.x) * 0.5f, + (boundsMin.y + boundsMax.y) * 0.5f, + (boundsMin.z + boundsMax.z) * 0.5f); + const idVec3 extents( + boundsMax.x - center.x, + boundsMax.y - center.y, + boundsMax.z - center.z); + const float radius = std::fabs(extents.x * plane.a) + + std::fabs(extents.y * plane.b) + + std::fabs(extents.z * plane.c); + const float distance = plane.Distance(center); + if (distance - radius > 0.1f) { + return 0; + } + if (distance + radius < -0.1f) { + return 1; + } + return 3; +} + +inline bool CM_BoundsShortPlaneCull(const idBoundsShort& bounds, + const idPlane& plane, const float compareDistance) { + const idBounds expanded = bounds.ToBounds(); + const idVec3 center = (expanded[0] + expanded[1]) * 0.5f; + const idVec3 extents = expanded[1] - center; + const float radius = std::fabs(extents.x * plane.a) + + std::fabs(extents.y * plane.b) + + std::fabs(extents.z * plane.c); + return std::fabs(plane.Distance(center)) > radius + compareDistance; +} diff --git a/source/engine/cm/jobs/polygonmodel/polygonmodel_rotate.cpp b/source/engine/cm/jobs/polygonmodel/polygonmodel_rotate.cpp new file mode 100644 index 0000000..da27d31 --- /dev/null +++ b/source/engine/cm/jobs/polygonmodel/polygonmodel_rotate.cpp @@ -0,0 +1,773 @@ +#include "cm/jobs/polygonmodel/polygonmodel.h" + +#include "cm/jobs/polygonmodel/polygonmodeldata.h" +#include "idlib/geometry/tracemodel.h" + +#include +#include +#include +#include + +namespace { + +constexpr float DEG2RAD = 0.01745329251994329577f; +constexpr float RAD2DEG = 57.295779513082320876f; +constexpr float ROTATION_EPSILON = 0.01f; + +idVec3 Vec3(const idVec4& value) { + return idVec3(value.x, value.y, value.z); +} + +void SetVec4(idVec4& target, const idVec3& value, const float w = 0.0f) { + target.Set(value.x, value.y, value.z, w); +} + +idVec3 ModelToWorldVector(const idMat3& axis, const idVec3& value) { + return idVec3( + axis[0].x * value.x + axis[1].x * value.y + axis[2].x * value.z, + axis[0].y * value.x + axis[1].y * value.y + axis[2].y * value.z, + axis[0].z * value.x + axis[1].z * value.y + axis[2].z * value.z); +} + +idVec3 WorldToModelVector(const idMat3& axis, const idVec3& value) { + return idVec3(axis[0].Dot(value), axis[1].Dot(value), + axis[2].Dot(value)); +} + +idVec3 RotateAroundAxis(const idVec3& point, const idVec3& origin, + idVec3 axis, const float angleDegrees) { + if (axis.NormalizeFast() == 0.0f || angleDegrees == 0.0f) { + return point; + } + const float angle = angleDegrees * DEG2RAD; + const float sine = std::sin(angle); + const float cosine = std::cos(angle); + const idVec3 relative = point - origin; + return origin + relative * cosine + axis.Cross(relative) * sine + + axis * (axis.Dot(relative) * (1.0f - cosine)); +} + +idVec3 RotateVector(const idVec3& value, idVec3 axis, + const float angleDegrees) { + return RotateAroundAxis(value, idVec3(0.0f, 0.0f, 0.0f), + axis, angleDegrees); +} + +bool TestAndSet(std::uint8_t* bits, const int index) { + if (bits == nullptr) { + return false; + } + const std::uint8_t mask = static_cast(1u << (index & 7)); + std::uint8_t& value = bits[index >> 3]; + const bool old = (value & mask) != 0; + value = static_cast(value | mask); + return old; +} + +void SetMaterial(contactInfo_t& contact, const cm_material_t& material) { + contact.contentFlags = material.contentFlags; + contact.surfaceFlags = material.surfaceFlags; + contact.surfaceType = material.surfaceType; + contact.surfaceColor[0] = material.surfaceColor[0]; + contact.surfaceColor[1] = material.surfaceColor[1]; + contact.surfaceColor[2] = material.surfaceColor[2]; +} + +bool PointInsidePolygon(const cm_subModelPtrs_t& model, + const cm_polygon_t& polygon, const idPlane& plane, const idVec3& point) { + bool positive = false; + bool negative = false; + for (int index = 0; index < polygon.numEdges; ++index) { + const std::uint16_t reference = model.polygonEdges[ + polygon.firstEdge + index]; + const cm_edge_t& edge = model.edges[CM_EdgeIndex(reference)]; + const idVec3& start = model.vertices[ + CM_EdgeStartVertex(edge, reference)].p; + const idVec3& end = model.vertices[ + CM_EdgeEndVertex(edge, reference)].p; + const float side = (end - start).Cross(point - start).Dot( + plane.Normal()); + positive |= side > ROTATION_EPSILON; + negative |= side < -ROTATION_EPSILON; + if (positive && negative) { + return false; + } + } + return true; +} + +void ClosestSegmentPoints(const idVec3& p1, const idVec3& q1, + const idVec3& p2, const idVec3& q2, idVec3& first, idVec3& second) { + const idVec3 d1 = q1 - p1; + const idVec3 d2 = q2 - p2; + const idVec3 r = p1 - p2; + const float a = d1.Dot(d1); + const float e = d2.Dot(d2); + const float f = d2.Dot(r); + float s = 0.0f; + float t = 0.0f; + if (a <= 1.0e-12f && e <= 1.0e-12f) { + first = p1; + second = p2; + return; + } + if (a <= 1.0e-12f) { + t = (std::max)(0.0f, (std::min)(1.0f, f / e)); + } else { + const float c = d1.Dot(r); + if (e <= 1.0e-12f) { + s = (std::max)(0.0f, (std::min)(1.0f, -c / a)); + } else { + const float b = d1.Dot(d2); + const float denominator = a * e - b * b; + if (std::fabs(denominator) > 1.0e-12f) { + s = (std::max)(0.0f, (std::min)(1.0f, + (b * f - c * e) / denominator)); + } + t = (b * s + f) / e; + if (t < 0.0f) { + t = 0.0f; + s = (std::max)(0.0f, (std::min)(1.0f, -c / a)); + } else if (t > 1.0f) { + t = 1.0f; + s = (std::max)(0.0f, + (std::min)(1.0f, (b - c) / a)); + } + } + } + first = p1 + d1 * s; + second = p2 + d2 * t; +} + +void StoreRotationCollision(idTraceWork& tw, const float fraction, + const contactType_t type, const idVec3& point, idVec3 normal, + const float distance, const cm_material_t& material, + const int modelFeature, const int trmFeature) { + if (tw.traceResult == nullptr || fraction >= tw.fraction) { + return; + } + if (normal.NormalizeFast() == 0.0f) { + return; + } + tw.fraction = (std::max)(0.0f, fraction); + trace_t& trace = *tw.traceResult; + trace.fraction = tw.fraction; + trace.c.type = type; + trace.c.point = point; + trace.c.normal = normal; + trace.c.dist = distance; + trace.c.separation = 0.0f; + SetMaterial(trace.c, material); + trace.c.modelFeature = modelFeature; + trace.c.trmFeature = trmFeature; + trace.c.flags = 0; +} + +bool PointInsideInitialTrace(const idTraceWork& tw, const idVec3& point, + int& nearestPlane) { + float nearestDistance = -std::numeric_limits::max(); + nearestPlane = 0; + for (unsigned int index = 0; index < tw.numPolys; ++index) { + const float distance = tw.polys[index].plane.Distance(point); + if (distance > 0.0f) { + return false; + } + if (distance > nearestDistance) { + nearestDistance = distance; + nearestPlane = static_cast(index); + } + } + return tw.numPolys != 0; +} + +} // namespace + +float CM_TanZeroHalfPI(const float angle) { + const float clamped = (std::max)(0.0f, + (std::min)(1.57079632679f, angle)); + return std::tan(clamped); +} + +float CM_ArcTanPositive(const float value) { + return std::atan((std::max)(0.0f, value)); +} + +void CM_PointRotationBounds(const idVec3& origin, const idVec3& axis, + const idVec3& start, const idVec3& end, idVec4& boundsMin, + idVec4& boundsMax) { + idVec3 minimum((std::min)(start.x, end.x), + (std::min)(start.y, end.y), (std::min)(start.z, end.z)); + idVec3 maximum((std::max)(start.x, end.x), + (std::max)(start.y, end.y), (std::max)(start.z, end.z)); + // Include quarter turns around the recovered axis; this captures extrema + // missed by an endpoint-only arc bound. + for (int step = 1; step < 4; ++step) { + const idVec3 point = RotateAroundAxis(start, origin, axis, + static_cast(step * 90)); + minimum.x = (std::min)(minimum.x, point.x); + minimum.y = (std::min)(minimum.y, point.y); + minimum.z = (std::min)(minimum.z, point.z); + maximum.x = (std::max)(maximum.x, point.x); + maximum.y = (std::max)(maximum.y, point.y); + maximum.z = (std::max)(maximum.z, point.z); + } + boundsMin.Set(minimum.x, minimum.y, minimum.z, 0.0f); + boundsMax.Set(maximum.x, maximum.y, maximum.z, 0.0f); +} + +bool idPolygonModelCollisionDetection::EdgeIntersectsBoundsShort( + const idBoundsShort& bounds, const idVec3& start, const idVec3& end) { + return bounds.ToBounds().LineIntersection(start, end); +} + +void idPolygonModelCollisionDetection::RotationSetup(idTraceWork* const tw, + const idVec3& rotationOrigin, const idVec3& rotationAxis, + const float angle, const idVec3& start, const idVec3& offset, + const idMat3& trmAxis, const idVec3& modelOrigin, + const idMat3& modelAxis) { + const idVec3 centerWorld = start + ModelToWorldVector(trmAxis, offset); + const idVec3 localCenter = WorldToModelVector(modelAxis, + centerWorld - modelOrigin); + const idVec3 localOrigin = WorldToModelVector(modelAxis, + rotationOrigin - modelOrigin); + idVec3 localAxis = WorldToModelVector(modelAxis, rotationAxis); + localAxis.NormalizeFast(); + SetVec4(tw->start, localCenter); + SetVec4(tw->origin, localOrigin); + SetVec4(tw->axis, localAxis); + tw->angle = angle; + tw->negAngle = -angle; + tw->initialTan = 0.0f; + tw->maxTan = std::tan(std::fabs(angle) * DEG2RAD * 0.5f); + const idVec3 endCenter = RotateAroundAxis(localCenter, + localOrigin, localAxis, angle); + SetVec4(tw->end, endCenter); + SetVec4(tw->dir, endCenter - localCenter); + SetVec4(tw->negDir, localCenter - endCenter); +} + +void idPolygonModelCollisionDetection::TransformFromOriginAxisAngle( + idMat3x4& transform, const idVec3& origin, const idVec3& axis, + const float angle) { + const idVec3 x = RotateVector(idVec3(1.0f, 0.0f, 0.0f), axis, angle); + const idVec3 y = RotateVector(idVec3(0.0f, 1.0f, 0.0f), axis, angle); + const idVec3 z = RotateVector(idVec3(0.0f, 0.0f, 1.0f), axis, angle); + transform.mat[0] = x.x; transform.mat[1] = y.x; transform.mat[2] = z.x; + transform.mat[4] = x.y; transform.mat[5] = y.y; transform.mat[6] = z.y; + transform.mat[8] = x.z; transform.mat[9] = y.z; transform.mat[10] = z.z; + const idVec3 translated = origin - RotateVector(origin, axis, angle); + transform.mat[3] = translated.x; + transform.mat[7] = translated.y; + transform.mat[11] = translated.z; +} + +void idPolygonModelCollisionDetection::TransformAxisToZAxis( + idMat3x4& transform, const idVec3& origin, const idVec3& axis) { + idVec3 z = axis; + if (z.NormalizeFast() == 0.0f) { + transform.Identity(); + return; + } + idVec3 reference = std::fabs(z.z) < 0.9f + ? idVec3(0.0f, 0.0f, 1.0f) : idVec3(0.0f, 1.0f, 0.0f); + idVec3 x = reference.Cross(z); + x.NormalizeFast(); + idVec3 y = z.Cross(x); + y.NormalizeFast(); + transform.mat[0] = x.x; transform.mat[1] = x.y; transform.mat[2] = x.z; + transform.mat[4] = y.x; transform.mat[5] = y.y; transform.mat[6] = y.z; + transform.mat[8] = z.x; transform.mat[9] = z.y; transform.mat[10] = z.z; + transform.mat[3] = -x.Dot(origin); + transform.mat[7] = -y.Dot(origin); + transform.mat[11] = -z.Dot(origin); +} + +void idPolygonModelCollisionDetection::RotationVerts(idTraceWork* const tw, + const idTraceModel&, idVec4* const zverts) { + for (unsigned int index = 0; index < tw->numVerts; ++index) { + const idVec3 start = Vec3(tw->vertexPosition[index]); + const idVec3 end = RotateAroundAxis(start, Vec3(tw->origin), + Vec3(tw->axis), tw->angle); + SetVec4(tw->vertexEndPosition[index], end); + if (zverts != nullptr) { + idVec3 transformed; + tw->ZAxisTransform.Transform(transformed, start); + SetVec4(zverts[index], transformed); + } + } +} + +void idPolygonModelCollisionDetection::RotationEdges(idTraceWork* const tw, + const idTraceModel&, const idVec4*) { + for (unsigned int index = 0; index < tw->numEdges; ++index) { + const idVec3 start = Vec3(tw->vertexPosition[ + tw->edges[index].vertexNum[0]]); + const idVec3 end = Vec3(tw->vertexPosition[ + tw->edges[index].vertexNum[1]]); + tw->edgePluecker[index].FromLine(start, end); + tw->edgeZAxisPluecker[index] = tw->edgePluecker[index]; + } +} + +void idPolygonModelCollisionDetection::RotationPolys(idTraceWork* const tw, + const idTraceModel&) { + for (unsigned int index = 0; index < tw->numPolys; ++index) { + tw->polyIsUsed[index] = tw->polys[index].numEdges != 0; + } +} + +void idPolygonModelCollisionDetection::RotationBounds(idTraceWork* const tw) { + idVec3 minimum(std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max()); + idVec3 maximum(-std::numeric_limits::max(), + -std::numeric_limits::max(), + -std::numeric_limits::max()); + const int steps = (std::max)(1, + static_cast(std::ceil(std::fabs(tw->angle) / 10.0f))); + for (unsigned int vertex = 0; vertex < tw->numVerts; ++vertex) { + const idVec3 original = Vec3(tw->vertexPosition[vertex]); + for (int step = 0; step <= steps; ++step) { + const idVec3 point = RotateAroundAxis(original, Vec3(tw->origin), + Vec3(tw->axis), tw->angle * step / steps); + minimum.x = (std::min)(minimum.x, point.x); + minimum.y = (std::min)(minimum.y, point.y); + minimum.z = (std::min)(minimum.z, point.z); + maximum.x = (std::max)(maximum.x, point.x); + maximum.y = (std::max)(maximum.y, point.y); + maximum.z = (std::max)(maximum.z, point.z); + } + } + tw->traceBoundsMin.Set(minimum.x - 1.0f, minimum.y - 1.0f, + minimum.z - 1.0f, 0.0f); + tw->traceBoundsMax.Set(maximum.x + 1.0f, maximum.y + 1.0f, + maximum.z + 1.0f, 0.0f); + idBounds bounds; + bounds[0].Set(std::floor(minimum.x) - 1.0f, + std::floor(minimum.y) - 1.0f, std::floor(minimum.z) - 1.0f); + bounds[1].Set(std::ceil(maximum.x) + 1.0f, + std::ceil(maximum.y) + 1.0f, std::ceil(maximum.z) + 1.0f); + tw->traceBoundsShort.SetBounds(bounds); +} + +void idPolygonModelCollisionDetection::RotationEdgePlueckerCache( + idTraceWork* const tw, const cm_polygon_t& polygon) { + TranslationPlueckerCache(tw, polygon); +} + +void idPolygonModelCollisionDetection::RotationCullPolygonEdges( + idTraceWork* const tw, const cm_polygon_t& polygon) { + for (int index = 0; index < polygon.numEdges && index < 20; ++index) { + tw->polygonEdgeSideCache[index].side = 0; + const idPluecker& modelEdge = tw->polygonEdgePlueckerCache[index]; + for (unsigned int trmEdge = 0; trmEdge < tw->numEdges; ++trmEdge) { + if (tw->edgePluecker[trmEdge] * modelEdge < 0.0f) { + tw->polygonEdgeSideCache[index].side |= 1u << trmEdge; + } + } + } +} + +int idPolygonModelCollisionDetection::CollisionBetweenEdgeBounds( + const idTraceWork&, const idVec3& firstStart, const idVec3& firstEnd, + const idVec3& secondStart, const idVec3& secondEnd, const float, + idVec3& collisionPoint, idVec3& collisionNormal) { + idVec3 firstPoint; + idVec3 secondPoint; + ClosestSegmentPoints(firstStart, firstEnd, secondStart, secondEnd, + firstPoint, secondPoint); + collisionNormal = firstPoint - secondPoint; + if (collisionNormal.LengthSqr() > ROTATION_EPSILON * ROTATION_EPSILON) { + return 0; + } + collisionPoint = (firstPoint + secondPoint) * 0.5f; + if (collisionNormal.NormalizeFast() == 0.0f) { + collisionNormal = (firstEnd - firstStart).Cross( + secondEnd - secondStart); + collisionNormal.NormalizeFast(); + } + return 1; +} + +int idPolygonModelCollisionDetection::RotateEdgeThroughEdge( + const idPluecker& first, const idPluecker& second, const float angle, + const float minTan, const float maxTan, float& tanHalfAngle) { + idVec3 firstStart; + idVec3 firstEnd; + idVec3 secondStart; + idVec3 secondEnd; + if (!first.ToLine(firstStart, firstEnd) + || !second.ToLine(secondStart, secondEnd)) { + return 0; + } + const int steps = (std::max)(8, + static_cast(std::ceil(std::fabs(angle) / 5.0f))); + for (int step = 0; step <= steps; ++step) { + const float fraction = static_cast(step) / steps; + const float tangent = std::tan(std::fabs(angle) * DEG2RAD + * fraction * 0.5f); + if (tangent < minTan || tangent > maxTan) { + continue; + } + const idVec3 rotatedStart = RotateAroundAxis(firstStart, + idVec3(0.0f, 0.0f, 0.0f), idVec3(0.0f, 0.0f, 1.0f), + angle * fraction); + const idVec3 rotatedEnd = RotateAroundAxis(firstEnd, + idVec3(0.0f, 0.0f, 0.0f), idVec3(0.0f, 0.0f, 1.0f), + angle * fraction); + idVec3 firstPoint; + idVec3 secondPoint; + ClosestSegmentPoints(rotatedStart, rotatedEnd, + secondStart, secondEnd, firstPoint, secondPoint); + if ((firstPoint - secondPoint).LengthSqr() + <= ROTATION_EPSILON * ROTATION_EPSILON) { + tanHalfAngle = tangent; + return 1; + } + } + return 0; +} + +int idPolygonModelCollisionDetection::EdgeFurthestFromEdge( + const idPluecker& first, const idPluecker& second, const float angle, + float& tanHalfAngle, float& direction) { + float bestDistance = -1.0f; + idVec3 firstStart; + idVec3 firstEnd; + idVec3 secondStart; + idVec3 secondEnd; + if (!first.ToLine(firstStart, firstEnd) + || !second.ToLine(secondStart, secondEnd)) { + return 0; + } + for (int step = 0; step <= 32; ++step) { + const float fraction = step / 32.0f; + const idVec3 start = RotateAroundAxis(firstStart, idVec3(0, 0, 0), + idVec3(0, 0, 1), angle * fraction); + const idVec3 end = RotateAroundAxis(firstEnd, idVec3(0, 0, 0), + idVec3(0, 0, 1), angle * fraction); + idVec3 a; + idVec3 b; + ClosestSegmentPoints(start, end, secondStart, secondEnd, a, b); + const float distance = (a - b).LengthSqr(); + if (distance > bestDistance) { + bestDistance = distance; + tanHalfAngle = std::tan(std::fabs(angle) * DEG2RAD + * fraction * 0.5f); + } + } + direction = angle < 0.0f ? -1.0f : 1.0f; + return bestDistance >= 0.0f; +} + +int idPolygonModelCollisionDetection::RotateTrmEdgesThroughPolygon( + idTraceWork* const tw, const cm_polygon_t& polygon) { + const cm_material_t& material = tw->subModelPtrs.materials[polygon.material]; + const int steps = (std::max)(8, + static_cast(std::ceil(std::fabs(tw->angle) / 5.0f))); + for (unsigned int trmEdge = 0; trmEdge < tw->numEdges; ++trmEdge) { + const idVec3 originalStart = Vec3(tw->vertexPosition[ + tw->edges[trmEdge].vertexNum[0]]); + const idVec3 originalEnd = Vec3(tw->vertexPosition[ + tw->edges[trmEdge].vertexNum[1]]); + for (int modelEdgeNumber = 0; + modelEdgeNumber < polygon.numEdges; ++modelEdgeNumber) { + const std::uint16_t reference = tw->subModelPtrs.polygonEdges[ + polygon.firstEdge + modelEdgeNumber]; + const int modelEdge = CM_EdgeIndex(reference); + const cm_edge_t& edge = tw->subModelPtrs.edges[modelEdge]; + const idVec3& modelStart = tw->subModelPtrs.vertices[ + CM_EdgeStartVertex(edge, reference)].p; + const idVec3& modelEnd = tw->subModelPtrs.vertices[ + CM_EdgeEndVertex(edge, reference)].p; + for (int step = 0; step <= steps; ++step) { + const float fraction = static_cast(step) / steps; + if (fraction >= tw->fraction) { + break; + } + const idVec3 start = RotateAroundAxis(originalStart, + Vec3(tw->origin), Vec3(tw->axis), tw->angle * fraction); + const idVec3 end = RotateAroundAxis(originalEnd, + Vec3(tw->origin), Vec3(tw->axis), tw->angle * fraction); + idVec3 trmPoint; + idVec3 modelPoint; + ClosestSegmentPoints(start, end, modelStart, modelEnd, + trmPoint, modelPoint); + if ((trmPoint - modelPoint).LengthSqr() + > ROTATION_EPSILON * ROTATION_EPSILON) { + continue; + } + idVec3 normal = (end - start).Cross(modelEnd - modelStart); + StoreRotationCollision(*tw, fraction, CONTACT_EDGE, + (trmPoint + modelPoint) * 0.5f, normal, + normal.Dot(modelPoint), material, + ((tw->subModelNum << 16) & 0x1FFF0000) + | 0x40000000 | modelEdge, + 0x40000000 | static_cast(trmEdge)); + break; + } + } + } + return tw->fraction <= 0.0f; +} + +int idPolygonModelCollisionDetection::RotatePointThroughPlane( + const idVec3& point, const idPlane& plane, const float angle, + const float minTan, const float maxTan, float& tanHalfAngle) { + const int steps = (std::max)(8, + static_cast(std::ceil(std::fabs(angle) / 5.0f))); + float previousDistance = plane.Distance(point); + for (int step = 1; step <= steps; ++step) { + const float fraction = static_cast(step) / steps; + const float tangent = std::tan(std::fabs(angle) * DEG2RAD + * fraction * 0.5f); + const idVec3 rotated = RotateAroundAxis(point, idVec3(0, 0, 0), + idVec3(0, 0, 1), angle * fraction); + const float distance = plane.Distance(rotated); + if (tangent >= minTan && tangent <= maxTan + && previousDistance > 0.0f && distance <= 0.0f) { + tanHalfAngle = tangent; + return 1; + } + previousDistance = distance; + } + return 0; +} + +int idPolygonModelCollisionDetection::PointFurthestFromPlane( + const idVec3& point, const idPlane& plane, const float angle, + float& tanHalfAngle, float& direction) { + float bestDistance = plane.Distance(point); + int bestStep = 0; + for (int step = 1; step <= 64; ++step) { + const idVec3 rotated = RotateAroundAxis(point, idVec3(0, 0, 0), + idVec3(0, 0, 1), angle * step / 64.0f); + const float distance = plane.Distance(rotated); + if (distance > bestDistance) { + bestDistance = distance; + bestStep = step; + } + } + tanHalfAngle = std::tan(std::fabs(angle) * DEG2RAD + * bestStep / 128.0f); + direction = angle < 0.0f ? -1.0f : 1.0f; + return bestStep != 0; +} + +int idPolygonModelCollisionDetection::RotatePointThroughEpsilonPlane( + const idTraceWork& tw, const idVec3& point, const idVec3&, + const idPlane& plane, const float angle, const idVec3& rotationOrigin, + float& tanHalfAngle, idVec3& collisionPoint, idVec3& endDirection) { + const int steps = (std::max)(8, + static_cast(std::ceil(std::fabs(angle) / 5.0f))); + float previousDistance = plane.Distance(point) - ROTATION_EPSILON; + for (int step = 1; step <= steps; ++step) { + const float fraction = static_cast(step) / steps; + const idVec3 rotated = RotateAroundAxis(point, rotationOrigin, + Vec3(tw.axis), angle * fraction); + const float distance = plane.Distance(rotated) - ROTATION_EPSILON; + if (previousDistance > 0.0f && distance <= 0.0f) { + tanHalfAngle = std::tan(std::fabs(angle) * DEG2RAD + * fraction * 0.5f); + collisionPoint = rotated; + endDirection = Vec3(tw.axis).Cross(rotated - rotationOrigin); + return 1; + } + previousDistance = distance; + } + return 0; +} + +int idPolygonModelCollisionDetection::RotateTrmVertsThroughPolygon( + idTraceWork* const tw, const cm_polygon_t& polygon, + const idPlane& polygonPlane) { + const cm_material_t& material = tw->subModelPtrs.materials[polygon.material]; + const int polygonNum = static_cast(&polygon - tw->subModelPtrs.polygons); + const int steps = (std::max)(8, + static_cast(std::ceil(std::fabs(tw->angle) / 5.0f))); + for (unsigned int vertex = 0; vertex < tw->numVerts; ++vertex) { + const idVec3 original = Vec3(tw->vertexPosition[vertex]); + idVec3 previous = original; + float previousDistance = polygonPlane.Distance(previous); + for (int step = 1; step <= steps; ++step) { + const float fraction = static_cast(step) / steps; + if (fraction >= tw->fraction) { + break; + } + const idVec3 current = RotateAroundAxis(original, + Vec3(tw->origin), Vec3(tw->axis), tw->angle * fraction); + const float distance = polygonPlane.Distance(current); + if (previousDistance > 0.0f && distance <= 0.0f) { + float low = static_cast(step - 1) / steps; + float high = fraction; + idVec3 collision = current; + for (int iteration = 0; iteration < 12; ++iteration) { + const float middle = (low + high) * 0.5f; + collision = RotateAroundAxis(original, Vec3(tw->origin), + Vec3(tw->axis), tw->angle * middle); + if (polygonPlane.Distance(collision) > 0.0f) { + low = middle; + } else { + high = middle; + } + } + if (PointInsidePolygon(tw->subModelPtrs, polygon, + polygonPlane, collision)) { + StoreRotationCollision(*tw, high, CONTACT_TRMVERTEX, + collision, polygonPlane.Normal(), polygonPlane.Dist(), + material, ((tw->subModelNum << 16) & 0x1FFF0000) + | 0x60000000 | polygonNum, + static_cast(vertex)); + } + } + previous = current; + previousDistance = distance; + } + } + return tw->fraction <= 0.0f; +} + +int idPolygonModelCollisionDetection::RotatePolygonVertsThroughTrm( + idTraceWork* const tw, const cm_polygon_t& polygon) { + if (!tw->isConvex || tw->numPolys == 0) { + return 0; + } + const cm_material_t& material = tw->subModelPtrs.materials[polygon.material]; + const int steps = (std::max)(8, + static_cast(std::ceil(std::fabs(tw->angle) / 5.0f))); + for (int edgeNumber = 0; edgeNumber < polygon.numEdges; ++edgeNumber) { + const std::uint16_t reference = tw->subModelPtrs.polygonEdges[ + polygon.firstEdge + edgeNumber]; + const cm_edge_t& edge = tw->subModelPtrs.edges[CM_EdgeIndex(reference)]; + const int vertexNumber = CM_EdgeStartVertex(edge, reference); + if (TestAndSet(tw->modelCheckCounts.vertexCheckCounts, vertexNumber)) { + continue; + } + const idVec3& modelPoint = tw->subModelPtrs.vertices[vertexNumber].p; + for (int step = 1; step <= steps; ++step) { + const float fraction = static_cast(step) / steps; + if (fraction >= tw->fraction) { + break; + } + // Inverse-rotate the stationary model point into the trace model's + // initial frame, then test its recovered convex planes. + const idVec3 localPoint = RotateAroundAxis(modelPoint, + Vec3(tw->origin), Vec3(tw->axis), -tw->angle * fraction); + int nearestPlane = 0; + if (!PointInsideInitialTrace(*tw, localPoint, nearestPlane)) { + continue; + } + idVec3 normal = -RotateVector(tw->polys[nearestPlane].plane.Normal(), + Vec3(tw->axis), tw->angle * fraction); + StoreRotationCollision(*tw, fraction, CONTACT_MODELVERTEX, + modelPoint, normal, normal.Dot(modelPoint), material, + ((tw->subModelNum << 16) & 0x1FFF0000) + | 0x20000000 | vertexNumber, + 0x60000000 | nearestPlane); + break; + } + } + return tw->fraction <= 0.0f; +} + +bool idPolygonModelCollisionDetection::RotateTrmThroughPolygon( + idTraceWork* const tw, const int polygonNum) { + if (TestAndSet(tw->modelCheckCounts.polygonCheckCounts, polygonNum)) { + return false; + } + const cm_polygon_t& polygon = tw->subModelPtrs.polygons[polygonNum]; + const cm_material_t& material = tw->subModelPtrs.materials[polygon.material]; + if ((material.contentFlags & tw->contents) == 0 + || !tw->traceBoundsShort.IntersectsBounds(polygon.bounds)) { + return false; + } + idPlane plane; + CM_GetPolygonPlane(tw->subModelPtrs, polygon, plane); + RotationEdgePlueckerCache(tw, polygon); + RotationCullPolygonEdges(tw, polygon); + if (RotateTrmVertsThroughPolygon(tw, polygon, plane) + || RotateTrmEdgesThroughPolygon(tw, polygon) + || RotatePolygonVertsThroughTrm(tw, polygon)) { + return true; + } + return false; +} + +int idPolygonModelCollisionDetection::StartRotation(idTraceWork* const tw, + trace_t* const result, const idVec3& rotationOrigin, + const idVec3& rotationAxis, const float angle, const idVec3& start, + const idTraceModel* const trm, const idMat3& trmAxis, + const int contentMask, const idVec3& modelOrigin, + const idMat3& modelAxis) { + if (trm == nullptr) { + return StartRotationPoint(tw, result, rotationOrigin, rotationAxis, + angle, start, contentMask, modelOrigin, modelAxis); + } + StartContents(tw, result, start, trm, trmAxis, contentMask, + modelOrigin, modelAxis); + tw->traceType = TRACE_ROTATION; + tw->fraction = 1.0f; + result->fraction = 1.0f; + RotationSetup(tw, rotationOrigin, rotationAxis, angle, start, + trm->offset, trmAxis, modelOrigin, modelAxis); + TransformAxisToZAxis(tw->ZAxisTransform, Vec3(tw->origin), + Vec3(tw->axis)); + TransformFromOriginAxisAngle(tw->endTransform, Vec3(tw->origin), + Vec3(tw->axis), angle); + idVec4 zverts[32]; + RotationVerts(tw, *trm, zverts); + RotationEdges(tw, *trm, zverts); + RotationPolys(tw, *trm); + RotationBounds(tw); + return angle != 0.0f; +} + +int idPolygonModelCollisionDetection::StartRotationPoint( + idTraceWork* const tw, trace_t* const result, + const idVec3& rotationOrigin, const idVec3& rotationAxis, + const float angle, const idVec3& start, const int contentMask, + const idVec3& modelOrigin, const idMat3& modelAxis) { + StartContentsPoint(tw, result, start, contentMask, modelOrigin, modelAxis); + tw->traceType = TRACE_ROTATION_POINT; + RotationSetup(tw, rotationOrigin, rotationAxis, angle, start, + idVec3(0.0f, 0.0f, 0.0f), idMat3(1.0f), + modelOrigin, modelAxis); + tw->vertexPosition[0] = tw->start; + tw->vertexEndPosition[0] = tw->end; + RotationBounds(tw); + return angle != 0.0f; +} + +void idPolygonModelCollisionDetection::FinishRotation(idTraceWork* const tw, + const idVec3& rotationOrigin, const idVec3& rotationAxis, + const float angle, const idVec3& start, const idMat3& trmAxis, + const idVec3& modelOrigin, const idMat3& modelAxis, + const int modelEntityNum, const int modelPhysicsId, + const int modelBodyId, const int selfId, + const int modelContentsOverride) { + if (tw->traceResult == nullptr) { + return; + } + trace_t& trace = *tw->traceResult; + trace.fraction = tw->fraction; + trace.endpos = RotateAroundAxis(start, rotationOrigin, rotationAxis, + angle * tw->fraction); + for (int column = 0; column < 3; ++column) { + trace.endAxis[column] = RotateVector(trmAxis[column], rotationAxis, + angle * tw->fraction); + } + if (trace.fraction >= 1.0f) { + return; + } + trace.c.normal = ModelToWorldVector(modelAxis, trace.c.normal); + trace.c.point = ModelToWorldVector(modelAxis, trace.c.point) + + modelOrigin; + trace.c.dist += modelOrigin.Dot(trace.c.normal); + trace.c.entityNum = modelEntityNum; + trace.c.physicsId = modelPhysicsId; + trace.c.bodyId = modelBodyId; + trace.c.selfId = selfId; + if (modelContentsOverride != 0 && trace.c.contentFlags != 0) { + trace.c.contentFlags = modelContentsOverride; + } +} diff --git a/source/engine/cm/jobs/polygonmodel/polygonmodel_trace.cpp b/source/engine/cm/jobs/polygonmodel/polygonmodel_trace.cpp new file mode 100644 index 0000000..1d5a985 --- /dev/null +++ b/source/engine/cm/jobs/polygonmodel/polygonmodel_trace.cpp @@ -0,0 +1,378 @@ +#include "cm/jobs/polygonmodel/polygonmodel.h" + +#include +#include +#include + +namespace { + +idVec3 Vec3(const idVec4& value) { + return idVec3(value.x, value.y, value.z); +} + +bool PointInsideBounds(const idVec3& point, const idBounds& bounds) { + return point.x >= bounds[0].x && point.x <= bounds[1].x + && point.y >= bounds[0].y && point.y <= bounds[1].y + && point.z >= bounds[0].z && point.z <= bounds[1].z; +} + +bool BoundsIntersect(const idBounds& lhs, const idBounds& rhs) { + return lhs[0].x <= rhs[1].x && lhs[1].x >= rhs[0].x + && lhs[0].y <= rhs[1].y && lhs[1].y >= rhs[0].y + && lhs[0].z <= rhs[1].z && lhs[1].z >= rhs[0].z; +} + +void ProcessLeaf(idTraceWork* const tw, const cm_node_t& node) { + // The recovered contents path tests convex polytopes before individual + // polygons. A polytope hit is sufficient and avoids reporting one of its + // boundary polygons as the containing primitive. + if (tw->traceType == TRACE_CONTENTS + || tw->traceType == TRACE_CONTENTS_POINT) { + for (int index = 0; index < node.numPolytopes; ++index) { + const int polytopeNum = tw->subModelPtrs.primitiveIndices[ + node.firstPrimitive + node.numPolygons + index]; + if (idPolygonModelCollisionDetection::TestTrmVertsInPolytope( + tw, polytopeNum)) { + return; + } + } + if (tw->traceType == TRACE_CONTENTS_POINT) { + return; + } + } + + for (int index = 0; index < node.numPolygons && !tw->quickExit; ++index) { + const int polygonNum = tw->subModelPtrs.primitiveIndices[ + node.firstPrimitive + index]; + switch (tw->traceType) { + case TRACE_TRANSLATION: + case TRACE_CONTACTS_UNI_DIR: + if (idPolygonModelCollisionDetection::TranslateTrmThroughPolygon( + tw, polygonNum)) { + return; + } + break; + case TRACE_TRANSLATION_POINT: + if (idPolygonModelCollisionDetection::TranslatePointThroughPolygon( + tw, polygonNum)) { + return; + } + break; + case TRACE_ROTATION: + case TRACE_ROTATION_POINT: + if (idPolygonModelCollisionDetection::RotateTrmThroughPolygon( + tw, polygonNum)) { + return; + } + break; + case TRACE_CONTENTS: + if (idPolygonModelCollisionDetection::TestTrmInPolygon( + tw, polygonNum)) { + return; + } + break; + case TRACE_CONTACTS_OMNI_DIR: + if (idPolygonModelCollisionDetection::TestTrmInContactWithPolygon( + tw, polygonNum)) { + return; + } + break; + case TRACE_CLIP: + if (idPolygonModelCollisionDetection::ClipPolygonWithTrm( + tw, polygonNum)) { + return; + } + break; + default: + break; + } + } + +} + +} // namespace + +// Recovered from engine/cm/jobs/polygonmodel/polygonmodel_trace.cpp. +void idPolygonModelCollisionDetection::SetupSubModelPtrsFromData( + cm_subModelPtrs_t& pointers, const cm_subModelData_t* const data) { + std::uint8_t* const base = reinterpret_cast( + const_cast(data)); + pointers.isConvex = data->isConvex; + pointers.nodes = reinterpret_cast(base + data->nodeOffset); + pointers.primitiveIndices = reinterpret_cast( + base + data->primitiveIndexOffset); + pointers.materials = reinterpret_cast( + base + data->materialOffset); + pointers.polygons = reinterpret_cast( + base + data->polygonOffset); + pointers.polygonEdges = reinterpret_cast( + base + data->polygonEdgeOffset); + pointers.edges = reinterpret_cast(base + data->edgeOffset); + pointers.vertices = reinterpret_cast( + base + data->vertexOffset); + pointers.polytopes = reinterpret_cast( + base + data->polytopeOffset); + pointers.polytopePlanes = reinterpret_cast( + base + data->polytopePlaneOffset); +} + +cm_subModelData_t* idPolygonModelCollisionDetection::SetupSubModelForBounds( + cm_subModelData_t* const data, const int size, const idBounds& bounds) { + constexpr int requiredSize = 608; + if (data == nullptr || size < requiredSize) { + return nullptr; + } + std::memset(data, 0, requiredSize); + data->header.totalSize = requiredSize; + data->header.loadedSize = requiredSize; + data->header.bounds = bounds; + data->isConvex = 1; + data->numNodes = 1; + data->nodeOffset = 112; + data->numPrimitiveIndices = 7; + data->primitiveIndexOffset = 128; + data->numMaterials = 1; + data->materialOffset = 144; + data->numPolygons = 6; + data->polygonOffset = 160; + data->numPolygonEdges = 28; + data->polygonEdgeOffset = 256; + data->numEdges = 12; + data->edgeOffset = 312; + data->numVertices = 8; + data->vertexOffset = 368; + data->numPolytopes = 1; + data->polytopeOffset = 496; + data->numPolytopePlanes = 6; + data->polytopePlaneOffset = 512; + + cm_subModelPtrs_t model; + SetupSubModelPtrsFromData(model, data); + cm_node_t& node = model.nodes[0]; + node.planeType = -1; + node.planeDist = 0.0f; + node.children[0] = node.children[1] = 0; + node.firstPrimitive = 0; + node.numPolygons = 6; + node.numPolytopes = 1; + for (int index = 0; index < 6; ++index) { + model.primitiveIndices[index] = static_cast(index); + } + model.primitiveIndices[6] = 0; + + cm_material_t& material = model.materials[0]; + material.contentFlags = -1; + material.surfaceFlags = -1; + material.surfaceType = 0; + material.surfaceColor[0] = material.surfaceColor[1] + = material.surfaceColor[2] = 0xFF; + material.pad = 0; + + idBounds expanded = bounds; + for (int axis = 0; axis < 3; ++axis) { + expanded[0][axis] -= 1.0f; + expanded[1][axis] += 1.0f; + } + for (int polygonNumber = 0; polygonNumber < 6; ++polygonNumber) { + model.polygons[polygonNumber].bounds.SetBounds(expanded); + model.polygons[polygonNumber].material = 0; + model.polygons[polygonNumber].numEdges = 4; + model.polygons[polygonNumber].firstEdge = + static_cast(polygonNumber * 4); + } + + const std::uint16_t polygonEdges[28] = { + 0x8003, 0x8002, 0x8001, 0x8000, + 4, 5, 6, 7, + 0, 9, 0x8004, 0x8008, + 1, 10, 0x8005, 0x8009, + 2, 11, 0x8006, 0x800A, + 3, 8, 0x8007, 0x800B, + 0x800B, 0x800B, 0x800B, 0x800B + }; + std::memcpy(model.polygonEdges, polygonEdges, sizeof(polygonEdges)); + + for (int index = 0; index < 4; ++index) { + model.edges[index].vertexNum[0] = static_cast(index); + model.edges[index].vertexNum[1] = + static_cast((index + 1) & 3); + model.edges[index + 4].vertexNum[0] = + static_cast(index + 4); + model.edges[index + 4].vertexNum[1] = + static_cast(((index + 1) & 3) + 4); + model.edges[index + 8].vertexNum[0] = + static_cast(index); + model.edges[index + 8].vertexNum[1] = + static_cast(index + 4); + } + + for (int index = 0; index < 8; ++index) { + model.vertices[index].p.Set( + (index == 1 || index == 2 || index == 5 || index == 6) + ? bounds[1].x : bounds[0].x, + (index == 2 || index == 3 || index == 6 || index == 7) + ? bounds[1].y : bounds[0].y, + index >= 4 ? bounds[1].z : bounds[0].z); + model.vertices[index].st[0] = model.vertices[index].st[1] = 0; + } + + model.polytopes[0].bounds.SetBounds(expanded); + model.polytopes[0].material = 0; + model.polytopes[0].numPlanes = 6; + model.polytopes[0].firstPlane = 0; + model.polytopePlanes[0] = idPlane(0.0f, 0.0f, -1.0f, bounds[0].z); + model.polytopePlanes[1] = idPlane(0.0f, 0.0f, 1.0f, -bounds[1].z); + model.polytopePlanes[2] = idPlane(0.0f, -1.0f, 0.0f, bounds[0].y); + model.polytopePlanes[3] = idPlane(0.0f, 1.0f, 0.0f, -bounds[1].y); + model.polytopePlanes[4] = idPlane(1.0f, 0.0f, 0.0f, -bounds[1].x); + model.polytopePlanes[5] = idPlane(-1.0f, 0.0f, 0.0f, bounds[0].x); + return data; +} + +bool idPolygonModelCollisionDetection::TestStuckInSubModelBounds( + idTraceWork* const tw, const idBounds& subModelBounds) { + if (tw->traceType <= TRACE_INVALID || tw->traceType > TRACE_CLIP) { + return false; + } + idBounds traceAtStart; + traceAtStart[0] = Vec3(tw->start) + Vec3(tw->trmBoundsMin); + traceAtStart[1] = Vec3(tw->start) + Vec3(tw->trmBoundsMax); + if (!BoundsIntersect(traceAtStart, subModelBounds)) { + return false; + } + int vertexNumber = 0; + for (; vertexNumber < static_cast(tw->numVerts); ++vertexNumber) { + if (PointInsideBounds(Vec3(tw->vertexPosition[vertexNumber]), + subModelBounds)) { + break; + } + } + if (vertexNumber >= static_cast(tw->numVerts)) { + return false; + } + if (tw->traceResult != nullptr) { + trace_t& trace = *tw->traceResult; + trace.fraction = 0.0f; + trace.c.type = CONTACT_MODELVERTEX; + trace.c.point = Vec3(tw->start); + trace.c.normal.Set(0.0f, 0.0f, 1.0f); + trace.c.dist = tw->start.w; + trace.c.separation = 0.0f; + trace.c.contentFlags = -1; + trace.c.surfaceFlags = 0; + trace.c.surfaceType = 0; + trace.c.surfaceColor[0] = trace.c.surfaceColor[1] + = trace.c.surfaceColor[2] = 0xFF; + trace.c.modelFeature = (tw->subModelNum << 16) & 0x1FFF0000; + trace.c.trmFeature = 0; + trace.c.flags = CONTACT_FLAG_SUBMODEL_NOT_RESIDENT; + } + return true; +} + +idVec3 idPolygonModelCollisionDetection::LocalExtentsFromUnTransformedBounds( + const idBounds& globalBounds, const idVec3& globalStart, + const idVec3& globalEnd, const idMat3& modelAxis) { + idVec3 globalExtents; + for (int axis = 0; axis < 3; ++axis) { + const float pathMinimum = (std::min)(globalStart[axis], globalEnd[axis]); + const float pathMaximum = (std::max)(globalStart[axis], globalEnd[axis]); + const float negativeExtent = pathMinimum - globalBounds[0][axis]; + const float positiveExtent = globalBounds[1][axis] - pathMaximum; + globalExtents[axis] = (std::max)(negativeExtent, positiveExtent); + } + return idVec3( + std::fabs(modelAxis[0].x) * globalExtents.x + + std::fabs(modelAxis[0].y) * globalExtents.y + + std::fabs(modelAxis[0].z) * globalExtents.z, + std::fabs(modelAxis[1].x) * globalExtents.x + + std::fabs(modelAxis[1].y) * globalExtents.y + + std::fabs(modelAxis[1].z) * globalExtents.z, + std::fabs(modelAxis[2].x) * globalExtents.x + + std::fabs(modelAxis[2].y) * globalExtents.y + + std::fabs(modelAxis[2].z) * globalExtents.z); +} + +void idPolygonModelCollisionDetection::TraceThroughSubModelTree( + idTraceWork* const tw) { + if (tw == nullptr || tw->subModelPtrs.nodes == nullptr || tw->quickExit) { + return; + } + std::uint16_t stack[128]; + int stackSize = 0; + stack[stackSize++] = 0; + int iterations = 0; + while (stackSize != 0 && !tw->quickExit && iterations++ < 65536) { + const std::uint16_t nodeNumber = stack[--stackSize]; + const cm_node_t& node = tw->subModelPtrs.nodes[nodeNumber]; + if (node.numPolygons != 0 || node.numPolytopes != 0) { + ProcessLeaf(tw, node); + } + if (node.planeType == -1) { + continue; + } + // Check-counts remove duplicate primitive work, so visiting both sides + // is a conservative scalar replacement for the VMX swept-tree walk. + if (stackSize <= 126) { + stack[stackSize++] = node.children[1]; + stack[stackSize++] = node.children[0]; + } + } +} + +void idPolygonModelCollisionDetection::TraceThroughSubModel( + idTraceWork* const tw, const cm_subModelData_t* subModelData, + const int subModelNum) { + if (tw == nullptr || subModelData == nullptr) { + return; + } + const cm_subModelData_t* data = subModelData; + if (data->header.loadedSize == sizeof(cm_subModelHeader_t)) { + if (TestStuckInSubModelBounds(tw, data->header.bounds)) { + return; + } + data = SetupSubModelForBounds( + reinterpret_cast(tw->subModelDataForBounds), + static_cast(sizeof(tw->subModelDataForBounds)), + data->header.bounds); + if (data == nullptr) { + return; + } + } + SetupSubModelPtrsFromData(tw->subModelPtrs, data); + tw->modelCheckCounts.SetupForSubModel(data); + tw->subModelNum = subModelNum; + TraceThroughSubModelTree(tw); + if (subModelData->header.loadedSize == sizeof(cm_subModelHeader_t) + && tw->traceResult != nullptr && tw->traceResult->fraction < 1.0f) { + tw->traceResult->c.flags |= CONTACT_FLAG_SUBMODEL_NOT_RESIDENT; + } +} + +unsigned int idPolygonModelCollisionDetection::GetSubModelsForTrace( + const cm_polygonModel_t& model, const idVec3&, const idVec3&, + const idVec3&, int* const subModelNums) { + if (subModelNums == nullptr || model.numSubModels <= 0) { + return 0; + } + const unsigned int count = static_cast((std::min)( + model.numSubModels, 128)); + for (unsigned int index = 0; index < count; ++index) { + subModelNums[index] = static_cast(index); + } + return count; +} + +void idPolygonModelCollisionDetection::TraceThroughModel( + idTraceWork* const tw, const cm_polygonModel_t& model) { + int subModelNums[128]; + const unsigned int count = GetSubModelsForTrace(model, Vec3(tw->start), + Vec3(tw->end), Vec3(tw->trmExtents), subModelNums); + for (unsigned int index = 0; index < count && !tw->quickExit; ++index) { + const int subModelNum = subModelNums[index]; + const cm_subModel_t& subModel = model.subModels[subModelNum]; + const cm_subModelData_t* const data = AcquireSubModelData(subModel); + TraceThroughSubModel(tw, data, subModelNum); + ReleaseSubModelData(subModel, data); + } +} diff --git a/source/engine/cm/jobs/polygonmodel/polygonmodel_translate.cpp b/source/engine/cm/jobs/polygonmodel/polygonmodel_translate.cpp new file mode 100644 index 0000000..30a108c --- /dev/null +++ b/source/engine/cm/jobs/polygonmodel/polygonmodel_translate.cpp @@ -0,0 +1,620 @@ +#include "cm/jobs/polygonmodel/polygonmodel.h" + +#include "cm/jobs/polygonmodel/polygonmodeldata.h" +#include "idlib/geometry/tracemodel.h" + +#include +#include +#include +#include + +namespace { + +constexpr float CM_CLIP_EPSILON = 0.25f; +constexpr float CM_GEOMETRY_EPSILON = 1.0e-6f; + +idVec3 Vec3(const idVec4& value) { + return idVec3(value.x, value.y, value.z); +} + +void SetVec4(idVec4& target, const idVec3& value, const float w = 0.0f) { + target.Set(value.x, value.y, value.z, w); +} + +idVec3 ModelToWorldVector(const idMat3& axis, const idVec3& value) { + return idVec3( + axis[0].x * value.x + axis[1].x * value.y + axis[2].x * value.z, + axis[0].y * value.x + axis[1].y * value.y + axis[2].y * value.z, + axis[0].z * value.x + axis[1].z * value.y + axis[2].z * value.z); +} + +idVec3 WorldToModelVector(const idMat3& axis, const idVec3& value) { + return idVec3(axis[0].Dot(value), axis[1].Dot(value), + axis[2].Dot(value)); +} + +bool IsIdentity(const idMat3& axis) { + return axis[0].x == 1.0f && axis[1].y == 1.0f + && axis[2].z == 1.0f + && axis[0].y == 0.0f && axis[0].z == 0.0f + && axis[1].x == 0.0f && axis[1].z == 0.0f + && axis[2].x == 0.0f && axis[2].y == 0.0f; +} + +bool TestAndSet(std::uint8_t* bits, const int index) { + if (bits == nullptr || index < 0) { + return false; + } + const std::uint8_t mask = static_cast(1u << (index & 7)); + std::uint8_t& value = bits[index >> 3]; + const bool old = (value & mask) != 0; + value = static_cast(value | mask); + return old; +} + +void SetMaterial(contactInfo_t& contact, const cm_material_t& material) { + contact.contentFlags = material.contentFlags; + contact.surfaceFlags = material.surfaceFlags; + contact.surfaceType = material.surfaceType; + contact.surfaceColor[0] = material.surfaceColor[0]; + contact.surfaceColor[1] = material.surfaceColor[1]; + contact.surfaceColor[2] = material.surfaceColor[2]; +} + +bool PointInsidePolygon(const cm_subModelPtrs_t& model, + const cm_polygon_t& polygon, const idPlane& plane, const idVec3& point) { + bool positive = false; + bool negative = false; + for (int index = 0; index < polygon.numEdges; ++index) { + const std::uint16_t reference = + model.polygonEdges[polygon.firstEdge + index]; + const cm_edge_t& edge = model.edges[CM_EdgeIndex(reference)]; + const idVec3& start = model.vertices[ + CM_EdgeStartVertex(edge, reference)].p; + const idVec3& end = model.vertices[ + CM_EdgeEndVertex(edge, reference)].p; + const float side = (end - start).Cross(point - start).Dot( + plane.Normal()); + positive |= side > CM_CLIP_EPSILON; + negative |= side < -CM_CLIP_EPSILON; + if (positive && negative) { + return false; + } + } + return true; +} + +float Triple(const idVec3& first, const idVec3& second, + const idVec3& third) { + return first.Dot(second.Cross(third)); +} + +bool MovingSegmentIntersection(const idVec3& firstStart, + const idVec3& firstEnd, const idVec3& movement, + const idVec3& secondStart, const idVec3& secondEnd, + float& fraction, float& firstScale, float& secondScale) { + const idVec3 firstDirection = firstEnd - firstStart; + const idVec3 secondDirection = secondEnd - secondStart; + const idVec3 negativeSecond = -secondDirection; + const idVec3 rhs = secondStart - firstStart; + const float determinant = Triple(firstDirection, movement, + negativeSecond); + if (std::fabs(determinant) <= CM_GEOMETRY_EPSILON) { + return false; + } + firstScale = Triple(rhs, movement, negativeSecond) / determinant; + fraction = Triple(firstDirection, rhs, negativeSecond) / determinant; + secondScale = Triple(firstDirection, movement, rhs) / determinant; + return firstScale >= -CM_GEOMETRY_EPSILON + && firstScale <= 1.0f + CM_GEOMETRY_EPSILON + && secondScale >= -CM_GEOMETRY_EPSILON + && secondScale <= 1.0f + CM_GEOMETRY_EPSILON + && fraction >= 0.0f && fraction <= 1.0f; +} + +void StoreCollision(idTraceWork& tw, const float fraction, + const contactType_t type, const idVec3& point, idVec3 normal, + const float planeDistance, const cm_material_t& material, + const int modelFeature, const int trmFeature) { + if (tw.traceResult == nullptr || fraction >= tw.fraction) { + return; + } + if (normal.NormalizeFast() == 0.0f) { + return; + } + tw.fraction = (std::max)(0.0f, fraction); + trace_t& trace = *tw.traceResult; + trace.fraction = tw.fraction; + trace.c.type = type; + trace.c.point = point; + trace.c.normal = normal; + trace.c.dist = planeDistance; + trace.c.separation = 0.0f; + SetMaterial(trace.c, material); + trace.c.modelFeature = modelFeature; + trace.c.trmFeature = trmFeature; + trace.c.flags = 0; + if (tw.contactsResult != nullptr) { + idPolygonModelCollisionDetection::AddContact(&tw); + } +} + +} // namespace + +void idPolygonModelCollisionDetection::TranslationSetup(idTraceWork* const tw, + const idVec3& start, const idVec3& end, const idVec3& offset, + const idMat3& trmAxis, const idVec3& modelOrigin, + const idMat3& modelAxis) { + const idVec3 startCenter = WorldToModelVector(modelAxis, + start + ModelToWorldVector(trmAxis, offset) - modelOrigin); + const idVec3 endCenter = WorldToModelVector(modelAxis, + end + ModelToWorldVector(trmAxis, offset) - modelOrigin); + SetVec4(tw->start, startCenter); + SetVec4(tw->end, endCenter); + SetVec4(tw->dir, endCenter - startCenter); + SetVec4(tw->negDir, startCenter - endCenter); +} + +void idPolygonModelCollisionDetection::TranslationUsedPrimitives( + idTraceWork* const tw, const idVec3&, const idVec3&, + const idTraceModel& trm, const idMat3&) { + std::memset(tw->vertIsUsed, 0, sizeof(tw->vertIsUsed)); + std::memset(tw->edgeIsUsed, 0, sizeof(tw->edgeIsUsed)); + std::memset(tw->polyIsUsed, 0, sizeof(tw->polyIsUsed)); + for (unsigned int index = 0; index < (std::min)(trm.numVerts, 32u); ++index) { + tw->vertIsUsed[index] = 1; + } + for (unsigned int index = 0; index < (std::min)(trm.numEdges, 32u); ++index) { + tw->edgeIsUsed[index] = 1; + } + for (unsigned int index = 0; index < (std::min)(trm.numPolys, 16u); ++index) { + tw->polyIsUsed[index] = 1; + } +} + +void idPolygonModelCollisionDetection::TranslationHeartPlanes( + idTraceWork* const tw) { + idVec3 direction = Vec3(tw->dir); + if (direction.NormalizeFast() == 0.0f) { + tw->heartPlane1 = idPlane(1.0f, 0.0f, 0.0f, -tw->start.x); + tw->heartPlane2 = idPlane(0.0f, 1.0f, 0.0f, -tw->start.y); + return; + } + idVec3 reference = std::fabs(direction.z) < 0.9f + ? idVec3(0.0f, 0.0f, 1.0f) : idVec3(0.0f, 1.0f, 0.0f); + idVec3 first = direction.Cross(reference); + first.NormalizeFast(); + idVec3 second = direction.Cross(first); + second.NormalizeFast(); + tw->heartPlane1 = idPlane(first, first.Dot(Vec3(tw->start))); + tw->heartPlane2 = idPlane(second, second.Dot(Vec3(tw->start))); + tw->maxDistFromHeartPlane1 = 0.0f; + tw->maxDistFromHeartPlane2 = 0.0f; + for (unsigned int index = 0; index < tw->numVerts; ++index) { + tw->maxDistFromHeartPlane1 = (std::max)( + tw->maxDistFromHeartPlane1, + std::fabs(tw->heartPlane1.Distance(Vec3(tw->vertexPosition[index])))); + tw->maxDistFromHeartPlane2 = (std::max)( + tw->maxDistFromHeartPlane2, + std::fabs(tw->heartPlane2.Distance(Vec3(tw->vertexPosition[index])))); + } +} + +void idPolygonModelCollisionDetection::TranslationVerts(idTraceWork* const tw, + const idTraceModel&) { + const idVec3 direction = Vec3(tw->dir); + for (unsigned int index = 0; index < tw->numVerts; ++index) { + SetVec4(tw->vertexEndPosition[index], + Vec3(tw->vertexPosition[index]) + direction); + tw->vertexPluecker[index].FromLine( + Vec3(tw->vertexPosition[index]), + Vec3(tw->vertexEndPosition[index])); + } +} + +void idPolygonModelCollisionDetection::TranslationEdges(idTraceWork* const tw, + const idTraceModel&) { + for (unsigned int index = 0; index < tw->numEdges; ++index) { + const idVec3 first = Vec3(tw->vertexPosition[ + tw->edges[index].vertexNum[0]]); + const idVec3 second = Vec3(tw->vertexPosition[ + tw->edges[index].vertexNum[1]]); + tw->edgePluecker[index].FromLine(first, second); + idVec3 normal = (second - first).Cross(Vec3(tw->dir)); + normal.NormalizeFast(); + SetVec4(tw->edgeNormal[index], normal); + } +} + +void idPolygonModelCollisionDetection::TranslationPolys(idTraceWork* const tw, + const idTraceModel&) { + for (unsigned int index = 0; index < tw->numPolys; ++index) { + tw->polyIsUsed[index] = tw->polys[index].numEdges != 0; + } +} + +void idPolygonModelCollisionDetection::TranslationBounds(idTraceWork* const tw) { + idVec3 minimum(std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max()); + idVec3 maximum(-std::numeric_limits::max(), + -std::numeric_limits::max(), + -std::numeric_limits::max()); + for (unsigned int index = 0; index < tw->numVerts; ++index) { + const idVec3 points[2] = { Vec3(tw->vertexPosition[index]), + Vec3(tw->vertexEndPosition[index]) }; + for (const idVec3& point : points) { + minimum.x = (std::min)(minimum.x, point.x); + minimum.y = (std::min)(minimum.y, point.y); + minimum.z = (std::min)(minimum.z, point.z); + maximum.x = (std::max)(maximum.x, point.x); + maximum.y = (std::max)(maximum.y, point.y); + maximum.z = (std::max)(maximum.z, point.z); + } + } + tw->traceBoundsMin.Set(minimum.x - 1.0f, minimum.y - 1.0f, + minimum.z - 1.0f, 0.0f); + tw->traceBoundsMax.Set(maximum.x + 1.0f, maximum.y + 1.0f, + maximum.z + 1.0f, 0.0f); + idBounds quantized; + quantized[0].Set(std::floor(minimum.x) - 1.0f, + std::floor(minimum.y) - 1.0f, std::floor(minimum.z) - 1.0f); + quantized[1].Set(std::ceil(maximum.x) + 1.0f, + std::ceil(maximum.y) + 1.0f, std::ceil(maximum.z) + 1.0f); + tw->traceBoundsShort.SetBounds(quantized); +} + +void idPolygonModelCollisionDetection::TranslationUpdateBounds( + idTraceWork* const tw) { + const idVec3 start = Vec3(tw->start); + const idVec3 partialEnd = start + Vec3(tw->dir) * tw->fraction; + tw->end.x = partialEnd.x; + tw->end.y = partialEnd.y; + tw->end.z = partialEnd.z; + TranslationBounds(tw); +} + +void idPolygonModelCollisionDetection::TranslationPlueckerCache( + idTraceWork* const tw, const cm_polygon_t& polygon) { + for (int index = 0; index < polygon.numEdges && index < 16; ++index) { + const std::uint16_t reference = tw->subModelPtrs.polygonEdges[ + polygon.firstEdge + index]; + const cm_edge_t& edge = tw->subModelPtrs.edges[CM_EdgeIndex(reference)]; + tw->polygonEdgePlueckerCache[index].FromLine( + tw->subModelPtrs.vertices[CM_EdgeStartVertex(edge, reference)].p, + tw->subModelPtrs.vertices[CM_EdgeEndVertex(edge, reference)].p); + } +} + +void idPolygonModelCollisionDetection::TranslationEdgePlueckerCache( + idTraceWork* const tw, const cm_polygon_t& polygon) { + TranslationPlueckerCache(tw, polygon); +} + +void idPolygonModelCollisionDetection::TranslationSideCache( + const idPluecker* const pluecker, const std::uint8_t* const used, + const unsigned int count, const idPluecker* const plueckerCache, + cm_sideCache_t* const sideCache, const unsigned int cacheSize) { + for (unsigned int cacheIndex = 0; cacheIndex < cacheSize; ++cacheIndex) { + std::uint32_t sides = 0; + for (unsigned int index = 0; index < count && index < 32; ++index) { + if ((used == nullptr || used[index] != 0) + && pluecker[index] * plueckerCache[cacheIndex] < 0.0f) { + sides |= 1u << index; + } + } + sideCache[cacheIndex].side = sides; + } +} + +void idPolygonModelCollisionDetection::TranslationPolygonSideCache( + idTraceWork* const tw, const cm_polygon_t& polygon) { + TranslationPlueckerCache(tw, polygon); + TranslationSideCache(tw->edgePluecker, tw->edgeIsUsed, tw->numEdges, + tw->polygonEdgePlueckerCache, tw->polygonEdgeSideCache, + (std::min)(static_cast(polygon.numEdges), 20u)); +} + +void idPolygonModelCollisionDetection::AddContact(idTraceWork* const tw) { + if (tw->contactsResult == nullptr || tw->traceResult == nullptr + || tw->contactsResult->numContacts >= 12) { + return; + } + tw->contactsResult->contacts[tw->contactsResult->numContacts++] = + tw->traceResult->c; +} + +float idPolygonModelCollisionDetection::TranslateEdgeThroughEdge( + const idPluecker& first, const idPluecker& second, + const idVec3& direction) { + idVec3 firstStart; + idVec3 firstEnd; + idVec3 secondStart; + idVec3 secondEnd; + if (!first.ToLine(firstStart, firstEnd) + || !second.ToLine(secondStart, secondEnd)) { + return 1.0f; + } + float fraction; + float firstScale; + float secondScale; + if (!MovingSegmentIntersection(firstStart, firstEnd, direction, + secondStart, secondEnd, fraction, firstScale, secondScale)) { + return 1.0f; + } + return fraction; +} + +int idPolygonModelCollisionDetection::TranslateTrmEdgesThroughPolygon( + idTraceWork* const tw, const cm_polygon_t& polygon) { + const cm_material_t& material = tw->subModelPtrs.materials[polygon.material]; + const idVec3 movement = Vec3(tw->dir); + bool collision = false; + for (unsigned int trmEdge = 0; trmEdge < tw->numEdges; ++trmEdge) { + const idVec3 firstStart = Vec3(tw->vertexPosition[ + tw->edges[trmEdge].vertexNum[0]]); + const idVec3 firstEnd = Vec3(tw->vertexPosition[ + tw->edges[trmEdge].vertexNum[1]]); + for (int modelEdgeNumber = 0; + modelEdgeNumber < polygon.numEdges; ++modelEdgeNumber) { + const std::uint16_t reference = tw->subModelPtrs.polygonEdges[ + polygon.firstEdge + modelEdgeNumber]; + const int modelEdge = CM_EdgeIndex(reference); + const cm_edge_t& edge = tw->subModelPtrs.edges[modelEdge]; + const idVec3& secondStart = tw->subModelPtrs.vertices[ + CM_EdgeStartVertex(edge, reference)].p; + const idVec3& secondEnd = tw->subModelPtrs.vertices[ + CM_EdgeEndVertex(edge, reference)].p; + float fraction; + float firstScale; + float secondScale; + if (!MovingSegmentIntersection(firstStart, firstEnd, movement, + secondStart, secondEnd, fraction, firstScale, secondScale) + || fraction >= tw->fraction) { + continue; + } + idVec3 normal = (firstEnd - firstStart).Cross( + secondEnd - secondStart); + if (normal.Dot(movement) > 0.0f) { + normal = -normal; + } + const idVec3 point = secondStart + + (secondEnd - secondStart) * secondScale; + StoreCollision(*tw, fraction, CONTACT_EDGE, point, normal, + normal.Dot(point), material, + ((tw->subModelNum << 16) & 0x1FFF0000) + | 0x40000000 | modelEdge, + 0x40000000 | static_cast(trmEdge)); + collision = true; + } + } + return collision && tw->fraction <= 0.0f; +} + +float idPolygonModelCollisionDetection::TranslatePointThroughPlane( + const idPlane& plane, const idVec3& start, const idVec3& end) { + const float startDistance = plane.Distance(start); + const float endDistance = plane.Distance(end); + const float denominator = startDistance - endDistance; + if (startDistance <= 0.0f || endDistance > 0.0f + || std::fabs(denominator) <= CM_GEOMETRY_EPSILON) { + return 1.0f; + } + return (startDistance - CM_CLIP_EPSILON) / denominator; +} + +int idPolygonModelCollisionDetection::TranslateTrmVertsThroughPolygon( + idTraceWork* const tw, const cm_polygon_t& polygon, + const idPlane& polygonPlane) { + const cm_material_t& material = tw->subModelPtrs.materials[polygon.material]; + for (unsigned int vertexNumber = 0; + vertexNumber < tw->numVerts; ++vertexNumber) { + const idVec3 start = Vec3(tw->vertexPosition[vertexNumber]); + const idVec3 end = Vec3(tw->vertexEndPosition[vertexNumber]); + const float fraction = TranslatePointThroughPlane( + polygonPlane, start, end); + if (fraction < 0.0f || fraction >= tw->fraction) { + continue; + } + const idVec3 point = start + (end - start) * fraction; + if (!PointInsidePolygon(tw->subModelPtrs, polygon, + polygonPlane, point)) { + continue; + } + StoreCollision(*tw, fraction, CONTACT_TRMVERTEX, point, + polygonPlane.Normal(), polygonPlane.Dist(), material, + ((tw->subModelNum << 16) & 0x1FFF0000) + | 0x60000000 + | static_cast(&polygon - tw->subModelPtrs.polygons), + static_cast(vertexNumber)); + } + return tw->fraction <= 0.0f; +} + +int idPolygonModelCollisionDetection::TranslatePolygonVertsThroughTrm( + idTraceWork* const tw, const cm_polygon_t& polygon) { + if (!tw->isConvex || tw->numPolys == 0) { + return 0; + } + const cm_material_t& material = tw->subModelPtrs.materials[polygon.material]; + const idVec3 movement = Vec3(tw->dir); + for (int edgeNumber = 0; edgeNumber < polygon.numEdges; ++edgeNumber) { + const std::uint16_t reference = tw->subModelPtrs.polygonEdges[ + polygon.firstEdge + edgeNumber]; + const cm_edge_t& modelEdge = tw->subModelPtrs.edges[ + CM_EdgeIndex(reference)]; + const int vertexNumber = CM_EdgeStartVertex(modelEdge, reference); + if (TestAndSet(tw->modelCheckCounts.vertexCheckCounts, vertexNumber)) { + continue; + } + const idVec3& point = tw->subModelPtrs.vertices[vertexNumber].p; + float enter = 0.0f; + float leave = tw->fraction; + int enterPlane = -1; + bool possible = true; + for (unsigned int planeNumber = 0; + planeNumber < tw->numPolys; ++planeNumber) { + const idPlane& plane = tw->polys[planeNumber].plane; + const float distance = plane.Distance(point); + const float rate = -plane.Normal().Dot(movement); + if (std::fabs(rate) <= CM_GEOMETRY_EPSILON) { + if (distance > 0.0f) { + possible = false; + break; + } + continue; + } + const float crossing = -distance / rate; + if (rate < 0.0f) { + if (crossing > enter) { + enter = crossing; + enterPlane = static_cast(planeNumber); + } + } else { + leave = (std::min)(leave, crossing); + } + if (enter > leave) { + possible = false; + break; + } + } + if (!possible || enterPlane < 0 || enter < 0.0f + || enter >= tw->fraction) { + continue; + } + idVec3 normal = -tw->polys[enterPlane].plane.Normal(); + StoreCollision(*tw, enter, CONTACT_MODELVERTEX, point, normal, + normal.Dot(point), material, + ((tw->subModelNum << 16) & 0x1FFF0000) + | 0x20000000 | vertexNumber, + 0x60000000 | enterPlane); + } + return tw->fraction <= 0.0f; +} + +int idPolygonModelCollisionDetection::TranslateTrmThroughPolygon( + idTraceWork* const tw, const int polygonNum) { + if (TestAndSet(tw->modelCheckCounts.polygonCheckCounts, polygonNum)) { + return 0; + } + const cm_polygon_t& polygon = tw->subModelPtrs.polygons[polygonNum]; + const cm_material_t& material = tw->subModelPtrs.materials[polygon.material]; + if ((material.contentFlags & tw->contents) == 0 + || !tw->traceBoundsShort.IntersectsBounds(polygon.bounds)) { + return 0; + } + idPlane plane; + CM_GetPolygonPlane(tw->subModelPtrs, polygon, plane); + TranslationPolygonSideCache(tw, polygon); + if (TranslateTrmVertsThroughPolygon(tw, polygon, plane)) { + return 1; + } + if (TranslateTrmEdgesThroughPolygon(tw, polygon)) { + return 1; + } + return TranslatePolygonVertsThroughTrm(tw, polygon); +} + +int idPolygonModelCollisionDetection::TranslatePointThroughPolygon( + idTraceWork* const tw, const int polygonNum) { + if (TestAndSet(tw->modelCheckCounts.polygonCheckCounts, polygonNum)) { + return 0; + } + const cm_polygon_t& polygon = tw->subModelPtrs.polygons[polygonNum]; + const cm_material_t& material = tw->subModelPtrs.materials[polygon.material]; + if ((material.contentFlags & tw->contents) == 0 + || !tw->traceBoundsShort.IntersectsBounds(polygon.bounds)) { + return 0; + } + idPlane plane; + CM_GetPolygonPlane(tw->subModelPtrs, polygon, plane); + const idVec3 start = Vec3(tw->vertexPosition[0]); + const idVec3 end = Vec3(tw->vertexEndPosition[0]); + const float fraction = TranslatePointThroughPlane(plane, start, end); + if (fraction < 0.0f || fraction >= tw->fraction) { + return 0; + } + const idVec3 point = start + (end - start) * fraction; + if (!PointInsidePolygon(tw->subModelPtrs, polygon, plane, point)) { + return 0; + } + StoreCollision(*tw, fraction, CONTACT_TRMVERTEX, point, + plane.Normal(), plane.Dist(), material, + ((tw->subModelNum << 16) & 0x1FFF0000) + | 0x60000000 | polygonNum, 0); + return tw->fraction <= 0.0f; +} + +int idPolygonModelCollisionDetection::StartTranslation(idTraceWork* const tw, + trace_t* const result, contactsResult_t* const contacts, + const idVec3& start, const idVec3& end, const idTraceModel* const trm, + const idMat3& trmAxis, const int contentMask, + const idVec3& modelOrigin, const idMat3& modelAxis) { + if (trm == nullptr) { + return StartTranslationPoint(tw, result, start, end, contentMask, + modelOrigin, modelAxis); + } + StartContents(tw, result, start, trm, trmAxis, contentMask, + modelOrigin, modelAxis); + tw->traceType = contacts == nullptr + ? TRACE_TRANSLATION : TRACE_CONTACTS_UNI_DIR; + tw->contactsResult = contacts; + tw->fraction = 1.0f; + result->fraction = 1.0f; + TranslationSetup(tw, start, end, trm->offset, trmAxis, + modelOrigin, modelAxis); + TranslationUsedPrimitives(tw, start, end, *trm, trmAxis); + TranslationVerts(tw, *trm); + TranslationEdges(tw, *trm); + TranslationPolys(tw, *trm); + TranslationHeartPlanes(tw); + TranslationBounds(tw); + return Vec3(tw->dir).LengthSqr() > 0.0f; +} + +int idPolygonModelCollisionDetection::StartTranslationPoint( + idTraceWork* const tw, trace_t* const result, const idVec3& start, + const idVec3& end, const int contentMask, const idVec3& modelOrigin, + const idMat3& modelAxis) { + StartContentsPoint(tw, result, start, contentMask, modelOrigin, modelAxis); + tw->traceType = TRACE_TRANSLATION_POINT; + const idVec3 localEnd = WorldToModelVector(modelAxis, end - modelOrigin); + SetVec4(tw->end, localEnd); + SetVec4(tw->dir, localEnd - Vec3(tw->start)); + SetVec4(tw->negDir, -Vec3(tw->dir)); + tw->vertexEndPosition[0] = tw->end; + tw->fraction = 1.0f; + result->fraction = 1.0f; + TranslationBounds(tw); + return Vec3(tw->dir).LengthSqr() > 0.0f; +} + +void idPolygonModelCollisionDetection::FinishTranslation( + idTraceWork* const tw, const idVec3& start, const idVec3& end, + const idVec3& modelOrigin, const idMat3& modelAxis, + const int modelEntityNum, const int modelPhysicsId, + const int modelBodyId, const int selfId, + const int modelContentsOverride) { + if (tw->traceResult == nullptr) { + return; + } + trace_t& trace = *tw->traceResult; + trace.fraction = tw->fraction; + trace.endpos = start + (end - start) * tw->fraction; + if (trace.fraction >= 1.0f) { + return; + } + if (!IsIdentity(modelAxis)) { + trace.c.normal = ModelToWorldVector(modelAxis, trace.c.normal); + trace.c.point = ModelToWorldVector(modelAxis, trace.c.point); + } + trace.c.point = trace.c.point + modelOrigin; + trace.c.dist += modelOrigin.Dot(trace.c.normal); + trace.c.entityNum = modelEntityNum; + trace.c.physicsId = modelPhysicsId; + trace.c.bodyId = modelBodyId; + trace.c.selfId = selfId; + if (modelContentsOverride != 0 && trace.c.contentFlags != 0) { + trace.c.contentFlags = modelContentsOverride; + } +} diff --git a/source/engine/cm/jobs/polygonmodel/polygonmodeldata.h b/source/engine/cm/jobs/polygonmodel/polygonmodeldata.h new file mode 100644 index 0000000..c329b3c --- /dev/null +++ b/source/engine/cm/jobs/polygonmodel/polygonmodeldata.h @@ -0,0 +1,45 @@ +#pragma once + +#include "cm/collisiontypes.h" +#include "cm/jobs/polygonmodel/polygonmodel_inline.h" + +inline int CM_EdgeIndex(const std::uint16_t edgeReference) { + return edgeReference & 0x7FFF; +} + +inline int CM_EdgeStartVertex(const cm_edge_t& edge, + const std::uint16_t edgeReference) { + return edge.vertexNum[edgeReference >> 15]; +} + +inline int CM_EdgeEndVertex(const cm_edge_t& edge, + const std::uint16_t edgeReference) { + return edge.vertexNum[(edgeReference >> 15) ^ 1]; +} + +// Recovered from engine/cm/jobs/polygonmodel/polygonmodeldata.h. +inline void CM_GetPolygonPlane(const cm_subModelPtrs_t& subModel, + const cm_polygon_t& polygon, idPlane& plane) { + const std::uint16_t firstReference = + subModel.polygonEdges[polygon.firstEdge]; + const std::uint16_t secondReference = + subModel.polygonEdges[polygon.firstEdge + 1]; + const cm_edge_t& firstEdge = + subModel.edges[CM_EdgeIndex(firstReference)]; + const cm_edge_t& secondEdge = + subModel.edges[CM_EdgeIndex(secondReference)]; + const idVec3& firstPoint = + subModel.vertices[CM_EdgeStartVertex(firstEdge, firstReference)].p; + const idVec3& sharedPoint = + subModel.vertices[CM_EdgeStartVertex(secondEdge, secondReference)].p; + const idVec3& secondPoint = + subModel.vertices[CM_EdgeEndVertex(secondEdge, secondReference)].p; + + idVec3 normal = (secondPoint - sharedPoint).Cross( + firstPoint - sharedPoint); + normal.NormalizeFast(); + plane.a = normal.x; + plane.b = normal.y; + plane.c = normal.z; + plane.d = -normal.Dot(sharedPoint); +} diff --git a/source/engine/cm/jobs/spheremodel/spheremodel.cpp b/source/engine/cm/jobs/spheremodel/spheremodel.cpp new file mode 100644 index 0000000..bfac7c4 --- /dev/null +++ b/source/engine/cm/jobs/spheremodel/spheremodel.cpp @@ -0,0 +1,106 @@ +#include "cm/jobs/spheremodel/spheremodel.h" + +#include "cm/jobs/collisionresults.h" +#include "idlib/geometry/jointtransform.h" + +#include +#include +#include + +int idSphereModelCollisionDetection::SetupCollisionSpherePtrs( + const cm_sphereModel_t* const model, + cm_sphereModelPtrs_t& pointers) { + std::uint8_t* const base = reinterpret_cast( + const_cast(model)); + pointers.joint = base + model->jointOffset; + pointers.offsetX = reinterpret_cast(base + model->offsetXOffset); + pointers.offsetY = reinterpret_cast(base + model->offsetYOffset); + pointers.offsetZ = reinterpret_cast(base + model->offsetZOffset); + pointers.radius = reinterpret_cast(base + model->radiusOffset); + pointers.surfType = base + model->surfTypeOffset; + return model->numSpheres; +} + +void idSphereModelCollisionDetection::TraceThroughModel(trace_t& trace, + const cm_sphereModel_t& model, const idVec3& start, const idVec3& end, + const float radius, const idMat3& trmAxis, + const idJointMat* const modelJoints, const idVec3& modelOrigin, + const idMat3& modelAxis, const int modelEntityNum, + const int modelPhysicsId, const int modelBodyId, const int selfId, + const int modelContentsOverride) { + std::memset(&trace, 0, sizeof(trace)); + trace.fraction = 1.0f; + trace.endpos = end; + trace.endAxis = trmAxis; + + cm_sphereModelPtrs_t spheres; + const int numSpheres = SetupCollisionSpherePtrs(&model, spheres); + const idVec3 movement = end - start; + const float movementLengthSqr = movement.LengthSqr(); + for (int sphereNumber = 0; sphereNumber < numSpheres; ++sphereNumber) { + const int jointNumber = spheres.joint[sphereNumber]; + const idVec3 offset(spheres.offsetX[sphereNumber], + spheres.offsetY[sphereNumber], spheres.offsetZ[sphereNumber]); + idVec3 jointSpace = offset; + if (modelJoints != nullptr) { + const idJointMat& joint = modelJoints[jointNumber]; + jointSpace.Set( + joint.mat[0] * offset.x + joint.mat[1] * offset.y + + joint.mat[2] * offset.z + joint.mat[3], + joint.mat[4] * offset.x + joint.mat[5] * offset.y + + joint.mat[6] * offset.z + joint.mat[7], + joint.mat[8] * offset.x + joint.mat[9] * offset.y + + joint.mat[10] * offset.z + joint.mat[11]); + } + const idVec3 sphereCenter = modelOrigin + idVec3( + modelAxis[0].x * jointSpace.x + modelAxis[1].x * jointSpace.y + + modelAxis[2].x * jointSpace.z, + modelAxis[0].y * jointSpace.x + modelAxis[1].y * jointSpace.y + + modelAxis[2].y * jointSpace.z, + modelAxis[0].z * jointSpace.x + modelAxis[1].z * jointSpace.y + + modelAxis[2].z * jointSpace.z); + const float combinedRadius = radius + spheres.radius[sphereNumber]; + const idVec3 relativeStart = start - sphereCenter; + float fraction = 1.0f; + if (relativeStart.LengthSqr() <= combinedRadius * combinedRadius) { + fraction = 0.0f; + } else if (movementLengthSqr > 1.0e-20f) { + const float b = relativeStart.Dot(movement); + const float c = relativeStart.LengthSqr() + - combinedRadius * combinedRadius; + const float discriminant = b * b - movementLengthSqr * c; + if (discriminant >= 0.0f) { + fraction = (-b - std::sqrt(discriminant)) + / movementLengthSqr; + } + } + if (fraction < 0.0f || fraction >= trace.fraction + || fraction > 1.0f) { + continue; + } + trace.fraction = fraction; + trace.endpos = start + movement * fraction; + idVec3 normal = trace.endpos - sphereCenter; + if (normal.NormalizeFast() == 0.0f) { + normal.Set(0.0f, 0.0f, 1.0f); + } + trace.c.type = CONTACT_SPHERE; + trace.c.normal = normal; + trace.c.point = trace.endpos - normal * radius; + trace.c.dist = normal.Dot(trace.c.point); + trace.c.separation = spheres.radius[sphereNumber]; + trace.c.contentFlags = modelContentsOverride != 0 + ? modelContentsOverride : static_cast(model.contents); + trace.c.surfaceFlags = 0; + trace.c.surfaceType = spheres.surfType[sphereNumber]; + trace.c.surfaceColor[0] = trace.c.surfaceColor[1] + = trace.c.surfaceColor[2] = 0xFF; + trace.c.modelFeature = (std::min)(sphereNumber, numSpheres - 1); + trace.c.trmFeature = jointNumber; + trace.c.entityNum = modelEntityNum; + trace.c.physicsId = modelPhysicsId; + trace.c.bodyId = modelBodyId; + trace.c.selfId = selfId; + trace.c.flags = 0; + } +} diff --git a/source/engine/cm/jobs/spheremodel/spheremodel.h b/source/engine/cm/jobs/spheremodel/spheremodel.h index 2faf702..a50d88f 100644 --- a/source/engine/cm/jobs/spheremodel/spheremodel.h +++ b/source/engine/cm/jobs/spheremodel/spheremodel.h @@ -1,35 +1,25 @@ #pragma once -// Reconstructed C++ declarations from IDA Local Types and PDB/DIA metadata. -// Original PDB header: w:\tech5\engine\cm\jobs\spheremodel\spheremodel.h -// Recovered logical types: 2 -// Signatures retain Xbox 360 ABI evidence and may still require manual review. +#include "cm/collisiontypes.h" +struct trace_t; +class idJointMat; -// IDA Local Type ordinal 20008; PDB kind: struct. -struct cm_sphereModel_t -{ - unsigned int totalSize; - unsigned int timeStamp; - idBounds bounds; - unsigned int contents; - unsigned __int16 numModelJoints; - unsigned __int16 numSpheres; - unsigned __int16 jointOffset; - unsigned __int16 offsetXOffset; - unsigned __int16 offsetYOffset; - unsigned __int16 offsetZOffset; - unsigned __int16 radiusOffset; - unsigned __int16 surfTypeOffset; +class idSphereModelCollisionDetection { +public: + static int SetupCollisionSpherePtrs(const cm_sphereModel_t* model, + cm_sphereModelPtrs_t& pointers); + static void TraceThroughModel(trace_t& trace, + const cm_sphereModel_t& model, const idVec3& start, + const idVec3& end, float radius, const idMat3& trmAxis, + const idJointMat* modelJoints, const idVec3& modelOrigin, + const idMat3& modelAxis, int modelEntityNum, int modelPhysicsId, + int modelBodyId, int selfId, int modelContentsOverride); }; -// IDA Local Type ordinal 20016; PDB kind: struct. -struct cm_sphereModelPtrs_t -{ - unsigned __int8 *joint; - float *offsetX; - float *offsetY; - float *offsetZ; - float *radius; - unsigned __int8 *surfType; -}; +#if INTPTR_MAX == INT32_MAX +static_assert(sizeof(cm_sphereModel_t) == 52, + "Recovered cm_sphereModel_t ABI changed"); +static_assert(sizeof(cm_sphereModelPtrs_t) == 24, + "Recovered cm_sphereModelPtrs_t ABI changed"); +#endif