找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: 活动 交友 discuz
查看: 94|回复: 2

NFP碰撞检测算法实现

[复制链接]

0

主题

0

回帖

26

积分

管理员

积分
26
发表于 2024-6-29 16:34:00 | 显示全部楼层
实现一个完整的CalculateDistance函数来处理多边形边之间的所有可能情况(相交、不相交、重合、平行等)是一个相当复杂的任务,因为它需要处理多种几何情况和边界条件。不过,我可以给你一个简化的框架和示例代码,说明如何开始这样的实现。

请注意,下面的代码仅用于演示目的,并不涵盖所有可能的边界情况和优化。对于生产环境的应用,你可能需要使用更健壮和经过优化的库或算法。

首先,我们需要定义一些辅助函数来处理线段和点的基本几何操作。然后,我们可以实现CalculateDistance函数来处理不同的几何关系。

#include <cmath>
#include <vector>
#include <limits>

// ... (之前定义的Vector2d和AcGePoint2d类) ...

// 判断点是否在线段上(包括端点)
bool IsPointOnSegment(const AcGePoint2d& point, const AcGePoint2d& segmentStart, const AcGePoint2d& segmentEnd) {
    Vector2d vec1 = segmentStart.toVector(point);
    Vector2d vec2 = segmentStart.toVector(segmentEnd);
    return std::fabs(vec1.cross(vec2)) < 1e-6 && // 点到线段的垂直距离几乎为0
           vec1.dot(vec2) >= 0 && // 点在线段的投影点在线段上
           (vec1.lengthSquared() <= vec2.lengthSquared()); // 点到线段起点的距离不超过线段长度
}

// 计算点到线段的最近点(垂足)和距离
std::pair<AcGePoint2d, double> ClosestPointOnSegment(const AcGePoint2d& point, const AcGePoint2d& segmentStart, const AcGePoint2d& segmentEnd) {
    Vector2d segmentVec = segmentStart.toVector(segmentEnd);
    double segmentLengthSquared = segmentVec.lengthSquared();
    if (segmentLengthSquared < 1e-6) { // 线段退化为点
        return {segmentStart, (point - segmentStart).length()};
    }

    Vector2d pointVec = segmentStart.toVector(point);
    double t = pointVec.dot(segmentVec) / segmentLengthSquared;
    if (t < 0) { // 垂足在线段起点之外
        return {segmentStart, pointVec.length()};
    } else if (t > 1) { // 垂足在线段终点之外
        return {segmentEnd, (point - segmentEnd).length()};
    }

    // 垂足在线段上
    AcGePoint2d closestPoint = segmentStart + segmentVec * t;
    return {closestPoint, (point - closestPoint).length()};
}

// 计算两个线段之间的最小距离(简化版,不考虑平行且不相交的情况)
double CalculateDistance(const AcGePoint2d& a1, const AcGePoint2d& a2, const AcGePoint2d& b1, const AcGePoint2d& b2) {
    double minDistance = std::numeric_limits<double>::max();

    // 检查线段a的端点是否在线段b上
    for (const auto& point : {a1, a2}) {
        if (IsPointOnSegment(point, b1, b2)) {
            return 0.0; // 重合
        }
    }

    // 检查线段b的端点是否在线段a上
    for (const auto& point : {b1, b2}) {
        if (IsPointOnSegment(point, a1, a2)) {
            return 0.0; // 重合
        }
    }

    // 计算a的每个端点到线段b的最短距离
    for (const auto& point : {a1, a2}) {
        auto [closestPoint, distance] = ClosestPointOnSegment(point, b1, b2);
        minDistance = std::min(minDistance, distance);
    }

    // 计算b的每个端点到线段a的最短距离
    for (const auto& point : {b1, b2}) {
        auto [closestPoint, distance] = ClosestPointOnSegment(point, a1, a2);
        minDistance = std::min(minDistance, distance);
    }

    return minDistance;
}

// ... 其他代码 ...

// 注意:这个CalculateDistance函数没有处理平行且不相交的情况,也没有考虑浮点数的精度问题。
// 在实际应用中,你可能需要添加额外的逻辑来处理这些情况,并使用更健壮的方法来比较浮点数。

0

主题

0

回帖

26

积分

管理员

积分
26
发表于 2024-6-29 16:35:28 | 显示全部楼层
  1. nfp 临界多边形算法移动碰撞法的基本步骤如下: 首先根据多边形A和B当前时刻的靠接状态,得到B下一步的移动方向,在该移动方向上,计算出A和B之间的最小碰撞距离,从而得到移动距离,根据移动方向和移动距离,将B移动到新的位置,然后重复以上过程,直至绕完一圈,回到初始位置。 完整c++代码   static        vector<AcGePoint2d>  NoFitPolygon(vector<AcGePoint2d> py1, vector<AcGePoint2d> py2)
  2. 实现NFP(Non-Fitting Polygon)临界多边形算法涉及多个步骤,包括计算多边形的凸包、确定接触点、计算最小碰撞距离等。下面是一个简化版的C++伪代码示例,用于演示如何使用NFP算法来处理两个多边形A和B的碰撞检测和响应:
  3. Cpp
  4. #include <vector>
  5. #include <iostream>
  6. struct AcGePoint2d {
  7.     double x;
  8.     double y;
  9. };
  10. // 简化版本的NFP算法实现
  11. std::vector<AcGePoint2d> NoFitPolygon(std::vector<AcGePoint2d> poly1, std::vector<AcGePoint2d> poly2) {
  12.     std::vector<AcGePoint2d> result;
  13.     // 假设poly1和poly2已经经过预处理,确保它们是凸的且在原点附近
  14.     while (true) {
  15.         // 计算poly1和poly2之间的最小距离和相应的法线
  16.         AcGePoint2d normal = calculateNormal(poly1, poly2);
  17.         double minDistance = calculateMinDistance(poly1, poly2, normal);
  18.         if (minDistance > 0) {
  19.             // 如果最小距离大于0,则poly1和poly2之间存在碰撞
  20.             // 根据碰撞响应调整poly2的位置
  21.             movePolygon(poly2, -minDistance * normal);
  22.         } else {
  23.             // 如果最小距离小于等于0,则poly1和poly2没有碰撞
  24.             break;
  25.         }
  26.         // 检查是否回到了初始位置
  27.         if (isBackToStart(poly1, poly2)) {
  28.             break;
  29.         }
  30.     }
  31.     return result; // 返回结果,这里可能需要返回一些关于NFP的信息,如碰撞点或碰撞次数
  32. }
  33. AcGePoint2d calculateNormal(std::vector<AcGePoint2d> poly1, std::vector<AcGePoint2d> poly2) {
  34.     // 计算poly1和poly2之间的最小距离的法线
  35.     // 这里省略了具体的实现细节
  36.     AcGePoint2d normal;
  37.     return normal;
  38. }
  39. double calculateMinDistance(std::vector<AcGePoint2d> poly1, std::vector<AcGePoint2d> poly2, AcGePoint2d normal) {
  40.     // 计算poly1和poly2之间的最小距离
  41.     // 这里省略了具体的实现细节
  42.     double minDistance = 0;
  43.     return minDistance;
  44. }
  45. void movePolygon(std::vector<AcGePoint2d>& poly, AcGePoint2d direction) {
  46.     // 根据方向移动多边形
  47.     for (auto& point : poly) {
  48.         point.x += direction.x;
  49.         point.y += direction.y;
  50.     }
  51. }
  52. bool isBackToStart(std::vector<AcGePoint2d> poly1, std::vector<AcGePoint2d> poly2) {
  53.     // 检查是否回到了初始位置
  54.     // 这里省略了具体的实现细节
  55.     bool backToStart = false;
  56.     return backToStart;
  57. }
  58. 请注意,上述代码中的calculateNormal、calculateMinDistance、movePolygon和isBackToStart函数都需要具体实现。此外,实际应用中可能还需要考虑多边形旋转、速度等因素。
  59. 由于NFP算法的具体实现较为复杂,涉及到几何计算、线性代数等多个领域的知识,上述代码仅提供了一个框架性的示例,具体实现时需要根据实际情况进行详细设计和编程。
  60. double computeCollisionDistance(vector<AcGePoint2d>& poly1, vector<AcGePoint2d>& poly2) {
  61. // 遍历poly2边界,与poly1边界对比
  62. // ...
  63. }  完整的C++代码 给出
  64. 计算两个多边形碰撞距离的过程通常涉及找到能够分离这两个多边形的轴,这个轴可以是其中一个多边形的边的法线。下面是一个基于分离轴定理(Separating Axis Theorem, SAT)的简化版C++函数,用于计算两个多边形之间的最小碰撞距离。请注意,为了简化问题,我们假设输入的多边形是凸的。
  65. Cpp
  66. #include <vector>
  67. #include <algorithm>
  68. #include <cmath>
  69. struct AcGePoint2d {
  70.     double x;
  71.     double y;
  72. };
  73. // 向量点乘
  74. double dotProduct(const AcGePoint2d& v1, const AcGePoint2d& v2) {
  75.     return v1.x * v2.x + v1.y * v2.y;
  76. }
  77. // 向量叉乘
  78. double crossProduct(const AcGePoint2d& v1, const AcGePoint2d& v2) {
  79.     return v1.x * v2.y - v1.y * v2.x;
  80. }
  81. // 计算向量的长度
  82. double vectorLength(const AcGePoint2d& v) {
  83.     return std::sqrt(v.x * v.x + v.y * v.y);
  84. }
  85. // 计算向量的单位化
  86. AcGePoint2d normalizeVector(const AcGePoint2d& v) {
  87.     double length = vectorLength(v);
  88.     return {v.x / length, v.y / length};
  89. }
  90. // 计算两个多边形之间的最小碰撞距离
  91. double computeCollisionDistance(std::vector<AcGePoint2d>& poly1, std::vector<AcGePoint2d>& poly2) {
  92.     double minDistance = std::numeric_limits<double>::max();
  93.     AcGePoint2d normal;
  94.     // 对于poly1的每条边
  95.     for (size_t i = 0; i < poly1.size(); ++i) {
  96.         size_t nextIndex = (i + 1) % poly1.size();
  97.         AcGePoint2d edge = {poly1[nextIndex].x - poly1[i].x, poly1[nextIndex].y - poly1[i].y};
  98.         AcGePoint2d edgeNormal = normalizeVector({-edge.y, edge.x});
  99.         double minProjPoly1 = dotProduct(edgeNormal, poly1[i]);
  100.         double maxProjPoly1 = minProjPoly1;
  101.         for (size_t j = 0; j < poly1.size(); ++j) {
  102.             double proj = dotProduct(edgeNormal, poly1[j]);
  103.             minProjPoly1 = std::min(minProjPoly1, proj);
  104.             maxProjPoly1 = std::max(maxProjPoly1, proj);
  105.         }
  106.         double minProjPoly2 = dotProduct(edgeNormal, poly2[0]);
  107.         double maxProjPoly2 = minProjPoly2;
  108.         for (size_t j = 0; j < poly2.size(); ++j) {
  109.             double proj = dotProduct(edgeNormal, poly2[j]);
  110.             minProjPoly2 = std::min(minProjPoly2, proj);
  111.             maxProjPoly2 = std::max(maxProjPoly2, proj);
  112.         }
  113.         if (maxProjPoly1 < minProjPoly2 || maxProjPoly2 < minProjPoly1) {
  114.             // 如果两个多边形在这个轴上的投影不重叠,则没有碰撞
  115.             return std::numeric_limits<double>::max();
  116.         }
  117.         double overlap = std::max(0.0, std::min(maxProjPoly1, maxProjPoly2) - std::max(minProjPoly1, minProjPoly2));
  118.         if (overlap < minDistance) {
  119.             minDistance = overlap;
  120.             normal = edgeNormal;
  121.         }
  122.     }
  123.     // 对于poly2的每条边,重复上述过程
  124.     for (size_t i = 0; i < poly2.size(); ++i) {
  125.         size_t nextIndex = (i + 1) % poly2.size();
  126.         AcGePoint2d edge = {poly2[nextIndex].x - poly2[i].x, poly2[nextIndex].y - poly2[i].y};
  127.         AcGePoint2d edgeNormal = normalizeVector({-edge.y, edge.x});
  128.         double minProjPoly1 = dotProduct(edgeNormal, poly1[0]);
  129.         double maxProjPoly1 = minProjPoly1;
  130.         for (size_t j = 0; j < poly1.size(); ++j) {
  131.             double proj = dotProduct(edgeNormal, poly1[j]);
  132.             minProjPoly1 = std::min(minProjPoly1, proj);
  133.             maxProjPoly1 = std::max(maxProjPoly1, proj);
  134.         }
  135.         double minProjPoly2 = dotProduct(edgeNormal, poly2[i]);
  136.         double maxProjPoly2 = minProjPoly2;
  137.         for (size_t j = 0; j < poly2.size(); ++j) {
  138.             double proj = dotProduct(edgeNormal, poly2[j]);
  139.             minProjPoly2 = std::min(minProjPoly2, proj);
  140.             maxProjPoly2 = std::max(maxProjPoly2, proj);
  141.         }
  142.         if (maxProjPoly1 < minProjPoly2 || maxProjPoly2 < minProjPoly1) {
  143.             return std::numeric_limits<double>::max();
  144.         }
  145.         double overlap = std::max(0.0, std::min(maxProjPoly1, maxProjPoly2) - std::max(minProjPoly1, minProjPoly2));
  146.         if (overlap < minDistance) {
  147.             minDistance = overlap;
  148.             normal = edgeNormal;
  149.         }
  150.     }
  151.     return minDistance;
  152. }
  153. 这段代码实现了通过遍历每个多边形的所有边,并检查它们的法线方向是否能作为分离轴来判断两个多边形是否有碰撞,以及如果碰撞发生,计算最小碰撞距离。请注意,这个函数返回的是两个多边形之间的最小重叠距离,如果返回值为正,则表示两个多边形有碰撞;如果返回值为std::numeric_limits<double>::max(),则表示没有碰撞。
  154. // 计算多边形之间的最小碰撞距离和碰撞方向 double CalculateCollisionDistanceAndDirection(const std::vector<AcGePoint2d>& polyA,                                              const std::vector<AcGePoint2d>& polyB,                                              Vector2d& collisionDirection) {     // 实现计算最小碰撞距离和碰撞方向的逻辑     // 这里需要使用到多边形顶点之间的距离计算、向量运算等     // ...     return minDistance; // 返回最小碰撞距离 }完整的C++代码
  155. 在计算两个多边形之间的最小碰撞距离及其方向时,我们可以采用分离轴定理(Separating Axis Theorem, SAT)。以下是基于SAT的C++代码实现,它不仅计算了最小碰撞距离,还返回了碰撞方向。我们使用Vector2d作为collisionDirection的类型,这可以是一个自定义的结构体或者类来表示二维向量。
  156. 首先,我们需要定义Vector2d类型:
  157. Cpp
  158. struct Vector2d {
  159.     double x, y;
  160.     Vector2d(double x = 0.0, double y = 0.0) : x(x), y(y) {}
  161.     Vector2d operator-(const Vector2d& other) const {
  162.         return Vector2d(x - other.x, y - other.y);
  163.     }
  164.     double dot(const Vector2d& other) const {
  165.         return x * other.x + y * other.y;
  166.     }
  167.     Vector2d normalized() const {
  168.         double len = sqrt(x * x + y * y);
  169.         return Vector2d(x / len, y / len);
  170.     }
  171. };
  172. 接下来是主要的函数实现:
  173. Cpp
  174. double CalculateCollisionDistanceAndDirection(
  175.     const std::vector<AcGePoint2d>& polyA,
  176.     const std::vector<AcGePoint2d>& polyB,
  177.     Vector2d& collisionDirection)
  178. {
  179.     double minOverlap = std::numeric_limits<double>::max();
  180.     Vector2d bestAxis;
  181.     // 遍历polyA的边,计算其法线并作为分离轴
  182.     for (size_t i = 0; i < polyA.size(); ++i) {
  183.         size_t nextIndex = (i + 1) % polyA.size();
  184.         Vector2d edge = Vector2d(polyA[nextIndex].x - polyA[i].x, polyA[nextIndex].y - polyA[i].y);
  185.         Vector2d axis = edge.normalized().rotated90CCW();
  186.         double minA = std::numeric_limits<double>::max();
  187.         double maxA = std::numeric_limits<double>::lowest();
  188.         for (const auto& p : polyA) {
  189.             double projection = Vector2d(p.x, p.y).dot(axis);
  190.             minA = std::min(minA, projection);
  191.             maxA = std::max(maxA, projection);
  192.         }
  193.         double minB = std::numeric_limits<double>::max();
  194.         double maxB = std::numeric_limits<double>::lowest();
  195.         for (const auto& p : polyB) {
  196.             double projection = Vector2d(p.x, p.y).dot(axis);
  197.             minB = std::min(minB, projection);
  198.             maxB = std::max(maxB, projection);
  199.         }
  200.         if (maxA < minB || maxB < minA) {
  201.             return std::numeric_limits<double>::max(); // 不相交
  202.         }
  203.         double overlap = std::max(0.0, std::min(maxA, maxB) - std::max(minA, minB));
  204.         if (overlap < minOverlap) {
  205.             minOverlap = overlap;
  206.             bestAxis = axis;
  207.         }
  208.     }
  209.     // 遍历polyB的边,计算其法线并作为分离轴
  210.     for (size_t i = 0; i < polyB.size(); ++i) {
  211.         size_t nextIndex = (i + 1) % polyB.size();
  212.         Vector2d edge = Vector2d(polyB[nextIndex].x - polyB[i].x, polyB[nextIndex].y - polyB[i].y);
  213.         Vector2d axis = edge.normalized().rotated90CCW();
  214.         double minA = std::numeric_limits<double>::max();
  215.         double maxA = std::numeric_limits<double>::lowest();
  216.         for (const auto& p : polyA) {
  217.             double projection = Vector2d(p.x, p.y).dot(axis);
  218.             minA = std::min(minA, projection);
  219.             maxA = std::max(maxA, projection);
  220.         }
  221.         double minB = std::numeric_limits<double>::max();
  222.         double maxB = std::numeric_limits<double>::lowest();
  223.         for (const auto& p : polyB) {
  224.             double projection = Vector2d(p.x, p.y).dot(axis);
  225.             minB = std::min(minB, projection);
  226.             maxB = std::max(maxB, projection);
  227.         }
  228.         if (maxA < minB || maxB < minA) {
  229.             return std::numeric_limits<double>::max(); // 不相交
  230.         }
  231.         double overlap = std::max(0.0, std::min(maxA, maxB) - std::max(minA, minB));
  232.         if (overlap < minOverlap) {
  233.             minOverlap = overlap;
  234.             bestAxis = axis;
  235.         }
  236.     }
  237.     collisionDirection = bestAxis;
  238.     return minOverlap;
  239. }
  240. 注意,上面的代码中,axis.rotated90CCW()函数用于计算一个向量逆时针旋转90度后的向量,这可以通过简单的坐标变换实现,例如:
  241. Cpp
  242. Vector2d Vector2d::rotated90CCW() const {
  243.     return Vector2d(-y, x);
  244. }
  245. 这个函数实现了计算两个多边形之间的最小碰撞距离和碰撞方向的核心逻辑,其中collisionDirection参数用于输出碰撞方向。如果两个多边形不相交,则函数会返回一个非常大的数(通过std::numeric_limits<double>::max()表示),表示没有碰撞发生。
复制代码
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|膜结构网

GMT+8, 2024-12-27 21:54 , Processed in 0.117726 second(s), 16 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表