分离轴定理:凸多边形相交检测算法

news2024/11/15 17:37:58

分离轴定理(Seperating Axis Theorem)是一种确定两个凸多边形是否相交的方法。该算法还可用于查找最小穿透向量,这对于物理模拟和许多其他应用非常有用。SAT 是一种快速通用算法,可以消除对每个形状类型对进行碰撞检测代码的需求,从而减少代码和维护。

NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割 

1、凸度

如前所述,SAT 是一种确定两个凸形是否相交的方法。如果任何穿过该形状的线只相交两次,则该形状被认为是凸形(convex)。如果一条线可以穿过该形状并相交两次以上,则该形状为非凸形或凹形(concave)。有关更正式的定义,请参阅 Wiki 的定义和 MathWorld 的定义。

让我们看一些例子:

图1:凸形状

图2:非凸形状

第一个形状被认为是凸的,因为不存在可以穿过该形状并相交超过两次的线。第二个形状不是凸的,因为确实存在相交超过两次的线。

SAT 只能处理凸形状,但这是可以的,因为非凸形状可以用凸形状的组合来表示,这被称为凸分解(convex decomposition)。因此,如果我们采用图 2 中的非凸形状并执行凸分解,我们可以得到两个凸形状。然后我们可以测试每个凸形状以确定整个形状的碰撞。

图3:凸分解

2、投影

SAT 使用的下一个概念是投影(projection)。想象一下,你有一个光源,它的光线都是平行的。如果你把这束光照射到一个物体上,它会在表面上产生阴影。阴影是三维物体的二维投影。二维物体的投影是一维“阴影”。

图 4:投影(或阴影)

3、SAT算法

SAT 指出:“如果两个凸面物体不相交,则存在一个轴,物体的投影不会在该轴上重叠。”

3.1 不相交

首先让我们讨论一下 SAT 如何确定两个形状不相交。在图 5 中,我们知道这两个形状不相交。在它们之间画一条线来说明这一点。

图 5:两个分离的凸形

如果我们选择图 5 中两个形状之间的垂直线,并将形状投影到该线上,我们可以看到它们的投影没有重叠。形状投影(阴影)不重叠的线称为分离轴。图 6 中的深灰色线是分离轴,相应的彩色线是形状在分离轴上的投影。请注意,图 6 中的投影没有重叠,因此根据 SAT,形状没有相交。

图 6:两个分离的凸形及其各自的投影

SAT 可能会测试多个轴的重叠情况,但是,如果投影不重叠的第一个轴,算法可以立即退出,并确定形状不相交。由于这种提前退出,SAT 非常适合具有许多对象但很少发生碰撞的应用程序(游戏、模拟等)。

为了进一步解释,请查看以下伪代码:

Axis[] axes = // get the axes to test;
// loop over the axes
for (int i = 0; i < axes.length; i++) {
  Axis axis = axes[i];
  // project both shapes onto the axis
  Projection p1 = shape1.project(axis);
  Projection p2 = shape2.project(axis);
  // do the projections overlap?
  if (!p1.overlap(p2)) {
    // then we can guarantee that the shapes do not overlap
    return false;
  }
}

3.2 相交

如果对于所有轴,形状的投影都重叠,那么我们可以得出结论,形状是相交的。图 7 展示了在多个轴上测试的两个凸形状。形状在这些轴上的投影全部重叠,因此我们可以得出结论,形状是相交的。

图 7:两个凸形相交

必须测试所有轴的重叠度才能确定相交。上面修改后的代码为:

Axis[] axes = // get the axes to test;
// loop over the axes
for (int i = 0; i < axes.length; i++) {
  Axis axis = axes[i];
  // project both shapes onto the axis
  Projection p1 = shape1.project(axis);
  Projection p2 = shape2.project(axis);
  // do the projections overlap?
  if (!p1.overlap(p2)) {
    // then we can guarantee that the shapes do not overlap
    return false;
  }
}
// if we get here then we know that every axis had overlap on it
// so we can guarantee an intersection
return true;

3、获取分离轴

我在实施此算法时遇到的第一个问题是如何知道要测试哪些轴?这实际上非常简单:

你必须测试的轴是每个形状边缘的法线。

图 8:边的法线

可以通过翻转坐标并取负一来获得边缘的法线。例如:

Vector[] axes = new Vector[shape.vertices.length];
// loop over the vertices
for (int i = 0; i < shape.vertices.length; i++) {
  // get the current vertex
  Vector p1 = shape.vertices[i];
  // get the next vertex
  Vector p2 = shape.vertices[i + 1 == shape.vertices.length ? 0 : i + 1];
  // subtract the two to get the edge vector
  Vector edge = p1.subtract(p2);
  // get either perpendicular vector
  Vector normal = edge.perp();
  // the perp method is just (x, y) =&gt; (-y, x) or (y, -x)
  axes[i] = normal;
}

在上述方法中,我们返回形状每个边缘的垂直向量。这些向量称为“法线”向量。但是,这些向量并未标准化(不是单位长度)。如果你只需要 SAT 算法的布尔结果,这就足够了,但如果你需要碰撞信息(稍后将在 MTV 部分讨论),则需要标准化这些向量(请参阅将形状投影到轴上部分)。

对每个形状执行此操作以获取要测试的两个轴列表。这样做会将上面的伪代码更改为:

Axis[] axes1 = shape1.getAxes();
Axis[] axes2 = shape2.getAxes();
// loop over the axes1
for (int i = 0; i < axes1.length; i++) {
  Axis axis = axes1[i];
  // project both shapes onto the axis
  Projection p1 = shape1.project(axis);
  Projection p2 = shape2.project(axis);
  // do the projections overlap?
  if (!p1.overlap(p2)) {
    // then we can guarantee that the shapes do not overlap
    return false;
  }
}
// loop over the axes2
for (int i = 0; i < axes2.length; i++) {
  Axis axis = axes2[i];
  // project both shapes onto the axis
  Projection p1 = shape1.project(axis);
  Projection p2 = shape2.project(axis);
  // do the projections overlap?
  if (!p1.overlap(p2)) {
    // then we can guarantee that the shapes do not overlap
    return false;
  }
}
// if we get here then we know that every axis had overlap on it
// so we can guarantee an intersection
return true;

4、将形状投影到轴上

另一件不清楚的事情是如何将形状投影到轴上。将多边形投影到轴上相对简单;循环遍历所有顶点,与轴执行点积并存储最小值和最大值。

double min = axis.dot(shape.vertices[0]);
double max = min;
for (int i = 1; i < shape.vertices.length; i++) {
  // NOTE: the axis must be normalized to get accurate projections
  double p = axis.dot(shape.vertices[i]);
  if (p < min) {
    min = p;
  } else if (p > max) {
    max = p;
  }
}
Projection proj = new Projection(min, max);
return proj;

5、查找 MTV

到目前为止,我们只在两个形状相交时返回 true 或 false。除此之外,SAT 还可以返回最小平移向量 (Minimum Translation Vector)。MTV 是用于将形状推离碰撞的最小幅度向量。如果我们回顾图 7,我们可以看到轴 C 具有最小重叠。该轴和重叠是 MTV,轴是向量部分,重叠是幅度部分。

要确定形状是否相交,我们必须循环遍历两个形状的所有轴,这样我们就可以同时跟踪最小重叠和轴。如果我们修改上面的伪代码以包含这一点,我们可以在形状相交时返回 MTV。

double overlap = // really large value;
Axis smallest = null;
Axis[] axes1 = shape1.getAxes();
Axis[] axes2 = shape2.getAxes();
// loop over the axes1
for (int i = 0; i < axes1.length; i++) {
  Axis axis = axes1[i];
  // project both shapes onto the axis
  Projection p1 = shape1.project(axis);
  Projection p2 = shape2.project(axis);
  // do the projections overlap?
  if (!p1.overlap(p2)) {
    // then we can guarantee that the shapes do not overlap
    return false;
  } else {
    // get the overlap
    double o = p1.getOverlap(p2);
    // check for minimum
    if (o < overlap) {
      // then set this one as the smallest
      overlap = o;
      smallest = axis;
    }
  }
}
// loop over the axes2
for (int i = 0; i < axes2.length; i++) {
  Axis axis = axes2[i];
  // project both shapes onto the axis
  Projection p1 = shape1.project(axis);
  Projection p2 = shape2.project(axis);
  // do the projections overlap?
  if (!p1.overlap(p2)) {
    // then we can guarantee that the shapes do not overlap
    return false;
  } else {
    // get the overlap
    double o = p1.getOverlap(p2);
    // check for minimum
    if (o < overlap) {
      // then set this one as the smallest
      overlap = o;
      smallest = axis;
    }
  }
}
MTV mtv = new MTV(smallest, overlap);
// if we get here then we know that every axis had overlap on it
// so we can guarantee an intersection
return mtv;

6、弯曲形状

我们已经了解了如何使用 SAT 测试多边形,但是像圆形这样的弯曲形状呢?弯曲形状对 SAT 来说是一个问题,因为弯曲形状有无数个分离轴需要测试。解决这个问题的方法是将“圆与圆”和“圆与多边形”测试分开,然后做一些更具体的工作。另一种选择是根本不使用弯曲形状,而是用高顶点数多边形代替它们。第二种选择不需要更改上述伪代码,但我想介绍第一种选择。

让我们首先看看“圆与圆”。通常,你会执行以下操作:

Vector c1 = circle1.getCenter();
Vector c2 = circle2.getCenter();
Vector v = c1.subtract(c2);
if (v.getMagnitude() < circle1.getRadius() + circle2.getRadius()) {
  // then there is an intersection
}
// else there isnt

如果两个圆心之间的距离小于圆半径之和,我们就知道它们相撞了。这个测试实际上是一个类似 SAT 的测试。为了在 SAT 中实现这一点,我们可以执行以下操作:

Vector[] axes = new Vector[1];
if (shape1.isCircle() && shape2.isCircle()) {
  // for two circles there is only one axis test
  axes[0] = shape1.getCenter().subtract(shape2.getCenter);
}
// then all the SAT code from above</pre>

圆形与多边形的问题更大。中心到中心测试以及多边形轴是不够的(事实上,中心到中心测试可以省略)。在这种情况下,您必须包含另一个轴:从多边形上最近的顶点到圆心的轴。可以通过多种方式找到多边形上最近的顶点,公认的解决方案是使用 Voronoi 区域,我不会在这篇文章中讨论。

其他弯曲形状将是一个更大的问题,必须以自己的方式处理。例如,胶囊形状可以分解为一个矩形和两个圆形。

7、包容性

许多开发人员选择忽略的问题之一是包容性。当一个形状包含另一个形状时会发生什么?这个问题通常不是什么大问题,因为大多数应用程序永远不会发生这种情况。首先让我解释一下这个问题以及如何处理它。然后我会解释为什么应该考虑它。

图9:包容性

如果一个形状包含在另一个形状 SAT 中,根据我们目前拥有的伪代码,将返回不正确的 MTV。矢量和幅度部分可能都不正确。图 9 显示返回的重叠不足以将形状移出相交。所以我们需要做的是在重叠测试中检查包含情况。仅从上面的 SAT 代码中获取 if 语句:

if (!p1.overlap(p2)) {
  // then we can guarantee that the shapes do not overlap
  return false;
} else {
  // get the overlap
  double o = p1.getOverlap(p2);
  // check for containment
  if (p1.contains(p2) || p2.contains(p1)) {
    // get the overlap plus the distance from the minimum end points
    double mins = abs(p1.min - p2.min);
    double maxs = abs(p1.max - p2.max);
    // NOTE: depending on which is smaller you may need to
    // negate the separating axis!!
    if (mins < maxs) {
      o += mins;
    } else {
      o += maxs;
    }
  }
  // check for minimum
  if (o < overlap) {
    // then set this one as the smallest
    overlap = o;
    smallest = axis;
  }
}

原因 1:形状有可能处于这种配置类型。如果不处理这种情况,则需要两次或更多次 SAT 迭代来解决碰撞,具体取决于形状的相对大小。

原因 2:如果您计划支持线段与其他形状,则必须这样做,因为在某些情况下重叠可能为零(这是因为线段是无限薄的形状)。

8、其他注意事项

其他一些注意事项:

  • 通过不测试平行轴可以减少要测试的轴数。这就是为什么矩形只有两个轴要测试的原因。
  • 如果某些形状(如矩形)具有自己的投影和 getAxes 代码,则可以执行得更快,因为矩形不需要测试 4 个轴,而实际上只需要 2 个。
  • 最后一个分离轴可用于启动 SAT 的下一次迭代,以便在非相交情况下算法可以是 O(1)。
  • 3D 中的 SAT 最终可能会测试很多轴。

原文链接:分离轴(SAT)相交检测算法 - BimAnt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1813021.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在Vue2和Vue3中ECharts如何使用,详细步骤,ref,$ref。echarts官网。

不管是在vue2中还是在vue3中其实本质上的原理和步骤都是一样的&#xff0c;只是语法可能有所不同。 为了方便大家看起乱&#xff0c;vue2和vue3我分开写。 echarts官网&#xff1a; https://echarts.apache.org/zh/index.html Vue2篇&#xff1a; 1.导入echarts包&#xf…

LaDM3IL:多实例学习用于免疫库分类

一个人的免疫组库由某一时间点的大量适应性免疫受体组成&#xff0c;代表了该个体的适应性免疫状态。免疫组库分类和相关受体识别有可能为新型疫苗的开发做出贡献。大量的实例对免疫组库分类提出了挑战&#xff0c;这可以表述为大规模多实例学习 (MMIL&#xff0c;Massive Mult…

AI实时免费在线图片工具4:WordArt艺术字生成;IC-Light打光模型;screenshot to code图像直接生成网页

1、艺术字生成WordArt https://modelscope.cn/studios/WordArt/WordArt/summary?reftop.aibase.com 2、打光模型IC-Light https://huggingface.co/spaces/lllyasviel/IC-Light Screenshot to Code图像直接生成网页 https://huggingface.co/spaces/HuggingFaceM4/screen…

Ubuntu,Linux服务器安装Mellanox MCX653105A IB网卡HCA卡驱动

驱动下载地址 https://network.nvidia.com/products/infiniband-drivers/linux/mlnx_ofed/ 选择对应操作系统 进入目录运行 安装成功显示 如果中途报错&#xff0c;需要核对下载的版本&#xff0c;并且把原来安装的卸载

钡铼BL102优化生产流程PLC-MQTT网关快速转换

在智能制造和工业4.0的推动下&#xff0c;优化生产流程、提升自动化水平已成为现代制造业的核心诉求。作为这一进程的关键环节&#xff0c;将传统的PLC控制系统与先进的物联网技术相结合&#xff0c;实现数据的高效采集与远程管理变得尤为重要。钡铼BL102作为一款专为优化生产流…

Jsch上传本地目录文件到服务器

文章目录 1.Jsch简介1.1 什么是Jsch1.2 Jsch使用步骤和简单示例 2.技术关键点3.Jsch实战3.1 maven依赖3.2 功能实现3.3 效果3.4 封装工具类 4.总结 摘要: 在一些框架开发工作中&#xff0c;需要为项目使用说明文档&#xff0c;来指导用户如何正确使用框架。比如通过markdown编写…

易舟云:让中小企业财务管理变得触手可及

在现代企业运营中&#xff0c;财务管理的精准和高效至关重要。对于中小企业来说&#xff0c;一款专业且易于使用的财务软件更是必不可少的工具。今天&#xff0c;我们就来详细了解一款深受中小企业喜爱的财务软件——易舟云。 财务管理的云端革命 随着云技术的发展&#xff0c;…

EVA-CLIP:在规模上改进CLIP的训练技术

摘要 对比性语言-图像预训练&#xff0c;简称CLIP&#xff0c;因其在各种场景中的潜力而备受关注。在本文中&#xff0c;我们提出了EVA-CLIP&#xff0c;一系列模型&#xff0c;这些模型显著提高了CLIP训练的效率和有效性。我们的方法结合了新的表示学习、优化和增强技术&…

Phaser-圆形路径

使用 Phaser 创建一个简单的路径动画 Phaser 是一个强大的 HTML5 游戏框架&#xff0c;适合用于开发 2D 游戏。在本文中&#xff0c;我们将展示如何使用 Phaser 创建一个简单的动画示例&#xff0c;其中一个红色的圆沿着椭圆路径移动。该示例将帮助你理解如何在 Phaser 中使用…

Linux用户,用户组,所有者权限分配,sftp用户权限分配

注意以下命令执行需要在root用户下执行 tenant命令切换至root命令 sudo -do root 删除用户信息 1.不删除用户主目录 userdel user_name 2.删除用户主目录 userdel -r user_name usermod命令修改用户账户权限 更改用户名 sudo usermod -l newusername oldusername 更…

Redis】Redis主从复制(二)————主从结构/流程

目录 回顾slaveof 命令断开主从复制关系切换主从复制关系只读网络延迟问题应对措施补充 主从结构一主一从结构问题改进 一主多从结构树形主从主从切换结构 主从复制流程简单来记关于数据同步两个参数replicationidoffset. psync 运行流程全量复制和部分复制全量复制流程&#x…

在镜像中添加Git提交号

文章目录 前言环境介绍思路内核cpuinfo中添加Git提交号修改setup.c获取Git提交号和生成GIT_COMMIT_INFO宏继续修改内核setup.c验证 内核设备树中添加Git提交号修改设备树验证 U-Boot版本号添加Git提交号U-Boot配置修改setlocalversion脚本验证 前言 在镜像中加入Git提交号&…

mysql和redis的双写一致性问题

一&#xff0c;使用方案 在使用redis作为缓存的场景下&#xff0c;我们一般使用流程如下 二&#xff0c;更新数据场景 我们此时修改个某条数据&#xff0c;如何保证mysql数据库和redis缓存中的数据一致呢&#xff1f; 按照常规思路有四种办法&#xff0c;1.先更新mysql数据&a…

计划任务!!!

目录 一、补充 1.1关闭防火墙 1.2安装php 二、计划任务 2.1at一次性计划任务 2.2周期性计划任务&#xff08;crontab&#xff09; 上篇我们学了rpm安装、yum安装还有编译安装。今天我们先补充一下上篇的东西再学习计划任务 一、补充 1.1关闭防火墙 systemctl stop fir…

亚马逊竞品分析之如何查找竞品

初选之后,要对产品进行竞品分析,查找竞品的方法: 1.Best Seller榜单查找 进入到该类目的BS榜单去找跟你选中的产品的竞品 看完BS榜单会找出一部分竞品 这个找相似也可以点击,是插件的一个以图搜图的功能,不过有的时候不太好使,某些同款产品可能搜不到。 Edge浏览器搭…

原生js写table表格固定表头

给表头添加以下属性 table表格写法参考 jquery写表格 手动合并单元格-CSDN博客 jquery写表格&#xff08;带滚动条&#xff09;_row.append($(<td>)-CSDN博客

面试题:什么是线程的上下文切换?

线程的上下文切换是指在操作系统中&#xff0c;CPU从执行一个线程的任务切换到执行另一个线程任务的过程。在现代操作系统中&#xff0c;为了实现多任务处理和充分利用CPU资源&#xff0c;会同时管理多个线程的执行。由于CPU在任意时刻只能执行一个线程&#xff0c;因此需要在这…

LeetCode 算法:螺旋矩阵c++

原题链接&#x1f517;&#xff1a;螺旋矩阵 难度&#xff1a;中等⭐️⭐️ 题目 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&…

西南交通大学【操作系统实验2】

实验目的 本实验要求学生了解什么是信号&#xff0c;掌握软中断的基本原理&#xff1b;掌握中断信号的使用、进程的创建以及系统计时器的使用。通过对本实验的学习&#xff0c;学生能够学会进程的创建方法&#xff0c;更能加深对Linux中的信号机制的认识&#xff0c;并会使用软…

【Qt 学习笔记】Qt窗口 | 标准对话框 | 消息对话框QMessageBox

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt窗口 | 标准对话框 | 消息对话框QMessageBox 文章编号&#xff1a;Q…