GJK算法计算凸多边形之间的距离

GJK是空间距离检测算法,是由三位(Gilbert, Johnson, and Keerthi )发明者的首字母组成的代称。  

GJK算法首先要解决计算Minkowski和的问题。所谓Minkowski和,指A、B两个集合,

令A+B={x+y,其中x属于A,y属于B}即二者的Minkowski和。类似的可以定义负集与Minkowski差。

 若A、B为凸多边形,顶点个数分别是n、m,则他们的Minkowski和一定是凸多边形且至多有n+m个顶点。

 

左上为多边形A、B,假设黑点为原点,三个图分别表示-B、A+B与A-B。A、B之间的点对距离即||x - y||,其中x属于A,y属于B;而A、B之间的距离就是||x-y||的最小值。因此,A、B之间的距离就是其Minkowski差A-B中的最小元素,这个最小指的是模最小,实际上就是距离原点的距离。因此,2个凸多边形的距离转化为点到凸多边形的距离,点指的是原点,凸多边形指的是原题中的2个凸包的Minkowski差。

    Minkowski和的算法如下,将A、B的边按逆时针方向拆成向量(顺时针实际上也可以),如上图可以得到6个向量。将这些向量按极角排序,然后依次首尾相连即可得到凸包。

    上述算法只是得到了和的形状与相对位置(因为首尾相连时出发的基点是原点),实际上该凸包还需做一个平移才能得到正确的坐标。如果排序时极角是取0~360度范围(NOTE:不必显式的求出极角,用先象限后叉积的方法排序),则求出A、B各自的最下最左点,将其坐标相加作为出发的基点即可。代码实现上这一点其实非常容易完成。

    求出Minkowski差之后(求差与求和本质是一样的),剩下的就是求点到凸多边形的距离,这个问题又转化为求点到边的距离。一个凸多边形有n条边,点到这n条边的距离的最小值就是点到凸多边形的距离。而且,点到边的距离是具有确定单调性的,因此运气好的话不需要求出所有边的距离,只需扫描到极小值即可。点到边的距离也就是点到线段的距离,利用叉积计算、点积进行判断,很容易求得。

    上述算法其实不是GJK算法,因为所求为凸多边形,而GJK算法可以用来求曲线凸包之间的距离(此情况下是一个数值逼近过程)。简单描述一下GJK的迭代过程,除了初始情况下,每一步迭代时均已求得凸包边缘上的3个点构成一个三角形Tk,然后求指定点p距离Tk最近的点,记作q,再将凸包投影到pq。qp方向上最远的投影点所对应的凸包上的点记作w,最后将Tk的3个点舍去1个,然后加上w形成新的Tk+1。最开始的3个点哪里来的?似乎可以随便选边缘上的3个点,最后应该可能也许大概一定可以迭代到结果,只是影响迭代次数而已。

Java版本代码片段

 

 

 

简化版本代码示例,便于理解算法实现流程。

import java.util.*;

public class GJKAlgorithm {
	//求向量叉乘
    public static double vectorCross(double[] vectorA, double[] vectorB) {
        return vectorA[0] * vectorB[1] - vectorA[1] * vectorB[0];
    }
    //求向量模
    public static double vectorMode(double[] vectorA) {
        return Math.sqrt(vectorA[0] * vectorA[0] + vectorA[1] * vectorA[1]);
    }
    //求向量夹角
    public static double vectorAngle(double[] vectorA, double[] vectorB) {
        return Math.acos((vectorA[0] * vectorB[0] + vectorA[1] * vectorB[1])
                         / (vectorMode(vectorA) * vectorMode(vectorB)));
    }
	
	//GJK算法
    public static boolean gjkAlgorithm(double[][] polygonA, double[][] polygonB) {
        //定义一个搜索方向
        double[] searchDirection = {1, 0};
        //定义一个差向量
        double[] diffVector = {0, 0};
        //定义一个点集合
        List<double[]> simplexList = new ArrayList<>();
        //根据搜索方向计算两个多边形的最小点
        double[] minA = getMinPoint(polygonA, searchDirection);
        double[] minB = getMinPoint(polygonB, searchDirection);
        //将最小点差向量添加到点集合
        diffVector[0] = minB[0] - minA[0];
        diffVector[1] = minB[1] - minA[1];
        simplexList.add(diffVector);
        //确定搜索方向
        searchDirection[0] = -diffVector[0];
        searchDirection[1] = -diffVector[1];
        //根据搜索方向计算两个多边形的最小点
        minA = getMinPoint(polygonA, searchDirection);
        minB = getMinPoint(polygonB, searchDirection);
        //将最小点差向量添加到点集合
        diffVector[0] = minB[0] - minA[0];
        diffVector[1] = minB[1] - minA[1];
        simplexList.add(diffVector);
        //循环搜索
        while (true) {
            //确定搜索方向
            searchDirection = getSearchDirection(simplexList);
            //根据搜索方向计算两个多边形的最小点
            minA = getMinPoint(polygonA, searchDirection);
            minB = getMinPoint(polygonB, searchDirection);
            //将最小点差向量添加到点集合
            diffVector[0] = minB[0] - minA[0];
            diffVector[1] = minB[1] - minA[1];
            simplexList.add(diffVector);
            //判断是否有解
            boolean isSolution = isSolution(simplexList, searchDirection);
            //如果有解,则返回true,否则返回false
            if (isSolution == true) {
                return true;
            } else {
                //如果没有解,则将最后一个点移除,重新搜索
                simplexList.remove(simplexList.size() - 1);
            }
        }
    }
	
	//根据搜索方向计算两个多边形的最小点
    public static double[] getMinPoint(double[][] polygon, double[] searchDirection) {
        //定义最小点
        double[] minPoint = {0, 0};
        //定义最小投影值
        double minProjection = 0;
        //计算投影值
        for (int i = 0; i < polygon.length; i++) {
            double projection = polygon[i][0] * searchDirection[0] + polygon[i][1] * searchDirection[1];
            //如果当前投影值小于最小投影值,则更新最小点
            if (i == 0 || projection < minProjection) {
                minProjection = projection;
                minPoint[0] = polygon[i][0];
                minPoint[1] = polygon[i][1];
            }
        }
        return minPoint;
    }
	
	//确定搜索方向
    public static double[] getSearchDirection(List<double[]> simplexList) {
        //定义搜索方向
        double[] searchDirection = {0, 0};
        //定义差向量
        double[] diffVector = {0, 0};
        //定义夹角
        double angle = 0;
        //获取最后一个点
        double[] lastPoint = simplexList.get(simplexList.size() - 1);
        //计算差向量
        diffVector[0] = -lastPoint[0];
        diffVector[1] = -lastPoint[1];
        //如果点集合中只有一个点,则搜索方向为差向量
        if (simplexList.size() == 1) {
            searchDirection[0] = diffVector[0];
            searchDirection[1] = diffVector[1];
        } else {
            //获取倒数第二个点
            double[] lastLastPoint = simplexList.get(simplexList.size() - 2);
            //计算夹角
            angle = vectorAngle(lastLastPoint, lastPoint);
            //如果夹角小于90度,则搜索方向为差向量
            if (angle < Math.PI / 2) {
                searchDirection[0] = diffVector[0];
                searchDirection[1] = diffVector[1];
            } else {
                //如果夹角大于90度,则搜索方向为叉乘结果
                searchDirection[0] = vectorCross(lastPoint, lastLastPoint);
                searchDirection[1] = -vectorCross(lastLastPoint, lastPoint);
            }
        }
        return searchDirection;
    }
	
	//判断是否有解
    public static boolean isSolution(List<double[]> simplexList, double[] searchDirection) {
        //如果点集合中只有一个点,则返回false
        if (simplexList.size() == 1) {
            return false;
        } else {
            //获取最后一个点
            double[] lastPoint = simplexList.get(simplexList.size() - 1);
            //如果最后一个点与搜索方向夹角为0度,则返回true
            if (vectorAngle(lastPoint, searchDirection) == 0) {
                return true;
            } else {
                return false;
            }
        }
    }
	
	//测试用例
    public static void main(String[] args) {
        //定义多边形A
        double[][] polygonA = {{1, 1}, {1, 4}, {4, 4}, {4, 1}};
        //定义多边形B
        double[][] polygonB = {{3, 3}, {3, 5}, {5, 5}, {5, 3}};
        //调用GJK算法
        boolean isCollision = gjkAlgorithm(polygonA, polygonB);
        //打印结果
        System.out.println(isCollision);
    }
}

//输出结果:true

 

posted on 2023-03-02 21:36  陈国利  阅读(511)  评论(0编辑  收藏  举报