Unity 小球融合 貝塞爾曲線模擬

前幾天做小球融合,讓我陷入困難,後來搜到用貝塞爾曲線模擬中間過渡效果的,但是他們的代碼都有顯然的和隱藏的錯誤之處,改了半天都無法達到預期效果,不夠直白易懂,但是經過一天的琢磨最後還是搞懂了思路並想到了算法,源碼和項目也分享給大家學習,註釋很詳細,所以我只簡單解釋思路,示意圖:

如圖,根據兩球的距離設置合適的a1和a2,求出P1,P2,P3,P4的座標,然後根據圓上切線的方向,自己根據圓的半徑和兩圓的距離,求出沿切線方向的切點座標,即可得出繪製貝塞爾曲線的四個座標點,特判一些特殊情況,加入融合時修改半徑的功能即可。不知道核心代碼怎麼使用的話可以下載項目運行看看。

效果圖:

把算法放到processing上運行,效率也很高:

unity2018.4 demo項目:https://pan.baidu.com/s/1u8GF-ziL3C84PDK_0N9Q9A 密碼:2333

核心代碼:

using UnityEngine;

public class Curve : MonoBehaviour {
    public int lineNum;                 //曲線分段數
    public Material line;               //材質顏色
    public Transform c1, c2;            //圓
    public Transform p1, p2, p3, p4;    //圓上的切點
    public Transform h1, h2, h3, h4;    //切線延伸方向的點
    private Vector2 P1, P2, P3, P4, H1, H2, H3, H4;     //8個點座標
    private float initAngle = 40;                       //初始不相交時的兩圓切點相對於各自圓心的夾角
    private float tangentLength1, tangentLength2;       //切線長度
    private float scaleRate = 0.01f;                    //圓融合時面積變化比例

    private void Update() {
        Vector2 scale = Metaball(c1.position, c2.position, c1.localScale.x * 2.5f, c2.localScale.x * 2.5f);
        SetScale(scale);
    }

    //計算8點位置
    private Vector2 Metaball(Vector2 c1, Vector2 c2, float r1, float r2) {
        Vector2 ans = new Vector2();
        float dis = Vector2.Distance(c1, c2);   //兩圓心的距離
        if (dis < r1 + r2) {    //吸收小圓
            ans.x = r1 * (1 - scaleRate);   //小圓新半徑
            ans.y = Mathf.Sqrt(r2 * r2 + r1 * r1 - ans.x * ans.x);  //大圓新半徑
        }
        else {  //分離,不吸收
            ans = new Vector2(r1, r2);
        }
        float tiltAngle1, tiltAngle2;           //兩圓心的相對角度
        tiltAngle1 = Vector2.Angle(c2 - c1, new Vector2(1, 0)); //angle函數返回夾角絕對值,下一句手動判斷正負
        if ((c2 - c1).y < 0) tiltAngle1 = -tiltAngle1;
        tiltAngle2 = Limit(tiltAngle1 + 180);
        float angle1, angle2, angle3, angle4;   //四個切點相對於圓心的角度
        angle1 = angle2 = angle3 = angle4 = 0;
        float tanAngle1, tanAngle2, tanAngle3, tanAngle4;   //四個切線方向的角度
        if (dis > r1 + r2) {   //兩圓不相交
            angle1 = Limit(tiltAngle1 + initAngle);
            angle2 = Limit(tiltAngle1 - initAngle);
            angle3 = Limit(tiltAngle2 - initAngle);
            angle4 = Limit(tiltAngle2 + initAngle);
            tangentLength1 = r1 * dis / 20;
            if (tangentLength1 > r1 / 4) tangentLength1 = r1 / 4;
            tangentLength2 = r2 * dis / 20;
            if (tangentLength2 > r2 / 4) tangentLength2 = r2 / 4;
        }
        else if (r1 < dis + r2 && r2 < dis + r1) {  //兩圓相交
            float interAngle1, interAngle2;         //相交點相對於圓心的角度
            interAngle1 = Mathf.Acos((r1 * r1 - r2 * r2 + dis * dis) / (2 * dis * r1)) * Mathf.Rad2Deg; //推導出的公式
            interAngle2 = Mathf.Acos((r2 * r2 - r1 * r1 + dis * dis) / (2 * dis * r2)) * Mathf.Rad2Deg;
            angle1 = Limit(tiltAngle1 + interAngle1 + initAngle);
            angle2 = Limit(tiltAngle1 - interAngle1 - initAngle);
            angle3 = Limit(tiltAngle2 - interAngle2 - initAngle);
            angle4 = Limit(tiltAngle2 + interAngle2 + initAngle);
            tangentLength1 = r1 / 4;
            tangentLength2 = r2 / 4;
        }
        tanAngle1 = Limit(angle1 - 90);
        tanAngle2 = Limit(angle2 + 90);
        tanAngle3 = Limit(angle3 + 90);
        tanAngle4 = Limit(angle4 - 90);
        P1 = c1 + new Vector2(Mathf.Cos(angle1 * Mathf.Deg2Rad), Mathf.Sin(angle1 * Mathf.Deg2Rad)) * r1;
        P2 = c1 + new Vector2(Mathf.Cos(angle2 * Mathf.Deg2Rad), Mathf.Sin(angle2 * Mathf.Deg2Rad)) * r1;
        P3 = c2 + new Vector2(Mathf.Cos(angle3 * Mathf.Deg2Rad), Mathf.Sin(angle3 * Mathf.Deg2Rad)) * r2;
        P4 = c2 + new Vector2(Mathf.Cos(angle4 * Mathf.Deg2Rad), Mathf.Sin(angle4 * Mathf.Deg2Rad)) * r2;
        H1 = P1 + new Vector2(Mathf.Cos(tanAngle1 * Mathf.Deg2Rad), Mathf.Sin(tanAngle1 * Mathf.Deg2Rad)) * tangentLength1;
        H2 = P2 + new Vector2(Mathf.Cos(tanAngle2 * Mathf.Deg2Rad), Mathf.Sin(tanAngle2 * Mathf.Deg2Rad)) * tangentLength1;
        H3 = P3 + new Vector2(Mathf.Cos(tanAngle3 * Mathf.Deg2Rad), Mathf.Sin(tanAngle3 * Mathf.Deg2Rad)) * tangentLength2;
        H4 = P4 + new Vector2(Mathf.Cos(tanAngle4 * Mathf.Deg2Rad), Mathf.Sin(tanAngle4 * Mathf.Deg2Rad)) * tangentLength2;
        /*
        P1 = p1.position;
        P2 = p2.position;
        P3 = p3.position;
        P4 = p4.position;
        H1 = h1.position;
        H2 = h2.position;
        H3 = h3.position;
        H4 = h4.position;
        */
        SetPointPos(P1, P2, P3, P4, H1, H2, H3, H4);
        if (r1 < dis + r2 && r2 < dis + r1) {   //圓沒有互相包含時
            DrawCurve();
        }
        return ans;
    }

    //設置小球新半徑
    private void SetScale(Vector2 scale) {
        c1.localScale = new Vector3(1, 1, 1) * scale.x / 2.5f;
        c2.localScale = new Vector3(1, 1, 1) * scale.y / 2.5f;
    }

    //限制角度在-180到180之間
    private float Limit(float angle) {
        if (angle > 180) return angle - 360;
        if (angle <= -180) return angle + 360;
        return angle;
    }

    //顯示8個點的位置
    private void SetPointPos(Vector2 P1, Vector2 P2, Vector2 P3, Vector2 P4, Vector2 H1, Vector2 H2, Vector2 H3, Vector2 H4) {
        p1.position = P1;
        p2.position = P2;
        p3.position = P3;
        p4.position = P4;
        h1.position = H1;
        h2.position = H2;
        h3.position = H3;
        h4.position = H4;
    }

    //編輯器界面繪製貝塞爾曲線
    private void DrawCurve() {
        float x;
        Vector2 A, B, C, D;
        Vector2[] Mpos = new Vector2[6];
        Vector2 last = Vector2.zero;
        A = P2;
        B = H2;
        C = H4;
        D = P4;
        x = 0;
        for (int i = 0; i <= lineNum; i++) {//貝塞爾曲線
            Mpos[0] = A + (B - A) * x;
            Mpos[1] = B + (C - B) * x;
            Mpos[2] = C + (D - C) * x;
            Mpos[3] = Mpos[0] + (Mpos[1] - Mpos[0]) * x;
            Mpos[4] = Mpos[1] + (Mpos[2] - Mpos[1]) * x;
            Mpos[5] = Mpos[3] + (Mpos[4] - Mpos[3]) * x;
            if (i != 0) {
                Debug.DrawLine(last, Mpos[5], Color.white);
            }
            x += 1.0f / lineNum;
            last = Mpos[5];
        }
        A = P1;
        B = H1;
        C = H3;
        D = P3;
        x = 0;
        for (int i = 0; i <= lineNum; i++) {//貝塞爾曲線
            Mpos[0] = A + (B - A) * x;
            Mpos[1] = B + (C - B) * x;
            Mpos[2] = C + (D - C) * x;
            Mpos[3] = Mpos[0] + (Mpos[1] - Mpos[0]) * x;
            Mpos[4] = Mpos[1] + (Mpos[2] - Mpos[1]) * x;
            Mpos[5] = Mpos[3] + (Mpos[4] - Mpos[3]) * x;
            if (i != 0) {
                Debug.DrawLine(last, Mpos[5], Color.white);
            }
            x += 1.0f / lineNum;
            last = Mpos[5];
        }

    }

    //遊戲界面繪製貝塞爾曲線
    private void OnPostRender() {
        float x;
        Vector2 A, B, C, D;
        Vector2[] Mpos = new Vector2[6];
        Vector2 last = Vector2.zero;
        GL.PushMatrix();
        GL.LoadPixelMatrix();
        line.SetPass(0);
        GL.Begin(GL.LINES);
        A = P2;
        B = H2;
        C = H4;
        D = P4;
        x = 0;
        for (int i = 0; i <= lineNum; i++) {//貝塞爾曲線
            Mpos[0] = A + (B - A) * x;
            Mpos[1] = B + (C - B) * x;
            Mpos[2] = C + (D - C) * x;
            Mpos[3] = Mpos[0] + (Mpos[1] - Mpos[0]) * x;
            Mpos[4] = Mpos[1] + (Mpos[2] - Mpos[1]) * x;
            Mpos[5] = Mpos[3] + (Mpos[4] - Mpos[3]) * x;
            Vector2 pos = Camera.main.WorldToScreenPoint(Mpos[5]);
            if (i == 0) {
                GL.Vertex3(pos.x, pos.y, 0);
            }
            else {
                GL.Vertex3(last.x, last.y, 0);
            }
            GL.Vertex3(pos.x, pos.y, 0);
            x += 1.0f / lineNum;
            last = pos;
        }
        GL.End();
        GL.Begin(GL.LINES);
        A = P1;
        B = H1;
        C = H3;
        D = P3;
        x = 0;
        for (int i = 0; i <= lineNum; i++) {//貝塞爾曲線
            Mpos[0] = A + (B - A) * x;
            Mpos[1] = B + (C - B) * x;
            Mpos[2] = C + (D - C) * x;
            Mpos[3] = Mpos[0] + (Mpos[1] - Mpos[0]) * x;
            Mpos[4] = Mpos[1] + (Mpos[2] - Mpos[1]) * x;
            Mpos[5] = Mpos[3] + (Mpos[4] - Mpos[3]) * x;
            Vector2 pos = Camera.main.WorldToScreenPoint(Mpos[5]);
            if (i == 0) {
                GL.Vertex3(pos.x, pos.y, 0);
            }
            else {
                GL.Vertex3(last.x, last.y, 0);
            }
            GL.Vertex3(pos.x, pos.y, 0);
            x += 1.0f / lineNum;
            last = pos;
        }
        GL.End();
        GL.PopMatrix();
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章