Unity动态绘制曲线Mesh的代码

介绍一个自己写的小工具,曲线网格生成器。起初是为了在地图界面绘制可修改的路径曲线,节约美术人员工作量而开发的小东西。

这是游戏中效果:

这是编辑器窗口里的样子:

这是督察里的样子:

这个小工具就一个脚本CurveMeshBuilder,它在场景里创建一个曲线形状的3D模型,配上贴图就能显示出曲线效果。在编辑器里我们可以看到它提供了几个配置参数,包括曲线的宽度,细腻度和贴图的重复密度,还有路径关键点的调整。

说说这个工具的基本工作流程(简要说下做法,细节实现就不多说了,自己看代码):

1.生成曲线。用多个关键点构造出一段曲线的方法很多,耳熟能详的贝塞尔曲线就是最出名的一种(它不穿过中途点,所以不适合这里使用)本文用的是Catmul -ROM曲线,一个常见的曲线生成计算方法,有很多文章介绍,此处就不再介绍了。

2.计算模型边线,我们所要做的线程,我们所要做的是在性能和​​效果之间做取舍)。然后将每段线段(或者说向量)向两侧平移,再重新计算接缝,如此获得了两组新的线段,所有线段的端点集合在一起,就是模型的顶点集合。

3.计算顶点,三角形,紫外线信息。使顶点两上两下组合为一个个四边形,再将每个四边形拆分为2个三角形,并计算出每个顶点的UV坐标。单独开一篇说明,可是网上的相关文章已经够多够详细了,感觉意义不是很大(一定不是因为懒)。

填充网格。将顶点信息,三角形信息和UV信息都填充到新建的Mesh里,新鲜出炉的Mesh数据就制成了。

说点补充事项:

1.为了让贴图循环展现,使得UV参数会大于1.所以图片文件的WrapMode必须是Repeat。

如果有个二维向量的值是(a,b),那么它的垂直向量是(-b,a)和(b,-a),一个在它的左侧一个在它的右侧自己画个坐标系看一眼就明白了。

。最后上代码这是组件代码:

使用UnityEngine;
使用System.Collections;
使用System.Collections.Generic;

/// <summary>
///通过给定的关键点动态构建曲线网格
///曲线类型是Catmul-Rom
/// </ summary>

[ExecuteInEditMode]
[RequireComponent(typeof(MeshFilter),typeof(MeshRenderer))]
公共类CurveMeshBuilder:MonoBehaviour
{
	公共结构CurveSegment2D
	{
		public Vector2 point1;
		public Vector2 point2;

		public CurveSegment2D(Vector2 point1,Vector2 point2)
		{
			this.point1 = point1;
			this.point2 = point2;
		}

		public Vector2 SegmentVector
		{
			得到
			{
				返回点2  - 点1;
			}
		}
	}

	[HideInInspector]
	public List <Vector2> nodeList = new List <Vector2>();

	public bool drawGizmos = true;
	public int smooth = 5;
	public float width = 0.2f;
	public float uvTiling = 1f;

	私人网

#if UNITY_EDITOR
	public float gizmosNodeBallSize = 0.1f;
	[System.NonSerialized]
	public int selectedNodeIndex = -1;
#万一

	void Awake()
	{
		在里面();
		BuildMesh();
	}

	void Start()
	{

	}

	void Update()
	{

	}

	void Init()
	{
		if(_mesh == null)
		{
			_mesh = new Mesh();
			_mesh.name =“CurveMesh”;
			GetComponent <MeshFilter>()。mesh = _mesh;
		}
	}

#if UNITY_EDITOR
	//在场景视图中绘制样条曲线
	void OnDrawGizmos()
	{
		如果(!drawGizmos)
		{
			返回;
		}
		Vector3 prevPosition = Vector3.zero;
		for(int i = 0; i <nodeList.Count; i ++)
		{
			if(i == 0)
			{
				prevPosition = transform.TransformPoint(nodeList [i]);
			}
			其他
			{
				Vector3 curPosition = transform.TransformPoint(nodeList [i]);
				Gizmos.DrawLine(prevPosition,curPosition);
				prevPosition = curPosition;
			}

			if(i == selectedNodeIndex)
			{
				颜色c = Gizmos.color;
				Gizmos.color = Color.yellow;
				Gizmos.DrawSphere(prevPosition,gizmosNodeBallSize * UnityEditor.HandleUtility.GetHandleSize(prevPosition)* 1.5f);
				Gizmos.color = c;
			}
			其他
			{
				Gizmos.DrawSphere(prevPosition,gizmosNodeBallSize * UnityEditor.HandleUtility.GetHandleSize(prevPosition));
			}
		}
	}
#万一

	#region Node Operate
	public void AddNode(Vector2 position)
	{
		nodeList.Add(位置);
	}

	public void InsertNode(int index,Vector2 position)
	{
		index = Mathf.Max(index,0);
		if(index> = nodeList.Count)
		{
			ADDNODE(位置);
		}
		其他
		{
			nodeList.Insert(index,position);
		}
	}

	public void RemoveNode(int index)
	{
		if(index <0 || index> = nodeList.Count)
		{
			返回;
		}
		nodeList.RemoveAt(索引);
	}

	public void ClearNodes()
	{
		nodeList.Clear();
	}
	#endregion

	public bool BuildMesh()
	{
		在里面();
		_mesh.Clear();
		if(nodeList.Count <2)
		{
			返回假;
		}
		List <Vector2> curvePoints = CalculateCurve(nodeList,smooth,false);
		列表<Vector2> vertices = GetVertices(curvePoints,width * 0.5f);
		列表<Vector2> verticesUV = GetVerticesUV(curvePoints);

		Vector3 [] _vertices = new Vector3 [vertices.Count];
		Vector2 [] _uv = new Vector2 [verticesUV.Count];
		int [] _triangles = new int [(vertices.Count  -  2)* 3];
		for(int i = 0; i <vertices.Count; i ++)
		{
			_vertices [i] .Set(vertices [i] .x,vertices [i] .y,0);
		}
		for(int i = 0; i <verticesUV.Count; i ++)
		{
			_uv [i] .Set(verticesUV [i] .x,verticesUV [i] .y);
		}
		for(int i = 2; i <vertices.Count; i + = 2)
		{
			int index =(i-2)* 3;
			_triangles [index] = i  -  2;
			_triangles [index + 1] = i  -  0;
			_triangles [index + 2] = i  -  1;
			_triangles [index + 3] = i  -  1;
			_triangles [index + 4] = i  -  0;
			_triangles [index + 5] = i + 1;
		}
		_mesh.vertices = _vertices;
		_mesh.triangles = _triangles;
		_mesh.uv = _uv;
		_mesh.RecalculateNormals();

		返回真
	}

	/// <summary>
	///计算Catmul-Rom曲线
	/// </ summary>
	/// <param name =“points”>要点</ param>
	/// <param name =“smooth”>两个附近点之间的段数</ param>
	/// <param name =“curveClose”>曲线是否是圆圈</ param>
	/// <returns> </ returns>
	public List <Vector2> CalculateCurve(IList <Vector2> points,int smooth,bool curveClose)
	{
		int pointCount = points.Count;
		int segmentCount = curveClose?pointCount:pointCount  -  1;

		列表<Vector2> allVertices = new List <Vector2>((smooth + 1)* segmentCount);
		Vector2 [] tempVertices = new Vector2 [smooth + 1];
		float smoothReciprocal = 1f / smooth;

		for(int i = 0; i <segmentCount; ++ i)
		{
			//得到4个相邻点,以计算p1和p2之间的位置
			Vector2 p0,p1,p2,p3;
			p1 =点[i];

			if(curveClose)
			{
				p0 = i == 0?点[segmentCount  -  1]:点[i  -  1];
				p2 = i + 1 <pointCount?点[i + 1]:点[i + 1  -  pointCount];
				p3 = i + 2 <pointCount?点[i + 2]:点[i + 2-pointCount];
			}
			其他
			{
				p0 = i == 0?p1:points [i-1];
				p2 =点[i + 1];
				p3 = i == segmentCount  -  1?p2:点[i + 2];
			}

			Vector2 pA = p1;
			Vector2 pB = 0.5f *(-p0 + p2);
			Vector2 pC = p0-2.5f * p1 + 2f * p2-0.5f * p3;
			Vector2 pD = 0.5f *(-p0 + 3f * p1-3f * p2 + p3);

			float t = 0;
			for(int j = 0; j <= smooth; j ++)
			{
				tempVertices [j] = pA + t *(pB + t *(pC + t * pD));
				t + = smoothReciprocal;
			}
			for(int j = allVertices.Count == 0?0:1; j <tempVertices.Length; j ++)
			{
				allVertices.Add(tempVertices [J]);
			}
		}
		返回allVertices;
	}

	私人列表<CurveSegment2D> GetSegments(List <Vector2> points)
	{
		列表<CurveSegment2D>段=新列表<CurveSegment2D>(points.Count  -  1);
		for(int i = 1; i <points.Count; i ++)
		{
			segment.Add(new CurveSegment2D(points [i-1],points [i]));
		}
		退货细分;
	}

	私人列表<Vector2> GetVertices(List <Vector2> points,float expand)
	{
		列表<CurveSegment2D>段= GetSegments(points);

		列表<CurveSegment2D> segments1 = new List <CurveSegment2D>(segments.Count);
		列表<CurveSegment2D> segments2 = new List <CurveSegment2D>(segments.Count);

		for(int i = 0; i <segments.Count; i ++)
		{
			Vector2 vOffset = new Vector2(-segments [i] .SegmentVector.y,segments [i] .SegmentVector.x).normalized;
			segment1.Add(新的CurveSegment2D(段[i] .point1 + vOffset *展开,段[i] .point2 + vOffset *展开));
			segment2.Add(新的CurveSegment2D(段[i] .point1  -  vOffset *展开,段[i] .point2  -  vOffset *展开));
		}

		List <Vector2> points1 = new List <Vector2>(points.Count);
		List <Vector2> points2 = new List <Vector2>(points.Count);

		for(int i = 0; i <segments1.Count; i ++)
		{
			if(i == 0)
			{
				points1.Add(segments1 [0] .point1);
			}
			其他
			{
				Vector2 crossPoint;
				if(!TryCalculateLinesIntersection(segment1 [i-1],segment1 [i],out crossPoint,0.1f))
				{
					crossPoint = segments1 [i] .point1;
				}
				points1.Add(交叉点);
			}
			if(i == segments1.Count  -  1)
			{
				points1.Add(segments1 [I] .point2);
			}
		}
		for(int i = 0; i <segments2.Count; i ++)
		{
			if(i == 0)
			{
				points2.Add(segments2 [0] .point1);
			}
			其他
			{
				Vector2 crossPoint;
				if(!TryCalculateLinesIntersection(segments2 [i-1],segments2 [i],out crossPoint,0.1f))
				{
					crossPoint = segments2 [i] .point1;
				}
				points2.Add(交叉点);
			}
			if(i == segments2.Count  -  1)
			{
				points2.Add(segments2 [I] .point2);
			}
		}

		List <Vector2> combinePoints = new List <Vector2>(points.Count * 2);
		for(int i = 0; i <points.Count; i ++)
		{
			combinePoints.Add(points1 [I]);
			combinePoints.Add(points2 [I]);
		}
		返回combinePoints;
	}

	私人列表<Vector2> GetVerticesUV(List <Vector2> points)
	{
		列表<Vector2> uvs = new List <Vector2>(points.Count * 2);
		float totalLength = 0;
		float totalLengthReciprocal = 0;
		float curLength = 0;
		for(int i = 1; i <points.Count; i ++)
		{
			totalLength + = Vector2.Distance(points [i-1],points [i]);
		}
		totalLengthReciprocal = uvTiling / totalLength;
		for(int i = 0; i <points.Count; i ++)
		{
			if(i == 0)
			{
				uvs.Add(new Vector2(0,1));
				uvs.Add(new Vector2(0,0));
			}
			其他
			{
				if(i == points.Count  -  1)
				{
					uvs.Add(new Vector2(uvTiling,1));
					uvs.Add(new Vector2(uvTiling,0));
				}
				其他
				{
					curLength + = Vector2.Distance(points [i-1],points [i]);
					float uvx = curLength * totalLengthReciprocal;

					uvs.Add(new Vector2(uvx,1));
					uvs.Add(new Vector2(uvx,0));
				}
			}
		}
		返回uvs;
	}

	private bool TryCalculateLinesIntersection(CurveSegment2D segment1,CurveSegment2D segment2,out Vector2 intersection,float angleLimit)
	{
		intersection = new Vector2();

		Vector2 p1 = segment1.point1;
		Vector2 p2 = segment1.point2;
		Vector2 p3 = segment2.point1;
		Vector2 p4 = segment2.point2;

		float denominator =(p2.y  -  p1.y)*(p4.x  -  p3.x) - (p1.x  -  p2.x)*(p3.y  -  p4.y);
		//如果分母为0,则表示并行
		if(denominator == 0)
		{
			返回假;
		}

		//检查段之间的角度
		float angle = Vector2.Angle(segment1.SegmentVector,segment2.SegmentVector);
		//如果两段之间的角度太小,我们将它们视为平行的
		if(angle <angleLimit ||(180f-angle)<angleLimit)
		{
			返回假;
		}

		float x =((p2.x  -  p1.x)*(p4.x  -  p3.x)*(p3.y  -  p1.y)
				+(p2.y  -  p1.y)*(p4.x  -  p3.x)* p1.x
				- (p4.y  -  p3.y)*(p2.x  -  p1.x)* p3.x)/分母;
		float y =  - ((p2.y  -  p1.y)*(p4.y  -  p3.y)*(p3.x  -  p1.x)
				+(p2.x  -  p1.x)*(p4.y  -  p3.y)* p1.y
				- (p4.x  -  p3.x)*(p2.y  -  p1.y)* p3.y)/分母;

		intersection.Set(x,y);
		返回真
	}
}

这是编辑代码:

使用UnityEngine;
使用UnityEditor;
使用System.Collections;
使用System.Collections.Generic;

[CustomEditor(typeof运算(CurveMeshBuilder))]
public class CurveMeshBuilderEditor:Editor
{
	私有CurveMeshBuilder _script;

	私人GUIStyle _guiStyle_Border1;
	私人GUIStyle _guiStyle_Border2;
	私人GUIStyle _guiStyle_Border3;
	私人GUIStyle _guiStyle_Button1;
	私人GUIStyle _guiStyle_Button2;

	void Awake()
	{
		_script = target为CurveMeshBuilder;

		_guiStyle_Border1 = new GUIStyle(“sv_iconselector_back”);
		_guiStyle_Border1.stretchHeight = false;
		_guiStyle_Border1.padding = new RectOffset(4,4,4,4);
		_guiStyle_Border2 = new GUIStyle(“U2D.createRect”);
		_guiStyle_Border3 = new GUIStyle(“SelectionRect”);
		_guiStyle_Border3.padding = new RectOffset(6,6,6,6);
		_guiStyle_Button1 = new GUIStyle(“PreButton”);
		_guiStyle_Button2 = new GUIStyle(“horizo​​ntalsliderthumb”);
	}

	public override void OnInspectorGUI()
	{
		base.OnInspectorGUI();

		EditorGUILayout.BeginVertical(_guiStyle_Border1);
		{
			if(_script.nodeList.Count <2)
			{
				GUILayout.Label(“关键点数不应小于2!”,“CN EntryWarn”);
			}
			for(int i = 0; i <_script.nodeList.Count; i ++)
			{
				EditorGUILayout.BeginHorizo​​ntal(i == _script.selectedNodeIndex?_guiStyle_Border2:_guiStyle_Border3);
				{
					if(GUILayout.Button(“”,_guiStyle_Button2,GUILayout.Width(20)))
					{
						_script.selectedNodeIndex = i;
					}
					GUILayout.Space(2);
					GUILayout.Label((i + 1).ToString());
					Vector2 newNodePos = EditorGUILayout.Vector2Field(“”,_script.nodeList [i]);
					if(_script.nodeList [i]!= newNodePos)
					{
						_script.nodeList [i] = newNodePos;
					}
					GUILayout.Space(6);
					if(GUILayout.Button(“<”,_guiStyle_Button1,GUILayout.Width(20)))
					{
						Vector2 pos = i == 0?_script.nodeList [i]  -  Vector2.right:(_script.nodeList [i  -  1] + _script.nodeList [i])* 0.5f;
						_script.InsertNode(i,pos);
						_script.selectedNodeIndex = i;
					}
					GUILayout.Space(2);
					if(GUILayout.Button(“✖”,_guiStyle_Button1,GUILayout.Width(20)))
					{
						_script.RemoveNode(ⅰ);
						_script.selectedNodeIndex = i <_script.nodeList.Count?我:i  -  1;
					}
				}
				EditorGUILayout.EndHorizo​​ntal();
			}
			EditorGUILayout.BeginHorizo​​ntal();
			{
				if(GUILayout.Button(“Add”,_guiStyle_Button1))
				{
					Vector2 pos = _script.nodeList.Count == 0?Vector2.zero:_script.nodeList [_script.nodeList.Count  -  1] + Vector2.right;
					_script.AddNode(POS);
					_script.selectedNodeIndex = _script.nodeList.Count  -  1;
				}
				if(GUILayout.Button(“Clear”,_guiStyle_Button1))
				{
					_script.ClearNodes();
				}
			}
			EditorGUILayout.EndHorizo​​ntal();
		}
		EditorGUILayout.EndVertical();

		if(GUILayout.Button(“Build Model”))
		{
			_script.BuildMesh();
		}

		if(GUI.changed)
		{
			EditorUtility.SetDirty(目标);
		}
	}
}

最后感谢:插件数学库Unity。本文参考了该插件的曲线工具,也推荐大家使用这个实用性极强的数学扩展插件,其丰富的功能尤其是常见几何图形的边界计算,能大幅提高生产效率,是每个开发者的必备插件。