刚好又用上了二维贝塞尔曲线,顺便写一篇博客。
贝塞尔曲线算是二维开发中偶尔会碰到的,我依稀记得我第一次认识贝塞尔曲线是选修photoshop课程用钢笔工具拉曲线。工作中第一次用贝塞尔计算,是做手游的时候,做了一个怪物爆装物品散乱掉落动画的功能。现在做的二维项目中一个模块功能又需要用到了,所以上百度百科:贝塞尔曲线
贝塞尔曲线的核心:根据两个或更多顶点绘制一条光滑的曲线(或直线),当然使用最频繁的是四个顶点组成的贝塞尔曲线。下面实现以下常见的贝塞尔曲线:
1.两个顶点
给定点p1、p2,线性贝塞尔曲线只是一条两点之间的直线,如图:
虽然数学上直线是矢量的,但是方程的解是离散的,我们假设t=1/100,也就是说在p1p2上每隔1/100长度算一次p(x,y)的坐标,求解的p点的点云(99个顶点)也可以看做一条直线。
这个一眼都看得出来我就不写验证了。
2.三个顶点
三个顶点就开始呈现曲线的样式了,如下:
假设二维平面上存在p1、p2、p3三个顶点,a1、a2分别为p1p2、p2p3上的动点,且|p1a1|/|p1p2| = |p2a2|/|p2p3| = t,同时a1、a2组成的线段a1a2上,存在动点p,同时|a1p|/|a1a2| = t,随着t由0-1离散的增大,也可以求得一个p点的点云数组,而且呈现出平滑的曲线。
下面我们就来实现一下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ThreePointBizierCurve : MonoBehaviour
{
[Range(0, 1)]
public float T = 0;
public RectTransform P1;
public RectTransform P2;
public RectTransform P3;
public RectTransform P;
private void Update()
{
#if UNITY_EDITOR
Vector2 halfscreen = new Vector2(Screen.width / 2f, Screen.height / 2f);
Debug.DrawLine(P1.anchoredPosition + halfscreen, P2.anchoredPosition + halfscreen, Color.red);
Debug.DrawLine(P2.anchoredPosition + halfscreen, P3.anchoredPosition + halfscreen, Color.yellow);
#endif
Vector2 p = CalculateBizierCurvePoint(P1.anchoredPosition, P2.anchoredPosition, P3.anchoredPosition, T);
#if UNITY_EDITOR
Debug.LogFormat("curve point p = {0}", p);
#endif
P.anchoredPosition = p;
}
private Vector2 CalculateBizierCurvePoint(Vector2 p1, Vector2 p2, Vector2 p3, float t)
{
Vector2 p = (1 - t) * (1 - t) * p1 + 2 * t * (1 - t) * p2 + t * t * p3;
return p;
}
}
效果如下:
为了方便看出白色小圆球的离散运动轨迹,我特意调整了camera->don’tclear,可以叠加帧缓冲的图像。
3.四个顶点
四个顶点的情况也类似,就是三个顶点的延伸,如下:
主要贝塞尔曲线的规则,让我们可以直接将4个顶点的问题降到3个顶点的条件来计算求解,如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FourPointBezierCurve : MonoBehaviour
{
[Range(0, 1)]
public float T = 0;
public RectTransform P1;
public RectTransform P2;
public RectTransform P3;
public RectTransform P4;
public RectTransform P;
void Start()
{
}
void Update()
{
#if UNITY_EDITOR
Vector2 halfscreen = new Vector2(Screen.width / 2f, Screen.height / 2f);
Debug.DrawLine(P1.anchoredPosition + halfscreen, P2.anchoredPosition + halfscreen, Color.red);
Debug.DrawLine(P2.anchoredPosition + halfscreen, P3.anchoredPosition + halfscreen, Color.yellow);
Debug.DrawLine(P3.anchoredPosition + halfscreen, P4.anchoredPosition + halfscreen, Color.blue);
#endif
Vector2 p1 = P1.anchoredPosition;
Vector2 p2 = P2.anchoredPosition;
Vector2 p3 = P3.anchoredPosition;
Vector2 p4 = P4.anchoredPosition;
float t = T;
Vector2 p5 = CalucuateTwoPointCurvePoint(p1, p2, t);
Vector2 p6 = CalucuateTwoPointCurvePoint(p2, p3, t);
Vector2 p7 = CalucuateTwoPointCurvePoint(p3, p4, t);
Vector2 p = CalculateThreePointCurvePoint(p5, p6, p7, t);
#if UNITY_EDITOR
Debug.LogFormat("curve point p = {0}", p);
#endif
P.anchoredPosition = p;
}
private Vector2 CalucuateTwoPointCurvePoint(Vector2 p1, Vector2 p2, float t)
{
Vector2 p = (1 - t) * p1 + t * p2;
return p;
}
private Vector2 CalculateThreePointCurvePoint(Vector2 p1, Vector2 p2, Vector2 p3, float t)
{
Vector2 p = (1 - t) * (1 - t) * p1 + 2 * t * (1 - t) * p2 + t * t * p3;
return p;
}
}
效果如图:
4.n个顶点
因为贝塞尔曲线的规则可以让n个顶点的问题降维到n-1的顶点的计算,那么我们递归套娃,是不是将n个顶点的问题变成由最基本的3个顶点计算规则来实现呢?
例如:5个顶点的问题,先降到成4个顶点,再降维到3个顶点,就能得到离散的曲线?我觉得是可以的,下面继续实现:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MultiPointBezierCurve : MonoBehaviour
{
[Range(0, 1)]
public float T = 0;
public RectTransform[] Ps;
public RectTransform P;
private Vector2[] origPoints; //储存原始坐标点
private Vector2[] reducePoints; //储存降维坐标点
private Vector2 halfScreen;
void Start()
{
int pslen = Ps.Length;
origPoints = new Vector2[pslen];
reducePoints = new Vector2[pslen];
for (int i = 0; i < Ps.Length; i++)
{
origPoints[i] = Ps[i].anchoredPosition;
reducePoints[i] = Ps[i].anchoredPosition;
}
halfScreen = new Vector2(Screen.width / 2f, Screen.height / 2f);
}
void Update()
{
#if UNITY_EDITOR
for (int i = 1; i < origPoints.Length; i++)
{
Vector2 fpos = origPoints[i - 1] + halfScreen;
Vector2 tpos = origPoints[i] + halfScreen;
float colr = (float)i / (float)origPoints.Length;
float colg = (float)(origPoints.Length - i) / (float)origPoints.Length;
Debug.DrawLine(fpos, tpos, new Color(colr, colg, colr, 1));
}
#endif
float t = T;
while (reducePoints.Length > 3)
{
reducePoints = ReduceBezierPoints(reducePoints, t);
}
Vector2 p1 = reducePoints[0];
Vector2 p2 = reducePoints[1];
Vector2 p3 = reducePoints[2];
Vector2 p = CalculateThreePointCurvePoint(p1, p2, p3, t);
#if UNITY_EDITOR
Debug.LogFormat("curve point p = {0}", p);
#endif
P.anchoredPosition = p;
//还原坐标点,刷新计算
reducePoints = new Vector2[origPoints.Length];
for (int i = 0; i < origPoints.Length; i++)
{
reducePoints[i] = origPoints[i];
}
}
/// <summary>
/// 降维n个坐标点到n-1个坐标点
/// </summary>
/// <param name="pts"></param>
/// <param name="t"></param>
/// <returns></returns>
private Vector2[] ReduceBezierPoints(Vector2[] pts, float t)
{
int len = pts.Length;
Vector2[] dpoints = new Vector2[len - 1];
for (int i = 0; i < dpoints.Length; i++)
{
Vector2 fpoint = pts[i];
Vector2 tpoint = pts[i + 1];
Vector2 ppoint = CalucuateTwoPointCurvePoint(fpoint, tpoint, t);
dpoints[i] = ppoint;
}
return dpoints;
}
private Vector2 CalucuateTwoPointCurvePoint(Vector2 p1, Vector2 p2, float t)
{
Vector2 p = (1 - t) * p1 + t * p2;
return p;
}
private Vector2 CalculateThreePointCurvePoint(Vector2 p1, Vector2 p2, Vector2 p3, float t)
{
Vector2 p = (1 - t) * (1 - t) * p1 + 2 * t * (1 - t) * p2 + t * t * p3;
return p;
}
}
代码核心就做了一下n到n-1个顶点的降维处理,效果如下:
这里我测试绘制了7个顶点的贝塞尔曲线,还是很ok的,那么说明我们的想法没错。
好,明天又是周一上班了,以后有时间我们继续。