几何向量:圆相交

        正好用上圆相交计算,来记录一下。

        问题:求Pa为圆心Ra为半径与Pb为圆心Rb为半径的两圆相交顶点P1、P2,顺便绘制相交区域。如图:

       

        首先我们要求出P1、P2交点的坐标。我们假设圆A(圆心Pa、半径Ra),圆B(圆心Pb、半径Rb)方程:

       

        这种方程其实解起来参数较多,挺麻烦的,我们可以使用相对坐标求解,将Pa(及Pb)平移到原点O(计算完毕再平移回来),那么Pa’=(0,0),Pb‘=Pb-Pa,则方程简化如下:

       

        根据y的一元二次方程及求根公式可以得到y1、y2,然后带入x=(p-wy)/v中得到x1、x2,用c#实现一个函数就能计算出来了,如下:

private Vector2[] GetPositiveCrossPoint(Vector2 pa, float ra, Vector2 pb, float rb)
    {
        Vector2 pb1 = pb - pa;
        Vector2[] cps = GetRelativeCrossPoint(ra, pb1, rb);
        if (cps != null)
        {
            for (int i = 0; i < cps.Length; i++)
            {
                cps[i] += pa;
            }
        }
        return cps;
    }

    private Vector2[] GetRelativeCrossPoint(float ra, Vector2 pb, float rb)
    {
        float a = pb.x;
        float b = pb.y;
        //2ax+2by=a2+b2+ra2+rb2
        float v = 2 * a;
        float w = 2 * b;
        float p = a * a + b * b + ra * ra - rb * rb;
        //x=(p-wy)/v
        //x2+y2=ra2
        //(w2+v2)y2-2pwy+(p2-ra2v2)=0
        //ax2+bx+c=0
        float arga = w * w + v * v;
        float argb = -2 * p * w;
        float argc = p * p - ra * ra * v * v;

        float dta = argb * argb - 4 * arga * argc;
        if (dta < 0)
        {
#if UNITY_EDITOR
            Debug.LogErrorFormat("方程无解");
#endif
            return null;
        }

        float y1 = (-argb + Mathf.Sqrt(dta)) / (2 * arga);
        float y2 = (-argb - Mathf.Sqrt(dta)) / (2 * arga);

        float x1 = (p - w * y1) / v;
        float x2 = (p - w * y2) / v;

        Vector2[] cps = new Vector2[]
        {
            new Vector2(x1,y1),
            new Vector2(x2,y2),
        };
        return cps;
    }

         中间用代数方式(也就是字母代替复杂的计算),刚好配合代码就能很方便的描绘出解法。接下来写一个demo验证一下:

 public Transform pa;
    [Range(0, 10)]
    public float ra;
    public Transform pb;
    [Range(0, 10)]
    public float rb;
    void Start()
    {

    }

    void Update()
    {
        Vector2[] cps = GetPositiveCrossPoint(pa.position, ra, pb.position, rb);
        if (cps != null)
        {
            Vector2 p1 = cps[0];
            Vector2 p2 = cps[1];
            Debug.DrawLine(pa.position, p1, Color.red);
            Debug.DrawLine(pb.position, p1, Color.red);
            Debug.DrawLine(pa.position, p2, Color.green);
            Debug.DrawLine(pb.position, p2, Color.green);
        }
        DrawCircle(pa.position, ra, Color.white);
        DrawCircle(pb.position, rb, Color.black);
    }

    /// <summary>
    /// 微分离散的绘制circle的边
    /// </summary>
    /// <param name="p"></param>
    /// <param name="r"></param>
    /// <param name="col"></param>
    private void DrawCircle(Vector2 p, float r, Color col)
    {
        int ang = 0;
        int step = 5;
        int count = 360 / step;
        Vector3[] poses = new Vector3[count];
        for (int i = 0; i < count; i++)
        {
            float x = Mathf.Cos(ang * Mathf.Deg2Rad) * r + p.x;
            float y = Mathf.Sin(ang * Mathf.Deg2Rad) * r + p.y;
            poses[i] = new Vector3(x, y, 0);
            ang += step;
        }
        for (int i = 0; i < count - 1; i++)
        {
            Vector3 pf = poses[i];
            Vector3 pt = poses[i + 1];
#if UNITY_EDITOR
            Debug.DrawLine(pf, pt, col);
#endif
        }
    }

            效果如下:

 

           接下来我们需要将黑白圆形相交的区域绘制出来(使用texture2d+pixels):绘制圆Pa和圆Pb的相交区域。因为栅格化就是逐行逐列扫描的过程,所以我们使用Pa和Pb组成的外接矩形进行“栅格化填充”。

           首先我们绘制圆形:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class CircleCrossPaint : MonoBehaviour
{
    public RawImage image;

    private int texWidth;
    private int texHeight;
    private Texture2D tex2d;

    private Color clearColor;
    private Color rectColor;
    private Color circleColor;
    private Color sectorColor;
    private Color crossColor;

    public Vector2 pa;
    public int ra;
    public Vector2 pb;
    public int rb;

    void Start()
    {
        InitTex();
        DrawOutRect(pa, ra);
        DrawCircle(pa, ra);
        ApplyTex();
    }

    private void InitTex()
    {
        texWidth = Screen.width;
        texHeight = Screen.height;
        tex2d = new Texture2D(texWidth, texHeight, TextureFormat.ARGB32, false);
        clearColor = new Color(0, 0, 0, 0);
        rectColor = Color.black;
        circleColor = Color.white;
        sectorColor = Color.yellow;
        crossColor = Color.green;
        int colsLen = texWidth * texHeight;
        Color[] cols = new Color[colsLen];
        for (int i = 0; i < colsLen; i++)
        {
            cols[i] = clearColor;
        }
        tex2d.SetPixels(cols);
        tex2d.Apply();
        image.texture = tex2d;
    }

    private void DrawOutRect(Vector2 p, int r)
    {
        int top = (int)p.y + r;
        int bottom = (int)p.y - r;
        int left = (int)p.x - r;
        int right = (int)p.x + r;
        top = Mathf.Clamp(top, 0, texHeight - 1);
        bottom = Mathf.Clamp(bottom, 0, texHeight - 1);
        left = Mathf.Clamp(left, 0, texWidth - 1);
        right = Mathf.Clamp(right, 0, texWidth - 1);
        int wid = right - left;
        int hei = top - bottom;
        int rectlen = wid * hei;
        Color[] rectcols = new Color[rectlen];
        Vector2 c = new Vector2(wid / 2, hei / 2);
        for (int i = 0; i < rectlen; i++)
        {
            rectcols[i] = rectColor;
        }
        tex2d.SetPixels(left, bottom, wid, hei, rectcols);
    }

    private void DrawCircle(Vector2 p, int r)
    {
        int top = (int)p.y + r;
        int bottom = (int)p.y - r;
        int left = (int)p.x - r;
        int right = (int)p.x + r;
        top = Mathf.Clamp(top, 0, texHeight - 1);
        bottom = Mathf.Clamp(bottom, 0, texHeight - 1);
        left = Mathf.Clamp(left, 0, texWidth - 1);
        right = Mathf.Clamp(right, 0, texWidth - 1);
        int wid = right - left;
        int hei = top - bottom;
        int rectlen = wid * hei;
        Color[] rectcols = tex2d.GetPixels(left, bottom, wid, hei);
        Vector2 c = new Vector2(wid / 2, hei / 2);
        for (int x = 0; x < wid; x++)
        {
            for (int y = 0; y < hei; y++)
            {
                Vector2 xy2c = new Vector2(x, y) - c;
                if ((xy2c.x * xy2c.x + xy2c.y * xy2c.y) < r * r)
                {
                    rectcols[y * wid + x] = circleColor;
                }
            }
        }
        tex2d.SetPixels(left, bottom, wid, hei, rectcols);
    }


    private void ApplyTex()
    {
        tex2d.Apply();
    }

}

           绘制如下:

  

           接下来绘制圆相交:

DrawCircleColor(px, rx, Color.white);
DrawCircleColor(py, ry, Color.black);
DrawCircleCross(px, rx, py, ry);

private void DrawCircleCross(Vector2 pa, float ra, Vector2 pb, float rb)
    {
        //计算两圆的外接矩形
        int[] paout = GetOutRect(pa, (int)ra);
        int[] pbout = GetOutRect(pb, (int)rb);
        //计算两圆公共外接矩形
        int left = paout[0] < pbout[0] ? paout[0] : pbout[0];
        int right = paout[1] > pbout[1] ? paout[1] : pbout[1];
        int bottom = paout[2] < pbout[2] ? paout[2] : pbout[2];
        int top = paout[3] > pbout[3] ? paout[3] : pbout[3];
        int wid = right - left;
        int hei = top - bottom;
        Color[] rectcols = tex2d.GetPixels(left, bottom, wid, hei);
        for (int x = left; x < right; x++)
        {
            for (int y = bottom; y < top; y++)
            {
                Vector2 xy = new Vector2(x, y);
                if (isInCircle(xy, pa, ra) && isInCircle(xy, pb, rb))
                {
                    rectcols[(y - bottom) * wid + (x - left)] = crossColor;
                }
            }
        }
        tex2d.SetPixels(left, bottom, wid, hei, rectcols);
    }
    private bool isInCircle(Vector2 xy, Vector2 p, float r)
    {
        Vector2 xy2p = xy - p;
        if ((xy2p.x * xy2p.x + xy2p.y * xy2p.y) <= (r * r))
        {
            return true;
        }
        return false;
    }

            效果如下:

  

            顺便绘制一下扇形,扇形我们使用Axis+Angle绘制,如图:

  

            我们根据axis和θ求出p1、p2,然后根据外接矩形(外接矩形Ptop为圆y轴最高顶点,Pbottom为p、p1、p2最低点,当然如果p1、p2处于第三四象限,那么外接矩形端点不同,所以我们是用整个圆外接矩形)“栅格化”判断Pxy是否处于扇形p1pp2中(也就是PxyPP1的夹角小于θ)。

 DrawSector(pb, rb, new Vector2(1, 0), sectorAngle);

/// <summary>
    /// 以axis为起始向量
    /// ang为逆时针旋转的角度
    /// 绘制扇形
    /// </summary>
    /// <param name="pc"></param>
    /// <param name="r"></param>
    /// <param name="aixs"></param>
    /// <param name="ang"></param>
    private void DrawSector(Vector2 p, int r, Vector2 axis, float ang)
    {
        int top = (int)p.y + r;
        int bottom = (int)p.y - r;
        int left = (int)p.x - r;
        int right = (int)p.x + r;
        top = Mathf.Clamp(top, 0, texHeight - 1);
        bottom = Mathf.Clamp(bottom, 0, texHeight - 1);
        left = Mathf.Clamp(left, 0, texWidth - 1);
        right = Mathf.Clamp(right, 0, texWidth - 1);
        int wid = right - left;
        int hei = top - bottom;
        int rectlen = wid * hei;
        Color[] rectcols = tex2d.GetPixels(left, bottom, wid, hei);
        Vector2 c = new Vector2(wid / 2, hei / 2);
        for (int x = 0; x < wid; x++)
        {
            for (int y = 0; y < hei; y++)
            {
                Vector2 xy2c = new Vector2(x, y) - c;
                if ((xy2c.x * xy2c.x + xy2c.y * xy2c.y) < (r * r))
                {
                    float pang = AngleNormalize(Vector2.SignedAngle(axis, xy2c));
                    if (pang < ang)
                    {
                        rectcols[y * wid + x] = sectorColor;
                    }
                }
            }
        }
        tex2d.SetPixels(left, bottom, wid, hei, rectcols);
    }

    private float AngleNormalize(float ang)
    {
        if (ang >= 0)
        {
            ang %= 360;
        }
        else
        {
            ang = ((ang %= 360) + 360);
        }
        return ang;
    }

             效果如下:

 

          顺便绘制圆Pa上扇形P1PaP2,如下:

 

          圆Pa的Axis为PaP2向量,Ang为2*θ,PaP2(PaP1)与PaPb的夹角正负就可以判断哪个是Axis。同理圆Pb一样,根据PbP1(PbP2)与PbPa的夹角正负来计算。

private void DrawSectorWithCrossPoint(Vector2 pa, float ra, Vector2 pb, float rb)
    {
        Vector2[] cps = GetPositiveCrossPoint(pa, ra, pb, rb);
        if (cps == null)
        {
            return;
        }
        Vector2 PaPb = pb - pa;
        float PbPaCp0 = Vector2.SignedAngle(PaPb, cps[0] - pa);
        float PbPaCp1 = Vector2.SignedAngle(PaPb, cps[1] - pa);
        Vector2 p1 = Vector2.zero, p2 = Vector2.zero;
        if (PbPaCp0 > 0)
        {
            p1 = cps[0];
            p2 = cps[1];
        }
        else if (PbPaCp1 > 0)
        {
            p1 = cps[1];
            p2 = cps[0];
        }
        Vector2 paaxis = p2 - pa;
        float paang = 2 * Mathf.Abs(PbPaCp0);
        //计算外接矩形
        int[] paout = GetOutRect(pa, (int)ra);
        int left = paout[0];
        int right = paout[1];
        int bottom = paout[2];
        int top = paout[3];
        int wid = right - left;
        int hei = top - bottom;
        int rectlen = wid * hei;
        Color[] rectcols = tex2d.GetPixels(left, bottom, wid, hei);
        for (int x = left; x < right; x++)
        {
            for (int y = bottom; y < top; y++)
            {
                Vector2 xy = new Vector2(x, y);
                if (isInSector(xy, pa, ra, paaxis, paang))
                {
                    rectcols[(y - bottom) * wid + (x - left)] = crossColor;
                }
            }
        }
        tex2d.SetPixels(left, bottom, wid, hei, rectcols);
    }

    private bool isInSector(Vector2 xy, Vector2 p, float r, Vector2 axis, float ang)
    {
        Vector2 xy2p = xy - p;
        if ((xy2p.x * xy2p.x + xy2p.y * xy2p.y) <= (r * r))
        {
            if (AngleNormalize(Vector2.SignedAngle(axis, xy2p)) < ang)
            {
                return true;
            }
            return false;
        }
        return false;
    }

    private int[] GetOutRect(Vector2 p, int r)
    {
        int top = (int)p.y + r;
        int bottom = (int)p.y - r;
        int left = (int)p.x - r;
        int right = (int)p.x + r;
        top = Mathf.Clamp(top, 0, texHeight - 1);
        bottom = Mathf.Clamp(bottom, 0, texHeight - 1);
        left = Mathf.Clamp(left, 0, texWidth - 1);
        right = Mathf.Clamp(right, 0, texWidth - 1);
        return new int[] { left, right, bottom, top };
    }

            效果如下:

 

             ok,基本上圆形和扇形计算和绘制差不多就这样。

猜你喜欢

转载自blog.csdn.net/yinhun2012/article/details/109730993