Files
tech5/source/engine/cm/collisionmodelbuilder.cpp
T
2026-08-09 01:29:43 -07:00

3213 lines
120 KiB
C++

#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 <algorithm>
#include <cctype>
#include <cfloat>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <functional>
#include <malloc.h>
#include <string>
#include <vector>
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<unsigned int>(size), TAG_COLLISION, clear, ALIGN_16,
HEAP_DEFAULTHEAP);
}
template<class Type>
Type* GrowBuildArray(Type* const oldArray, const int oldCount,
const int newCapacity) {
Type* const array = static_cast<Type*>(AllocBuildMemory(
sizeof(Type) * static_cast<std::size_t>(newCapacity)));
if (array == nullptr) {
return nullptr;
}
if (oldArray != nullptr && oldCount > 0) {
std::memcpy(array, oldArray,
sizeof(Type) * static_cast<std::size_t>(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<unsigned char>(*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<int>(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<unsigned char>(*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<cm_procNode_t*>(AllocBuildMemory(
sizeof(cm_procNode_t) * static_cast<std::size_t>(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<std::size_t>(blockSize);
cm_buildNodeBlock_t* const block =
static_cast<cm_buildNodeBlock_t*>(
AllocBuildMemory(allocationSize, true));
if (block == nullptr) {
return nullptr;
}
block->size = static_cast<int>(allocationSize);
block->nextNode = reinterpret_cast<cm_buildNode_t*>(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<std::size_t>(blockSize);
cm_buildPolygonRefBlock_t* const block =
static_cast<cm_buildPolygonRefBlock_t*>(
AllocBuildMemory(allocationSize, true));
if (block == nullptr) {
return nullptr;
}
block->size = static_cast<int>(allocationSize);
block->nextRef = reinterpret_cast<cm_buildPolygonRef_t*>(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<std::size_t>(blockSize);
cm_buildPolytopeRefBlock_t* const block =
static_cast<cm_buildPolytopeRefBlock_t*>(
AllocBuildMemory(allocationSize, true));
if (block == nullptr) {
return nullptr;
}
block->size = static_cast<int>(allocationSize);
block->nextRef = reinterpret_cast<cm_buildPolytopeRef_t*>(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<int>(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<int>(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<cm_buildModel_t*>(
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 : "<unnamed>",
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<char>(input)),
std::istreambuf_iterator<char>());
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<int>(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<int>(
vertex.x - buildData.cm_modelBounds[0].x + 0.5f);
const int y = static_cast<int>(
vertex.y - buildData.cm_modelBounds[0].y + 0.5f);
const int z = static_cast<int>(
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<int>(
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<int>(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<int> edgeNumbers;
edgeNumbers.reserve(static_cast<std::size_t>(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<int>(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<std::size_t>(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<cm_buildModel_t*>(
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<std::uint8_t>(
sourceMaterial.index);
destination.firstEdge = static_cast<std::uint16_t>(
counts.numPolygonEdges);
destination.numEdges = static_cast<std::uint8_t>(
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<std::uint16_t>(writable->vertices[
sourceEdge.vertexNum[0]].index);
destinationEdge.vertexNum[1] =
static_cast<std::uint16_t>(writable->vertices[
sourceEdge.vertexNum[1]].index);
}
std::uint16_t packedReference =
static_cast<std::uint16_t>(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<std::uint16_t>(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<std::uint8_t>(
sourceMaterial.index);
destination.firstPlane = static_cast<std::uint16_t>(
counts.numPolytopePlanes);
destination.numPlanes = static_cast<std::uint8_t>(
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<std::uint16_t>(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<std::uint16_t>(
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<std::uint16_t>(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<cm_buildModel_t*>(
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<cm_buildModel_t*>(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<cm_modelTreeNode_t*>(_aligned_malloc(
sizeof(cm_modelTreeNode_t)
* static_cast<std::size_t>(totalTreeNodes), 16));
if (newTree == nullptr) {
return;
}
if (oldTreeNodes > 0) {
std::memcpy(newTree, model->polygonModel.modelTreeNodes,
sizeof(cm_modelTreeNode_t)
* static_cast<std::size_t>(oldTreeNodes));
}
}
cm_subModel_t* const newSubModels =
static_cast<cm_subModel_t*>(_aligned_malloc(
sizeof(cm_subModel_t)
* static_cast<std::size_t>(totalSubModels), 16));
volatile std::uint8_t* const newStates =
static_cast<volatile std::uint8_t*>(_aligned_malloc(
static_cast<std::size_t>(totalSubModels), 16));
if (newSubModels == nullptr || newStates == nullptr) {
_aligned_free(newTree);
_aligned_free(newSubModels);
_aligned_free(const_cast<std::uint8_t*>(newStates));
return;
}
std::memset(newSubModels, 0,
sizeof(cm_subModel_t) * static_cast<std::size_t>(totalSubModels));
if (oldSubModels > 0) {
std::memcpy(newSubModels, model->polygonModel.subModels,
sizeof(cm_subModel_t) * static_cast<std::size_t>(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<std::uint8_t*>(
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<bool(int, idFixedWinding*, bool)> 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<int> rotated(static_cast<std::size_t>(polygon->numEdges));
for (int index = 0; index < polygon->numEdges; ++index) {
rotated[static_cast<std::size_t>(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<idFixedWinding> surviving;
surviving.reserve(static_cast<std::size_t>(list->numWindings));
for (int sourceIndex = 0; sourceIndex < list->numWindings;
++sourceIndex) {
std::vector<idFixedWinding> candidates;
candidates.push_back(list->w[sourceIndex]);
std::vector<idFixedWinding> 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<idFixedWinding> 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<int>((std::min)(surviving.size(),
static_cast<std::size_t>(256)));
for (int index = 0; index < list->numWindings; ++index) {
list->w[index] = surviving[static_cast<std::size_t>(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<cm_buildPolytope_t&>(
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<int> boundary;
boundary.reserve(static_cast<std::size_t>(
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<int> 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<int>(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<std::size_t>(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<int>(
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<int> 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<std::size_t>(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<cm_subModelData_t*>(_aligned_malloc(
static_cast<std::size_t>(checked.totalMemory), 16));
if (subModel.data == nullptr) {
return false;
}
std::memset(subModel.data, 0,
static_cast<std::size_t>(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<int>(traceModel.numPolys) +
(traceModel.isConvex ? 1 : 0);
stats.numMaterials = 1;
stats.numPolygons = static_cast<int>(traceModel.numPolys);
stats.numEdges = static_cast<int>(traceModel.numEdges);
stats.numVertices = static_cast<int>(traceModel.numVerts);
stats.numPolytopes = traceModel.isConvex ? 1 : 0;
stats.numPolytopePlanes = traceModel.isConvex
? static_cast<int>(traceModel.numPolys)
: 0;
for (unsigned int polygon = 0; polygon < traceModel.numPolys;
++polygon) {
stats.numPolygonEdges +=
static_cast<int>(traceModel.numPolyEdges[polygon]);
}
const int polygonEdgePadding = traceModel.numPolys == 0
? 4
: 4 - (static_cast<int>(
traceModel.numPolyEdges[traceModel.numPolys - 1]) & 3);
stats.numPolygonEdges += polygonEdgePadding;
CalculateSubModelDataSize(stats);
if (!stats.canCreateSubModel) {
return false;
}
model->polygonModel.subModels = static_cast<cm_subModel_t*>(
_aligned_malloc(sizeof(cm_subModel_t), 16));
model->polygonModel.subModelState =
static_cast<volatile std::uint8_t*>(_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<cm_subModelData_t*>(
_aligned_malloc(static_cast<std::size_t>(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<std::size_t>(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<std::uint8_t>(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<std::uint16_t>(polygonEdgeCursor);
polygon.numEdges = static_cast<std::uint8_t>(
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<std::uint16_t>(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<std::uint16_t>(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<std::uint8_t>(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<int> selectedParts;
std::vector<int> 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<cm_subModel_t*>(
_aligned_malloc(sizeof(cm_subModel_t), 16));
model->polygonModel.subModelState =
static_cast<volatile std::uint8_t*>(_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<int> 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<std::uint16_t>(primitiveOffset);
destination.numPolygons = static_cast<std::uint8_t>(
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<std::uint16_t>(
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<std::uint16_t>(polygonCursor++);
}
}
}
const std::uint16_t padding = edgeCursor > 0
? pointers.polygonEdges[edgeCursor - 1] : 0;
while (edgeCursor < stats.numPolygonEdges) {
pointers.polygonEdges[edgeCursor++] = padding;
}
return true;
}