在运行状态下创建一条可以使用贝塞尔方法实时编辑的网格曲线。
unity建立网格曲线可以参考Unity程序化网格体的实现方法。主要分为顶点,三角面,UV和法线。笔者有类似的文章unity 线绳管道纯代码创建方法_,详细的讲解了网格线的创建方法,这次的不同点在于法线的确立方法上。
笔者有文章Unity 贝塞尔曲线的创建_描述了贝塞尔的创建方法。
线的组成原理
曲线由横截面圆和中心轴线组成。横截面的法线方向为前后两点向量差,如下图绿色线为中心轴线,A点为横截面所在点,横截面的法线方法为向量 A D ⃗ \vec{AD} AD既向量 C B ⃗ \vec{CB} CB的单位向量;终点和起点的法线方向为自身和前点或者后一点的向量。
代码源码
#region 横切圆创建
///
/// 得到管线横切圆
///
/// 段数
/// 半径
///
Vector3[] CircularSection(int Count, float R)
{
Vector3[] vector3s = new Vector3[Count];
float angle = 360 / Count;
Vector3 vector3 = new Vector3(R, 0, 0);
for (int i = 0; i < Count; i++)
{
//根据角度得到圆的分布点
vector3s[i] = vector3.ToAngle(angle * i, Vector3.zero, Vector3.forward);
}
return vector3s;
}
#endregion
vector3旋转扩展方法
///
/// 角度旋转
///
///
/// 旋转角度
/// 旋转中心点
/// 旋转轴
///
public static Vector3 ToAngle(this Vector3 vector3, float angle, Vector3 center, Vector3 direction)
{
Vector3 pos = center;
Quaternion quaternion = Quaternion.AngleAxis(angle, direction);
Matrix4x4 matrix = new Matrix4x4();
matrix.SetTRS(pos, quaternion, Vector3.one);
vector3 = matrix.MultiplyPoint3x4(vector3);
return vector3;
}
class LinePoint
{
Vector3 location;
Vector3 direction;
public Vector3 Location { get => location; set => location = value; }
public Vector3 Direction { get => direction; set => direction = value; }
}
///
/// 中心线的确立
///
/// 曲线点
///
List SetLinePoint(Vector3[] createPoint)
{
List pipePoints = new List();
int length = createPoint.Length;
for (int i = 0; i < length; i++)
{
if (i == 0)
{
Vector3 tangent = (createPoint[i + 1] - createPoint[i]).normalized;//法线
AddPipePoints(createPoint[i], tangent, ref pipePoints);
}
else if (i == length - 1)
{
Vector3 tangent = (createPoint[i] - createPoint[i - 1]).normalized;//法线
AddPipePoints(createPoint[i], tangent, ref pipePoints);
}
else
{
Vector3 tangent = (createPoint[i+1] - createPoint[i - 1]).normalized;//法线
AddPipePoints(createPoint[i], tangent, ref pipePoints);
}
}
return pipePoints;
}
///
/// 增加中心轴线点
///
/// 位置
/// 法线
void AddPipePoints(Vector3 location, Vector3 direction, ref List pipePoints)
{
LinePoint pipePoint = new LinePoint();
pipePoint.Location = location;
pipePoint.Direction = direction;
pipePoints.Add(pipePoint);
}
///
/// 立体网格创建
///
/// 创建的点数据
/// 圆的段数
/// 圆的半径
///
public Mesh CreateLine3D(Vector3[] createPoint, int circularCount, float circularR)
{
//截面圆
Vector3[] circul = CircularSection(circularCount, circularR);
//中心线
List centreLine = SetLinePoint(createPoint);
//网格点数据
Vector3[] meshPoint = CreateMeshPoint(centreLine, circul);
float uvX = Vector3.Distance(circul[0], circul[1]);
//返回网格
return CreatMesh(centreLine, meshPoint, circul.Length, uvX);
}
///
/// 创建网格点数据
///
///
///
///
Vector3[] CreateMeshPoint(List linePoint, Vector3[] circular)
{
int length = linePoint.Count;
int circularCount = circular.Length;
Vector3[] meshPoint = new Vector3[length * circularCount];
for (int i = 0; i < length; i++)
{
for (int j = 0; j < circularCount; j++)
{
meshPoint[(i * circularCount) + j] = circular[j].FromToMoveRotation(linePoint[i].Location, linePoint[i].Direction);
}
}
return meshPoint;
}
///
/// 网格创建
///
/// 线的轴心线组
/// 网格点
/// 段数
/// uv宽度
///
Mesh CreatMesh(List linePoints, Vector3[] meshPoint, int count, float uvX)
{
Mesh mesh = new Mesh();
mesh.vertices = meshPoint;
mesh.triangles = GetTriangles(linePoints.Count, count);
mesh.uv = GetUV(linePoints, count, uvX);
mesh.RecalculateNormals();
mesh.RecalculateBounds();
return mesh;
}
/// 线段段数
/// 横截面段数(也就是圆的段数)
///
int[] GetTriangles(int length, int count)
{
int[] triangles = new int[(count * (length - 1)) * 6];
int k = 0;
if (count == 1)
{
for (int i = 0; i < length-1; i++)
{
int a = i * 2;
triangles[k] = a;
triangles[k + 1] = a + 1;
triangles[k + 2] = a + 3;
triangles[k + 3] = a;
triangles[k + 4] = a + 3;
triangles[k + 5] = a + 2;
k += 6;
}
}
else
{
for (int i = 0; i < length - 1; i++)
{
for (int j = 0; j < count; j++)
{
if (j == count - 1)
{
// Debug.Log("k=" + k);
triangles[k] = (i * count) + j;
triangles[k + 1] = (i * count) + 0;
triangles[k + 2] = ((i + 1) * count) + 0;
triangles[k + 3] = (i * count) + j;
triangles[k + 4] = ((i + 1) * count) + 0;
triangles[k + 5] = ((i + 1) * count) + j;
}
else
{
triangles[k] = (i * count) + j;
triangles[k + 1] = (i * count) + j + 1;
triangles[k + 2] = ((i + 1) * count) + j + 1;
triangles[k + 3] = (i * count) + j;
triangles[k + 4] = ((i + 1) * count) + j + 1;
triangles[k + 5] = ((i + 1) * count) + j;
}
k += 6;
}
}
}
return triangles;
}
///
/// 创建uv
///
///
///
///
///
Vector2[] GetUV(List linePoints,int count, float uvX)
{
int length = linePoints.Count;
if (count == 1) { count = 2; }
Vector2[] uvs = new Vector2[(count * length)];
float lineDis = 0;
int k = 0;
for (int i = 0; i < length; i ++)
{
if (i != 0)
{
lineDis += Vector3.Distance(linePoints[i].Location, linePoints[i - 1].Location);
}
for (int j = 0; j < count; j++)
{
Vector2 vector2;
if (j % 2 != 0)
{
vector2 = new Vector2(uvX, lineDis);
}
else
{
vector2 = new Vector2(0, lineDis);
}
uvs[k] = vector2;
k += 1;
}
}
return uvs;
}
源码
///
/// 获取绘制点
///
///
///
///
public List GetDrawingPoints(List controlPoints, int segmentsPerCurve)
{
List points = new List();
// 下一段的起始点和上段终点是一个,所以是 i+=3
for (int i = 0; i <= controlPoints.Count - 4; i += 3)
{
var p0 = controlPoints[i];
var p1 = controlPoints[i + 1];
var p2 = controlPoints[i + 2];
var p3 = controlPoints[i + 3];
float dis = Vector3.Distance(p0, p3);
int count = Mathf.CeilToInt(segmentsPerCurve * dis);
if (count < segmentsPerCurve)
{
count = segmentsPerCurve;
}
for (int j = 0; j <= count; j++)
{
var t = j / (float)count;
points.Add(CalculateBezierPoint(t, p0, p1, p2, p3));
}
}
return points;
}
// 三阶公式
Vector3 CalculateBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
{
Vector3 result;
Vector3 p0p1 = (1 - t) * p0 + t * p1;
Vector3 p1p2 = (1 - t) * p1 + t * p2;
Vector3 p2p3 = (1 - t) * p2 + t * p3;
Vector3 p0p1p2 = (1 - t) * p0p1 + t * p1p2;
Vector3 p1p2p3 = (1 - t) * p1p2 + t * p2p3;
result = (1 - t) * p0p1p2 + t * p1p2p3;
return result;
}
基于上述方法实现了贝塞尔创建,保存,读取,在编辑功能。
案例下载地址
创建曲线
保存曲线
保存方法有两种分别是,长期保存和暂时保存。长期保存是将保存数据写入本地文件,项目重启也可以读取;暂时保存是在项目运行期间保存数据,重启后丢失。demo使用暂时保存的方法
读取再编辑
读取曲线后可以继续编辑当前曲线
曲线浏览