多边形碰撞检测 -- 分离轴算法
2017-02-24 19:04
330 查看
转自: http://www.jianshu.com/p/4000a301c32a
多边形碰撞检测在游戏开发中是非常常用的算法,最直接的算法是检测两个多边形的每个点是否被包含,但是由于多边形的数量和多边形点的数量导致这种最直接的算法的效率非常之低。本文将介绍一个非常简单并且效率极高的算法——“分离轴算法”,并用C语言和Lua语言分别实现该算法,可以分别用于Cocos2d和Corona开发。
上图就是分离轴算法的图示,首先需要知道分离轴算法只适用于“凸多边形”,但是由于“凹多边形”可以分成多个凸多边形组成,所以该算法可以用于所有多边形碰撞检测。不知道凹多边形是什么的看下图:
<!-- more -->
凹多边形就是含有顶点的内角度超过180°的多边形,反之就是凸多边形。
简单的说,分离轴算法就是指两个多边形能有一条直线将彼此分开,如图中黑线“Seperating line”,而与之垂直的绿线就是分离轴“Separating axis”。图中虚线表示的是多边形在分离轴上的投影(Projection)。详细的数学理论请查看wiki,我这里只讲该算法的实现方式。如果用伪代码来表示就是:
首先取多边形a的一边,得出该边的法线(即分离轴)。然后算出两个多边形在该法线上的投影,如果两个投影没有重叠则说明两个多边形不相交。遍历多边形a所有的边,如果所有法线都不满足条件,则说明两多边形相交。
首先我们需要定义几个数据类型和函数。
Lua:
vec为矢量或者向量,也可表示点;dot为矢量点投影运算;normalize为求模运算;perp计算法线向量;segment表示线段;polygon为多边形,包括顶点vertices和边edges,所有点的顺序必须按顺时针或者逆时针。如:
下面是C语言版的:
有了数据类型然后就是算法的判断函数。
Lua:
project为计算投影函数,先计算所有边长的投影,然后算出投影的最大和最小点即起始点;overlap函数判断两条线段是否重合。
C:
最后是算法实现函数,使用到上面的数据和函数。
Lua:
遍历a和b两个多边形的所有边长,判断投影是否重合。
C:
两个函数的使用方法很简单,只要定义好了多边形就行了。
Lua:
C:
完整的函数下载:Lua、C
多边形碰撞检测在游戏开发中是非常常用的算法,最直接的算法是检测两个多边形的每个点是否被包含,但是由于多边形的数量和多边形点的数量导致这种最直接的算法的效率非常之低。本文将介绍一个非常简单并且效率极高的算法——“分离轴算法”,并用C语言和Lua语言分别实现该算法,可以分别用于Cocos2d和Corona开发。
分离轴算法
上图就是分离轴算法的图示,首先需要知道分离轴算法只适用于“凸多边形”,但是由于“凹多边形”可以分成多个凸多边形组成,所以该算法可以用于所有多边形碰撞检测。不知道凹多边形是什么的看下图:
<!-- more -->
凹多边形就是含有顶点的内角度超过180°的多边形,反之就是凸多边形。
简单的说,分离轴算法就是指两个多边形能有一条直线将彼此分开,如图中黑线“Seperating line”,而与之垂直的绿线就是分离轴“Separating axis”。图中虚线表示的是多边形在分离轴上的投影(Projection)。详细的数学理论请查看wiki,我这里只讲该算法的实现方式。如果用伪代码来表示就是:
bool sat(polygon a, polygon b){ for (int i = 0; i < a.edges.length; i++){ vector axis = a.edges[i].direction; // Get the direction vector of the edge axis = vec_normal(axis); // We need to find the normal of the axis vector. axis = vec_unit(axis); // We also need to "normalize" this vector, or make its length/magnitude equal to 1 // Find the projection of the two polygons onto the axis segment proj_a = project(a, axis), proj_b = project(b, axis); if(!seg_overlap(proj_a, proj_b)) return false; // If they do not overlap, then return false } ... // Same thing for polygon b // At this point, we know that there were always intersections, hence the two polygons must be colliding return true; }
首先取多边形a的一边,得出该边的法线(即分离轴)。然后算出两个多边形在该法线上的投影,如果两个投影没有重叠则说明两个多边形不相交。遍历多边形a所有的边,如果所有法线都不满足条件,则说明两多边形相交。
算法实现
首先我们需要定义几个数据类型和函数。Lua:
function vec(x, y) return {x, y} end v = vec -- shortcut function dot(v1, v2) return v1[1]*v2[1] + v1[2]*v2[2] end function normalize(v) local mag = math.sqrt(v[1]^2 + v[2]^2) return vec(v[1]/mag, v[2]/mag) end function perp(v) return {v[2],-v[1]} end function segment(a, b) local obj = {a=a, b=b, dir={b[1] - a[1], b[2] - a[2]}} obj[1] = obj.dir[1]; obj[2] = obj.dir[2] return obj end function polygon(vertices) local obj = {} obj.vertices = vertices obj.edges = {} for i=1,#vertices do table.insert(obj.edges, segment(vertices[i], vertices[1+i%(#vertices)])) end return obj end
vec为矢量或者向量,也可表示点;dot为矢量点投影运算;normalize为求模运算;perp计算法线向量;segment表示线段;polygon为多边形,包括顶点vertices和边edges,所有点的顺序必须按顺时针或者逆时针。如:
a = polygon{v(0,0),v(0,1),v(1,1),v(1,0)}
下面是C语言版的:
typedef struct {float x, y;} vec; vec v(float x, float y){ vec a = {x, y}; // shorthand for declaration return a; } float dot(vec a, vec b){ return a.x*b.x+a.y*b.y; } #include <math.h> vec normalize(vec v){ float mag = sqrt(v.x*v.x + v.y*v.y); vec b = {v.x/mag, v.y/mag}; // vector b is only of distance 1 from the origin return b; } vec perp(vec v){ vec b = {v.y, -v.x}; return b; } typedef struct {vec p0, p1, dir;} seg; seg segment(vec p0, vec p1){ vec dir = {p1.x-p0.x, p1.y-p0.y}; seg s = {p0, p1, dir}; return s; } typedef struct {int n; vec *vertices; seg *edges;} polygon; // Assumption: Simply connected => chain vertices together polygon new_polygon(int nvertices, vec *vertices){ seg *edges = (seg*)malloc(sizeof(seg)*(nvertices)); int i; for (i = 0; i < nvertices-1; i++){ vec dir = {vertices[i+1].x-vertices[i].x, vertices[i+1].y-vertices[i].y};seg cur = {vertices[i], vertices[i+1], dir}; // We can also use the segment method here, but this is more explicit edges[i] = cur; } vec dir = {vertices[0].x-vertices[nvertices-1].x, vertices[0].y-vertices[nvertices-1].y};seg cur = {vertices[nvertices-1], vertices[0], dir}; edges[nvertices-1] = cur; // The last edge is between the first vertex and the last vertex polygon shape = {nvertices, vertices, edges}; return shape; } polygon Polygon(int nvertices, ...){ va_list args; va_start(args, nvertices); vec *vertices = (vec*)malloc(sizeof(vec)*nvertices); int i; for (i = 0; i < nvertices; i++){ vertices[i] = va_arg(args, vec); } va_end(args); return new_polygon(nvertices, vertices); }
有了数据类型然后就是算法的判断函数。
Lua:
-- We keep a running range (min and max) values of the projection, and then use that as our shadow function project(a, axis) axis = normalize(axis) local min = dot(a.vertices[1],axis) local max = min for i,v in ipairs(a.vertices) do local proj = dot(v, axis) -- projection if proj < min then min = proj end if proj > max then max = proj end end return {min, max} end function contains(n, range) local a, b = range[1], range[2] if b < a then a = b; b = range[1] end return n >= a and n <= b end function overlap(a_, b_) if contains(a_[1], b_) then return true elseif contains(a_[2], b_) then return true elseif contains(b_[1], a_) then return true elseif contains(b_[2], a_) then return true end return false end
project为计算投影函数,先计算所有边长的投影,然后算出投影的最大和最小点即起始点;overlap函数判断两条线段是否重合。
C:
float* project(polygon a, vec axis){ axis = normalize(axis); int i; float min = dot(a.vertices[0],axis); float max = min; // min and max are the start and finish points for (i=0;i<a.n;i++){ float proj = dot(a.vertices[i],axis); // find the projection of every point on the polygon onto the line. if (proj < min) min = proj; if (proj > max) max = proj; } float* arr = (float*)malloc(2*sizeof(float)); arr[0] = min; arr[1] = max; return arr; } int contains(float n, float* range){ float a = range[0], b = range[1]; if (b<a) {a = b; b = range[0];} return (n >= a && n <= b); } int overlap(float* a_, float* b_){ if (contains(a_[0],b_)) return 1; if (contains(a_[1],b_)) return 1; if (contains(b_[0],a_)) return 1; if (contains(b_[1],a_)) return 1; return 0; }
最后是算法实现函数,使用到上面的数据和函数。
Lua:
function sat(a, b) for i,v in ipairs(a.edges) do local axis = perp(v) local a_, b_ = project(a, axis), project(b, axis) if not overlap(a_, b_) then return false end end for i,v in ipairs(b.edges) do local axis = perp(v) local a_, b_ = project(a, axis), project(b, axis) if not overlap(a_, b_) then return false end end return true end
遍历a和b两个多边形的所有边长,判断投影是否重合。
C:
int sat(polygon a, polygon b){ int i; for (i=0;i<a.n;i++){ vec axis = a.edges[i].dir; // Get the direction vector axis = perp(axis); // Get the normal of the vector (90 degrees) float *a_ = project(a,axis), *b_ = project(b,axis); // Find the projection of a and b onto axis if (!overlap(a_,b_)) return 0; // If they do not overlap, then no collision } for (i=0;i<b.n;i++){ // repeat for b vec axis = b.edges[i].dir; axis = perp(axis); float *a_ = project(a,axis), *b_ = project(b,axis); if (!overlap(a_,b_)) return 0; } return 1; }
两个函数的使用方法很简单,只要定义好了多边形就行了。
Lua:
a = polygon{v(0,0),v(0,5),v(5,4),v(3,0)} b = polygon{v(4,4),v(4,6),v(6,6),v(6,4)} print(sat(a,b)) -- true
C:
polygon a = Polygon(4, v(0,0),v(0,3),v(3,3),v(3,0)), b = Polygon(4, v(4,4),v(4,6),v(6,6),v(6,4)); printf("%d\n", sat(a,b)); // false a = Polygon(4, v(0,0),v(0,5),v(5,4),v(3,0)); b = Polygon(4, v(4,4),v(4,6),v(6,6),v(6,4)); printf("%d\n", sat(a,b)); // true
完整的函数下载:Lua、C
相关文章推荐
- HGE像素碰撞,像素检测,多边形碰撞检测
- 基于分离轴定理的碰撞检测
- 碰撞检测之分离轴定理算法讲解
- 2D多边形碰撞检测和反馈(转)
- 2D多边形碰撞检测和反馈
- 碰撞检测之分离轴测试(Separate Axis Testing)
- ugui Image 控件多边形碰撞检测
- 基于2D多边形的碰撞检测和响应(三)
- JavaScript实现碰撞检测(分离轴定理)
- C 实现射线检测多边形碰撞
- 凸多边形碰撞检测算法——分离坐标轴方法
- 基于分离轴定理的碰撞检测
- 碰撞检测中函数(线与面,线与多边形,点到线的最近点) _ 转 - [叁]
- 基于2D多边形的碰撞检测和响应(四)
- 基于2D多边形的碰撞检测和响应(五)
- 用数学方法解决工程问题系列(二) 两个矩形(多边形)的碰撞检测
- 基于2D多边形的碰撞检测和响应(六)
- 基于2D多边形的碰撞检测和响应(七)
- H5游戏开发之多边形碰撞检测
- 任意多边形的碰撞检测——向量积判断方法