#include "cm/jobs/polygonmodel/polygonmodel.h" #include #include #include namespace { idVec3 Vec3(const idVec4& value) { return idVec3(value.x, value.y, value.z); } bool PointInsideBounds(const idVec3& point, const idBounds& bounds) { return point.x >= bounds[0].x && point.x <= bounds[1].x && point.y >= bounds[0].y && point.y <= bounds[1].y && point.z >= bounds[0].z && point.z <= bounds[1].z; } bool BoundsIntersect(const idBounds& lhs, const idBounds& rhs) { return lhs[0].x <= rhs[1].x && lhs[1].x >= rhs[0].x && lhs[0].y <= rhs[1].y && lhs[1].y >= rhs[0].y && lhs[0].z <= rhs[1].z && lhs[1].z >= rhs[0].z; } void ProcessLeaf(idTraceWork* const tw, const cm_node_t& node) { // The recovered contents path tests convex polytopes before individual // polygons. A polytope hit is sufficient and avoids reporting one of its // boundary polygons as the containing primitive. if (tw->traceType == TRACE_CONTENTS || tw->traceType == TRACE_CONTENTS_POINT) { for (int index = 0; index < node.numPolytopes; ++index) { const int polytopeNum = tw->subModelPtrs.primitiveIndices[ node.firstPrimitive + node.numPolygons + index]; if (idPolygonModelCollisionDetection::TestTrmVertsInPolytope( tw, polytopeNum)) { return; } } if (tw->traceType == TRACE_CONTENTS_POINT) { return; } } for (int index = 0; index < node.numPolygons && !tw->quickExit; ++index) { const int polygonNum = tw->subModelPtrs.primitiveIndices[ node.firstPrimitive + index]; switch (tw->traceType) { case TRACE_TRANSLATION: case TRACE_CONTACTS_UNI_DIR: if (idPolygonModelCollisionDetection::TranslateTrmThroughPolygon( tw, polygonNum)) { return; } break; case TRACE_TRANSLATION_POINT: if (idPolygonModelCollisionDetection::TranslatePointThroughPolygon( tw, polygonNum)) { return; } break; case TRACE_ROTATION: case TRACE_ROTATION_POINT: if (idPolygonModelCollisionDetection::RotateTrmThroughPolygon( tw, polygonNum)) { return; } break; case TRACE_CONTENTS: if (idPolygonModelCollisionDetection::TestTrmInPolygon( tw, polygonNum)) { return; } break; case TRACE_CONTACTS_OMNI_DIR: if (idPolygonModelCollisionDetection::TestTrmInContactWithPolygon( tw, polygonNum)) { return; } break; case TRACE_CLIP: if (idPolygonModelCollisionDetection::ClipPolygonWithTrm( tw, polygonNum)) { return; } break; default: break; } } } } // namespace // Recovered from engine/cm/jobs/polygonmodel/polygonmodel_trace.cpp. void idPolygonModelCollisionDetection::SetupSubModelPtrsFromData( cm_subModelPtrs_t& pointers, const cm_subModelData_t* const data) { std::uint8_t* const base = reinterpret_cast( const_cast(data)); pointers.isConvex = data->isConvex; pointers.nodes = reinterpret_cast(base + data->nodeOffset); pointers.primitiveIndices = reinterpret_cast( base + data->primitiveIndexOffset); pointers.materials = reinterpret_cast( base + data->materialOffset); pointers.polygons = reinterpret_cast( base + data->polygonOffset); pointers.polygonEdges = reinterpret_cast( base + data->polygonEdgeOffset); pointers.edges = reinterpret_cast(base + data->edgeOffset); pointers.vertices = reinterpret_cast( base + data->vertexOffset); pointers.polytopes = reinterpret_cast( base + data->polytopeOffset); pointers.polytopePlanes = reinterpret_cast( base + data->polytopePlaneOffset); } cm_subModelData_t* idPolygonModelCollisionDetection::SetupSubModelForBounds( cm_subModelData_t* const data, const int size, const idBounds& bounds) { constexpr int requiredSize = 608; if (data == nullptr || size < requiredSize) { return nullptr; } std::memset(data, 0, requiredSize); data->header.totalSize = requiredSize; data->header.loadedSize = requiredSize; data->header.bounds = bounds; data->isConvex = 1; data->numNodes = 1; data->nodeOffset = 112; data->numPrimitiveIndices = 7; data->primitiveIndexOffset = 128; data->numMaterials = 1; data->materialOffset = 144; data->numPolygons = 6; data->polygonOffset = 160; data->numPolygonEdges = 28; data->polygonEdgeOffset = 256; data->numEdges = 12; data->edgeOffset = 312; data->numVertices = 8; data->vertexOffset = 368; data->numPolytopes = 1; data->polytopeOffset = 496; data->numPolytopePlanes = 6; data->polytopePlaneOffset = 512; cm_subModelPtrs_t model; SetupSubModelPtrsFromData(model, data); cm_node_t& node = model.nodes[0]; node.planeType = -1; node.planeDist = 0.0f; node.children[0] = node.children[1] = 0; node.firstPrimitive = 0; node.numPolygons = 6; node.numPolytopes = 1; for (int index = 0; index < 6; ++index) { model.primitiveIndices[index] = static_cast(index); } model.primitiveIndices[6] = 0; cm_material_t& material = model.materials[0]; material.contentFlags = -1; material.surfaceFlags = -1; material.surfaceType = 0; material.surfaceColor[0] = material.surfaceColor[1] = material.surfaceColor[2] = 0xFF; material.pad = 0; idBounds expanded = bounds; for (int axis = 0; axis < 3; ++axis) { expanded[0][axis] -= 1.0f; expanded[1][axis] += 1.0f; } for (int polygonNumber = 0; polygonNumber < 6; ++polygonNumber) { model.polygons[polygonNumber].bounds.SetBounds(expanded); model.polygons[polygonNumber].material = 0; model.polygons[polygonNumber].numEdges = 4; model.polygons[polygonNumber].firstEdge = static_cast(polygonNumber * 4); } const std::uint16_t polygonEdges[28] = { 0x8003, 0x8002, 0x8001, 0x8000, 4, 5, 6, 7, 0, 9, 0x8004, 0x8008, 1, 10, 0x8005, 0x8009, 2, 11, 0x8006, 0x800A, 3, 8, 0x8007, 0x800B, 0x800B, 0x800B, 0x800B, 0x800B }; std::memcpy(model.polygonEdges, polygonEdges, sizeof(polygonEdges)); for (int index = 0; index < 4; ++index) { model.edges[index].vertexNum[0] = static_cast(index); model.edges[index].vertexNum[1] = static_cast((index + 1) & 3); model.edges[index + 4].vertexNum[0] = static_cast(index + 4); model.edges[index + 4].vertexNum[1] = static_cast(((index + 1) & 3) + 4); model.edges[index + 8].vertexNum[0] = static_cast(index); model.edges[index + 8].vertexNum[1] = static_cast(index + 4); } for (int index = 0; index < 8; ++index) { model.vertices[index].p.Set( (index == 1 || index == 2 || index == 5 || index == 6) ? bounds[1].x : bounds[0].x, (index == 2 || index == 3 || index == 6 || index == 7) ? bounds[1].y : bounds[0].y, index >= 4 ? bounds[1].z : bounds[0].z); model.vertices[index].st[0] = model.vertices[index].st[1] = 0; } model.polytopes[0].bounds.SetBounds(expanded); model.polytopes[0].material = 0; model.polytopes[0].numPlanes = 6; model.polytopes[0].firstPlane = 0; model.polytopePlanes[0] = idPlane(0.0f, 0.0f, -1.0f, bounds[0].z); model.polytopePlanes[1] = idPlane(0.0f, 0.0f, 1.0f, -bounds[1].z); model.polytopePlanes[2] = idPlane(0.0f, -1.0f, 0.0f, bounds[0].y); model.polytopePlanes[3] = idPlane(0.0f, 1.0f, 0.0f, -bounds[1].y); model.polytopePlanes[4] = idPlane(1.0f, 0.0f, 0.0f, -bounds[1].x); model.polytopePlanes[5] = idPlane(-1.0f, 0.0f, 0.0f, bounds[0].x); return data; } bool idPolygonModelCollisionDetection::TestStuckInSubModelBounds( idTraceWork* const tw, const idBounds& subModelBounds) { if (tw->traceType <= TRACE_INVALID || tw->traceType > TRACE_CLIP) { return false; } idBounds traceAtStart; traceAtStart[0] = Vec3(tw->start) + Vec3(tw->trmBoundsMin); traceAtStart[1] = Vec3(tw->start) + Vec3(tw->trmBoundsMax); if (!BoundsIntersect(traceAtStart, subModelBounds)) { return false; } int vertexNumber = 0; for (; vertexNumber < static_cast(tw->numVerts); ++vertexNumber) { if (PointInsideBounds(Vec3(tw->vertexPosition[vertexNumber]), subModelBounds)) { break; } } if (vertexNumber >= static_cast(tw->numVerts)) { return false; } if (tw->traceResult != nullptr) { trace_t& trace = *tw->traceResult; trace.fraction = 0.0f; trace.c.type = CONTACT_MODELVERTEX; trace.c.point = Vec3(tw->start); trace.c.normal.Set(0.0f, 0.0f, 1.0f); trace.c.dist = tw->start.w; trace.c.separation = 0.0f; trace.c.contentFlags = -1; trace.c.surfaceFlags = 0; trace.c.surfaceType = 0; trace.c.surfaceColor[0] = trace.c.surfaceColor[1] = trace.c.surfaceColor[2] = 0xFF; trace.c.modelFeature = (tw->subModelNum << 16) & 0x1FFF0000; trace.c.trmFeature = 0; trace.c.flags = CONTACT_FLAG_SUBMODEL_NOT_RESIDENT; } return true; } idVec3 idPolygonModelCollisionDetection::LocalExtentsFromUnTransformedBounds( const idBounds& globalBounds, const idVec3& globalStart, const idVec3& globalEnd, const idMat3& modelAxis) { idVec3 globalExtents; for (int axis = 0; axis < 3; ++axis) { const float pathMinimum = (std::min)(globalStart[axis], globalEnd[axis]); const float pathMaximum = (std::max)(globalStart[axis], globalEnd[axis]); const float negativeExtent = pathMinimum - globalBounds[0][axis]; const float positiveExtent = globalBounds[1][axis] - pathMaximum; globalExtents[axis] = (std::max)(negativeExtent, positiveExtent); } return idVec3( std::fabs(modelAxis[0].x) * globalExtents.x + std::fabs(modelAxis[0].y) * globalExtents.y + std::fabs(modelAxis[0].z) * globalExtents.z, std::fabs(modelAxis[1].x) * globalExtents.x + std::fabs(modelAxis[1].y) * globalExtents.y + std::fabs(modelAxis[1].z) * globalExtents.z, std::fabs(modelAxis[2].x) * globalExtents.x + std::fabs(modelAxis[2].y) * globalExtents.y + std::fabs(modelAxis[2].z) * globalExtents.z); } void idPolygonModelCollisionDetection::TraceThroughSubModelTree( idTraceWork* const tw) { if (tw == nullptr || tw->subModelPtrs.nodes == nullptr || tw->quickExit) { return; } std::uint16_t stack[128]; int stackSize = 0; stack[stackSize++] = 0; int iterations = 0; while (stackSize != 0 && !tw->quickExit && iterations++ < 65536) { const std::uint16_t nodeNumber = stack[--stackSize]; const cm_node_t& node = tw->subModelPtrs.nodes[nodeNumber]; if (node.numPolygons != 0 || node.numPolytopes != 0) { ProcessLeaf(tw, node); } if (node.planeType == -1) { continue; } // Check-counts remove duplicate primitive work, so visiting both sides // is a conservative scalar replacement for the VMX swept-tree walk. if (stackSize <= 126) { stack[stackSize++] = node.children[1]; stack[stackSize++] = node.children[0]; } } } void idPolygonModelCollisionDetection::TraceThroughSubModel( idTraceWork* const tw, const cm_subModelData_t* subModelData, const int subModelNum) { if (tw == nullptr || subModelData == nullptr) { return; } const cm_subModelData_t* data = subModelData; if (data->header.loadedSize == sizeof(cm_subModelHeader_t)) { if (TestStuckInSubModelBounds(tw, data->header.bounds)) { return; } data = SetupSubModelForBounds( reinterpret_cast(tw->subModelDataForBounds), static_cast(sizeof(tw->subModelDataForBounds)), data->header.bounds); if (data == nullptr) { return; } } SetupSubModelPtrsFromData(tw->subModelPtrs, data); tw->modelCheckCounts.SetupForSubModel(data); tw->subModelNum = subModelNum; TraceThroughSubModelTree(tw); if (subModelData->header.loadedSize == sizeof(cm_subModelHeader_t) && tw->traceResult != nullptr && tw->traceResult->fraction < 1.0f) { tw->traceResult->c.flags |= CONTACT_FLAG_SUBMODEL_NOT_RESIDENT; } } unsigned int idPolygonModelCollisionDetection::GetSubModelsForTrace( const cm_polygonModel_t& model, const idVec3&, const idVec3&, const idVec3&, int* const subModelNums) { if (subModelNums == nullptr || model.numSubModels <= 0) { return 0; } const unsigned int count = static_cast((std::min)( model.numSubModels, 128)); for (unsigned int index = 0; index < count; ++index) { subModelNums[index] = static_cast(index); } return count; } void idPolygonModelCollisionDetection::TraceThroughModel( idTraceWork* const tw, const cm_polygonModel_t& model) { int subModelNums[128]; const unsigned int count = GetSubModelsForTrace(model, Vec3(tw->start), Vec3(tw->end), Vec3(tw->trmExtents), subModelNums); for (unsigned int index = 0; index < count && !tw->quickExit; ++index) { const int subModelNum = subModelNums[index]; const cm_subModel_t& subModel = model.subModels[subModelNum]; const cm_subModelData_t* const data = AcquireSubModelData(subModel); TraceThroughSubModel(tw, data, subModelNum); ReleaseSubModelData(subModel, data); } }