Unity根据路径创建带有一定宽度的Mesh(面)
我也要试着写一下,对自己做一个总结,第一次写哦,乱完了!
最近在做一个项目,需要根据路径点生成一定宽度一定厚度的mesh,我在网上找了很多生成mesh的博客,网上的都是mesh的基础生成,一些简单的mesh图形,但是我的需求是生成的mesh可以是任意角度,可能带有弧度,没有办法,只有自己写一个根据点位生成的Mesh,虽然我的数学基础不好,但是最后也勉勉强强能用。
Demo下载:https://download.csdn.net/download/zhaojunkuan/13153786
最开始的时候我只能绘制一些简单的图形,比如四边形五边形,但是现在需求明显仅仅有规则图形,根本达不到要求,再开始前,需要看一下“九德真君”的一篇博客,我也是看到他的博客受到的启发,https://blog.csdn.net/lzdidiv/article/details/53736068
首先有几种情况(手绘图,做项目的时候画的),需要根据这几种情况计算边上的顶点。
一、普通情况
如下图,首先看一个通常的情况,两向量夹角小于90°的情况,先根据好理解的图计算几个顶点
1、首先计算a,b向量并进行归一化,并对xz面做投影(所以可以用等腰三角形表示)
Vector3 a =Vector3 .ProjectOnPlane((p1 - p0).normalized,Vector3.up);//计算两向量并进行归一化
Vector3 b =Vector3 .ProjectOnPlane((p2 - p1).normalized,Vector3.up) ;
将两向量逆时针旋转90°,归一化,方便计算后续的偏移
Vector3 AVerticle =new Vector3(-a.z,a.y,a.x).normalized;//逆时针旋转90° 与a向量垂直
Vector3 BVerticle = new Vector3(-b.z,b.y,b.x).normalized;//逆时针旋转90°与b向量垂直
2、获取两向量的和向量dire,并获取逆时针旋转90°垂直向量DireVerticle
Vector3 dire = a + b;//a与b的和向量
Vector3 DireVerticle = new Vector3(-dire.z,dire.y,dire.x).normalized;//逆时针旋转90°,获得垂直向量
3、计算角度,获取a向量与和向量的垂直向量的夹角t,如上图
float t = Mathf.Asin(Vector3.Dot(a, DireVerticle));//角度
if (t < 0) t = -t;
4、计算偏移量,以计算p11和p22两个拐点
float direOffset;
if (Mathf.Cos(t) == 0)//a,b垂直
{
direOffset=offset;
}
else
{
direOffset = offset / Mathf.Cos(t);
}
Vector3 p11 = p1 + DireVerticle * direOffset;
Vector3 p12=p1- DireVerticle * direOffset;
5、计算p01,p02,p21,p22点
Vector3 p01=Vector3.zero ;
Vector3 p02 = Vector3.zero;
Vector3 p21 = p2 + BVerticle * offset;
Vector3 p22 = p2 - BVerticle * offset;
根据第一个手绘图,分别做mesh顶点的添加和 triangles的添加
在每段进行绘制时,需要判定,将上一次的第二段作为此次的第一段,所以有顶点的删除,没次进行绘制时都要进行,
360°和0°做一样的处理
二、180°的时候是两段重叠,进行一次单独处理就好,根据图示和代码很好理解
三、90-180°和180-360°,这两段因为有一个大的拐角,两向量夹角比较大,需要判断一下p12(90-180°)或者p11(180-360°)在不在要绘制的图形内,如果不在直接连接首尾两定点就可以
在处理这两种情况的时候,需要对拐角进行一次贝塞尔处理,以保证顶点不会因为角度太小偏离太远
和0-90°的时候计算原理基本一样,只有顶点位置不一样,一定要注意a和b逆时针旋转获取垂直向量,p01、p02、p11、p12、 p21、p22位置可能在三角形外边,也可能在三角形里边
代码写的也比较乱,黔驴技穷了
下边是三个个脚本的全部代码,地面要设置Layer层为9
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bezier
{
//贝塞尔曲线公式:B(t)=P0*(1-t)^3 + 3*P1*t(1-t)^2 + 3*P2*t^2*(1-t) + P3*t^3 ,t属于[0,1].
public static Vector3 CalculateCubicBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
{
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;
Vector3 p = uuu * p0;
p += 3 * uu * t * p1;
p += 3 * u * tt * p2;
p += ttt * p3;
return p;
}
// B(t) = (1-t)^2P0+ 2 (1-t) tP1 + t^2P2
public static Vector3 CalculateCubicBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2)
{
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float ttt = tt * t;
Vector3 p = uu * p0;
p += 2 * u * t * p1;
p += tt * p2;
return p;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 在xz平面内
/// </summary>
public class CreatePathMeshData
{
public List<Vector3> verticles;
public List<int> triangles;
public List<Vector2> uv;
int beizierT ;
public MeshCollider meshCollider;
bool LastIsSpecialAngle;
public CreatePathMeshData()
{
verticles = new List<Vector3>();
triangles = new List<int>();
uv = new List<Vector2>();
beizierT = 60;
}
public void GetVeriticles(Vector3 p0, Vector3 p1, Vector3 p2, float Linewidth)
{
RemoveUnwantFace();
float offset = Linewidth / 2;
Vector3 a =Vector3 .ProjectOnPlane((p1 - p0).normalized,Vector3.up);
Vector3 b =Vector3 .ProjectOnPlane((p2 - p1).normalized,Vector3.up) ;
float angle = Angle_360(a, b);//计算从a顺时针到b的角度 范围360°
Vector3 AVerticle =new Vector3(-a.z,a.y,a.x).normalized;//逆时针旋转90° 与a向量垂直
Vector3 BVerticle = new Vector3(-b.z,b.y,b.x).normalized;//逆时针旋转90°与b向量垂直
Vector3 dire = a + b;//a与b的和向量
Vector3 p01=Vector3.zero ;
Vector3 p02 = Vector3.zero;
Vector3 p21 = p2 + BVerticle * offset;
Vector3 p22 = p2 - BVerticle * offset;
Vector3 DireVerticle = new Vector3(-dire.z,dire.y,dire.x).normalized;//逆时针旋转90°,获得垂直向量
float t = Mathf.Asin(Vector3.Dot(a, DireVerticle));//角度
if (t < 0) t = -t;
float direOffset;
if (Mathf.Cos(t) == 0)//a,b垂直
{
direOffset=offset;
}
else
{
direOffset = offset / Mathf.Cos(t);
}
Vector3 p11 = p1 + DireVerticle * direOffset;
Vector3 p12=p1- DireVerticle * direOffset;
#region 特殊角度
if (angle >= 90 && angle < 180)
{
if (!Isinside(p12, p0, p1, p2))
{
LastIsSpecialAngle = true;
Vector3 p1n = p1 + AVerticle * offset;
Vector3 p1m = p1 + BVerticle * offset;
Vector3 p1np1m = p1m - p1n;
Vector3 middle = p1n + p1np1m / 2;
Vector3 p1p1m = p1m - p1;
float anglep1p1m = Vector3.Angle(p1p1m, p1np1m);
float DisMiddlep1 = offset * Mathf.Sin(anglep1p1m * Mathf.PI / 180);
p11 = p1 + (middle - p1).normalized * DisMiddlep1 * 2;
Vector3[] ttt = new Vector3[beizierT + 1];
for (int i = 0; i <= beizierT; i++)
{
float t1 = i / (float)beizierT;
ttt[i] = Bezier.CalculateCubicBezierPoint(t1, p1n, p11, p1m);
Vector3 pos = ttt[i];
}
//上
if (CreateLineMesh.FirstCreate)
{
p01 = p0 + AVerticle * offset;
p02 = p0 - AVerticle * offset;
CreateLineMesh.FirstCreate = false;
}
else
{
p01 = verticles[verticles.Count - 1];
}
Addverticles(p01);
Addverticles(p1n);
Addverticles(p0);
Addverticles(p1);
Addverticles(p2);
AddTriangle(verticles.Count-5);
AddTriangle(verticles.Count - 4);
AddTriangle(verticles.Count - 2);
AddTriangle(verticles.Count - 5);
AddTriangle(verticles.Count - 2);
AddTriangle(verticles.Count - 3);
AddTriangle(verticles.Count - 3);
AddTriangle(verticles.Count - 2);
AddTriangle(verticles.Count - 1);
int IndexOfP1 = verticles.Count - 2;//记录P1的位置
int countTemp = verticles.Count;//记录贝塞尔前的顶点数量
int tttNum = ttt.Length;
for (int i = 0; i < tttNum; i++)
{
Addverticles(ttt[i]);
}
for (int i = 0; i < tttNum-1; i++)
{
AddTriangle(IndexOfP1);
AddTriangle(i+ countTemp);
AddTriangle(i+1+ countTemp);
}
Addverticles(p1);
Addverticles(p1m);
Addverticles(p2);
Addverticles(p21);
AddTriangle(verticles.Count-4);
AddTriangle(verticles.Count - 3);
AddTriangle(verticles.Count - 1);
AddTriangle(verticles.Count - 4);
AddTriangle(verticles.Count - 1);
AddTriangle(verticles.Count - 2);
return;
}
}
else if (angle > 180 && angle <= 270)
{
if (!Isinside(p11, p0, p1, p2))
{
LastIsSpecialAngle = true;
Vector3 p1n = p1 - AVerticle * offset;
Vector3 p1m = p1 - BVerticle * offset;
Vector3 p1np1m = p1m - p1n;
Vector3 middle = p1n + p1np1m / 2;
Vector3 p1p1m = p1m - p1;
float anglep1p1m = Vector3.Angle(p1p1m, p1np1m);
float DisMiddlep1 = offset * Mathf.Sin(anglep1p1m * Mathf.PI / 180);
p12 = p1 + (middle - p1).normalized * DisMiddlep1 * 2;
Vector3[] ttt = new Vector3[beizierT + 1];
for (int i = 0; i <= beizierT; i++)
{
float t1 = i / (float)beizierT;
ttt[i] = Bezier.CalculateCubicBezierPoint(t1, p1m, p12, p1n);
Vector3 pos = ttt[i];
}
//上
if (CreateLineMesh.FirstCreate)
{
p01 = p0 + AVerticle * offset;
p02 = p0 - AVerticle * offset;
CreateLineMesh.FirstCreate = false;
}
else
{
p02 = verticles[verticles.Count - 1];
}
Addverticles(p0);
Addverticles(p1);
Addverticles(p02);
Addverticles(p1n);
Addverticles(p2);
AddTriangle(verticles.Count - 5);
AddTriangle(verticles.Count - 4);
AddTriangle(verticles.Count - 2);
AddTriangle(verticles.Count - 5);
AddTriangle(verticles.Count - 2);
AddTriangle(verticles.Count - 3);
AddTriangle(verticles.Count - 5);
AddTriangle(verticles.Count - 1);
AddTriangle(verticles.Count - 4);
int IndexOfP1 = verticles.Count - 4;//记录P1的位置
int countTemp = verticles.Count;//记录贝塞尔前的顶点数量
int tttNum = ttt.Length;
for (int i = 0; i < ttt.Length; i++)
{
Addverticles(ttt[i]);
}
for (int i = 0; i < tttNum - 1; i++)
{
AddTriangle(IndexOfP1);
AddTriangle(i + countTemp);
AddTriangle(i + 1 + countTemp);
}
Addverticles(p1);
Addverticles(p1m);
Addverticles(p2);
Addverticles(p22);
AddTriangle(verticles.Count - 4);
AddTriangle(verticles.Count - 2);
AddTriangle(verticles.Count - 1);
AddTriangle(verticles.Count - 4);
AddTriangle(verticles.Count - 1);
AddTriangle(verticles.Count - 3);
return;
}
}
else if (angle == 180)
{
if (CreateLineMesh.FirstCreate)
{
p01 = p0 + AVerticle * offset;
p02 = p0 - AVerticle * offset;
CreateLineMesh.FirstCreate = false;
}
else
{
p01 = verticles[verticles.Count - 3];
if (LastIsSpecialAngle)
{
p01 = verticles[verticles.Count - 2];
LastIsSpecialAngle = false;
}
p02 = verticles[verticles.Count - 1];
}
//上
Addverticles(p01);
Addverticles(p11);
Addverticles(p02);
Addverticles(p11);
Addverticles(p22);
Addverticles(p21);
int verticleCount = verticles.Count;
AddTriangle(verticleCount - 6);
AddTriangle(verticleCount - 5);
AddTriangle(verticleCount - 3);
AddTriangle(verticleCount - 6);
AddTriangle(verticleCount - 3);
AddTriangle(verticleCount - 4);
AddTriangle(verticleCount - 2);
AddTriangle(verticleCount - 5);
AddTriangle(verticleCount - 3);
AddTriangle(verticleCount - 2);
AddTriangle(verticleCount - 3);
AddTriangle(verticleCount - 1);
return;
}
#endregion
#region 小于90度
if (CreateLineMesh.FirstCreate)
{
p01 = p0 + AVerticle * offset;//根据宽度计算周围平移顶点
p02 = p0 - AVerticle * offset;
CreateLineMesh.FirstCreate = false;
}
else
{
p01 = verticles[verticles.Count - 3];//同一个的Mesh的情况下,起点要取上一次的中间两端点
if (LastIsSpecialAngle)
{
p01 = verticles[verticles.Count - 2];
LastIsSpecialAngle = false;
}
p02 = verticles[verticles.Count - 1];
}
//上表面
Addverticles(p01);
Addverticles(p11);
Addverticles(p02);
Addverticles(p12);
Addverticles(p21);
Addverticles(p22);
int count = verticles.Count;
AddTriangle(count-6);
AddTriangle(count - 5);
AddTriangle(count - 3);
AddTriangle(count - 6);
AddTriangle(count - 3);
AddTriangle(count - 4);
AddTriangle(count - 5);
AddTriangle(count - 2);
AddTriangle(count - 1);
AddTriangle(count - 5);
AddTriangle(count - 1);
AddTriangle(count - 3);
#endregion
}
void Addverticles(Vector3 p)
{
verticles.Add(p);
}
void AddTriangle(int index)
{
triangles.Add(index);
}
void RemoveUnwantFace()
{
if (verticles.Count < 6) return;
verticles.RemoveAt(verticles.Count-1);
verticles.RemoveAt(verticles.Count - 1);
for (int i = 0; i < 6; i++)
{
triangles.Remove(triangles.Count-1);
}
}
//判断一个点在三角形内还是外
bool Isinside(Vector3 point, Vector3 a, Vector3 b, Vector3 c)
{
Vector3 pa = a - point;
Vector3 pb = b - point;
Vector3 pc = c - point;
Vector3 pab = Vector3.Cross(pa, pb);
Vector3 pbc = Vector3.Cross(pb, pc);
Vector3 pca = Vector3.Cross(pc, pa);
float d1 = Vector3.Dot(pab, pbc);
float d2 = Vector3.Dot(pab, pca);
float d3 = Vector3.Dot(pbc, pca);
if (d1 > 0 && d2 > 0 && d3 > 0) return true;
return false;
}
//判断逆时针旋转两向量夹角0-360度
public float Angle_360(Vector3 from_, Vector3 to_)
{
Vector3 v3 = Vector3.Cross(from_, to_);
if (v3.y > 0)
return Vector3.Angle(from_, to_);
else
return 360 - Vector3.Angle(from_, to_);
}
/// <summary>
/// 获取两向量的法线,左手坐标系,从a-b
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static Vector3 GetNormalVector3(Vector3 a, Vector3 b)
{
return Vector3.Cross(a, b).normalized;
}
/// <summary>
/// 绕轴旋转
/// </summary>
/// <param name="position">点</param>
/// <param name="center">绕的中心点</param>
/// <param name="axis"></param>
/// <param name="angle">angle>0顺时针</param>
/// <returns></returns>
public static Vector3 RotateRound(Vector3 position, Vector3 center, Vector3 axis, float angle)
{
Vector3 point = Quaternion.AngleAxis(angle, axis) * (position - center);
Vector3 resultVec3 = center + point;
return resultVec3;
}
#region 暂时不用
/ <summary>
/ 判断一个点在三角形内
/ </summary>
/ <param name="point"></param>
/ <param name="a"></param>
/ <param name="b"></param>
/ <param name="c"></param>
/ <returns></returns>
//bool Isinside2(Vector3 point, Vector3 a, Vector3 b, Vector3 c)
//{
// float s1 = Area(a, b, c);
// float s2 = Area(point, a, b);
// float s3 = Area(point, a, c);
// float s4 = Area(point, b, c);
// //需考虑边界情况
// if (s2 == 0 || s3 == 0 || s4 == 0) return false;
// //不能用“==”判断两个浮点类型的值是否相等,可使用如下,差小于等于某个精度值即可。
// if (s1 - (s2 + s3 + s4) <= 0.00001f) return true;
// return false;
//}
//float Area(Vector3 a, Vector3 b, Vector3 c)//计算三角形面积
//{
// //海伦公式:p=(a+b+c)/2; S = √[p(p-a)(p-b)(p-c)] //这里a,b,c代表边长
// float dab = Vector3.Distance(a, b);
// float dac = Vector3.Distance(a, c);
// float dbc = Vector3.Distance(b, c);
// float half = (dab + dac + dbc) / 2;
// return Mathf.Sqrt(half * (half - dab) * (half - dac) * (half - dbc));
//}
#endregion
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CreateLineMesh : MonoBehaviour {
public static bool FirstCreate;
public Material material;
GameObject mesh;
MeshCollider meshCollider;
MeshFilter meshFilter;
MeshRenderer meshRenderer;
CreatePathMeshData meshData;
bool down;
Vector3 startPos;
Vector3 lastPOs;
public float Width=0.5f;
public float Thickness=0.2f;
public void CreateUpdateMeshPos()
{
if (Input.GetMouseButtonDown(0))
{
Create();
FirstCreate = true;
Vector3 _pos = SendRay();
startPos = lastPOs = _pos;
down = true;
}
if (down)
{
Vector3 pos = SendRay();
if (pos != lastPOs)
{
float dis = Vector3.Distance(pos, lastPOs);
if (dis >= 0.1f)
{
Vector3 p0 = startPos;
Vector3 p1 = lastPOs;
Vector3 p2 = pos;
if (p1 == p0 || p1 == p2)
{
p1 = (p0 + p2) / 2;
}
if (meshData.verticles.Count > 60000)//一个Mesh顶点数量限制在65000
Create();
meshData.GetVeriticles(p0, p1, p2, Width);
GetMesh();
startPos = lastPOs;
lastPOs = pos;
}
}
}
if (Input.GetMouseButtonUp(0))
{
down = false;
}
}
void GetMesh()
{
meshFilter.mesh.vertices = meshData.verticles.ToArray();
meshFilter.mesh.triangles = meshData.triangles.ToArray();
if (meshData.meshCollider == null)
meshData.meshCollider = mesh.AddComponent<MeshCollider>();
meshData.meshCollider.enabled = false;
meshData.meshCollider.enabled = true;
}
void Create()
{
mesh = new GameObject("Mesh", typeof(MeshFilter), typeof(MeshRenderer));
meshFilter = mesh.GetComponent<MeshFilter>();
meshRenderer = mesh.GetComponent<MeshRenderer>();
meshRenderer.material = material;
meshData = new CreatePathMeshData();
}
Vector3 SendRay()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
int layer = 1 << 9;
if (Physics.Raycast(ray, out hit,1000, layer))
{
Vector3 pos = hit.point;
pos.y += 1;
return pos;
}
return Vector3.zero;
}
// Update is called once per frame
void Update ()
{
CreateUpdateMeshPos();
}
}