使用轴分离法对凸多边形进行碰撞检测的核心思想


 

1.如图,△ABC和△DEF相交,假设以△ABC为参考物,我们如何把顶点D移到直线BC上,并使其看起来合理?
我们可以使△DEF沿BC的法向DG移动DG的长度,这样,就能得到我们所要的“看起来合理”

2.△ABC和△DEF的每个顶点的坐标已知,因此各边的法向量容易求出,求出后我们把各边的法向量归一化,以便
进行向量的计算。

3.现在要求DG向量。如图,DF与BC交于H点,这样,向量DC在BC边上的单位法向量的投影向量就是向量DG,而
向量DC和BC的单位法向量容易求出,于是
 向量DG = 向量DC dot BC的单位法向量
DG向量求出后,使△DEF各顶点坐标加上向量DG,就求出最后结果

在程序中实现,还有一个重要的问题,在程序中如何得知哪条边被穿插(对于上图,BC边被穿插)?下面代码会告诉你如何解决这个问题。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;


namespace PolygonIntersection {

	public partial class MainForm : Form 
    {

		// Structure that stores the results of the PolygonCollision function
		public struct PolygonCollisionResult 
        {
			public bool WillIntersect; // Are the polygons going to intersect forward in time?
			public bool Intersect; // Are the polygons currently intersecting
			public Vector MinimumTranslationVector; // The translation to apply to polygon A to push the polygons appart.
		}

		// Check if polygon A is going to collide with polygon B for the given velocity
        /// <summary>
        /// 两多边形碰撞检测
        /// 此函数必须在两多边形在当前帧并未移动时调用
        /// </summary>
        /// <param name="polygonA"></param>
        /// <param name="polygonB"></param>
        /// <param name="velocity">多边形A相对于B的速度,即A与B的合速度</param>
        /// <returns></returns>
		public PolygonCollisionResult PolygonCollision(Polygon polygonA, Polygon polygonB, Vector velocity) 
        {
			PolygonCollisionResult result = new PolygonCollisionResult();
			result.Intersect = true;
			result.WillIntersect = true;

			int edgeCountA = polygonA.Edges.Count;
			int edgeCountB = polygonB.Edges.Count;
			float minIntervalDistance = float.PositiveInfinity;
			Vector translationAxis = new Vector();
			Vector edge;

			// Loop through all the edges of both polygons
            //遍历两多边的所有边
			for (int edgeIndex = 0; edgeIndex < edgeCountA + edgeCountB; edgeIndex++) 
            {
				if (edgeIndex < edgeCountA) 
                {
					edge = polygonA.Edges[edgeIndex];
				} 
                else 
                {
					edge = polygonB.Edges[edgeIndex - edgeCountA];
				}

				// ===== 1. Find if the polygons are currently intersecting =====

				// Find the axis perpendicular to the current edge
                //计算当前边的单位法向量
				Vector axis = new Vector(-edge.Y, edge.X);
				axis.Normalize();


				// Find the projection of the polygon on the current axis
                //找出各顶点向量在当前单位法向量投影并返回此投影的最小和最大长度
				float minA = 0; float minB = 0; float maxA = 0; float maxB = 0;
				ProjectPolygon(axis, polygonA, ref minA, ref maxA);
				ProjectPolygon(axis, polygonB, ref minB, ref maxB);

				// Check if the polygon projections are currentlty intersecting
                //检测多边形A,B在当前单位法向量的投影是否相交
				if (IntervalDistance(minA, maxA, minB, maxB) > 0) result.Intersect = false;

				// ===== 2. Now find if the polygons *will* intersect =====

                // Project the velocity on the current axis 
                //计算A,B合速度在当前单位法向量的投影的长度(可正,可负)
				float velocityProjection = axis.DotProduct(velocity);

				// Get the projection of polygon A during the movement
				if (velocityProjection < 0) 
                {
                    //速度与投影的夹角大于90度小于180度
                    //说明minA > maxA 且minA,maxA,velocityProjection同时小于零
					minA += velocityProjection;
				} 
                else
                {
                    //速度与投影的夹角大于0度小于90度
                    //说明minA < maxA 且minA,maxA,velocityProjection同时大于零
					maxA += velocityProjection;
				}

				// Do the same test as above for the new projection
                //判断在当前帧的A物体移动后,是否与B相交
				float intervalDistance = IntervalDistance(minA, maxA, minB, maxB);
				if (intervalDistance > 0) result.WillIntersect = false;

				// If the polygons are not intersecting and won't intersect, exit the loop
                //如果当前帧不相交 且 移动后不相交  则表示两物体被分离
				if (!result.Intersect && !result.WillIntersect) break;

				// Check if the current interval distance is the minimum one. If so store
				// the interval distance and the current distance.
				// This will be used to calculate the minimum translation vector
                //保存在所有边的单位法向量的投影向量中最小长度的相交向量
				intervalDistance = Math.Abs(intervalDistance);
				if (intervalDistance < minIntervalDistance) 
                {
					minIntervalDistance = intervalDistance;
					translationAxis = axis;

					Vector d = polygonA.Center - polygonB.Center;
					if (d.DotProduct(translationAxis) < 0) translationAxis = -translationAxis;
				}
			}

			// The minimum translation vector can be used to push the polygons appart.
			// First moves the polygons by their velocity
			// then move polygonA by MinimumTranslationVector.
            //如果移动后相交则计算要移动向量(例如像上图中的DG向量)
			if (result.WillIntersect) 
                result.MinimumTranslationVector = translationAxis * minIntervalDistance;
			
			return result;
		}

		// Calculate the distance between [minA, maxA] and [minB, maxB]
		// The distance will be negative if the intervals overlap
        //返回的值小于0.0表示轴分离A,B,大于0.0表示A,B在轴上的投影相交
		public float IntervalDistance(float minA, float maxA, float minB, float maxB) 
        {
			if (minA < minB) 
            {
				return minB - maxA;
			} 
            else 
            {
				return minA - maxB;
			}
		}

		// Calculate the projection of a polygon on an axis and returns it as a [min, max] interval
		public void ProjectPolygon(Vector axis, Polygon polygon, ref float min, ref float max) 
        {
			// To project a point on an axis use the dot product
			float d = axis.DotProduct(polygon.Points[0]);
			min = d;
			max = d;
			for (int i = 0; i < polygon.Points.Count; i++) 
            {
				d = polygon.Points[i].DotProduct(axis);
				if (d < min) 
                {
					min = d;
				} 
                else 
                {
					if (d > max) { max = d;}
				}
			}
		}


	}

}

 

下面是实现的代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;

namespace PolygonIntersection {

	public partial class MainForm : Form 
    {

		List<Polygon> polygons = new List<Polygon>();
		Polygon player;

		public MainForm() 
        {
			InitializeComponent();

			Paint += new PaintEventHandler(Form1_Paint);
			KeyDown += new KeyEventHandler(Form1_KeyDown);

			KeyPreview = true;
			DoubleBuffered = true;

			Polygon p = new Polygon();
			p.Points.Add(new Vector(100, 0));
			p.Points.Add(new Vector(150, 50));
			p.Points.Add(new Vector(100, 150));
			p.Points.Add(new Vector(0, 100));

			polygons.Add(p);

			p = new Polygon();
			p.Points.Add(new Vector(50, 50));
			p.Points.Add(new Vector(100, 0));
			p.Points.Add(new Vector(150, 150));
			p.Offset(80, 80);

			polygons.Add(p);

            p = new Polygon();
            p.Points.Add(new Vector(300, 10));
            p.Points.Add(new Vector(300, 20));
            p.Points.Add(new Vector(310, 20));
            polygons.Add(p);

			p = new Polygon();
			p.Points.Add(new Vector(0, 50));
			p.Points.Add(new Vector(50,0));
			p.Points.Add(new Vector(150,80));
			p.Points.Add(new Vector(160,200));
			p.Points.Add(new Vector(-10, 190));
			p.Offset(300, 300);
			
			polygons.Add(p);

			foreach (Polygon polygon in polygons) polygon.BuildEdges();

			player = polygons[0];
		}

		void Form1_Paint(object sender, PaintEventArgs e) 
        {
			Vector p1;
			Vector p2;
			foreach (Polygon polygon in polygons) 
            {
				for (int i = 0; i < polygon.Points.Count; i++) 
                {
					p1 = polygon.Points[i];
					if (i + 1 >= polygon.Points.Count) 
                    {
						p2 = polygon.Points[0];
					} 
                    else 
                    {
						p2 = polygon.Points[i + 1];
					}
					e.Graphics.DrawLine(new Pen(Color.Black), p1, p2);
				}
			}

			Invalidate();
		}

		void Form1_KeyDown(object sender, KeyEventArgs e) 
        {
			int i = 14;
			Vector velocity = new Vector();
            #region
            switch (e.KeyValue) 
            {

				case 32: //SPACE

					break;

				case 38: // UP

					velocity = new Vector(0, -i/10);
					break;

				case 40: // DOWN

					velocity = new Vector(0, i/10);
					break;

				case 39: // RIGHT

					velocity = new Vector(i, 0);
					break;

				case 37: // LEFT

					velocity = new Vector(-i, 0);
					break;

            }
            #endregion
            Vector playerTranslation = velocity;

			foreach (Polygon polygon in polygons) 
            {
				if (polygon == player) continue;

				PolygonCollisionResult r = PolygonCollision(player, polygon, velocity);

				if (r.WillIntersect) 
                {
					playerTranslation = velocity + r.MinimumTranslationVector;
					break;
				}

			}

			player.Offset(playerTranslation);
			
		}

	}

}

如果大家喜欢游戏编程技术,可以加我QQ282891168,我不喜欢加势利的人。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值