#include "cm/collisionmodelbuilder.h" #include "cm/collisiongrid.h" #include "cm/collisionmodel.h" #include "cm/jobs/polygonmodel/polygonmodel.h" #include "cm/jobs/polygonmodel/polygonmodeldata.h" #include "idlib/geometry/tracemodel.h" #include "idlib/containers/hashindex.h" #include "idlib/lib_print.h" #include "idlib/sys/sys_alloc.h" #include "idlib/text/lexer.h" #include #include #include #include #include #include #include #include #include #include namespace { struct cm_procNode_t { idPlane plane; int children[2]; }; struct idBuildData { int numProcNodes = 0; cm_procNode_t* procNodes = nullptr; cm_windingList_t* cm_windingList = nullptr; cm_windingList_t* cm_outList = nullptr; cm_windingList_t* cm_tmpList = nullptr; idHashIndex* cm_vertexHash = nullptr; idHashIndex* cm_edgeHash = nullptr; idBounds cm_modelBounds; int cm_vertexShift = 1; }; idBuildData buildData; int Align(const int value, const int alignment) { return (value + alignment - 1) & ~(alignment - 1); } void* AllocBuildMemory(const std::size_t size, const bool clear = false) { return mem.AllocWithLocation( "engine/cm/collisionmodelbuilder.cpp : TAG_COLLISION", static_cast(size), TAG_COLLISION, clear, ALIGN_16, HEAP_DEFAULTHEAP); } template Type* GrowBuildArray(Type* const oldArray, const int oldCount, const int newCapacity) { Type* const array = static_cast(AllocBuildMemory( sizeof(Type) * static_cast(newCapacity))); if (array == nullptr) { return nullptr; } if (oldArray != nullptr && oldCount > 0) { std::memcpy(array, oldArray, sizeof(Type) * static_cast(oldCount)); } mem.Free(oldArray, ALIGN_16); return array; } void AddBounds(idBounds& destination, const idBounds& source) { for (int axis = 0; axis < 3; ++axis) { destination[0][axis] = (std::min)( destination[0][axis], source[0][axis]); destination[1][axis] = (std::max)( destination[1][axis], source[1][axis]); } } bool BoundsOverlap(const idBounds& first, const idBounds& second, const float epsilon = 0.0f) { for (int axis = 0; axis < 3; ++axis) { if (first[0][axis] > second[1][axis] + epsilon || first[1][axis] < second[0][axis] - epsilon) { return false; } } return true; } int DirectedEdgeStart(const cm_buildModel_t* const model, const int edgeReference) { const cm_buildEdge_t& edge = model->edges[std::abs(edgeReference)]; return edge.vertexNum[edgeReference < 0 ? 1 : 0]; } int DirectedEdgeEnd(const cm_buildModel_t* const model, const int edgeReference) { const cm_buildEdge_t& edge = model->edges[std::abs(edgeReference)]; return edge.vertexNum[edgeReference < 0 ? 0 : 1]; } void CopyPolygonFields(cm_buildPolygon_t& destination, const cm_buildPolygon_t& source) { destination.plane = source.plane; destination.bounds = source.bounds; destination.contents = source.contents; destination.material = source.material; destination.primitiveNum = source.primitiveNum; destination.checkCount = 0; } class ProcTokenScanner { public: ProcTokenScanner(const char* const begin, const char* const end) : current(begin), end(end) { } bool Read(std::string& token) { SkipWhitespace(); token.clear(); if (current == nullptr || current >= end) { return false; } const char first = *current; if (first == '{' || first == '}' || first == '(' || first == ')' || first == '[' || first == ']') { token.push_back(*current++); return true; } if (first == '"') { ++current; while (current < end && *current != '"') { token.push_back(*current++); } if (current < end) { ++current; } return true; } while (current < end && !std::isspace( static_cast(*current)) && std::strchr("{}()[]", *current) == nullptr) { token.push_back(*current++); } return !token.empty(); } bool Expect(const char* const expected) { std::string token; return Read(token) && token == expected; } bool ReadInt(int& value) { std::string token; if (!Read(token)) { return false; } char* endPointer = nullptr; const long parsed = std::strtol(token.c_str(), &endPointer, 10); if (endPointer == token.c_str() || *endPointer != '\0') { return false; } value = static_cast(parsed); return true; } bool ReadFloat(float& value) { std::string token; if (!Read(token)) { return false; } char* endPointer = nullptr; value = std::strtof(token.c_str(), &endPointer); return endPointer != token.c_str() && *endPointer == '\0'; } bool SkipBracedSection() { if (!Expect("{")) { return false; } int depth = 1; std::string token; while (depth > 0 && Read(token)) { if (token == "{") { ++depth; } else if (token == "}") { --depth; } } return depth == 0; } const char* Position() const { return current; } private: void SkipWhitespace() { while (current != nullptr && current < end) { if (std::isspace(static_cast(*current))) { ++current; continue; } if (end - current >= 2 && current[0] == '/' && current[1] == '/') { current += 2; while (current < end && *current != '\n') { ++current; } continue; } if (end - current >= 2 && current[0] == '/' && current[1] == '*') { current += 2; while (end - current >= 2 && !(current[0] == '*' && current[1] == '/')) { ++current; } if (end - current >= 2) { current += 2; } continue; } break; } } const char* current; const char* end; }; bool ParseProcNodesFromScanner(ProcTokenScanner& scanner) { if (!scanner.Expect("{")) { return false; } int numNodes = 0; if (!scanner.ReadInt(numNodes) || numNodes < 0 || numNodes > 0x1000000) { return false; } cm_procNode_t* nodes = nullptr; if (numNodes > 0) { nodes = static_cast(AllocBuildMemory( sizeof(cm_procNode_t) * static_cast(numNodes), true)); if (nodes == nullptr) { return false; } } for (int index = 0; index < numNodes; ++index) { if (!scanner.Expect("(") || !scanner.ReadFloat(nodes[index].plane.a) || !scanner.ReadFloat(nodes[index].plane.b) || !scanner.ReadFloat(nodes[index].plane.c) || !scanner.ReadFloat(nodes[index].plane.d) || !scanner.Expect(")") || !scanner.ReadInt(nodes[index].children[0]) || !scanner.ReadInt(nodes[index].children[1])) { mem.Free(nodes, ALIGN_16); return false; } } if (!scanner.Expect("}")) { mem.Free(nodes, ALIGN_16); return false; } mem.Free(buildData.procNodes, ALIGN_16); buildData.procNodes = nodes; buildData.numProcNodes = numNodes; return true; } } // namespace int CM_R_CountChildren(cm_buildNode_t* const node) { if (node == nullptr || node->planeType == -1) { return 0; } return CM_R_CountChildren(node->children[1]) + CM_R_CountChildren(node->children[0]) + 2; } void CM_R_TestOptimisation(cm_buildNode_t* node, int& numSavedPolygonIndices, int& numSavedPolytopeIndices) { while (node != nullptr && node->planeType != -1) { int numPolygons = 0; int numPolytopes = 0; for (cm_buildPolygonRef_t* reference = node->polygons; reference != nullptr; reference = reference->next) { ++numPolygons; } for (cm_buildPolytopeRef_t* reference = node->polytopes; reference != nullptr; reference = reference->next) { ++numPolytopes; } if (numPolygons != 0 || numPolytopes != 0) { const int savedCopies = CM_R_CountChildren(node->children[1]) + CM_R_CountChildren(node->children[0]) + 1; numSavedPolygonIndices += savedCopies * numPolygons; numSavedPolytopeIndices += savedCopies * numPolytopes; } CM_R_TestOptimisation(node->children[0], numSavedPolygonIndices, numSavedPolytopeIndices); node = node->children[1]; } } bool CM_R_InsideAllChildren(cm_buildNode_t* const node, const idBounds& bounds) { if (node == nullptr || node->planeType == -1) { return true; } const int axis = node->planeType; if (bounds[0][axis] >= node->planeDist || bounds[1][axis] <= node->planeDist) { return false; } return CM_R_InsideAllChildren(node->children[0], bounds) && CM_R_InsideAllChildren(node->children[1], bounds); } cm_buildNode_t* idCollisionModelBuilder::AllocNode( cm_buildModel_t* const model, int blockSize) { if (model == nullptr) { return nullptr; } blockSize = (std::max)(1, blockSize); if (model->nodeBlocks == nullptr || model->nodeBlocks->nextNode == nullptr) { const std::size_t allocationSize = sizeof(cm_buildNodeBlock_t) + sizeof(cm_buildNode_t) * static_cast(blockSize); cm_buildNodeBlock_t* const block = static_cast( AllocBuildMemory(allocationSize, true)); if (block == nullptr) { return nullptr; } block->size = static_cast(allocationSize); block->nextNode = reinterpret_cast(block + 1); block->next = model->nodeBlocks; model->nodeBlocks = block; for (int index = 0; index + 1 < blockSize; ++index) { block->nextNode[index].parent = &block->nextNode[index + 1]; } block->nextNode[blockSize - 1].parent = nullptr; } cm_buildNode_t* const node = model->nodeBlocks->nextNode; model->nodeBlocks->nextNode = node->parent; std::memset(node, 0, sizeof(*node)); node->planeType = -1; ++model->numNodes; return node; } cm_buildPolygonRef_t* idCollisionModelBuilder::AllocPolygonReference( cm_buildModel_t* const model, int blockSize) { if (model == nullptr) { return nullptr; } blockSize = (std::max)(1, blockSize); if (model->polygonRefBlocks == nullptr || model->polygonRefBlocks->nextRef == nullptr) { const std::size_t allocationSize = sizeof(cm_buildPolygonRefBlock_t) + sizeof(cm_buildPolygonRef_t) * static_cast(blockSize); cm_buildPolygonRefBlock_t* const block = static_cast( AllocBuildMemory(allocationSize, true)); if (block == nullptr) { return nullptr; } block->size = static_cast(allocationSize); block->nextRef = reinterpret_cast(block + 1); block->next = model->polygonRefBlocks; model->polygonRefBlocks = block; for (int index = 0; index + 1 < blockSize; ++index) { block->nextRef[index].next = &block->nextRef[index + 1]; } block->nextRef[blockSize - 1].next = nullptr; } cm_buildPolygonRef_t* const reference = model->polygonRefBlocks->nextRef; model->polygonRefBlocks->nextRef = reference->next; reference->next = nullptr; return reference; } cm_buildPolytopeRef_t* idCollisionModelBuilder::AllocPolytopeReference( cm_buildModel_t* const model, int blockSize) { if (model == nullptr) { return nullptr; } blockSize = (std::max)(1, blockSize); if (model->polytopeRefBlocks == nullptr || model->polytopeRefBlocks->nextRef == nullptr) { const std::size_t allocationSize = sizeof(cm_buildPolytopeRefBlock_t) + sizeof(cm_buildPolytopeRef_t) * static_cast(blockSize); cm_buildPolytopeRefBlock_t* const block = static_cast( AllocBuildMemory(allocationSize, true)); if (block == nullptr) { return nullptr; } block->size = static_cast(allocationSize); block->nextRef = reinterpret_cast(block + 1); block->next = model->polytopeRefBlocks; model->polytopeRefBlocks = block; for (int index = 0; index + 1 < blockSize; ++index) { block->nextRef[index].next = &block->nextRef[index + 1]; } block->nextRef[blockSize - 1].next = nullptr; } cm_buildPolytopeRef_t* const reference = model->polytopeRefBlocks->nextRef; model->polytopeRefBlocks->nextRef = reference->next; reference->next = nullptr; return reference; } cm_buildPolygon_t* idCollisionModelBuilder::AllocPolygon( cm_buildModel_t* const model, const int numEdges) { if (model == nullptr || numEdges < 0) { return nullptr; } if (model->numPolygons + 1 > model->maxPolygons) { const int capacity = model->maxPolygons + 1024; cm_buildPolygon_t* const array = GrowBuildArray( model->polygons, model->numPolygons, capacity); if (array == nullptr) { return nullptr; } model->polygons = array; model->maxPolygons = capacity; } if (model->numPolygonEdges + numEdges > model->maxPolygonEdges) { int capacity = model->maxPolygonEdges; do { capacity += 1024; } while (capacity < model->numPolygonEdges + numEdges); int* const array = GrowBuildArray(model->polygonEdges, model->numPolygonEdges, capacity); if (array == nullptr) { return nullptr; } model->polygonEdges = array; model->maxPolygonEdges = capacity; } cm_buildPolygon_t* const polygon = &model->polygons[model->numPolygons++]; std::memset(polygon, 0, sizeof(*polygon)); polygon->numEdges = numEdges; polygon->firstEdge = model->numPolygonEdges; model->numPolygonEdges += numEdges; return polygon; } cm_buildPolytope_t* idCollisionModelBuilder::AllocPolytope( cm_buildModel_t* const model, const int numPlanes) { if (model == nullptr || numPlanes < 0) { return nullptr; } if (model->numPolytopes + 1 > model->maxPolytopes) { int growth = model->maxPolytopes > 256 ? 256 : model->maxPolytopes; if (growth == 0) { growth = 4; } const int capacity = model->maxPolytopes + growth; cm_buildPolytope_t* const array = GrowBuildArray( model->polytopes, model->numPolytopes, capacity); if (array == nullptr) { return nullptr; } model->polytopes = array; model->maxPolytopes = capacity; } if (model->numPolytopePlanes + numPlanes > model->maxPolytopePlanes) { int growth = model->maxPolytopePlanes > 256 ? 256 : model->maxPolytopePlanes; if (growth == 0) { growth = 8; } int capacity = model->maxPolytopePlanes; do { capacity += growth; growth = (std::min)(growth * 2, 256); } while (capacity < model->numPolytopePlanes + numPlanes); idPlane* const array = GrowBuildArray(model->polytopePlanes, model->numPolytopePlanes, capacity); if (array == nullptr) { return nullptr; } model->polytopePlanes = array; model->maxPolytopePlanes = capacity; } cm_buildPolytope_t* const polytope = &model->polytopes[model->numPolytopes++]; std::memset(polytope, 0, sizeof(*polytope)); polytope->numPlanes = numPlanes; polytope->firstPlane = model->numPolytopePlanes; model->numPolytopePlanes += numPlanes; return polytope; } void idCollisionModelBuilder::AddPolygonToNode(cm_buildModel_t* const model, cm_buildNode_t* const node, cm_buildPolygon_t* const polygon) { if (model == nullptr || node == nullptr || polygon == nullptr) { return; } const int blockSize = model->numPolygonRefs < 8 ? 8 : 256; cm_buildPolygonRef_t* const reference = AllocPolygonReference(model, blockSize); if (reference != nullptr) { reference->polygonNum = static_cast(polygon - model->polygons); reference->next = node->polygons; node->polygons = reference; ++model->numPolygonRefs; } } void idCollisionModelBuilder::AddPolytopeToNode( cm_buildModel_t* const model, cm_buildNode_t* const node, cm_buildPolytope_t* const polytope) { if (model == nullptr || node == nullptr || polytope == nullptr) { return; } const int blockSize = model->numPolytopeRefs < 8 ? 8 : 256; cm_buildPolytopeRef_t* const reference = AllocPolytopeReference(model, blockSize); if (reference != nullptr) { reference->polytopeNum = static_cast(polytope - model->polytopes); reference->next = node->polytopes; node->polytopes = reference; ++model->numPolytopeRefs; } } void idCollisionModelBuilder::GetPrimitiveCounts( const cm_buildNode_t* const node, int& polygonCount, int& polytopeCount) { polygonCount = 0; polytopeCount = 0; if (node == nullptr) { return; } for (const cm_buildPolygonRef_t* reference = node->polygons; reference != nullptr; reference = reference->next) { ++polygonCount; } for (const cm_buildPolytopeRef_t* reference = node->polytopes; reference != nullptr; reference = reference->next) { ++polytopeCount; } } int idCollisionModelBuilder::GetNodeContents( const cm_buildModel_t* const model, const cm_buildNode_t* node) { if (model == nullptr || node == nullptr) { return 0; } int contents = 0; while (node != nullptr) { for (const cm_buildPolygonRef_t* reference = node->polygons; reference != nullptr; reference = reference->next) { contents |= model->polygons[reference->polygonNum].contents; } for (const cm_buildPolytopeRef_t* reference = node->polytopes; reference != nullptr; reference = reference->next) { contents |= model->polytopes[reference->polytopeNum].contents; } if (node->planeType == -1) { break; } contents |= GetNodeContents(model, node->children[1]); node = node->children[0]; } return contents; } void idCollisionModelBuilder::FindSubModels_r( const cm_buildModel_t*, cm_buildNode_t* buildNode, int& numModelTreeNodes, int& numSubModels) { while (buildNode != nullptr) { if (buildNode->stats.canCreateSubModel) { ++numSubModels; return; } ++numModelTreeNodes; if (buildNode->planeType == -1) { return; } FindSubModels_r(nullptr, buildNode->children[0], numModelTreeNodes, numSubModels); buildNode = buildNode->children[1]; } } void idCollisionModelBuilder::FreeModelMemory(cm_buildModel_t* const model) { if (model == nullptr) { return; } while (model->polygonRefBlocks != nullptr) { cm_buildPolygonRefBlock_t* const block = model->polygonRefBlocks; model->polygonRefBlocks = block->next; mem.Free(block, ALIGN_16); } while (model->polytopeRefBlocks != nullptr) { cm_buildPolytopeRefBlock_t* const block = model->polytopeRefBlocks; model->polytopeRefBlocks = block->next; mem.Free(block, ALIGN_16); } while (model->nodeBlocks != nullptr) { cm_buildNodeBlock_t* const block = model->nodeBlocks; model->nodeBlocks = block->next; mem.Free(block, ALIGN_16); } mem.Free(model->polygonEdges, ALIGN_16); mem.Free(model->polygons, ALIGN_16); mem.Free(model->polytopePlanes, ALIGN_16); mem.Free(model->polytopes, ALIGN_16); mem.Free(model->edges, ALIGN_16); mem.Free(model->vertices, ALIGN_16); model->maxVertices = model->numVertices = 0; model->vertices = nullptr; model->maxEdges = model->numEdges = 0; model->edges = nullptr; model->maxPolygonEdges = model->numPolygonEdges = 0; model->polygonEdges = nullptr; model->maxPolygons = model->numPolygons = 0; model->polygons = nullptr; model->maxPolytopePlanes = model->numPolytopePlanes = 0; model->polytopePlanes = nullptr; model->maxPolytopes = model->numPolytopes = 0; model->polytopes = nullptr; model->numNodes = 0; model->node = nullptr; model->nodeBlocks = nullptr; model->polygonRefBlocks = nullptr; model->polytopeRefBlocks = nullptr; model->checkCount = 0; model->isWorldModel = false; model->numPolytopeRefs = model->numPolygonRefs = 0; model->numInternalEdges = model->numSharpEdges = 0; model->numRemovedPolys = model->numMergedPolys = 0; } void idCollisionModelBuilder::RemovePolygon(cm_buildModel_t* const model, cm_buildNode_t* node, const int polygonNum) { if (model == nullptr || node == nullptr || polygonNum < 0 || polygonNum >= model->numPolygons) { return; } while (node != nullptr) { cm_buildPolygonRef_t* previous = nullptr; cm_buildPolygonRef_t* reference = node->polygons; while (reference != nullptr) { cm_buildPolygonRef_t* const next = reference->next; if (reference->polygonNum == polygonNum) { if (previous != nullptr) { previous->next = next; } else { node->polygons = next; } --model->numPolygonRefs; } else { previous = reference; } reference = next; } if (node->planeType == -1) { return; } const cm_buildPolygon_t& polygon = model->polygons[polygonNum]; const int axis = node->planeType; if (polygon.bounds[0][axis] <= node->planeDist) { if (polygon.bounds[1][axis] >= node->planeDist) { RemovePolygon(model, node->children[1], polygonNum); node = node->children[0]; } else { node = node->children[1]; } } else { node = node->children[0]; } } } bool idCollisionModelBuilder::PointInsidePolygon( cm_buildModel_t* const model, cm_buildPolygon_t* const polygon, const idVec3& point) { if (model == nullptr || polygon == nullptr) { return false; } const idVec3 normal(polygon->plane.a, polygon->plane.b, polygon->plane.c); for (int edgeIndex = 0; edgeIndex < polygon->numEdges; ++edgeIndex) { const int reference = model->polygonEdges[polygon->firstEdge + edgeIndex]; const cm_buildEdge_t& edge = model->edges[std::abs(reference)]; const int startVertex = reference < 0 ? edge.vertexNum[1] : edge.vertexNum[0]; const int endVertex = reference < 0 ? edge.vertexNum[0] : edge.vertexNum[1]; const idVec3& start = model->vertices[startVertex].p; const idVec3& end = model->vertices[endVertex].p; const idVec3 delta = end - start; const idVec3 relative = point - start; const float side = (normal.x * delta.z - normal.z * delta.x) * relative.y + (normal.z * delta.y - normal.y * delta.z) * relative.x + (normal.y * delta.x - normal.x * delta.y) * relative.z; if (side > 0.1f) { return false; } } return true; } bool idCollisionModelBuilder::SplitterDividesPrimitives( cm_buildModel_t* const model, const cm_buildNode_t* node, const int planeType, const float planeDist) { if (model == nullptr || node == nullptr || planeType < 0 || planeType >= 3) { return false; } bool front = false; bool back = false; while (node != nullptr) { for (const cm_buildPolygonRef_t* reference = node->polygons; reference != nullptr; reference = reference->next) { const idBounds& bounds = model->polygons[reference->polygonNum].bounds; if (bounds[0][planeType] < planeDist) { if (bounds[1][planeType] <= planeDist) { back = true; } } else { front = true; } } for (const cm_buildPolytopeRef_t* reference = node->polytopes; reference != nullptr; reference = reference->next) { const idBounds& bounds = model->polytopes[reference->polytopeNum].bounds; if (bounds[0][planeType] < planeDist) { if (bounds[1][planeType] <= planeDist) { back = true; } } else { front = true; } } if (front && back) { return true; } node = node->parent; } return false; } void idCollisionModelBuilder::GetNodeBounds_r( const cm_buildModel_t* const model, const cm_buildNode_t* node, idBounds& bounds) { if (model == nullptr || node == nullptr) { return; } while (node != nullptr) { for (const cm_buildPolygonRef_t* reference = node->polygons; reference != nullptr; reference = reference->next) { AddBounds(bounds, model->polygons[reference->polygonNum].bounds); } for (const cm_buildPolytopeRef_t* reference = node->polytopes; reference != nullptr; reference = reference->next) { AddBounds(bounds, model->polytopes[reference->polytopeNum].bounds); } if (node->planeType == -1) { return; } GetNodeBounds_r(model, node->children[1], bounds); node = node->children[0]; } } void idCollisionModelBuilder::GetNodeBounds( const cm_buildModel_t* const model, const cm_buildNode_t* const node, idBounds& bounds) { bounds[0].Set(1.0e30f, 1.0e30f, 1.0e30f); bounds[1].Set(-1.0e30f, -1.0e30f, -1.0e30f); GetNodeBounds_r(model, node, bounds); if (bounds[0].x > bounds[1].x) { bounds[0].Zero(); bounds[1].Zero(); } } void idCollisionModelBuilder::GetStatsFromNode( const cm_buildModel_t* const buildModel, const cm_buildNode_t* const buildNode, cm_buildNodeStats_t& stats) { if (buildModel == nullptr || buildNode == nullptr) { return; } cm_buildModel_t* const writable = const_cast( buildModel); for (const cm_buildPolygonRef_t* reference = buildNode->polygons; reference != nullptr; reference = reference->next) { ++stats.numPrimitiveIndices; cm_buildPolygon_t& polygon = writable->polygons[reference->polygonNum]; if (polygon.checkCount == writable->checkCount) { continue; } polygon.checkCount = writable->checkCount; ++stats.numPolygons; stats.numPolygonEdges += polygon.numEdges; stats.lastNumPolygonEdges = polygon.numEdges; for (int index = 0; index < polygon.numEdges; ++index) { cm_buildEdge_t& edge = writable->edges[std::abs( writable->polygonEdges[polygon.firstEdge + index])]; if (edge.checkCount == writable->checkCount) { continue; } edge.checkCount = writable->checkCount; ++stats.numEdges; for (int endpoint = 0; endpoint < 2; ++endpoint) { cm_buildVertex_t& vertex = writable->vertices[edge.vertexNum[endpoint]]; if (vertex.checkCount != writable->checkCount) { vertex.checkCount = writable->checkCount; ++stats.numVertices; } } } cm_buildMaterial_t& material = writable->materials[polygon.material]; if (material.checkCount != writable->checkCount) { material.checkCount = writable->checkCount; ++stats.numMaterials; } } for (const cm_buildPolytopeRef_t* reference = buildNode->polytopes; reference != nullptr; reference = reference->next) { ++stats.numPrimitiveIndices; cm_buildPolytope_t& polytope = writable->polytopes[reference->polytopeNum]; if (polytope.checkCount == writable->checkCount) { continue; } polytope.checkCount = writable->checkCount; ++stats.numPolytopes; stats.numPolytopePlanes += polytope.numPlanes; cm_buildMaterial_t& material = writable->materials[polytope.material]; if (material.checkCount != writable->checkCount) { material.checkCount = writable->checkCount; ++stats.numMaterials; } } } void idCollisionModelBuilder::CreateStatsForSubTree_r( const cm_buildModel_t* const buildModel, const cm_buildNode_t* buildNode, cm_buildNodeStats_t& stats) { while (buildNode != nullptr) { ++stats.numNodes; GetStatsFromNode(buildModel, buildNode, stats); if (buildNode->planeType == -1) { return; } CreateStatsForSubTree_r(buildModel, buildNode->children[0], stats); buildNode = buildNode->children[1]; } } bool idCollisionModelBuilder::TestBoundsRange(const char* const modelName, const idBounds& bounds) { for (int axis = 0; axis < 3; ++axis) { if (bounds[0][axis] < -32768.0f || bounds[1][axis] > 32767.0f) { idLibPrint::Warning( "model '%s' [%1.0f, %1.0f, %1.0f] - " "[%1.0f, %1.0f, %1.0f] out of range", modelName != nullptr ? modelName : "", bounds[0].x, bounds[0].y, bounds[0].z, bounds[1].x, bounds[1].y, bounds[1].z); return true; } } return false; } void idCollisionModelBuilder::ParseProcNodes(idLexer* const source) { if (source == nullptr || source->script_p == nullptr || source->end_p == nullptr || source->script_p > source->end_p) { idLibPrint::Warning("ParseProcNodes: invalid lexer source"); return; } ProcTokenScanner scanner(source->script_p, source->end_p); if (!ParseProcNodesFromScanner(scanner)) { idLibPrint::Warning("ParseProcNodes: malformed proc node block"); return; } source->script_p = scanner.Position(); } void idCollisionModelBuilder::LoadProcBSP(const char* const name) { mem.Free(buildData.procNodes, ALIGN_16); buildData.procNodes = nullptr; buildData.numProcNodes = 0; if (name == nullptr || *name == '\0') { return; } std::string fileName(name); const std::size_t slash = fileName.find_last_of("/\\"); const std::size_t dot = fileName.find_last_of('.'); if (dot == std::string::npos || (slash != std::string::npos && dot < slash)) { fileName += ".proc"; } else { fileName.replace(dot, std::string::npos, ".proc"); } std::ifstream input(fileName, std::ios::binary); if (!input) { idLibPrint::Warning( "idCollisionModelBuilder::LoadProcBSP: couldn't load %s", fileName.c_str()); return; } std::string text((std::istreambuf_iterator(input)), std::istreambuf_iterator()); ProcTokenScanner scanner(text.data(), text.data() + text.size()); std::string token; if (!scanner.Read(token) || (token != "mapProcFile006" && token != "mapProcFile005")) { idLibPrint::Warning( "idCollisionModelBuilder::LoadProcBSP: bad proc file id"); return; } while (scanner.Read(token)) { if (token == "nodes") { if (!ParseProcNodesFromScanner(scanner)) { idLibPrint::Warning( "idCollisionModelBuilder::LoadProcBSP: malformed nodes"); } return; } if (token == "model" || token == "shadowModel" || token == "interAreaPortals" || token == "areas") { if (!scanner.SkipBracedSection()) { break; } } } idLibPrint::Warning( "idCollisionModelBuilder::LoadProcBSP: nodes section not found"); } void idCollisionModelBuilder::SetupHash() { if (buildData.cm_vertexHash == nullptr) { buildData.cm_vertexHash = new idHashIndex(4096, 1024, TAG_COLLISION); } if (buildData.cm_edgeHash == nullptr) { buildData.cm_edgeHash = new idHashIndex(0x4000, 1024, TAG_COLLISION); } if (buildData.cm_windingList == nullptr) { buildData.cm_windingList = new cm_windingList_t{}; } if (buildData.cm_outList == nullptr) { buildData.cm_outList = new cm_windingList_t{}; } if (buildData.cm_tmpList == nullptr) { buildData.cm_tmpList = new cm_windingList_t{}; } } void idCollisionModelBuilder::ClearHash(const idBounds& bounds) { SetupHash(); buildData.cm_vertexHash->Clear(); buildData.cm_edgeHash->Clear(); buildData.cm_modelBounds = bounds; const float maximumExtent = (std::max)(bounds[1].x - bounds[0].x, (std::max)(bounds[1].y - bounds[0].y, bounds[1].z - bounds[0].z)); const int target = static_cast(maximumExtent * (1.0f / 64.0f)); int shift = 1; int power = 2; while (power < target) { power <<= 1; ++shift; } buildData.cm_vertexShift = shift; } void idCollisionModelBuilder::ShutdownHash() { delete buildData.cm_vertexHash; buildData.cm_vertexHash = nullptr; delete buildData.cm_edgeHash; buildData.cm_edgeHash = nullptr; delete buildData.cm_tmpList; buildData.cm_tmpList = nullptr; delete buildData.cm_outList; buildData.cm_outList = nullptr; delete buildData.cm_windingList; buildData.cm_windingList = nullptr; mem.Free(buildData.procNodes, ALIGN_16); buildData.procNodes = nullptr; buildData.numProcNodes = 0; } bool idCollisionModelBuilder::GetVertex(cm_buildModel_t* const model, const idVec3& inputVertex, int& vertexNum) { if (model == nullptr) { vertexNum = -1; return false; } if (buildData.cm_vertexHash == nullptr) { SetupHash(); } idVec3 vertex = inputVertex; for (int axis = 0; axis < 3; ++axis) { const float rounded = std::floor(vertex[axis] + 0.5f); if (std::fabs(vertex[axis] - rounded) < 0.01f) { vertex[axis] = rounded; } } const int x = static_cast( vertex.x - buildData.cm_modelBounds[0].x + 0.5f); const int y = static_cast( vertex.y - buildData.cm_modelBounds[0].y + 0.5f); const int z = static_cast( vertex.z - buildData.cm_modelBounds[0].z + 0.5f); const int key = ((((y + 2) >> 2) << 6) + ((z + 2) >> 2) + ((x + 2) >> 2)) & 0xFFF; for (int index = buildData.cm_vertexHash->First(key); index >= 0; index = buildData.cm_vertexHash->Next(index)) { const idVec3& existing = model->vertices[index].p; if (std::fabs(vertex.x - existing.x) < 0.1f && std::fabs(vertex.y - existing.y) < 0.1f && std::fabs(vertex.z - existing.z) < 0.1f) { vertexNum = index; return true; } } if (model->numVertices >= model->maxVertices) { const int capacity = static_cast( model->maxVertices * 1.5f + 1.0f); cm_buildVertex_t* const array = GrowBuildArray(model->vertices, model->numVertices, capacity); if (array == nullptr) { vertexNum = -1; return false; } model->vertices = array; model->maxVertices = capacity; buildData.cm_vertexHash->ResizeIndex(capacity); } vertexNum = model->numVertices; cm_buildVertex_t& destination = model->vertices[model->numVertices++]; std::memset(&destination, 0, sizeof(destination)); destination.p = vertex; buildData.cm_vertexHash->Add(key, vertexNum); return false; } bool idCollisionModelBuilder::GetEdge(cm_buildModel_t* const model, const idVec3& vertex1, const idVec3& vertex2, int& edgeNum, int vertex1Num) { if (model == nullptr) { edgeNum = 0; return false; } if (buildData.cm_edgeHash == nullptr) { SetupHash(); } if (model->numEdges == 0) { model->numEdges = 1; } bool firstExisted = true; if (vertex1Num == -1) { firstExisted = GetVertex(model, vertex1, vertex1Num); } int vertex2Num = -1; const bool secondExisted = GetVertex(model, vertex2, vertex2Num); if (vertex1Num == vertex2Num) { edgeNum = 0; return true; } const int key = (vertex1Num + vertex2Num) & buildData.cm_edgeHash->hashMask; if (firstExisted && secondExisted) { for (int index = buildData.cm_edgeHash->First(key); index >= 0; index = buildData.cm_edgeHash->Next(index)) { cm_buildEdge_t& edge = model->edges[index]; if (edge.numUsers == 1 && edge.vertexNum[0] == vertex2Num && edge.vertexNum[1] == vertex1Num) { edgeNum = -index; ++edge.numUsers; return true; } } } if (model->numEdges >= model->maxEdges) { const int capacity = (std::max)(model->numEdges + 1, static_cast(model->maxEdges * 1.5f + 1.0f)); cm_buildEdge_t* const array = GrowBuildArray(model->edges, model->numEdges, capacity); if (array == nullptr) { edgeNum = 0; return false; } model->edges = array; model->maxEdges = capacity; buildData.cm_edgeHash->ResizeIndex(capacity); } edgeNum = model->numEdges; cm_buildEdge_t& edge = model->edges[model->numEdges++]; std::memset(&edge, 0, sizeof(edge)); edge.vertexNum[0] = vertex1Num; edge.vertexNum[1] = vertex2Num; edge.numUsers = 1; buildData.cm_edgeHash->Add(key, edgeNum); return false; } cm_buildModel_t* idCollisionModelBuilder::AllocBuildModel() { cm_buildModel_t* const model = new cm_buildModel_t{}; model->isWorldModel = false; model->checkCount = 0; model->maxVertices = model->numVertices = 0; model->vertices = nullptr; model->maxEdges = model->numEdges = 0; model->edges = nullptr; model->maxPolygonEdges = model->numPolygonEdges = 0; model->polygonEdges = nullptr; model->maxPolygons = model->numPolygons = 0; model->polygons = nullptr; model->maxPolytopePlanes = model->numPolytopePlanes = 0; model->polytopePlanes = nullptr; model->maxPolytopes = model->numPolytopes = 0; model->polytopes = nullptr; model->numNodes = 0; model->node = nullptr; model->nodeBlocks = nullptr; model->polygonRefBlocks = nullptr; model->polytopeRefBlocks = nullptr; model->numPolytopeRefs = model->numPolygonRefs = 0; model->numInternalEdges = model->numSharpEdges = 0; model->numRemovedPolys = model->numMergedPolys = 0; return model; } int idCollisionModelBuilder::FindMaterial(cm_buildModel_t* const model, const int contentFlags, const int surfaceFlags, const int surfaceType) { if (model == nullptr) { return -1; } for (int index = 0; index < model->materials.Num(); ++index) { const cm_buildMaterial_t& material = model->materials[index]; if (material.contentFlags == contentFlags && material.surfaceFlags == surfaceFlags && material.surfaceType == surfaceType) { return index; } } cm_buildMaterial_t material{}; material.contentFlags = contentFlags; material.surfaceFlags = surfaceFlags; material.surfaceType = surfaceType; return model->materials.Append(material); } bool idCollisionModelBuilder::IsStaticRenderModel( const char* const fileName) { if (fileName == nullptr) { return false; } const char* extension = std::strrchr(fileName, '.'); if (extension == nullptr) { return false; } ++extension; return _stricmp(extension, "ase") == 0 || _stricmp(extension, "lwo") == 0 || _stricmp(extension, "obj") == 0 || _stricmp(extension, "model") == 0 || _stricmp(extension, "bmodel") == 0; } void idCollisionModelBuilder::FilterPolygonIntoTree_r( cm_buildModel_t* const model, cm_buildNode_t* node, cm_buildPolygonRef_t* const reference, cm_buildPolygon_t* const polygon) { if (model == nullptr || node == nullptr || polygon == nullptr) { return; } while (node->planeType != -1) { if (CM_R_InsideAllChildren(node, polygon->bounds)) { break; } const int axis = node->planeType; if (polygon->bounds[0][axis] >= node->planeDist) { node = node->children[0]; } else if (polygon->bounds[1][axis] <= node->planeDist) { node = node->children[1]; } else { FilterPolygonIntoTree_r(model, node->children[1], nullptr, polygon); node = node->children[0]; } if (node == nullptr) { return; } } if (reference != nullptr) { reference->next = node->polygons; node->polygons = reference; } else { AddPolygonToNode(model, node, polygon); } } void idCollisionModelBuilder::FilterPolytopeIntoTree_r( cm_buildModel_t* const model, cm_buildNode_t* node, cm_buildPolytopeRef_t* const reference, cm_buildPolytope_t* const polytope) { if (model == nullptr || node == nullptr || polytope == nullptr) { return; } while (node->planeType != -1) { if (CM_R_InsideAllChildren(node, polytope->bounds)) { break; } const int axis = node->planeType; if (polytope->bounds[0][axis] >= node->planeDist) { node = node->children[0]; } else if (polytope->bounds[1][axis] <= node->planeDist) { node = node->children[1]; } else { FilterPolytopeIntoTree_r(model, node->children[1], nullptr, polytope); node = node->children[0]; } if (node == nullptr) { return; } } if (reference != nullptr) { reference->next = node->polytopes; node->polytopes = reference; } else { AddPolytopeToNode(model, node, polytope); } } bool idCollisionModelBuilder::FindSplitter(cm_buildModel_t* const model, const cm_buildNode_t* const node, const idBounds& bounds, int& planeType, float& planeDist) { if (model == nullptr || node == nullptr) { return false; } int polygonCount = 0; int polytopeCount = 0; GetPrimitiveCounts(node, polygonCount, polytopeCount); if (polygonCount <= 4 && polytopeCount <= 4) { return false; } int axes[3] = {0, 1, 2}; std::sort(axes, axes + 3, [&bounds](const int left, const int right) { return bounds[1][left] - bounds[0][left] > bounds[1][right] - bounds[0][right]; }); const bool dense = polygonCount >= 32 || polytopeCount >= 32; float bestBalance = FLT_MAX; bool found = false; for (const int axis : axes) { const float extent = bounds[1][axis] - bounds[0][axis]; if (!dense && extent < 128.0f) { continue; } for (const cm_buildNode_t* source = node; source != nullptr; source = source->parent) { for (const cm_buildPolytopeRef_t* reference = source->polytopes; reference != nullptr; reference = reference->next) { const idBounds& primitiveBounds = model->polytopes[reference->polytopeNum].bounds; for (int side = 0; side < 2; ++side) { const float candidate = primitiveBounds[side][axis]; const float balance = std::fabs( (bounds[1][axis] - candidate) - (candidate - bounds[0][axis])); if (candidate > bounds[0][axis] && candidate < bounds[1][axis] && balance < bestBalance && SplitterDividesPrimitives(model, node, axis, candidate)) { planeType = axis; planeDist = candidate; bestBalance = balance; found = true; } } } for (const cm_buildPolygonRef_t* reference = source->polygons; reference != nullptr; reference = reference->next) { const idBounds& primitiveBounds = model->polygons[reference->polygonNum].bounds; for (int side = 0; side < 2; ++side) { const float candidate = primitiveBounds[side][axis]; const float balance = std::fabs( (bounds[1][axis] - candidate) - (candidate - bounds[0][axis])); if (candidate > bounds[0][axis] && candidate < bounds[1][axis] && balance < bestBalance && SplitterDividesPrimitives(model, node, axis, candidate)) { planeType = axis; planeDist = candidate; bestBalance = balance; found = true; } } } } if (found && (dense || (planeDist - bounds[0][axis] >= 32.0f && bounds[1][axis] - planeDist >= 32.0f))) { return true; } } return false; } cm_buildNode_t* idCollisionModelBuilder::CreateAxialBSPTree_r( cm_buildModel_t* const model, cm_buildNode_t* const node) { if (model == nullptr || node == nullptr) { return node; } int planeType = -1; float planeDist = 0.0f; if (!FindSplitter(model, node, node->bounds, planeType, planeDist)) { int polygonCount = 0; int polytopeCount = 0; GetPrimitiveCounts(node, polygonCount, polytopeCount); if (polygonCount > 255) { idLibPrint::Warning("node has %d polygons", polygonCount); } if (polytopeCount > 255) { idLibPrint::Warning("node has %d polytopes", polytopeCount); } node->planeType = -1; return node; } cm_buildNode_t* const front = AllocNode(model, 256); cm_buildNode_t* const back = AllocNode(model, 256); if (front == nullptr || back == nullptr) { node->planeType = -1; return node; } front->parent = node; back->parent = node; front->bounds = node->bounds; back->bounds = node->bounds; front->bounds[0][planeType] = planeDist; back->bounds[1][planeType] = planeDist; node->planeType = planeType; node->planeDist = planeDist; node->children[0] = front; node->children[1] = back; cm_buildPolygonRef_t* polygonReference = node->polygons; node->polygons = nullptr; while (polygonReference != nullptr) { cm_buildPolygonRef_t* const next = polygonReference->next; polygonReference->next = nullptr; FilterPolygonIntoTree_r(model, node, polygonReference, &model->polygons[polygonReference->polygonNum]); polygonReference = next; } cm_buildPolytopeRef_t* polytopeReference = node->polytopes; node->polytopes = nullptr; while (polytopeReference != nullptr) { cm_buildPolytopeRef_t* const next = polytopeReference->next; polytopeReference->next = nullptr; FilterPolytopeIntoTree_r(model, node, polytopeReference, &model->polytopes[polytopeReference->polytopeNum]); polytopeReference = next; } CreateAxialBSPTree_r(model, front); CreateAxialBSPTree_r(model, back); return node; } cm_buildNode_t* idCollisionModelBuilder::CreateAxialBSPTree( cm_buildModel_t* const model) { if (model == nullptr) { return nullptr; } while (model->polygonRefBlocks != nullptr) { cm_buildPolygonRefBlock_t* const block = model->polygonRefBlocks; model->polygonRefBlocks = block->next; mem.Free(block, ALIGN_16); } while (model->polytopeRefBlocks != nullptr) { cm_buildPolytopeRefBlock_t* const block = model->polytopeRefBlocks; model->polytopeRefBlocks = block->next; mem.Free(block, ALIGN_16); } while (model->nodeBlocks != nullptr) { cm_buildNodeBlock_t* const block = model->nodeBlocks; model->nodeBlocks = block->next; mem.Free(block, ALIGN_16); } model->numNodes = 0; model->numPolygonRefs = 0; model->numPolytopeRefs = 0; model->node = AllocNode(model, 256); if (model->node == nullptr) { return nullptr; } model->node->bounds[0].Set(1.0e30f, 1.0e30f, 1.0e30f); model->node->bounds[1].Set(-1.0e30f, -1.0e30f, -1.0e30f); for (int index = 0; index < model->numPolygons; ++index) { if (model->polygons[index].numEdges != 0) { AddBounds(model->node->bounds, model->polygons[index].bounds); FilterPolygonIntoTree_r(model, model->node, nullptr, &model->polygons[index]); } } for (int index = 0; index < model->numPolytopes; ++index) { if (model->polytopes[index].numPlanes != 0) { AddBounds(model->node->bounds, model->polytopes[index].bounds); FilterPolytopeIntoTree_r(model, model->node, nullptr, &model->polytopes[index]); } } if (model->node->bounds[0].x > model->node->bounds[1].x) { model->node->bounds[0].Zero(); model->node->bounds[1].Zero(); } model->node = CreateAxialBSPTree_r(model, model->node); int savedPolygons = 0; int savedPolytopes = 0; CM_R_TestOptimisation(model->node, savedPolygons, savedPolytopes); return model->node; } void idCollisionModelBuilder::CreatePolygon(cm_buildModel_t* const model, idFixedWinding* const winding, const idPlane& plane, const idMaterial* const material, const int primitiveNum) { if (model == nullptr || winding == nullptr || winding->GetNumPoints() < 3) { return; } std::vector edgeNumbers; edgeNumbers.reserve(static_cast(winding->GetNumPoints())); int previousVertex = -1; for (int pointIndex = 0; pointIndex < winding->GetNumPoints(); ++pointIndex) { const int nextIndex = (pointIndex + 1) % winding->GetNumPoints(); int edgeNumber = 0; GetEdge(model, idVec3((*winding)[pointIndex].x, (*winding)[pointIndex].y, (*winding)[pointIndex].z), idVec3((*winding)[nextIndex].x, (*winding)[nextIndex].y, (*winding)[nextIndex].z), edgeNumber, previousVertex); if (edgeNumber == 0) { continue; } bool duplicate = false; for (const int existing : edgeNumbers) { duplicate |= std::abs(existing) == std::abs(edgeNumber); } if (duplicate) { return; } edgeNumbers.push_back(edgeNumber); const cm_buildEdge_t& edge = model->edges[std::abs(edgeNumber)]; previousVertex = edge.vertexNum[edgeNumber >= 0 ? 1 : 0]; } if (edgeNumbers.size() < 3) { return; } cm_buildPolygon_t* const polygon = AllocPolygon(model, static_cast(edgeNumbers.size())); if (polygon == nullptr) { return; } winding->GetBounds(polygon->bounds); cm_materialBuildInfo_t materialInfo{}; CM_GetMaterialBuildInfo(material, materialInfo); polygon->contents = materialInfo.contents; polygon->material = FindMaterial(model, materialInfo.contents, materialInfo.surfaceFlags, materialInfo.surfaceType); polygon->plane = plane; polygon->primitiveNum = std::abs(primitiveNum); polygon->checkCount = 0; for (int index = 0; index < polygon->numEdges; ++index) { model->polygonEdges[polygon->firstEdge + index] = edgeNumbers[static_cast(index)]; } if (model->node == nullptr) { model->node = AllocNode(model, 8); model->node->bounds = polygon->bounds; } FilterPolygonIntoTree_r(model, model->node, nullptr, polygon); } void idCollisionModelBuilder::PolygonFromWinding( cm_buildModel_t* const model, idFixedWinding* const winding, const idPlane& plane, const idMaterial* const material, const int primitiveNum) { if (model == nullptr || winding == nullptr) { return; } if (winding->IsHuge(131072.0f)) { idLibPrint::Warning( "PolygonFromWinding: model %s primitive %d is degenerate", model->name.c_str(), std::abs(primitiveNum)); return; } CreatePolygon(model, winding, plane, material, primitiveNum); } void idCollisionModelBuilder::AddBuildNodePrimitivesToSubModelNode( const cm_buildModel_t* const buildModel, cm_buildNode_t* const buildNode, cm_subModelPtrs_t& subModelPtrs, cm_subModelData_t& counts, cm_node_t& node) { if (buildModel == nullptr || buildNode == nullptr) { return; } cm_buildModel_t* const writable = const_cast( buildModel); const auto packMaterial = [&](cm_buildMaterial_t& source) { if (source.checkCount == writable->checkCount) { return; } source.checkCount = writable->checkCount; source.index = counts.numMaterials; cm_material_t& destination = subModelPtrs.materials[counts.numMaterials++]; destination.contentFlags = source.contentFlags; destination.surfaceFlags = source.surfaceFlags; destination.surfaceType = source.surfaceType; destination.surfaceColor[0] = 0xFF; destination.surfaceColor[1] = 0xFF; destination.surfaceColor[2] = 0xFF; destination.pad = 0; }; for (cm_buildPolygonRef_t* reference = buildNode->polygons; reference != nullptr; reference = reference->next) { cm_buildPolygon_t& source = writable->polygons[reference->polygonNum]; if (source.checkCount != writable->checkCount) { source.checkCount = writable->checkCount; cm_buildMaterial_t& sourceMaterial = writable->materials[source.material]; packMaterial(sourceMaterial); source.index = counts.numPolygons; cm_polygon_t& destination = subModelPtrs.polygons[counts.numPolygons++]; destination.bounds.SetBounds(source.bounds); destination.material = static_cast( sourceMaterial.index); destination.firstEdge = static_cast( counts.numPolygonEdges); destination.numEdges = static_cast( source.numEdges); for (int polygonEdge = 0; polygonEdge < source.numEdges; ++polygonEdge) { const int sourceReference = writable->polygonEdges[ source.firstEdge + polygonEdge]; cm_buildEdge_t& sourceEdge = writable->edges[std::abs(sourceReference)]; if (sourceEdge.checkCount != writable->checkCount) { sourceEdge.checkCount = writable->checkCount; for (int endpoint = 0; endpoint < 2; ++endpoint) { cm_buildVertex_t& sourceVertex = writable->vertices[ sourceEdge.vertexNum[endpoint]]; if (sourceVertex.checkCount != writable->checkCount) { sourceVertex.checkCount = writable->checkCount; sourceVertex.index = counts.numVertices; cm_vertex_t& destinationVertex = subModelPtrs.vertices[ counts.numVertices++]; destinationVertex.p = sourceVertex.p; destinationVertex.st[0] = sourceVertex.st[0]; destinationVertex.st[1] = sourceVertex.st[1]; } } sourceEdge.index = counts.numEdges; cm_edge_t& destinationEdge = subModelPtrs.edges[counts.numEdges++]; destinationEdge.vertexNum[0] = static_cast(writable->vertices[ sourceEdge.vertexNum[0]].index); destinationEdge.vertexNum[1] = static_cast(writable->vertices[ sourceEdge.vertexNum[1]].index); } std::uint16_t packedReference = static_cast(sourceEdge.index); if (sourceReference <= 0) { packedReference |= 0x8000u; } if (sourceEdge.internal != 0) { packedReference |= 0x4000u; } subModelPtrs.polygonEdges[counts.numPolygonEdges++] = packedReference; } } for (int index = node.numPolytopes; index > 0; --index) { subModelPtrs.primitiveIndices[node.firstPrimitive + node.numPolygons + index] = subModelPtrs.primitiveIndices[node.firstPrimitive + node.numPolygons + index - 1]; } subModelPtrs.primitiveIndices[node.firstPrimitive + node.numPolygons++] = static_cast(source.index); ++counts.numPrimitiveIndices; } for (cm_buildPolytopeRef_t* reference = buildNode->polytopes; reference != nullptr; reference = reference->next) { cm_buildPolytope_t& source = writable->polytopes[reference->polytopeNum]; if (source.checkCount != writable->checkCount) { source.checkCount = writable->checkCount; cm_buildMaterial_t& sourceMaterial = writable->materials[source.material]; packMaterial(sourceMaterial); source.index = counts.numPolytopes; cm_polytope_t& destination = subModelPtrs.polytopes[counts.numPolytopes++]; destination.bounds.SetBounds(source.bounds); destination.material = static_cast( sourceMaterial.index); destination.firstPlane = static_cast( counts.numPolytopePlanes); destination.numPlanes = static_cast( source.numPlanes); for (int plane = 0; plane < source.numPlanes; ++plane) { subModelPtrs.polytopePlanes[ counts.numPolytopePlanes++] = writable->polytopePlanes[source.firstPlane + plane]; } } subModelPtrs.primitiveIndices[node.firstPrimitive + node.numPolygons + node.numPolytopes++] = static_cast(source.index); ++counts.numPrimitiveIndices; } } void idCollisionModelBuilder::CreateSingleSubModel_r( const cm_buildModel_t* const buildModel, cm_buildNode_t* buildNode, cm_subModelPtrs_t& subModelPtrs, cm_subModelData_t& counts, cm_node_t* parent) { while (buildNode != nullptr) { const int nodeIndex = counts.numNodes++; cm_node_t& node = subModelPtrs.nodes[nodeIndex]; node.planeType = buildNode->planeType; node.planeDist = buildNode->planeDist; node.children[0] = node.children[1] = 0; node.firstPrimitive = static_cast( counts.numPrimitiveIndices); node.numPolygons = 0; node.numPolytopes = 0; if (parent != nullptr && buildNode->parent != nullptr) { const int side = buildNode == buildNode->parent->children[1] ? 1 : 0; parent->children[side] = static_cast(nodeIndex); } else { for (cm_buildNode_t* ancestor = buildNode->parent; ancestor != nullptr; ancestor = ancestor->parent) { AddBuildNodePrimitivesToSubModelNode(buildModel, ancestor, subModelPtrs, counts, node); } } AddBuildNodePrimitivesToSubModelNode(buildModel, buildNode, subModelPtrs, counts, node); if (buildNode->planeType == -1) { return; } parent = &node; CreateSingleSubModel_r(buildModel, buildNode->children[0], subModelPtrs, counts, parent); buildNode = buildNode->children[1]; } } void idCollisionModelBuilder::CreateNodeStats_r( const cm_buildModel_t* const buildModel, cm_buildNode_t* buildNode) { if (buildModel == nullptr) { return; } cm_buildModel_t* const writable = const_cast( buildModel); while (buildNode != nullptr) { ++writable->checkCount; std::memset(&buildNode->stats, 0, sizeof(buildNode->stats)); for (cm_buildNode_t* ancestor = buildNode->parent; ancestor != nullptr; ancestor = ancestor->parent) { GetStatsFromNode(buildModel, ancestor, buildNode->stats); } CreateStatsForSubTree_r(buildModel, buildNode, buildNode->stats); if (buildNode->stats.lastNumPolygonEdges > 0) { buildNode->stats.numPolygonEdges = buildNode->stats.numPolygonEdges - (buildNode->stats.lastNumPolygonEdges & 3) + 4; } CalculateSubModelDataSize(buildNode->stats); if (buildNode->planeType == -1) { return; } CreateNodeStats_r(buildModel, buildNode->children[0]); buildNode = buildNode->children[1]; } } void idCollisionModelBuilder::CreateSubModels_r( const cm_buildModel_t* const buildModel, cm_buildNode_t* buildNode, idCollisionModelLocal* const model, cm_modelTreeNode_t* parent) { if (buildModel == nullptr || model == nullptr) { return; } while (buildNode != nullptr) { if (buildNode->stats.canCreateSubModel) { const int subModelIndex = model->polygonModel.numSubModels; cm_subModel_t& subModel = model->polygonModel.subModels[subModelIndex]; cm_subModelPtrs_t pointers{}; if (!AllocSubModelData(buildNode->stats, buildNode->bounds, subModel, pointers)) { return; } subModel.fileOffset = -1; subModel.numUsers = 0; subModel.state = &model->polygonModel.subModelState[ subModelIndex]; *subModel.state = SUBMODEL_STATE_LOADED; cm_subModelData_t counts{}; ++const_cast(buildModel)->checkCount; CreateSingleSubModel_r(buildModel, buildNode, pointers, counts, nullptr); if (counts.numPolygons > 0) { const int padding = 4 - (pointers.polygons[ counts.numPolygons - 1].numEdges & 3); for (int index = 0; index < padding; ++index) { pointers.polygonEdges[counts.numPolygonEdges] = pointers.polygonEdges[counts.numPolygonEdges - 1]; ++counts.numPolygonEdges; } } subModel.data->header = subModel.header; subModel.data->header.loadedSize = subModel.data->header.totalSize; ++model->polygonModel.numSubModels; if (parent != nullptr && buildNode->parent != nullptr) { const int side = buildNode == buildNode->parent->children[1] ? 1 : 0; parent->children[side] = -model->polygonModel.numSubModels; } return; } const int treeNodeIndex = model->polygonModel.numModelTreeNodes++; cm_modelTreeNode_t& node = model->polygonModel.modelTreeNodes[treeNodeIndex]; node.planeType = buildNode->planeType; node.planeDist = buildNode->planeDist; node.children[0] = node.children[1] = 0; if (parent != nullptr && buildNode->parent != nullptr) { const int side = buildNode == buildNode->parent->children[1] ? 1 : 0; parent->children[side] = treeNodeIndex; } if (buildNode->planeType == -1) { return; } parent = &node; CreateSubModels_r(buildModel, buildNode->children[0], model, parent); buildNode = buildNode->children[1]; } } void idCollisionModelBuilder::AddSubModelsToCollisionModel( idCollisionModelLocal* const model, const cm_buildModel_t* const buildModel) { if (model == nullptr || buildModel == nullptr || buildModel->node == nullptr) { return; } idBounds nodeBounds; GetNodeBounds(buildModel, buildModel->node, nodeBounds); if (model->bounds[0].x > model->bounds[1].x) { model->bounds = nodeBounds; } else { AddBounds(model->bounds, nodeBounds); } model->contents |= GetNodeContents(buildModel, buildModel->node); CreateNodeStats_r(buildModel, buildModel->node); int addTreeNodes = 0; int addSubModels = 0; FindSubModels_r(buildModel, buildModel->node, addTreeNodes, addSubModels); const int oldTreeNodes = model->polygonModel.numModelTreeNodes; const int oldSubModels = model->polygonModel.numSubModels; const int totalTreeNodes = oldTreeNodes + addTreeNodes; const int totalSubModels = oldSubModels + addSubModels; if (totalTreeNodes > 0x8000 || totalSubModels > 0x4000) { idLibPrint::Warning("collision model tree exceeds recovered limits"); return; } cm_modelTreeNode_t* newTree = nullptr; if (totalTreeNodes > 0) { newTree = static_cast(_aligned_malloc( sizeof(cm_modelTreeNode_t) * static_cast(totalTreeNodes), 16)); if (newTree == nullptr) { return; } if (oldTreeNodes > 0) { std::memcpy(newTree, model->polygonModel.modelTreeNodes, sizeof(cm_modelTreeNode_t) * static_cast(oldTreeNodes)); } } cm_subModel_t* const newSubModels = static_cast(_aligned_malloc( sizeof(cm_subModel_t) * static_cast(totalSubModels), 16)); volatile std::uint8_t* const newStates = static_cast(_aligned_malloc( static_cast(totalSubModels), 16)); if (newSubModels == nullptr || newStates == nullptr) { _aligned_free(newTree); _aligned_free(newSubModels); _aligned_free(const_cast(newStates)); return; } std::memset(newSubModels, 0, sizeof(cm_subModel_t) * static_cast(totalSubModels)); if (oldSubModels > 0) { std::memcpy(newSubModels, model->polygonModel.subModels, sizeof(cm_subModel_t) * static_cast(oldSubModels)); } for (int index = 0; index < totalSubModels; ++index) { newStates[index] = SUBMODEL_STATE_LOADED; newSubModels[index].state = &newStates[index]; } _aligned_free(model->polygonModel.modelTreeNodes); _aligned_free(model->polygonModel.subModels); _aligned_free(const_cast( model->polygonModel.subModelState)); model->polygonModel.modelTreeNodes = newTree; model->polygonModel.subModels = newSubModels; model->polygonModel.subModelState = newStates; model->polygonModel.numModelTreeNodes = oldTreeNodes; model->polygonModel.numSubModels = oldSubModels; CreateSubModels_r(buildModel, buildModel->node, model, nullptr); } int idCollisionModelBuilder::CountModelTreeNodes_r( idCollisionModelLocal* const model, const int nodeNum, idBounds& bounds) { if (model == nullptr) { return 0; } if (nodeNum < 0) { const int subModelIndex = -nodeNum - 1; if (subModelIndex >= 0 && subModelIndex < model->polygonModel.numSubModels) { AddBounds(bounds, model->polygonModel.subModels[ subModelIndex].header.bounds); } return 0; } if (nodeNum >= model->polygonModel.numModelTreeNodes) { return 0; } const cm_modelTreeNode_t& node = model->polygonModel.modelTreeNodes[nodeNum]; if (node.planeType == -1) { return 1; } return CountModelTreeNodes_r(model, node.children[1], bounds) + CountModelTreeNodes_r(model, node.children[0], bounds) + 1; } void idCollisionModelBuilder::MergeModelTrees( idCollisionModelLocal* const model) { if (model == nullptr || model->polygonModel.numModelTreeNodes <= 0 || model->polygonModel.modelTreeNodes == nullptr) { return; } // The recovered retail function inventories each independently appended // root and its bounds; it does not rewrite the serialized node array. // Keeping the roots sequential is significant because stream-area // submodels refer to the original negative child indices. int root = 0; while (root < model->polygonModel.numModelTreeNodes) { idBounds bounds; bounds[0].Set(1.0e30f, 1.0e30f, 1.0e30f); bounds[1].Set(-1.0e30f, -1.0e30f, -1.0e30f); const int descendants = CountModelTreeNodes_r(model, root, bounds); const int nextRoot = root + descendants + 1; if (nextRoot <= root) { break; } root = nextRoot; } } void idCollisionModelBuilder::GenerateEdgeNormals_r( cm_buildModel_t* const model, cm_buildNode_t* node) { if (model == nullptr) { return; } while (node != nullptr) { for (cm_buildPolygonRef_t* reference = node->polygons; reference != nullptr; reference = reference->next) { cm_buildPolygon_t& polygon = model->polygons[reference->polygonNum]; if (polygon.checkCount == model->checkCount) { continue; } polygon.checkCount = model->checkCount; const idVec3 planeNormal(polygon.plane.a, polygon.plane.b, polygon.plane.c); for (int polygonEdge = 0; polygonEdge < polygon.numEdges; ++polygonEdge) { const int edgeReference = model->polygonEdges[ polygon.firstEdge + polygonEdge]; cm_buildEdge_t& edge = model->edges[std::abs(edgeReference)]; if (edge.normal.LengthSqr() != 0.0f) { if (edge.normal.Dot(planeNormal) >= -0.7f) { edge.normal = edge.normal + planeNormal; edge.normal.NormalizeFast(); } else { const int startIndex = edgeReference < 0 ? edge.vertexNum[1] : edge.vertexNum[0]; const int endIndex = edgeReference < 0 ? edge.vertexNum[0] : edge.vertexNum[1]; idVec3 direction = model->vertices[endIndex].p - model->vertices[startIndex].p; direction.NormalizeFast(); idVec3 sharp = direction.Cross(edge.normal) + planeNormal.Cross(direction); sharp.NormalizeFast(); edge.normal = sharp * 3.3333333f; ++model->numSharpEdges; } } else if (edge.numUsers == 1) { const int startIndex = edgeReference < 0 ? edge.vertexNum[1] : edge.vertexNum[0]; const int endIndex = edgeReference < 0 ? edge.vertexNum[0] : edge.vertexNum[1]; idVec3 direction = model->vertices[endIndex].p - model->vertices[startIndex].p; direction.NormalizeFast(); edge.normal = direction.Cross(planeNormal); edge.normal.NormalizeFast(); edge.normal = edge.normal + planeNormal; edge.normal.NormalizeFast(); } else { edge.normal = planeNormal; } } } if (node->planeType == -1) { return; } GenerateEdgeNormals_r(model, node->children[1]); node = node->children[0]; } } bool idCollisionModelBuilder::ChoppedAwayByProcBSP_r( int nodeNum, idFixedWinding* const winding, const idVec3& normal, const idVec3& origin, const float radius) { if (winding == nullptr || buildData.procNodes == nullptr || buildData.numProcNodes <= 0) { return false; } std::function walk; walk = [&](int currentNode, idFixedWinding* fragment, bool rootNode) -> bool { while (rootNode || currentNode > 0) { rootNode = false; if (currentNode < 0 || currentNode >= buildData.numProcNodes) { return false; } const cm_procNode_t& node = buildData.procNodes[currentNode]; const float distance = node.plane.Distance(origin); if (distance > radius) { currentNode = node.children[0]; continue; } if (distance < -radius) { currentNode = node.children[1]; continue; } idFixedWinding back; const int side = fragment->SplitInPlace(node.plane, 0.1f, &back); if (side == 0) { currentNode = node.children[0]; } else if (side == 1) { currentNode = node.children[1]; } else if (side == 2) { currentNode = node.plane.Normal().Dot(normal) > 0.0f ? node.children[0] : node.children[1]; } else { if (!walk(node.children[1], &back, false)) { return false; } currentNode = node.children[0]; } } return currentNode == 0; }; return walk(nodeNum, winding, nodeNum == 0); } bool idCollisionModelBuilder::ChoppedAwayByProcBSP( const idFixedWinding& winding, const idPlane& plane, const int contents) { if (buildData.procNodes == nullptr || buildData.numProcNodes == 0 || (contents & 1) == 0) { return false; } idFixedWinding clipped; clipped = winding; idBounds bounds; clipped.GetBounds(bounds); const idVec3 origin = (bounds[0] + bounds[1]) * 0.5f; const float radius = (bounds[1] - origin).Length() + 0.1f; return ChoppedAwayByProcBSP_r(0, &clipped, plane.Normal(), origin, radius); } void idCollisionModelBuilder::ReplacePolygons(cm_buildModel_t* const model, cm_buildNode_t* node, const int polygonNum1, const int polygonNum2, const int newPolygonNum) { if (model == nullptr || node == nullptr) { return; } while (node != nullptr) { bool replaced = false; cm_buildPolygonRef_t* previous = nullptr; cm_buildPolygonRef_t* reference = node->polygons; while (reference != nullptr) { cm_buildPolygonRef_t* const next = reference->next; if (reference->polygonNum == polygonNum1 || reference->polygonNum == polygonNum2) { if (!replaced) { reference->polygonNum = newPolygonNum; replaced = true; previous = reference; } else { if (previous != nullptr) { previous->next = next; } else { node->polygons = next; } --model->numPolygonRefs; } } else { previous = reference; } reference = next; } if (node->planeType == -1) { return; } const int axis = node->planeType; const idBounds& first = model->polygons[polygonNum1].bounds; const idBounds& second = model->polygons[polygonNum2].bounds; if (first[0][axis] <= node->planeDist || second[0][axis] <= node->planeDist) { if (first[1][axis] >= node->planeDist || second[1][axis] >= node->planeDist) { ReplacePolygons(model, node->children[1], polygonNum1, polygonNum2, newPolygonNum); node = node->children[0]; } else { node = node->children[1]; } } else { node = node->children[0]; } } } void idCollisionModelBuilder::FindInternalEdgesOnPolygon( cm_buildModel_t* const model, cm_buildPolygon_t* const polygon1, cm_buildPolygon_t* const polygon2) { if (model == nullptr || polygon1 == nullptr || polygon2 == nullptr) { return; } for (int axis = 0; axis < 3; ++axis) { if (polygon1->bounds[0][axis] > polygon2->bounds[1][axis] || polygon1->bounds[1][axis] < polygon2->bounds[0][axis]) { return; } } const idVec3 firstNormal(polygon1->plane.a, polygon1->plane.b, polygon1->plane.c); const idVec3 secondNormal(polygon2->plane.a, polygon2->plane.b, polygon2->plane.c); for (int polygonEdge = 0; polygonEdge < polygon1->numEdges; ++polygonEdge) { const int edgeReference = model->polygonEdges[ polygon1->firstEdge + polygonEdge]; cm_buildEdge_t& edge = model->edges[std::abs(edgeReference)]; if (edge.internal != 0) { continue; } const int startIndex = edgeReference < 0 ? edge.vertexNum[1] : edge.vertexNum[0]; const int endIndex = edgeReference < 0 ? edge.vertexNum[0] : edge.vertexNum[1]; const idVec3& start = model->vertices[startIndex].p; const idVec3& end = model->vertices[endIndex].p; bool insideBounds = true; for (int axis = 0; axis < 3; ++axis) { insideBounds &= start[axis] <= polygon2->bounds[1][axis] + 0.1f && end[axis] <= polygon2->bounds[1][axis] + 0.1f && start[axis] >= polygon2->bounds[0][axis] - 0.1f && end[axis] >= polygon2->bounds[0][axis] - 0.1f; } if (!insideBounds) { continue; } int matchingEdge = -1; for (int otherEdge = 0; otherEdge < polygon2->numEdges; ++otherEdge) { if (std::abs(model->polygonEdges[polygon2->firstEdge + otherEdge]) == std::abs(edgeReference)) { matchingEdge = otherEdge; break; } } if (matchingEdge < 0 && (std::fabs(polygon2->plane.Distance(start)) > 0.1f || std::fabs(polygon2->plane.Distance(end)) > 0.1f)) { continue; } if (matchingEdge >= 0 && (edge.numUsers > 2 || edgeReference == model->polygonEdges[ polygon2->firstEdge + matchingEdge])) { continue; } if (secondNormal.Dot(firstNormal.Cross(end - start)) < 0.0f) { return; } if (matchingEdge >= 0 || (PointInsidePolygon(model, polygon2, start) && PointInsidePolygon(model, polygon2, end))) { edge.internal = 1; ++model->numInternalEdges; } } } void idCollisionModelBuilder::FindInternalPolygonEdges( cm_buildModel_t* const model, cm_buildNode_t* node, cm_buildPolygon_t* const polygon) { if (model == nullptr || polygon == nullptr) { return; } while (node != nullptr) { for (cm_buildPolygonRef_t* reference = node->polygons; reference != nullptr; reference = reference->next) { cm_buildPolygon_t& candidate = model->polygons[reference->polygonNum]; if (&candidate != polygon && candidate.material == polygon->material && candidate.contents == polygon->contents) { FindInternalEdgesOnPolygon(model, polygon, &candidate); } } if (node->planeType == -1) { return; } const int axis = node->planeType; if (polygon->bounds[0][axis] <= node->planeDist) { if (polygon->bounds[1][axis] >= node->planeDist) { FindInternalPolygonEdges(model, node->children[1], polygon); node = node->children[0]; } else { node = node->children[1]; } } else { node = node->children[0]; } } } void idCollisionModelBuilder::FindInternalEdges(cm_buildModel_t* const model, cm_buildNode_t* node) { if (model == nullptr) { return; } while (node != nullptr) { for (cm_buildPolygonRef_t* reference = node->polygons; reference != nullptr; reference = reference->next) { cm_buildPolygon_t& polygon = model->polygons[reference->polygonNum]; if (polygon.checkCount != model->checkCount) { polygon.checkCount = model->checkCount; FindInternalPolygonEdges(model, model->node, &polygon); } } if (node->planeType == -1) { return; } FindInternalEdges(model, node->children[1]); node = node->children[0]; } } void idCollisionModelBuilder::OffsetPolygonEdges( cm_buildModel_t* const model, cm_buildPolygon_t* const polygon) { if (model == nullptr || polygon == nullptr || polygon->numEdges <= 1) { return; } int bestStart = 0; float lowestAlignment = 1.0f; for (int edgeIndex = 0; edgeIndex < polygon->numEdges; ++edgeIndex) { const int previousIndex = (edgeIndex + polygon->numEdges - 1) % polygon->numEdges; const int currentReference = model->polygonEdges[ polygon->firstEdge + edgeIndex]; const int previousReference = model->polygonEdges[ polygon->firstEdge + previousIndex]; const cm_buildEdge_t& current = model->edges[std::abs(currentReference)]; const cm_buildEdge_t& previous = model->edges[std::abs(previousReference)]; const int cornerIndex = currentReference < 0 ? current.vertexNum[1] : current.vertexNum[0]; const int currentEnd = currentReference < 0 ? current.vertexNum[0] : current.vertexNum[1]; const int previousStart = previousReference < 0 ? previous.vertexNum[1] : previous.vertexNum[0]; idVec3 outgoing = model->vertices[currentEnd].p - model->vertices[cornerIndex].p; idVec3 incoming = model->vertices[previousStart].p - model->vertices[cornerIndex].p; outgoing.NormalizeFast(); incoming.NormalizeFast(); const float alignment = std::fabs(outgoing.Dot(incoming)); if (alignment < lowestAlignment) { lowestAlignment = alignment; bestStart = edgeIndex; } } std::vector rotated(static_cast(polygon->numEdges)); for (int index = 0; index < polygon->numEdges; ++index) { rotated[static_cast(index)] = model->polygonEdges[ polygon->firstEdge + (bestStart + index) % polygon->numEdges]; } std::copy(rotated.begin(), rotated.end(), model->polygonEdges + polygon->firstEdge); } void idCollisionModelBuilder::OffsetPolygonEdges_r( cm_buildModel_t* const model, cm_buildNode_t* node) { if (model == nullptr) { return; } while (node != nullptr) { for (cm_buildPolygonRef_t* reference = node->polygons; reference != nullptr; reference = reference->next) { cm_buildPolygon_t& polygon = model->polygons[reference->polygonNum]; if (polygon.checkCount != model->checkCount) { polygon.checkCount = model->checkCount; OffsetPolygonEdges(model, &polygon); } } if (node->planeType == -1) { return; } OffsetPolygonEdges_r(model, node->children[1]); node = node->children[0]; } } void idCollisionModelBuilder::ChopWindingListWithPolytope( cm_windingList_t* const list, const cm_buildModel_t* const model, const cm_buildPolytope_t* const polytope) { if (list == nullptr || model == nullptr || polytope == nullptr || list->numWindings <= 0 || polytope->numPlanes <= 0 || polytope->numPlanes > 64) { return; } // A polytope plane points out of the solid. Partition every source // winding by the inward-facing planes: front fragments are outside and // survive, while the final back fragment lies inside all planes and is // removed. This is the scalar PC spelling of the recovered list ping- // pong implementation. std::vector surviving; surviving.reserve(static_cast(list->numWindings)); for (int sourceIndex = 0; sourceIndex < list->numWindings; ++sourceIndex) { std::vector candidates; candidates.push_back(list->w[sourceIndex]); std::vector outside; for (int planeIndex = 0; planeIndex < polytope->numPlanes && !candidates.empty(); ++planeIndex) { const idPlane& outward = model->polytopePlanes[ polytope->firstPlane + planeIndex]; idPlane inward(-outward.a, -outward.b, -outward.c, -outward.d); std::vector nextCandidates; for (idFixedWinding& candidate : candidates) { idFixedWinding back; const int side = candidate.SplitInPlace(inward, 0.1f, &back); if (side == 0) { nextCandidates.push_back(candidate); } else if (side == 1) { outside.push_back(candidate); } else if (side == 2) { // Coplanar primitive faces are only discarded when they // face into the clipping solid. This preserves the // recovered world/non-world coplanar convention. const idVec3 planeNormal(outward.a, outward.b, outward.c); if (list->primitiveNum >= 0 && planeNormal.Dot(list->normal) > 0.0f) { outside.push_back(candidate); } else { nextCandidates.push_back(candidate); } } else { nextCandidates.push_back(candidate); outside.push_back(back); } if (outside.size() + nextCandidates.size() >= 256u) { break; } } candidates.swap(nextCandidates); } // Anything left in candidates is inside every plane and is chopped. for (const idFixedWinding& fragment : outside) { if (surviving.size() >= 256u) { break; } surviving.push_back(fragment); } } list->numWindings = static_cast((std::min)(surviving.size(), static_cast(256))); for (int index = 0; index < list->numWindings; ++index) { list->w[index] = surviving[static_cast(index)]; } } void idCollisionModelBuilder::ChopWindingListWithTreePolytopes_r( cm_windingList_t* const list, const cm_buildModel_t* const model, const cm_buildNode_t* node) { if (list == nullptr || model == nullptr || node == nullptr || list->numWindings <= 0) { return; } while (node != nullptr) { for (cm_buildPolytopeRef_t* reference = node->polytopes; reference != nullptr; reference = reference->next) { cm_buildPolytope_t& polytope = const_cast( model->polytopes[reference->polytopeNum]); if (polytope.checkCount == model->checkCount) { continue; } polytope.checkCount = model->checkCount; if (polytope.primitiveNum != list->primitiveNum && polytope.contents == list->contents && BoundsOverlap(polytope.bounds, list->bounds)) { ChopWindingListWithPolytope(list, model, &polytope); if (list->numWindings == 0) { return; } } } if (node->planeType == -1) { return; } const int axis = node->planeType; if (list->bounds[0][axis] <= node->planeDist) { if (list->bounds[1][axis] >= node->planeDist) { ChopWindingListWithTreePolytopes_r(list, model, node->children[1]); if (list->numWindings == 0) { return; } node = node->children[0]; } else { node = node->children[1]; } } else { node = node->children[0]; } } } cm_buildPolygon_t* idCollisionModelBuilder::TryMergePolygons( cm_buildModel_t* const model, const int polygonNum1, const int polygonNum2) { if (model == nullptr || polygonNum1 < 0 || polygonNum2 < 0 || polygonNum1 >= model->numPolygons || polygonNum2 >= model->numPolygons || polygonNum1 == polygonNum2) { return nullptr; } const cm_buildPolygon_t& first = model->polygons[polygonNum1]; const cm_buildPolygon_t& second = model->polygons[polygonNum2]; if (first.numEdges < 3 || second.numEdges < 3 || first.material != second.material || first.contents != second.contents || std::fabs(first.plane.a - second.plane.a) > 0.0001f || std::fabs(first.plane.b - second.plane.b) > 0.0001f || std::fabs(first.plane.c - second.plane.c) > 0.0001f || std::fabs(first.plane.d - second.plane.d) > 0.01f || !BoundsOverlap(first.bounds, second.bounds)) { return nullptr; } for (int edgeIndex = 0; edgeIndex < second.numEdges; ++edgeIndex) { const int reference = model->polygonEdges[ second.firstEdge + edgeIndex]; const idVec3& point = model->vertices[ DirectedEdgeStart(model, reference)].p; if (std::fabs(first.plane.Distance(point)) > 0.1f) { return nullptr; } } std::vector boundary; boundary.reserve(static_cast( first.numEdges + second.numEdges)); int sharedEdges = 0; const auto appendUnshared = [&](const cm_buildPolygon_t& polygon, const cm_buildPolygon_t& other) { for (int edgeIndex = 0; edgeIndex < polygon.numEdges; ++edgeIndex) { const int reference = model->polygonEdges[ polygon.firstEdge + edgeIndex]; bool shared = false; for (int otherIndex = 0; otherIndex < other.numEdges; ++otherIndex) { const int otherReference = model->polygonEdges[ other.firstEdge + otherIndex]; if (reference == -otherReference) { shared = true; break; } } if (!shared) { boundary.push_back(reference); } else if (&polygon == &first) { ++sharedEdges; } } }; appendUnshared(first, second); appendUnshared(second, first); if (sharedEdges == 0 || boundary.size() < 3u || boundary.size() > 64u) { return nullptr; } // Reorder the surviving directed edges into one closed boundary. This // also rejects point-touching or disconnected polygon unions. std::vector ordered; ordered.reserve(boundary.size()); ordered.push_back(boundary.front()); boundary.erase(boundary.begin()); while (!boundary.empty()) { const int endVertex = DirectedEdgeEnd(model, ordered.back()); const auto next = std::find_if(boundary.begin(), boundary.end(), [&](const int reference) { return DirectedEdgeStart(model, reference) == endVertex; }); if (next == boundary.end()) { return nullptr; } ordered.push_back(*next); boundary.erase(next); } if (DirectedEdgeEnd(model, ordered.back()) != DirectedEdgeStart(model, ordered.front())) { return nullptr; } const idVec3 normal(first.plane.a, first.plane.b, first.plane.c); for (std::size_t index = 0; index < ordered.size(); ++index) { const int previous = ordered[(index + ordered.size() - 1) % ordered.size()]; const int current = ordered[index]; const idVec3& corner = model->vertices[ DirectedEdgeStart(model, current)].p; idVec3 incoming = corner - model->vertices[ DirectedEdgeStart(model, previous)].p; idVec3 outgoing = model->vertices[ DirectedEdgeEnd(model, current)].p - corner; if (incoming.NormalizeFast() == 0.0f || outgoing.NormalizeFast() == 0.0f || normal.Dot(incoming.Cross(outgoing)) < -0.005f) { return nullptr; } } cm_buildPolygon_t* const merged = AllocPolygon(model, static_cast(ordered.size())); if (merged == nullptr) { return nullptr; } // AllocPolygon may grow the polygon array, so reacquire the source. const cm_buildPolygon_t& currentFirst = model->polygons[polygonNum1]; const cm_buildPolygon_t& currentSecond = model->polygons[polygonNum2]; CopyPolygonFields(*merged, currentFirst); AddBounds(merged->bounds, currentSecond.bounds); for (int index = 0; index < merged->numEdges; ++index) { const int reference = ordered[static_cast(index)]; model->polygonEdges[merged->firstEdge + index] = reference; ++model->edges[std::abs(reference)].numUsers; } return merged; } bool idCollisionModelBuilder::MergePolygonWithTreePolygons( cm_buildModel_t* const model, cm_buildNode_t* node, const int polygonNum, const bool mergePrimitives) { if (model == nullptr || node == nullptr || polygonNum < 0 || polygonNum >= model->numPolygons) { return false; } cm_buildPolygon_t* source = &model->polygons[polygonNum]; while (node != nullptr) { for (cm_buildPolygonRef_t* reference = node->polygons; reference != nullptr; reference = reference->next) { const int otherNum = reference->polygonNum; if (otherNum == polygonNum || (!mergePrimitives && model->polygons[otherNum].primitiveNum != source->primitiveNum)) { continue; } cm_buildPolygon_t* const merged = TryMergePolygons(model, polygonNum, otherNum); if (merged == nullptr) { continue; } source = &model->polygons[polygonNum]; cm_buildPolygon_t* const other = &model->polygons[otherNum]; const int mergedNum = static_cast( merged - model->polygons); ReplacePolygons(model, model->node, polygonNum, otherNum, mergedNum); for (int edge = 0; edge < source->numEdges; ++edge) { --model->edges[std::abs(model->polygonEdges[ source->firstEdge + edge])].numUsers; } for (int edge = 0; edge < other->numEdges; ++edge) { --model->edges[std::abs(model->polygonEdges[ other->firstEdge + edge])].numUsers; } source->numEdges = 0; other->numEdges = 0; ++model->numMergedPolys; return true; } if (node->planeType == -1) { return false; } const int axis = node->planeType; if (source->bounds[0][axis] <= node->planeDist) { if (source->bounds[1][axis] >= node->planeDist) { if (MergePolygonWithTreePolygons(model, node->children[1], polygonNum, mergePrimitives)) { return true; } node = node->children[0]; } else { node = node->children[1]; } } else { node = node->children[0]; } } return false; } void idCollisionModelBuilder::MergeTreePolygons( cm_buildModel_t* const model, cm_buildNode_t* node, const bool mergePrimitives) { if (model == nullptr || node == nullptr) { return; } while (node != nullptr) { bool merged; do { merged = false; ++model->checkCount; for (cm_buildPolygonRef_t* reference = node->polygons; reference != nullptr; reference = reference->next) { cm_buildPolygon_t& polygon = model->polygons[reference->polygonNum]; if (polygon.numEdges == 0 || polygon.checkCount == model->checkCount) { continue; } polygon.checkCount = model->checkCount; if (MergePolygonWithTreePolygons(model, model->node, reference->polygonNum, mergePrimitives)) { merged = true; break; } } } while (merged); if (node->planeType == -1) { return; } MergeTreePolygons(model, node->children[1], mergePrimitives); node = node->children[0]; } } void idCollisionModelBuilder::SplitPolygon(cm_buildModel_t* const model, const int polygonNum) { if (model == nullptr || polygonNum < 0 || polygonNum >= model->numPolygons) { return; } cm_buildPolygon_t* source = &model->polygons[polygonNum]; if (source->numEdges <= 16) { return; } const int originalEdges = source->numEdges; const int splitStart = 0; const int splitEnd = originalEdges / 2; const int startReference = model->polygonEdges[ source->firstEdge + splitStart]; const int endReference = model->polygonEdges[ source->firstEdge + splitEnd]; const int startVertex = DirectedEdgeStart(model, startReference); const int endVertex = DirectedEdgeStart(model, endReference); if (startVertex == endVertex) { return; } int diagonal = 0; GetEdge(model, model->vertices[startVertex].p, model->vertices[endVertex].p, diagonal, startVertex); if (diagonal == 0) { return; } // GetEdge accounts for one user. Both split polygons use the diagonal. ++model->edges[std::abs(diagonal)].numUsers; const std::vector original(model->polygonEdges + source->firstEdge, model->polygonEdges + source->firstEdge + originalEdges); const cm_buildPolygon_t originalFields = *source; for (int side = 0; side < 2; ++side) { const int begin = side == 0 ? splitStart : splitEnd; const int end = side == 0 ? splitEnd : originalEdges; const int count = end - begin + 1; cm_buildPolygon_t* const split = AllocPolygon(model, count); if (split == nullptr) { return; } CopyPolygonFields(*split, originalFields); split->bounds[0].Set(FLT_MAX, FLT_MAX, FLT_MAX); split->bounds[1].Set(-FLT_MAX, -FLT_MAX, -FLT_MAX); int cursor = 0; split->numEdges = count; const int closing = side == 0 ? -diagonal : diagonal; model->polygonEdges[split->firstEdge + cursor++] = closing; for (int edge = begin; edge < end; ++edge) { const int reference = original[static_cast(edge)]; model->polygonEdges[split->firstEdge + cursor++] = reference; ++model->edges[std::abs(reference)].numUsers; } for (int edge = 0; edge < split->numEdges; ++edge) { const int vertex = DirectedEdgeStart(model, model->polygonEdges[split->firstEdge + edge]); const idVec3& point = model->vertices[vertex].p; for (int axis = 0; axis < 3; ++axis) { split->bounds[0][axis] = (std::min)( split->bounds[0][axis], point[axis]); split->bounds[1][axis] = (std::max)( split->bounds[1][axis], point[axis]); } } FilterPolygonIntoTree_r(model, model->node, nullptr, split); } source = &model->polygons[polygonNum]; RemovePolygon(model, model->node, polygonNum); for (int edge = 0; edge < source->numEdges; ++edge) { --model->edges[std::abs(model->polygonEdges[ source->firstEdge + edge])].numUsers; } source->numEdges = 0; } void idCollisionModelBuilder::SplitPolygons(cm_buildModel_t* const model) { if (model == nullptr) { return; } for (int polygonNum = 0; polygonNum < model->numPolygons; ++polygonNum) { if (model->polygons[polygonNum].numEdges > 16) { SplitPolygon(model, polygonNum); } } } idFixedWinding* idCollisionModelBuilder::WindingOutsidePolytopes( cm_buildModel_t* const model, idFixedWinding* const winding, const idPlane& plane, const int contents, const int primitiveNum) { if (model == nullptr || winding == nullptr || model->node == nullptr || winding->GetNumPoints() < 3) { return winding; } if (buildData.cm_windingList == nullptr) { SetupHash(); } cm_windingList_t& list = *buildData.cm_windingList; list.numWindings = 1; list.w[0] = *winding; list.normal.Set(plane.a, plane.b, plane.c); winding->GetBounds(list.bounds); list.origin = (list.bounds[0] + list.bounds[1]) * 0.5f; list.radius = (list.bounds[1] - list.origin).Length() + 0.1f; for (int axis = 0; axis < 3; ++axis) { list.bounds[0][axis] -= 0.1f; list.bounds[1][axis] += 0.1f; } list.contents = contents; list.primitiveNum = primitiveNum; ++model->checkCount; ChopWindingListWithTreePolytopes_r(&list, model, model->node); if (list.numWindings == 0) { return nullptr; } if (list.numWindings == 1) { return &list.w[0]; } if (!model->isWorldModel) { return winding; } int outsideFragment = -1; for (int index = 0; index < list.numWindings; ++index) { if (!ChoppedAwayByProcBSP(list.w[index], plane, contents)) { if (outsideFragment >= 0) { return winding; } outsideFragment = index; } } return outsideFragment >= 0 ? &list.w[outsideFragment] : nullptr; } int idCollisionModelBuilder::SetupSubModelData(cm_subModelData_t& data, const cm_buildNodeStats_t& stats) { data.isConvex = 0; data.numNodes = stats.numNodes; data.numPrimitiveIndices = stats.numPrimitiveIndices; data.numMaterials = stats.numMaterials; data.numPolygons = stats.numPolygons; data.numPolygonEdges = stats.numPolygonEdges; data.numEdges = stats.numEdges; data.numVertices = stats.numVertices; data.numPolytopes = stats.numPolytopes; data.numPolytopePlanes = stats.numPolytopePlanes; data.pad = 0; data.nodeOffset = 112; data.primitiveIndexOffset = Align( data.nodeOffset + 16 * data.numNodes, 2); data.materialOffset = Align(data.primitiveIndexOffset + 2 * data.numPrimitiveIndices, 16); data.polygonOffset = Align(data.materialOffset + 16 * data.numMaterials, 16); data.polygonEdgeOffset = Align(data.polygonOffset + 16 * data.numPolygons, 2); data.edgeOffset = Align(data.polygonEdgeOffset + 2 * data.numPolygonEdges, 4); data.vertexOffset = Align(data.edgeOffset + 4 * data.numEdges, 16); data.polytopeOffset = Align(data.vertexOffset + 16 * data.numVertices, 16); data.polytopePlaneOffset = Align(data.polytopeOffset + 16 * data.numPolytopes, 16); return data.polytopePlaneOffset + 16 * data.numPolytopePlanes; } void idCollisionModelBuilder::CalculateSubModelDataSize( cm_buildNodeStats_t& stats) { cm_subModelData_t data{}; stats.totalMemory = SetupSubModelData(data, stats); stats.canCreateSubModel = stats.numNodes <= 0x10000 && stats.numPrimitiveIndices <= 0x10000 && stats.numMaterials <= 256 && stats.numPolygons <= 0x10000 && stats.numPolygonEdges <= 0x10000 && stats.numEdges <= 0x4000 && stats.numVertices <= 0x10000 && stats.numPolytopes <= 0x10000 && stats.numPolytopePlanes <= 0x10000 && stats.totalMemory <= 0x10000; } bool idCollisionModelBuilder::AllocSubModelData( const cm_buildNodeStats_t& stats, const idBounds& bounds, cm_subModel_t& subModel, cm_subModelPtrs_t& pointers) { cm_buildNodeStats_t checked = stats; CalculateSubModelDataSize(checked); if (!checked.canCreateSubModel) { return false; } std::memset(&subModel, 0, sizeof(subModel)); subModel.header.totalSize = checked.totalMemory; subModel.header.loadedSize = checked.totalMemory; subModel.header.bounds = bounds; subModel.data = static_cast(_aligned_malloc( static_cast(checked.totalMemory), 16)); if (subModel.data == nullptr) { return false; } std::memset(subModel.data, 0, static_cast(checked.totalMemory)); SetupSubModelData(*subModel.data, checked); subModel.data->header = subModel.header; idPolygonModelCollisionDetection::SetupSubModelPtrsFromData( pointers, subModel.data); return true; } bool idCollisionModelBuilder::BuildForTrm(idCollisionModelLocal* const model, const char* const modelName, const idTraceModel& traceModel, const idMaterial*) { if (model == nullptr) { return false; } model->SetName(modelName); model->bounds = traceModel.bounds; model->contents = 1; model->isWorldModel = false; model->isTraceModel = true; model->isConvex = traceModel.isConvex; model->modelType = CM_POLYGONMODEL; model->polygonModel.numModelTreeNodes = 0; model->polygonModel.modelTreeNodes = nullptr; model->polygonModel.numSubModels = 1; cm_buildNodeStats_t stats{}; stats.numNodes = 1; stats.numPrimitiveIndices = static_cast(traceModel.numPolys) + (traceModel.isConvex ? 1 : 0); stats.numMaterials = 1; stats.numPolygons = static_cast(traceModel.numPolys); stats.numEdges = static_cast(traceModel.numEdges); stats.numVertices = static_cast(traceModel.numVerts); stats.numPolytopes = traceModel.isConvex ? 1 : 0; stats.numPolytopePlanes = traceModel.isConvex ? static_cast(traceModel.numPolys) : 0; for (unsigned int polygon = 0; polygon < traceModel.numPolys; ++polygon) { stats.numPolygonEdges += static_cast(traceModel.numPolyEdges[polygon]); } const int polygonEdgePadding = traceModel.numPolys == 0 ? 4 : 4 - (static_cast( traceModel.numPolyEdges[traceModel.numPolys - 1]) & 3); stats.numPolygonEdges += polygonEdgePadding; CalculateSubModelDataSize(stats); if (!stats.canCreateSubModel) { return false; } model->polygonModel.subModels = static_cast( _aligned_malloc(sizeof(cm_subModel_t), 16)); model->polygonModel.subModelState = static_cast(_aligned_malloc(1, 16)); if (model->polygonModel.subModels == nullptr || model->polygonModel.subModelState == nullptr) { model->FreeData(); return false; } std::memset(model->polygonModel.subModels, 0, sizeof(cm_subModel_t)); cm_subModel_t& subModel = model->polygonModel.subModels[0]; subModel.header.totalSize = stats.totalMemory; subModel.header.loadedSize = 32; subModel.header.bounds = traceModel.bounds; subModel.data = static_cast( _aligned_malloc(static_cast(stats.totalMemory), 16)); subModel.fileOffset = -1; subModel.numUsers = 0; subModel.state = model->polygonModel.subModelState; *subModel.state = SUBMODEL_STATE_LOADED; if (subModel.data == nullptr) { model->FreeData(); return false; } std::memset(subModel.data, 0, static_cast(stats.totalMemory)); SetupSubModelData(*subModel.data, stats); subModel.data->header = subModel.header; subModel.data->header.loadedSize = subModel.data->header.totalSize; subModel.data->isConvex = traceModel.isConvex ? 1 : 0; cm_subModelPtrs_t pointers{}; idPolygonModelCollisionDetection::SetupSubModelPtrsFromData( pointers, subModel.data); cm_node_t& node = pointers.nodes[0]; node.planeType = -1; node.planeDist = 0.0f; node.children[0] = 0; node.children[1] = 0; node.firstPrimitive = 0; node.numPolygons = static_cast(traceModel.numPolys); node.numPolytopes = traceModel.isConvex ? 1 : 0; if (traceModel.type == TRM_INVALID || traceModel.numPolys == 0) { return false; } cm_material_t& material = pointers.materials[0]; material.contentFlags = 1; material.surfaceFlags = 0; material.surfaceType = 0; material.surfaceColor[0] = 0xFF; material.surfaceColor[1] = 0xFF; material.surfaceColor[2] = 0xFF; material.pad = 0; for (unsigned int vertex = 0; vertex < traceModel.numVerts; ++vertex) { pointers.vertices[vertex].p.Set(traceModel.vertsX[vertex], traceModel.vertsY[vertex], traceModel.vertsZ[vertex]); pointers.vertices[vertex].st[0] = 0; pointers.vertices[vertex].st[1] = 0; } for (unsigned int edge = 0; edge < traceModel.numEdges; ++edge) { pointers.edges[edge].vertexNum[0] = traceModel.edges[edge].v[0]; pointers.edges[edge].vertexNum[1] = traceModel.edges[edge].v[1]; } int polygonEdgeCursor = 0; for (unsigned int polygonIndex = 0; polygonIndex < traceModel.numPolys; ++polygonIndex) { cm_polygon_t& polygon = pointers.polygons[polygonIndex]; polygon.material = 0; polygon.firstEdge = static_cast(polygonEdgeCursor); polygon.numEdges = static_cast( traceModel.numPolyEdges[polygonIndex]); idBounds polygonBounds; polygonBounds[0].Set(FLT_MAX, FLT_MAX, FLT_MAX); polygonBounds[1].Set(-FLT_MAX, -FLT_MAX, -FLT_MAX); for (unsigned int edge = 0; edge < traceModel.numPolyEdges[polygonIndex]; ++edge) { const std::uint8_t traceReference = traceModel.polyEdges[polygonIndex][edge]; const std::uint16_t modelReference = static_cast(traceReference & 0x7F) | ((traceReference & 0x80) != 0 ? 0x8000 : 0); pointers.polygonEdges[polygonEdgeCursor++] = modelReference; const cm_edge_t& modelEdge = pointers.edges[CM_EdgeIndex(modelReference)]; const idVec3& point = pointers.vertices[ CM_EdgeStartVertex(modelEdge, modelReference)].p; for (int axis = 0; axis < 3; ++axis) { polygonBounds[0][axis] = (std::min)( polygonBounds[0][axis], point[axis]); polygonBounds[1][axis] = (std::max)( polygonBounds[1][axis], point[axis]); } } polygon.bounds.SetBounds(polygonBounds); pointers.primitiveIndices[polygonIndex] = static_cast(polygonIndex); } const std::uint16_t paddingValue = polygonEdgeCursor > 0 ? pointers.polygonEdges[polygonEdgeCursor - 1] : 0; for (int padding = 0; padding < polygonEdgePadding; ++padding) { pointers.polygonEdges[polygonEdgeCursor++] = paddingValue; } if (traceModel.isConvex) { pointers.primitiveIndices[traceModel.numPolys] = 0; cm_polytope_t& polytope = pointers.polytopes[0]; polytope.bounds.SetBounds(traceModel.bounds); polytope.material = 0; polytope.numPlanes = static_cast(traceModel.numPolys); polytope.firstPlane = 0; for (unsigned int plane = 0; plane < traceModel.numPolys; ++plane) { pointers.polytopePlanes[plane].a = traceModel.polyPlaneX[plane]; pointers.polytopePlanes[plane].b = traceModel.polyPlaneY[plane]; pointers.polytopePlanes[plane].c = traceModel.polyPlaneZ[plane]; pointers.polytopePlanes[plane].d = traceModel.polyPlaneW[plane]; } } return true; } bool idCollisionModelBuilder::BuildForGrid( idCollisionModelLocal* const model, const char* const modelName, const idGenGridModel& grid, const idCollisionGridState& state, const idMaterial*) { if (model == nullptr || modelName == nullptr || state.numActive <= 0) { return false; } std::vector selectedParts; std::vector nodePolygonCounts((std::max)(1, grid.nodes.Num()), 0); int sourcePolygonCount = 0; int sourcePolygonEdges = 0; idBounds modelBounds; modelBounds[0].Set(FLT_MAX, FLT_MAX, FLT_MAX); modelBounds[1].Set(-FLT_MAX, -FLT_MAX, -FLT_MAX); for (int active = state.FirstActive(); active >= 0; active = state.NextActive(active)) { if (active >= grid.indices.Num()) { continue; } const int partIndex = grid.indices[active]; if (partIndex == idGenGridModel::INVALID_INDEX || partIndex < 0 || partIndex >= grid.parts.Num()) { continue; } const cm_gridPart_t& part = grid.parts[partIndex]; if (part.nodeIndex >= nodePolygonCounts.size() || part.firstPolygonIndex + part.numPolygons > grid.polygons.Num()) { return false; } selectedParts.push_back(partIndex); sourcePolygonCount += part.numPolygons; nodePolygonCounts[part.nodeIndex] += 2 * part.numPolygons; const idBounds partBounds = part.bounds.ToBounds(); for (int axis = 0; axis < 3; ++axis) { modelBounds[0][axis] = (std::min)(modelBounds[0][axis], partBounds[0][axis]); modelBounds[1][axis] = (std::max)(modelBounds[1][axis], partBounds[1][axis]); } for (int polygon = 0; polygon < part.numPolygons; ++polygon) { sourcePolygonEdges += grid.polygons[ part.firstPolygonIndex + polygon].numEdges; } } if (sourcePolygonCount <= 0) { return false; } for (int count : nodePolygonCounts) { if (count > 255) { return false; } } const int edgePadding = 4 - ((2 * sourcePolygonEdges) & 3); cm_buildNodeStats_t stats{}; stats.numNodes = (std::max)(1, grid.nodes.Num()); stats.numPrimitiveIndices = 2 * sourcePolygonCount; stats.numMaterials = 1; stats.numPolygons = 2 * sourcePolygonCount; stats.numPolygonEdges = 2 * sourcePolygonEdges + edgePadding; stats.numEdges = grid.edges.Num(); stats.numVertices = grid.vertices.Num(); stats.numPolytopes = 0; stats.numPolytopePlanes = 0; model->FreeData(); model->SetName(modelName); model->modelType = CM_POLYGONMODEL; model->bounds = modelBounds; model->contents = 1; model->isWorldModel = false; model->isTraceModel = false; model->isConvex = false; model->isStreamed = false; model->polygonModel.numModelTreeNodes = 0; model->polygonModel.modelTreeNodes = nullptr; model->polygonModel.numSubModels = 1; model->polygonModel.subModels = static_cast( _aligned_malloc(sizeof(cm_subModel_t), 16)); model->polygonModel.subModelState = static_cast(_aligned_malloc(1, 16)); if (model->polygonModel.subModels == nullptr || model->polygonModel.subModelState == nullptr) { model->FreeData(); return false; } cm_subModel_t& subModel = model->polygonModel.subModels[0]; cm_subModelPtrs_t pointers{}; if (!AllocSubModelData(stats, modelBounds, subModel, pointers)) { model->FreeData(); return false; } subModel.fileOffset = -1; subModel.numUsers = 0; subModel.state = model->polygonModel.subModelState; *subModel.state = SUBMODEL_STATE_LOADED; subModel.data->isConvex = 0; cm_material_t& material = pointers.materials[0]; material.contentFlags = 1; material.surfaceFlags = 0; material.surfaceType = 0; material.surfaceColor[0] = material.surfaceColor[1] = material.surfaceColor[2] = 0xFF; material.pad = 0; for (int index = 0; index < grid.vertices.Num(); ++index) { pointers.vertices[index].p = grid.vertices[index]; pointers.vertices[index].st[0] = 0; pointers.vertices[index].st[1] = 0; } for (int index = 0; index < grid.edges.Num(); ++index) { pointers.edges[index] = grid.edges[index]; } std::vector nodeCursors(nodePolygonCounts.size(), 0); int primitiveOffset = 0; for (int node = 0; node < stats.numNodes; ++node) { cm_node_t& destination = pointers.nodes[node]; if (node < grid.nodes.Num()) { const cm_gridNodeBSP_t& source = grid.nodes[node]; destination.planeType = source.planeType; destination.planeDist = source.planeDist; destination.children[0] = source.children[0]; destination.children[1] = source.children[1]; } else { destination.planeType = -1; destination.planeDist = 0.0f; destination.children[0] = destination.children[1] = 0; } destination.firstPrimitive = static_cast(primitiveOffset); destination.numPolygons = static_cast( nodePolygonCounts[node]); destination.numPolytopes = 0; nodeCursors[node] = primitiveOffset; primitiveOffset += nodePolygonCounts[node]; } int polygonCursor = 0; int edgeCursor = 0; for (int partIndex : selectedParts) { const cm_gridPart_t& part = grid.parts[partIndex]; for (int partPolygon = 0; partPolygon < part.numPolygons; ++partPolygon) { const cm_polygon_t& source = grid.polygons[ part.firstPolygonIndex + partPolygon]; for (int side = 0; side < 2; ++side) { cm_polygon_t& destination = pointers.polygons[polygonCursor]; destination = source; destination.material = 0; destination.firstEdge = static_cast( edgeCursor); for (int edge = 0; edge < source.numEdges; ++edge) { const int sourceEdge = side == 0 ? edge : source.numEdges - edge - 1; std::uint16_t reference = grid.polygonEdges[ source.firstEdge + sourceEdge]; if (side != 0) { reference ^= 0x8000; } pointers.polygonEdges[edgeCursor++] = reference; } pointers.primitiveIndices[ nodeCursors[part.nodeIndex]++] = static_cast(polygonCursor++); } } } const std::uint16_t padding = edgeCursor > 0 ? pointers.polygonEdges[edgeCursor - 1] : 0; while (edgeCursor < stats.numPolygonEdges) { pointers.polygonEdges[edgeCursor++] = padding; } return true; }