【Unity】判断一个圆是否被一组圆覆盖(2)

分析之前的代码,主要是两个问题。

问题1:在一个新圆加入后,要对所有和新圆相关的组都进行一次完整的覆盖算法。

问题2:对每两个圆都要反复求交点,基于问题1上就更严重了。(200+个随机圆,求交点函数调用次数达到6000+)

首先解决比较简单的问题2。在参数中传入两圆的唯一id,存储两圆的交点,下次调用时直接返回。这样就避免了重复计算。(从6000+直降到1000)

计算交点函数修改代码如下:

private Dictionary<int,Dictionary<int,Vector3[]>> intersectionBuffer = new Dictionary<int, Dictionary<int, Vector3[]>>();
    void CalIntersection(Vector3 a, Vector3 b, out Vector3 intersec1, out Vector3 intersec2,int aid,int bid)
    {
        if(intersectionBuffer.ContainsKey(aid) && intersectionBuffer[aid].ContainsKey(bid))
        {
            Vector3[] rets = intersectionBuffer[aid][bid];
            intersec1 = rets[0];
            intersec2 = rets[1];
            return;
        }
        //圆a需要在圆b的左下方以方便计算夹角
        if (a.x > b.x || a.y > b.y)
        {
            Vector3 i = a;
            a = b;
            b = i;
        }
        float dx = b.x - a.x;
        float dy = b.y - a.y;
        float dis2 = dx*dx + dy*dy;
        float t = Mathf.Atan2(dy, dx);
        float t2 = Mathf.Acos(dis2 / (2 * radius *(Mathf.Sqrt(dis2))));
        intersec1 = new Vector3(a.x + radius * Mathf.Cos(t + t2), a.y + radius * Mathf.Sin(t + t2), 0);
        intersec2 = new Vector3(a.x + radius * Mathf.Cos(t - t2), a.y + radius * Mathf.Sin(t - t2), 0);
        if (!intersectionBuffer.ContainsKey(aid))
        {
            intersectionBuffer[aid] = new Dictionary<int, Vector3[]>();
        }
        Vector3[] res = new Vector3[2];
        res[0] = intersec1;
        res[1] = intersec2;
        intersectionBuffer[aid][bid] = res;
    }

然后解决问题1。在之前的算法中,针对每个目标圆,每组都要完整地进行覆盖算法,直到遇到未被覆盖的交点才返回。因此有大量的圆都被重复计算了。实际上,只需要保存上一次计算完的所有未被覆盖的交点,每次新增新圆,判断旧交点是否在新圆内。再计算新圆和所有旧圆产生的交点,判断是否在某个旧圆内。如果某次添加新圆,消除了所有旧交点,且没有产生新的未覆盖交点,则说明该目标圆完全被覆盖。

新的算法代码如下:

    private Dictionary<int,List<Vector3>> uncoveredGroups = new Dictionary<int,List<Vector3>>();
    bool IsCovered2(GameObject[] circles)
    {
        int length = circles.Length;
        //两个非目标圆才能产生交点
        if (length <= 2) return false;

        GameObject baseCircle = circles[0];
        GameObject newCircle = circles[length - 1];
        if (!uncoveredGroups.ContainsKey(baseCircle.GetInstanceID()))
        {
            List<Vector3> newUncoveredList = new List<Vector3>();
            uncoveredGroups[baseCircle.GetInstanceID()] = newUncoveredList;
        }
        List<Vector3> uncoveredList = uncoveredGroups[baseCircle.GetInstanceID()];
        //如果没有产生过未覆盖交点,则肯定未被覆盖
        bool newListTag = uncoveredList.Count == 0;
        //判断旧交点是否在新圆内
        for (int i = uncoveredList.Count - 1;i >= 0; i--)
        {
            if (InCircle(uncoveredList[i], newCircle.transform.position))
            {
                uncoveredList.RemoveAt(i);
            }
        }
        for (int i = length - 2; i >= 0; i--)
        {
            Vector3 target = circles[i].transform.position;
            if (IsIntersecting(target, newCircle.transform.position))
            {
                Vector3 interSec1;
                Vector3 interSec2;
                CalIntersection(target, newCircle.transform.position, out interSec1, out interSec2, circles[i].GetInstanceID(), newCircle.GetInstanceID());
                if (InCircle(interSec1, baseCircle.transform.position))
                {
                    bool inList = false;
                    for (int k = 1; k < circles.Length; k++)
                    {
                        if (k != i)
                        {
                            if (InCircle(interSec1, circles[k].transform.position))
                            {
                                inList = true;
                                break;
                            }
                        }
                    }
                    //有交点不在任意非目标圆内,加入缓存
                    if (inList == false)
                    {
                        uncoveredList.Add(interSec1);
                    }
                }
                if (InCircle(interSec2, baseCircle.transform.position))
                {
                    bool inList = false;
                    for (int k = 1; k < circles.Length; k++)
                    {
                        if (k != i)
                        {
                            if (InCircle(interSec2, circles[k].transform.position))
                            {
                                inList = true;
                                break;
                            }
                        }
                    }
                    if (inList == false)
                    {
                        uncoveredList.Add(interSec2);
                    }
                }
            }
        }
        //被覆盖的判断:产生过新交点,且当前没有新交点没被覆盖
        return !newListTag && uncoveredList.Count == 0;
    }

性能分析:在200+筹码时缓存算法和直接算法耗时基本相当,原因是缓存算法需要计算所有未覆盖交点,而直接算法遇到未覆盖交点就返回了。缓存算法节省的时间正好被多出来的求交点填上了。(200+筹码时缓存算法计算交点次数大致为1400+,而直接算法上面有写是1000+)

但是当筹码数达到一个更高数量级,2000+筹码时,缓存算法的优势就明显体现出来了。耗时仅0.5秒,而直接算法需要3秒多。

所以第二步优化还需要看需要计算的圆的数量而定,毕竟缓存算法还额外耗费了内存。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值