Unity中动态绘制圆柱体Mesh
问题背景
上次写了动态绘制立方体,这最近又来了新功能,绘制圆柱(风筒),要求是给了很多节点,根据节点去动态绘制风筒,风筒就是圆柱连接而成的,可以理解为管道,还有就是拐角处注意倒角,圆润过度过来。
实现原理
动态绘制圆柱mesh,注意,圆柱的mesh绘制远比立方体复杂得多,上节阐述过基本mesh创建立方体,有兴趣可以去看看https://www.cnblogs.com/answer-yj/p/11231247.html,顶点以及倒角需要你自己去插值出来,其中倒角是使用贝塞尔曲线插值过度出来的。
实现步骤
1.顶点坐标
2.uv坐标
3.构建索引
4.构建实体
具体过程
1.准备顶点数据
构建圆柱应当理解为这是多个圆插值出来的,首先设置圆的顶点坐标,首先说第一种方式,我先前考虑,在世界坐标空间中,创建一个Gamobject通过它的坐标作为圆上顶点坐标,不从本地创建坐标了(因为涉及坐标转换,矩阵变换),所以我用了这种方式
第一种方式:
具体代码:
/// <summary> /// 设置起中间点断面顶点数据 /// </summary> /// <param name="currPos">当前点坐标</param> /// <param name="curDirection">朝向</param> /// <param name="radius">半径</param> private void SetSectionVertexData(Vector3 currPos, Vector3 curDirection, float radius,float u) { // 首先计算导线点相对场景中心点的偏移坐标 Vector3 position = currPos - center; // 计算direction、right、up向量(注:Unity3d中使用左手坐标系) Vector3 direction = curDirection.normalized; Vector3 right = Vector3.Cross(Vector3.up, direction).normalized; Vector3 up = Vector3.Cross(direction, right).normalized; angleStep = 360.0f / (float)devide; GameObject gameObject = new GameObject(); gameObject.transform.position = position + radius * right;for (int i = 0; i < 360.0f; i += (int)angleStep) { gameObject.transform.RotateAround(currPos, direction, angleStep); vertices.Add(gameObject.transform.position); } Destroy(gameObject); }
代码中direction是方向向量(上一点指向下一点坐标),构建顶点也需要顺序,我这是在右侧90度逆时针创建的,gameObject.transform.position = position + radius * right;这是起点顶点,一共十个顶点。
第二种方式:
我已经用第一种方式功能都做完了,创建好了,完事领导说我们风筒可能要在子线程创建,哇,一瞬间透心凉,GameObject就意味着淘汰了,矩阵变换是躲不开了。从本地创建圆,这句话的意思是指模型本地坐标,即左手坐标系(0,0,0)点 找一点为(0,0,0)点去创建。
具体代码:
1 /// <summary> 2 /// 设置起点圆顶点数据 3 /// </summary> 4 /// <param name="startPos">起点坐标</param> 5 /// <param name="nextPos">下一点坐标</param> 6 private void SetStartVertexData(Vector3 startPos, Vector3 nextPos) 7 { 8 // 首先计算导线点相对场景中心点的偏移坐标 9 Vector3 position = startPos - center; 10 Vector3 nextPosition = nextPos - center; 11 12 // 计算direction、right、up向量(注:Unity3d中使用左手坐标系) 13 Vector3 direction = (nextPosition - position).normalized;//z轴 14 Vector3 right = Vector3.Cross(Vector3.up, direction).normalized;//x轴,右 15 Vector3 up = Vector3.Cross(direction, right).normalized;//x和z得到up 16 // 设起点圆定点数据 17 SetSectionVertexData(position, direction, up, right, uvPosU); 18 } 19 20 21 /// <summary> 22 /// 设置断面顶点数据 23 /// </summary> 24 /// <param name="currentPos">当前点坐标</param> 25 /// <param name="direction">朝向</param> 26 private void SetSectionVertexData(Vector3 currentPos, Vector3 direction, Vector3 up, Vector3 right, float u) 27 { 28 // 构建旋转矩阵 29 Matrix4x4 m = Utils.MakeBasis(right, up, direction); 30 for (float i = 0f; i < 360.0f; i += 36.0f) 31 { 32 // 计算顶点 33 float rad = Mathf.Deg2Rad * i; 34 Vector3 targetPos = currentPos + m.MultiplyPoint3x4(new Vector3(Mathf.Cos(rad) * radius, Mathf.Sin(rad) * radius, 0.0f)); 35 // 计算V坐标 36 float v = ((baseValue * 36.0f) / 360.0f); 37 // 保存顶点坐标&纹理坐标 38 vertices.Add(targetPos); 39 } 40 }
这里解释下,过程是这样的,在本地坐标构建圆,通过旋转矩阵变换到世界坐标。Vector3 direction = (nextPosition - position).normalized;,一般将指向方向向量为坐标系Z轴,假设y轴(0,1,0)为Up方向,那么Vector3 right = Vector3.Cross(Vector3.up, direction).normalized;Z轴与假设Y轴的叉乘得到的是X轴的方向向量(即right方向),垂直与zy平面,但是接下来求方向向量(Z轴)与X轴的叉乘,指定是我们要的Y方向,因为确定的Z和X(垂直XZ平面)。
Utils.MakeBasis具体逻辑如下:
1 /// <summary> 2 /// 通过坐标轴构建矩阵 3 /// </summary> 4 /// <param name="xAxis">x轴</param> 5 /// <param name="yAxis">y轴</param> 6 /// <param name="zAxis">z轴</param> 7 /// <returns>4x4矩阵</returns> 8 public static Matrix4x4 MakeBasis(Vector3 xAxis, Vector3 yAxis, Vector3 zAxis) 9 { 10 Matrix4x4 mat = Matrix4x4.identity; 11 12 mat.m00 = xAxis.x; 13 mat.m10 = xAxis.y; 14 mat.m20 = xAxis.z; 15 16 mat.m01 = yAxis.x; 17 mat.m11 = yAxis.y; 18 mat.m21 = yAxis.z; 19 20 mat.m02 = zAxis.x; 21 mat.m12 = zAxis.y; 22 mat.m22 = zAxis.z; 23 24 return mat; 25 }
矩阵就不解释了,自己看吧。Matrix4x4 m = Utils.MakeBasis(right, up, direction);这是在得到所构建节点的坐标系即坐标走向。因为要把构建的圆放到该点坐标下。m.MultiplyPoint3x4是将构建圆的顶点转换到当前点坐标系下,new Vector3(Mathf.Cos(rad) * radius, Mathf.Sin(rad) * radius, 0.0f)圆上点坐标XY平面构建元,Z为0,到这里圆心始终是(0,0,0),那么将这个圆放到目标点下需要再加上目标点在当下坐标系的偏移量(即坐标值)。所以加currentPos。
到这就将两种设置顶点坐标方式说完了。还有就是拐角处圆的构建,需要用贝塞尔曲线差值出来。
代码:
1 /// <summary> 2 /// 设置中间点顶点数据 3 /// </summary> 4 /// <param name="currentPos"></param> 5 /// <param name="prevPos"></param> 6 /// <param name="nextPos"></param> 7 private void SetPrepareVertexData(Vector3 currentPos, Vector3 prevPos, Vector3 nextPos) 8 { 9 // 首先计算导线点相对场景中心点的偏移坐标 10 Vector3 prevPosition = prevPos - center; 11 Vector3 position = currentPos - center; 12 Vector3 anitherposition = nextPos - center; 13 14 // 计算前一段巷道的direction、right、up向量(注:Unity3d中使用左手坐标系) 15 Vector3 prevDirection = (position - prevPosition).normalized; 16 Vector3 prevRight = Vector3.Cross(Vector3.up, prevDirection).normalized; 17 Vector3 prevUp = Vector3.Cross(prevDirection, prevRight).normalized; 18 19 // 计算后一段巷道的direction、right、up向量(注:Unity3d中使用左手坐标系) 20 Vector3 anitherDirection = (anitherposition - position).normalized; 21 Vector3 anitherlRight = Vector3.Cross(Vector3.up, anitherDirection).normalized; 22 Vector3 anitherUp = Vector3.Cross(anitherDirection, anitherlRight).normalized; 23 24 float angle = Vector3.Angle(-prevDirection, anitherDirection); 25 26 if (angle >= 179.0) 27 { 28 //生成断面数据不倒角处理 29 uvPosU += (GetPointDistance(position, prevPosition) / textureSizeL); 30 SetSectionVertexData(position, prevDirection, prevUp, prevRight, uvPosU); 31 return; 32 } 33 //倒角处理 34 35 //前后两段风筒长度 36 float PrevLength = Vector3.Distance(position, prevPosition); 37 float anithorLength = Vector3.Distance(position, anitherposition); 38 39 indentationValue = PrevLength > anithorLength ? (anithorLength * 0.25f) : (PrevLength * 0.25f);//缩进为短风筒的1/4 40 41 // 计算缩进后的位置 42 Vector3 prevEnd = position - prevDirection * indentationValue;//左 43 Vector3 behindStart = position + anitherDirection * indentationValue;// 右 44 45 uvPosU += (GetPointDistance(prevPosition, prevEnd) / textureSizeL); 46 // 生成前段结束断面顶点数据 47 SetSectionVertexData(prevEnd, prevDirection, prevUp, prevRight, uvPosU); 48 49 // 生成中间倒角断面顶点数据 50 //插值0-1 51 float timer = 0.1f; 52 Vector3 prevpos = prevEnd; 53 for (float i = 1.0f; i <= 9.0f; i++) 54 { 55 Vector3 pos = Utils.CalculateCubicBezierPoint(timer, prevEnd, position, behindStart); 56 // 计算断面方向 57 Vector3 direction = (pos - prevpos).normalized; 58 Vector3 right = Vector3.Cross(Vector3.up, direction).normalized; 59 Vector3 up = Vector3.Cross(direction, right).normalized; 60 uvPosU += (GetPointDistance(pos, prevpos) / textureSizeL); 61 // 生成断面顶点数据 62 SetSectionVertexData(pos, direction, up, right, uvPosU); 63 //递增插值时间 64 timer += 0.1f; 65 //更新前一个点坐标 66 prevpos = pos; 67 } 68 // 生成后段起始断面顶点数据 69 SetSectionVertexData(behindStart, anitherDirection, anitherUp, anitherlRight, ++uvPosU); 70 }
1到9插10个圆。需要找拐角处前后两个点分别为开始和结束插值点,Utils.CalculateCubicBezierPoint(timer, prevEnd, position, behindStart);timer为每次插值(可理解为时刻)。此函数(三阶贝塞尔曲线)具体逻辑:
1 /// <summary> 2 /// 计算三阶贝塞尔曲线点坐标 3 /// </summary> 4 /// <param name="t">时刻(0.0~1.0)</param> 5 /// <param name="p0">起点坐标</param> 6 /// <param name="p1">中间点坐标</param> 7 /// <param name="p2">终点坐标</param> 8 /// <returns>坐标点</returns> 9 public static Vector3 CalculateCubicBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2) 10 { 11 float u = 1.0f - t; 12 float tt = t * t; 13 float uu = u * u; 14 15 return (p0 * uu + p1 * 2 * u * t + p2 * tt); 16 }
这样准备顶点坐标就结束了。
2.uv坐标
UV坐标是与顶点一一对应的,所以在创建顶点时最好一起计算出来,我这里的要求是,上下面贴想同纹理,而且是重复帖,所以V向基本就贴1次上下两面贴,就要从圆上10个点分成两半,或者按角度角度分,一般从0-1后一半从1-0这样就可以实现,重点是U方向坐标值的计算,每个横向重复帖很多次纹理,需要根据贴图比例以及长度去设置,但是U的值应该是累加的。一次从起点到终点贴。知道这一点就好办了。
代码:
//横向代码,U是累加的,根据圆柱长度去设置计算。 uvPosU += (GetPointDistance(pos, prevpos) / textureSizeL); //V方向按上述规律贴 /// <summary> /// 设置断面顶点数据 /// </summary> /// <param name="currentPos">当前点坐标</param> /// <param name="direction">朝向</param> private void SetSectionVertexData(Vector3 currentPos, Vector3 direction, Vector3 up, Vector3 right, float u) { // 构建旋转矩阵 Matrix4x4 m = Utils.MakeBasis(right, up, direction); int baseValue = 2; for (float i = 0f; i < 360.0f; i += 36.0f) { // 计算顶点 float rad = Mathf.Deg2Rad * i; Vector3 targetPos = currentPos + m.MultiplyPoint3x4(new Vector3(Mathf.Cos(rad) * radius, Mathf.Sin(rad) * radius, 0.0f)); // 计算V坐标 float v = ((baseValue * 36.0f) / 360.0f); // 保存顶点坐标&纹理坐标 vertices.Add(targetPos); uvs.Add(new Vector2(u, v)); if (i > 108) { baseValue -= 2; } else { baseValue += 2; } } }
这float v = ((baseValue * 36.0f) / 360.0f);算法需要按要求设置。
3.构建索引
构建索引就要按照定点顺序去构建,连接圆与圆之间的曲面,索引值也是累加的比如这里十个点,第一个圆是0-9/第二个10-19/以此类推。
1 /// <summary> 2 /// 设置索引数据 3 /// </summary> 4 private void SetIndexData() 5 { 6 int faceCount = (vertices.Count / devide) - 1; 7 int baseValue; 8 for (int i = 0; i < faceCount; i++) 9 { 10 baseValue = 0; 11 for (int j = 1; j <= 10; j++) 12 { 13 if (j < 10) 14 { 15 triangles.Add(i * 10 + baseValue); 16 triangles.Add(i * 10 + 11 + baseValue); 17 triangles.Add(i * 10 + baseValue + 10); 18 19 triangles.Add(i * 10 + baseValue); 20 triangles.Add(i * 10 + baseValue + 1); 21 triangles.Add(i * 10 + 11 + baseValue); 22 } 23 else 24 { 25 triangles.Add(i * 10 + baseValue); 26 triangles.Add(i * 10 + 10); 27 triangles.Add(i * 10 + baseValue + 10); 28 29 triangles.Add(i * 10 + baseValue); 30 triangles.Add(i * 10); 31 triangles.Add(i * 10 + 10); 32 } 33 baseValue++; 34 } 35 } 36 }
注意构建顺序不然顺逆时针容易反了。
4.构建实体
这里解释最后一步了,也是最简单的一步了,不解释上代码:
1 /// <summary> 2 /// 绘制风筒实体 3 /// </summary> 4 /// <param name="posList">风筒节点数据集合</param> 5 /// <param name="radius">风筒半径</param> 6 public void DrawAirDuct(List<Vector3> posList, float radius) 7 { 8 this.radius = radius; 9 PrepareVertexData(posList); 10 SetIndexData(); 11 Mesh mesh = new Mesh 12 { 13 vertices = vertices.ToArray(), 14 triangles = triangles.ToArray(), 15 uv = uvs.ToArray(), 16 }; 17 mesh.RecalculateNormals(); 18 GameObject airDuctObj = new GameObject(name); 19 airDuctObj.AddComponent<MeshFilter>().mesh = mesh; 20 airDuctObj.AddComponent<MeshRenderer>().material = new Material(Resources.Load<Material>("Materials/Mine/LanewayColor")); 21 22 // 添加碰撞器MeshCollider 23 airDuctObj.AddComponent<MeshCollider>(); 24 }
切记别忘了重新计算法线mesh.RecalculateNormals();这才出预期效果。
完整代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using System; 2 using System.Collections.Generic; 3 using UnityEngine; 4 5 namespace Tx3d.Framework 6 { 7 /// <summary> 8 /// 绘制风筒实体 9 /// </summary> 10 public class AirDuct : Entity 11 { 12 #region Fields 13 14 //计算圆角的缩进值 15 private float indentationValue; 16 17 //圆划分为多少等份 18 private int devide = 10; 19 20 //中心点坐标 21 private Vector3 center = Vector3.zero; 22 23 //顶点坐标集合 24 private List<Vector3> vertices = new List<Vector3>(); 25 26 //uv坐标集合 27 private List<Vector2> uvs = new List<Vector2>(); 28 29 //索引集合 30 private List<int> triangles = new List<int>(); 31 32 //半径 33 private float radius; 34 35 //贴图长度缩放尺寸 36 private float textureSizeL = 10.24f; 37 38 //uv坐标的u坐标值 39 private float uvPosU = 0; 40 41 #endregion 42 43 #region Public Methods 44 45 /// <summary> 46 /// 风筒实体 47 /// </summary> 48 /// <param name="name">实体名</param> 49 public AirDuct(string name) : base(name) 50 { 51 52 } 53 54 /// <summary> 55 /// 绘制风筒实体 56 /// </summary> 57 /// <param name="posList">风筒节点数据集合</param> 58 /// <param name="radius">风筒半径</param> 59 public void DrawAirDuct(List<Vector3> posList, float radius) 60 { 61 this.radius = radius; 62 PrepareVertexData(posList); 63 SetIndexData(); 64 Mesh mesh = new Mesh 65 { 66 vertices = vertices.ToArray(), 67 triangles = triangles.ToArray(), 68 uv = uvs.ToArray(), 69 }; 70 mesh.RecalculateNormals(); 71 GameObject airDuctObj = new GameObject(name); 72 airDuctObj.AddComponent<MeshFilter>().mesh = mesh; 73 airDuctObj.AddComponent<MeshRenderer>().material = new Material(Resources.Load<Material>("Materials/Mine/LanewayColor")); 74 75 // 添加碰撞器MeshCollider 76 airDuctObj.AddComponent<MeshCollider>(); 77 } 78 79 /// <summary> 80 /// 释放风筒实体 81 /// </summary> 82 public override void Dispose() 83 { 84 85 } 86 87 /// <summary> 88 /// 射线查询 89 /// </summary> 90 /// <param name="ray">射线</param> 91 /// <param name="hit">拾取结果</param> 92 /// <param name="maxDistance">最大拾取距离</param> 93 /// <returns>拾取成功返回true,否则返回false</returns> 94 public override bool Raycast(Ray ray, out RaycastHit hit, float maxDistance) 95 { 96 var collider = gameObject.GetComponent<MeshCollider>(); 97 return collider.Raycast(ray, out hit, maxDistance); 98 } 99 100 /// <summary> 101 /// 体积查询 <see cref="Entity.VolumeQuery(PlaneBoundedVolume)"/> 102 /// </summary> 103 /// <param name="volume">查询体</param> 104 /// <returns>查询成功返回true,否则返回false</returns> 105 public override bool VolumeQuery(PlaneBoundedVolume volume) 106 { 107 throw new NotImplementedException(); 108 } 109 110 #endregion 111 112 #region Private Methods 113 114 /// <summary> 115 /// 准备顶点数据 116 /// </summary> 117 private void PrepareVertexData(List<Vector3> posList) 118 { 119 for (int i = 0; i < posList.Count; i++) 120 { 121 //起点圆面 122 if (i == 0) 123 { 124 SetStartVertexData(posList[i], posList[i + 1]); 125 } 126 else if (i != posList.Count - 1) 127 { 128 ////中间点(求缩进点设置圆角) 129 SetPrepareVertexData(posList[i], posList[i - 1], posList[i + 1]); 130 } 131 else 132 { 133 //终点圆面 134 SetEndVertexData(posList[i], posList[i - 1]); 135 } 136 } 137 } 138 139 /// <summary> 140 /// 设置起点圆顶点数据 141 /// </summary> 142 /// <param name="startPos">起点坐标</param> 143 /// <param name="nextPos">下一点坐标</param> 144 private void SetStartVertexData(Vector3 startPos, Vector3 nextPos) 145 { 146 // 首先计算导线点相对场景中心点的偏移坐标 147 Vector3 position = startPos - center; 148 Vector3 nextPosition = nextPos - center; 149 150 // 计算direction、right、up向量(注:Unity3d中使用左手坐标系) 151 Vector3 direction = (nextPosition - position).normalized;//z轴 152 Vector3 right = Vector3.Cross(Vector3.up, direction).normalized;//x轴,右 153 Vector3 up = Vector3.Cross(direction, right).normalized;//x和z得到up 154 // 设起点圆定点数据 155 SetSectionVertexData(position, direction, up, right, uvPosU); 156 } 157 158 /// <summary> 159 /// 设置终点圆顶点数据 160 /// </summary> 161 /// <param name="endPos">终点坐标</param> 162 /// <param name="previousPos">上一点坐标</param> 163 private void SetEndVertexData(Vector3 endPos, Vector3 previousPos) 164 { 165 // 首先计算导线点相对场景中心点的偏移坐标 166 Vector3 position = endPos - center; 167 Vector3 PreviousPosition = previousPos - center; 168 169 // 计算direction、right、up向量(注:Unity3d中使用左手坐标系) 170 Vector3 direction = (position - PreviousPosition).normalized;//指向下一点(结束)方向向量 171 Vector3 right = Vector3.Cross(Vector3.up, direction).normalized; 172 Vector3 up = Vector3.Cross(direction, right).normalized; 173 //计算U值 174 uvPosU += (GetPointDistance(position, PreviousPosition) / textureSizeL); 175 SetSectionVertexData(position, direction, up, right, uvPosU); 176 } 177 178 /// <summary> 179 /// 设置断面顶点数据 180 /// </summary> 181 /// <param name="currentPos">当前点坐标</param> 182 /// <param name="direction">朝向</param> 183 private void SetSectionVertexData(Vector3 currentPos, Vector3 direction, Vector3 up, Vector3 right, float u) 184 { 185 // 构建旋转矩阵 186 Matrix4x4 m = Utils.MakeBasis(right, up, direction); 187 int baseValue = 2; 188 for (float i = 0f; i < 360.0f; i += 36.0f) 189 { 190 // 计算顶点 191 float rad = Mathf.Deg2Rad * i; 192 Vector3 targetPos = currentPos + m.MultiplyPoint3x4(new Vector3(Mathf.Cos(rad) * radius, Mathf.Sin(rad) * radius, 0.0f)); 193 // 计算V坐标 194 float v = ((baseValue * 36.0f) / 360.0f); 195 // 保存顶点坐标&纹理坐标 196 vertices.Add(targetPos); 197 uvs.Add(new Vector2(u, v)); 198 if (i > 108) 199 { 200 baseValue -= 2; 201 } 202 else 203 { 204 baseValue += 2; 205 } 206 } 207 } 208 209 /// <summary> 210 /// 设置中间点顶点数据 211 /// </summary> 212 /// <param name="currentPos"></param> 213 /// <param name="prevPos"></param> 214 /// <param name="nextPos"></param> 215 private void SetPrepareVertexData(Vector3 currentPos, Vector3 prevPos, Vector3 nextPos) 216 { 217 // 首先计算导线点相对场景中心点的偏移坐标 218 Vector3 prevPosition = prevPos - center; 219 Vector3 position = currentPos - center; 220 Vector3 anitherposition = nextPos - center; 221 222 // 计算前一段巷道的direction、right、up向量(注:Unity3d中使用左手坐标系) 223 Vector3 prevDirection = (position - prevPosition).normalized; 224 Vector3 prevRight = Vector3.Cross(Vector3.up, prevDirection).normalized; 225 Vector3 prevUp = Vector3.Cross(prevDirection, prevRight).normalized; 226 227 // 计算后一段巷道的direction、right、up向量(注:Unity3d中使用左手坐标系) 228 Vector3 anitherDirection = (anitherposition - position).normalized; 229 Vector3 anitherlRight = Vector3.Cross(Vector3.up, anitherDirection).normalized; 230 Vector3 anitherUp = Vector3.Cross(anitherDirection, anitherlRight).normalized; 231 232 float angle = Vector3.Angle(-prevDirection, anitherDirection); 233 234 if (angle >= 179.0) 235 { 236 //生成断面数据不倒角处理 237 uvPosU += (GetPointDistance(position, prevPosition) / textureSizeL); 238 SetSectionVertexData(position, prevDirection, prevUp, prevRight, uvPosU); 239 return; 240 } 241 //倒角处理 242 243 //前后两段风筒长度 244 float PrevLength = Vector3.Distance(position, prevPosition); 245 float anithorLength = Vector3.Distance(position, anitherposition); 246 247 indentationValue = PrevLength > anithorLength ? (anithorLength * 0.25f) : (PrevLength * 0.25f);//缩进为短风筒的1/4 248 249 // 计算缩进后的位置 250 Vector3 prevEnd = position - prevDirection * indentationValue;//左 251 Vector3 behindStart = position + anitherDirection * indentationValue;// 右 252 253 uvPosU += (GetPointDistance(prevPosition, prevEnd) / textureSizeL); 254 // 生成前段结束断面顶点数据 255 SetSectionVertexData(prevEnd, prevDirection, prevUp, prevRight, uvPosU); 256 257 // 生成中间倒角断面顶点数据 258 //插值0-1 259 float timer = 0.1f; 260 Vector3 prevpos = prevEnd; 261 for (float i = 1.0f; i <= 9.0f; i++) 262 { 263 Vector3 pos = Utils.CalculateCubicBezierPoint(timer, prevEnd, position, behindStart); 264 // 计算断面方向 265 Vector3 direction = (pos - prevpos).normalized; 266 Vector3 right = Vector3.Cross(Vector3.up, direction).normalized; 267 Vector3 up = Vector3.Cross(direction, right).normalized; 268 uvPosU += (GetPointDistance(pos, prevpos) / textureSizeL); 269 // 生成断面顶点数据 270 SetSectionVertexData(pos, direction, up, right, uvPosU); 271 //递增插值时间 272 timer += 0.1f; 273 //更新前一个点坐标 274 prevpos = pos; 275 } 276 // 生成后段起始断面顶点数据 277 SetSectionVertexData(behindStart, anitherDirection, anitherUp, anitherlRight, ++uvPosU); 278 } 279 280 /// <summary> 281 /// 设置索引数据 282 /// </summary> 283 private void SetIndexData() 284 { 285 int faceCount = (vertices.Count / devide) - 1; 286 int baseValue; 287 for (int i = 0; i < faceCount; i++) 288 { 289 baseValue = 0; 290 for (int j = 1; j <= 10; j++) 291 { 292 if (j < 10) 293 { 294 triangles.Add(i * 10 + baseValue); 295 triangles.Add(i * 10 + 11 + baseValue); 296 triangles.Add(i * 10 + baseValue + 10); 297 298 triangles.Add(i * 10 + baseValue); 299 triangles.Add(i * 10 + baseValue + 1); 300 triangles.Add(i * 10 + 11 + baseValue); 301 } 302 else 303 { 304 triangles.Add(i * 10 + baseValue); 305 triangles.Add(i * 10 + 10); 306 triangles.Add(i * 10 + baseValue + 10); 307 308 triangles.Add(i * 10 + baseValue); 309 triangles.Add(i * 10); 310 triangles.Add(i * 10 + 10); 311 } 312 baseValue++; 313 } 314 } 315 } 316 317 /// <summary> 318 /// 求两点距离 319 /// </summary> 320 /// <param name="prevPos">前一点坐标</param> 321 /// <param name="nextPos">后一点坐标</param> 322 /// <returns></returns> 323 private float GetPointDistance(Vector3 prevPos, Vector3 nextPos) 324 { 325 return Vector3.Distance(prevPos, nextPos); 326 } 327 328 #endregion 329 } 330 }
这里定了五个测试点,
效果如下:
到这基本结束了,但是这期间计算思考过程还是还是有点东西的,这玩意也是会的不难难的不会,做过一次了就好办了,这里给没搞过的小朋友启发一下。
渴望指正交流。
最新:
最近在扭曲问题上羁绊住了,记录下,原因是在创建本地正交基时有特殊情况没考虑进去,就是方向与Up方向平行时导致计算的Right无意义,所以改了下:
完整代码:
using System.Collections.Generic; using LitJson; using UnityEngine; namespace Tx3d.Framework { /// <summary> /// 所需要的巷道节点顺序(由交叉口决定) /// </summary> public enum BuildOrder { None, Positive,//s-e Reverse,//e-s } /// <summary> /// 绘制风筒实体 /// </summary> public class AirDuct : Entity { #region Fields /// <summary> /// 获取或设置风筒起点颜色 /// </summary> public Color StartColor { get { return startColor; } set { startColor = value; if (gameObject != null) { gameObject.GetComponent<MeshRenderer>().material.SetColor("_StartColor", value); } } } /// <summary> /// 获取或设置风筒终点颜色 /// </summary> public Color EndColor { get { return endColor; } set { endColor = value; if (gameObject != null) { gameObject.GetComponent<MeshRenderer>().material.SetColor("_EndColor", value); } } } /// <summary> /// 获取或设置风筒是否显示流动箭头 /// </summary> public bool IsShowFlowArrow { get { return isShowFlowArrow; } set { isShowFlowArrow = value; if (gameObject != null) { gameObject.GetComponent<MeshRenderer>().material.SetFloat("_IsShow", value ? 1 : 0); } } } /// <summary> /// 获取或设置风筒的流动箭头流动速度 /// </summary> public float FlowArrowSpeed { get { return flowArrowSpeed; } set { flowArrowSpeed = value; if (gameObject != null) { gameObject.GetComponent<MeshRenderer>().material.SetFloat("_Speed", value); } } } /// <summary> /// 获取或设置流动箭头方向 /// </summary> public float FlowArrowDirection { get { return direction; } set { direction = value; if (gameObject != null) { gameObject.GetComponent<MeshRenderer>().material.SetFloat("_Direction", direction); } } } /// <summary> /// 获取风筒外包盒 /// </summary> public override Bounds Bounds { get { if (gameObject != null) { return gameObject.GetComponent<MeshCollider>().bounds; } return new Bounds(); } } /// <summary> /// 获取&设置风筒是否高亮 /// </summary> public override bool Highlight { get { return highlight; } set { highlight = value; if (gameObject != null) { gameObject.GetComponent<MeshRenderer>().material.SetFloat("_FillAmount", highlight ? 0.25f : 0.0f); } } } /// <summary> /// 获取风筒总长度 /// </summary> public float Length { get; private set; } /// <summary> /// 获取风筒半径 /// </summary> public float Radius { get; private set; } //竖直偏移量 public float VerticalOffset { get; private set; } // 风筒最终点坐标 public List<Vector3> airductPosList; //startColor private Color startColor = new Color(0.7490f, 1.0f, 0.0f, 1.0f); //endColor private Color endColor = new Color(0.7490f, 1.0f, 0.0f, 1.0f); //是否显示流向箭头纹理 private bool isShowFlowArrow = true; //流向箭头流动速度 private float flowArrowSpeed = 1.0f; //是否反向纹理流向箭头 private float direction = 1.0f; //计算圆角的缩进值 private float indentationValue; //圆划分为多少等份 //private readonly int devide = 10; //中心点坐标 private Vector3 center = Vector3.zero; //顶点坐标集合 private List<Vector3> vertices = new List<Vector3>(); //uv坐标集合 private List<Vector2> uvs = new List<Vector2>(); //索引集合 private List<int> triangles = new List<int>(); //贴图长度缩放尺寸 private float textureSizeL = 5.12f; //uv坐标的u坐标值 private float uvPosU = 0; //风筒Mesh过滤器 private MeshFilter airDuctMeshFilter; //风筒Mesh渲染器 private MeshRenderer airDuctMeshRenderer; //风筒MeshCollider private MeshCollider airDuctMeshCollider; //本地缓存巷道信息 private List<string> lanewaysList; //本地缓存巷道序列信息 private List<BuildOrder> buildOrdersList; //基本纵向偏移量(底面到顶板) private float baseHeight = 0; //贴图渐变集合数据 private List<Vector2> mapUVList = new List<Vector2>(); //保存比例值 private List<float> saveRatioList = new List<float>(); //风筒数据JsonData private List<JsonData> lanewayJsonDataList; //查询风筒坐标(实际处理数据)列表 private List<Vector3> askPosList; #endregion #region Public Methods /// <summary> /// 通过名称风筒实体 /// </summary> /// <param name="name">实体名</param> public AirDuct(string name) : base(name) { type = "AirDuct"; queryMask = QueryMask.AirDuct; } /// <summary> /// 通过名称以及GUID风筒实体 /// </summary> /// <param name="name">实体名</param> /// <param name="guid">GUID</param> public AirDuct(string name, string guid) : base(name, guid) { type = "AirDuct"; queryMask = QueryMask.AirDuct; } /// <summary> /// 通过名称以及数据列表风筒实体 /// </summary> /// <param name="name">实体名</param> /// <param name="guidList">巷道坐标信息列表</param> /// <param name="buildOrdersList">巷道坐标序列(顺逆)</param> /// <param name="radius">风筒半径</param> /// <param name="verticalOffset">风筒相对巷道的竖直偏移量</param> public AirDuct(string name, List<string> guidList, List<BuildOrder> buildOrdersList, float radius, float verticalOffset, float isReverse = 1.0f) : base(name) { type = "AirDuct"; queryMask = QueryMask.AirDuct; this.Radius = radius; this.VerticalOffset = verticalOffset; this.direction = isReverse; InitCopyLanewayData(guidList, buildOrdersList); } /// <summary> /// 通过名称以及GUID和数据列表风筒实体 /// </summary> /// <param name="name">实体名</param> /// <param name="posList">巷道坐标信息列表</param> /// <param name="buildOrdersList">巷道坐标序列(顺逆)</param> /// <param name="radius">风筒半径</param> /// <param name="verticalOffset">风筒相对巷道的竖直偏移量</param> public AirDuct(string name, string guid, List<string> posList, List<BuildOrder> buildOrdersList, float radius, float verticalOffset, float isReverse = 1.0f) : base(name, guid) { type = "AirDuct"; this.guid = guid; queryMask = QueryMask.AirDuct; this.Radius = radius; this.VerticalOffset = verticalOffset; this.direction = isReverse; InitCopyLanewayData(posList, buildOrdersList); } /// <summary> /// 释放风筒实体 /// </summary> public override void Dispose() { UnityEngine.Object.Destroy(airDuctMeshCollider); UnityEngine.Object.Destroy(airDuctMeshRenderer); UnityEngine.Object.Destroy(airDuctMeshFilter); UnityEngine.Object.Destroy(gameObject); } /// <summary> /// 生成风筒Json数据 <see cref="Entity.ToJson"/> /// </summary> /// <returns>风筒Json数据</returns> public override JsonData ToJson() { var data = new JsonData { ["guid"] = guid, ["name"] = name, ["radius"] = Radius, ["verticalOffset"] = VerticalOffset, ["startColor"] = startColor.ToJson(), ["endColor"] = endColor.ToJson(), ["direction"] = direction, ["AirDuctPos"] = new JsonData() }; foreach (var node in lanewayJsonDataList) { data["AirDuctPos"].Add(node); } return data; } /// <summary> /// 射线查询 /// </summary> /// <param name="ray">射线</param> /// <param name="hit">拾取结果</param> /// <param name="maxDistance">最大拾取距离</param> /// <returns>拾取成功返回true,否则返回false</returns> public override bool Raycast(Ray ray, out RaycastHit hit, float maxDistance) { if (gameObject != null && gameObject.activeSelf) { var collider = gameObject.GetComponent<MeshCollider>(); return collider.Raycast(ray, out hit, maxDistance); } else { hit = new RaycastHit(); return false; } } /// <summary> /// <see cref="Entity.RectangleQuery(float, float, float, float)"/> /// </summary> /// <param name="screenLeft">框选框左侧屏幕坐标</param> /// <param name="screenTop">框选框顶部屏幕坐标</param> /// <param name="screenRight">框选框右侧屏幕坐标</param> /// <param name="screenBottom">框选框底部屏幕坐标</param> /// <returns>拾取成功返回true,否则返回false</returns> public override bool RectangleQuery(float screenLeft, float screenTop, float screenRight, float screenBottom) { // 判断风筒是否已经渲染 if (gameObject == null || !gameObject.activeSelf) { return false; } // 判断是否相交 for (var i = 0; i < airductPosList.Count - 1; i++) { var start = Utils.WorldToScreenPointProjected(CameraControllerManager.Instance.MainCamera, airductPosList[i]); var end = Utils.WorldToScreenPointProjected(CameraControllerManager.Instance.MainCamera, airductPosList[i + 1]); // 判断起点或终点是否落在矩形框范围内 if (start.x >= screenLeft && start.x <= screenRight && start.y >= screenBottom && start.y <= screenTop) { return true; } if (end.x >= screenLeft && end.x <= screenRight && end.y >= screenBottom && end.y <= screenTop) { return true; } // 判断起点到终点的线段是否与矩形框相交 var s = new Vector2(start.x, start.y); var e = new Vector2(end.x, end.y); var leftTop = new Vector2(screenLeft, screenTop); var rightTop = new Vector2(screenRight, screenTop); var rightBottom = new Vector2(screenRight, screenBottom); var leftBottom = new Vector2(screenLeft, screenBottom); // 判断是否与顶边相交 if (Utils.IsTwoSegmentsIntersect(s, e, leftTop, rightTop)) { return true; } // 判断是否与右边相交 if (Utils.IsTwoSegmentsIntersect(s, e, rightTop, rightBottom)) { return true; } // 判断是否与底边相交 if (Utils.IsTwoSegmentsIntersect(s, e, rightBottom, leftBottom)) { return true; } // 判断是否与左边相交 if (Utils.IsTwoSegmentsIntersect(s, e, leftBottom, leftTop)) { return true; } } // 返回不相交 return false; } /// <summary> /// 判断实体是否与外包盒相交<see cref="Entity.IntersectBounds(Bounds)"/> /// </summary> /// <param name="bounds">外包盒</param> /// <returns>相交返回true,否则返回false</returns> public override bool IntersectBounds(Bounds bounds) { if (gameObject.activeSelf) { return bounds.Intersects(Bounds); } return false; } /// <summary> /// 更新风筒实体 /// </summary> /// <param name="posList">更新的节点数据</param> public void UpdateAirDuct() { Dispose(); PrepareRenderData(); RenderAirDuct(); } /// <summary> /// 反转箭头流向 /// </summary> public void ReverseArrow() { direction = -direction; if (gameObject != null) { gameObject.GetComponent<MeshRenderer>().material.SetFloat("_Direction", direction); } } /// <summary> /// 判断某点是否可以投影到风筒的中线上 /// </summary> /// <param name="point">检测点坐标</param> /// <param name="projectionPoint">投影点坐标</param> /// <param name="startIndex">投影点位于的风筒段起点索引</param> /// <param name="endIndex">投影点位于的风筒段终点索引</param> /// <returns>true可以投影到风筒上,false不能投影到风筒上</returns> public bool IsPointProjectedToAirDuct(Vector3 point, out Vector3 projectionPoint, out int startIndex, out int endIndex) { // 初始化输出参数 projectionPoint = Vector3.zero; startIndex = -1; endIndex = -1; // 标识是否投影到巷道上 bool isProjectedTo = false; // 最小投影距离 var minProjectionDistance = float.PositiveInfinity; for (int i = 0; i < askPosList.Count - 1; i++) { var startPosition = askPosList[i]; var endPosition = askPosList[i + 1]; // 计算风筒长度 var length = Vector3.Distance(endPosition, startPosition); // 风筒段是否与Y轴平行 var dir = (endPosition - startPosition).normalized; var dot = Mathf.Abs(Vector3.Dot(dir, Vector3.up)); // 计算向量&向量模长 var vector0 = point - startPosition; // 风筒段起点指向检测点 var vector1 = endPosition - startPosition; // 风筒段起点指向终点 var length0 = vector0.magnitude; var length1 = vector1.magnitude; // 计算向量v0在向量v1的点积 dot = Vector3.Dot(vector0, vector1); // 判断检测点是否可以投影到巷道段上 var ratio = dot / (length1 * length1); if (ratio >= 0.0f && ratio <= 1.0f) { // 计算向量v0在向量v1上的投影长度 var projectionLength = dot / length1; // 计算向量v0到向量v1的投影距离 var projectionDistance = Mathf.Sqrt(length0 * length0 - projectionLength * projectionLength); // 判断是否是最短距离 if (projectionDistance < minProjectionDistance) { // 标识投影到巷道上 isProjectedTo = true; // 更新最短投影距离 minProjectionDistance = projectionDistance; // 计算投影点坐标 projectionPoint = startPosition + (dir * length * ratio); // 记录前后导线点索引 startIndex = i; endIndex = i + 1; } } } return isProjectedTo; } /// <summary> /// (最终)获取风筒坐标节点数目 /// </summary> /// <returns>坐标节点数目</returns> public int GetAirDuctPosCount() { return askPosList.Count; } /// <summary> /// (最终)通过索引获取风筒坐标点 /// </summary> /// <param name="index"></param> /// <returns></returns> public Vector3 GetAirDuctPos(int index) { if (index < 0 || index >= askPosList.Count) { return Vector3.zero; } return askPosList[index]; } /// <summary> /// (初始)获取风筒原始数据(巷道数据)结点数目 /// </summary> /// <returns>结点数目</returns> public int GetAirDuctNodeCount() { return lanewaysList.Count; } /// <summary> /// (初始)获取风筒原始数据(巷道数据)对应的巷道GUID /// </summary> /// <param name="index">索引</param> /// <returns>巷道GUID</returns> public string GetAirDuctLanwayGUID(int index) { if (index < 0 || index >= lanewaysList.Count) { return string.Empty; } return lanewaysList[index]; } /// <summary> /// (初始)获取风筒对应的巷道走向 /// </summary> /// <param name="index">索引</param> /// <returns>构建顺序</returns> public BuildOrder GetAirDuctBuildOrder(int index) { if (index < 0 || index >= buildOrdersList.Count) { return BuildOrder.None; } return buildOrdersList[index]; } #endregion #region internal Methods /// <summary> /// 准备风筒渲染数据 /// </summary> /// <param name="posList">风筒节点数据集合</param> /// <param name="radius">风筒半径</param> internal void PrepareRenderData() { airductPosList = GetPosListData(); askPosList = new List<Vector3>(); for (int i = 0; i < airductPosList.Count; i++) { if (i != 0 || i != airductPosList.Count - 1) { askPosList.Add(airductPosList[i]); } } PrepareVertexData(airductPosList); SetIndexData(); } /// <summary> /// 渲染风筒实体 /// </summary> internal void RenderAirDuct() { gameObject = new GameObject(name); gameObject.transform.position = center; //渲染风筒 Mesh mesh = new Mesh(); mesh.SetVertices(vertices); mesh.SetTriangles(triangles.ToArray(), 0); mesh.SetUVs(0, uvs); mesh.SetUVs(1, mapUVList); mesh.RecalculateNormals(); mesh.RecalculateBounds(); airDuctMeshFilter = gameObject.AddComponent<MeshFilter>(); airDuctMeshFilter.mesh = mesh; airDuctMeshRenderer = gameObject.AddComponent<MeshRenderer>(); airDuctMeshRenderer.material = new Material(Resources.Load<Material>("Materials/Mine/LanewayColor")); airDuctMeshRenderer.material.SetColor("_StartColor", startColor); airDuctMeshRenderer.material.SetColor("_EndColor", endColor); airDuctMeshRenderer.material.SetFloat("_IsShow", isShowFlowArrow ? 1 : 0); airDuctMeshRenderer.material.SetFloat("_Speed", flowArrowSpeed); airDuctMeshRenderer.material.SetFloat("_Direction", direction); // 添加碰撞器MeshCollider airDuctMeshCollider = gameObject.AddComponent<MeshCollider>(); } #endregion #region Private Methods /// <summary> /// 准备顶点数据 /// </summary> private void PrepareVertexData(List<Vector3> posList) { // 更新风筒总长度 UpdateAirDuctLength(posList); for (int i = 0; i < posList.Count; i++) { //起点圆面 if (i == 0) { SetStartVertexData(posList[i], posList[i + 1], posList[i + 2], GetCurrPosRatioValue(i)); } else if (i != posList.Count - 1) { //中间点(求缩进点设置圆角) if (i == 1 || i == posList.Count - 2) { SetPrepareVertexData(posList[i], posList[i - 1], posList[i + 1], true, GetCurrPosRatioValue(i)); } else { SetPrepareVertexData(posList[i], posList[i - 1], posList[i + 1], false, GetCurrPosRatioValue(i)); } } else { //终点圆面 SetEndVertexData(posList[i], posList[i - 1], posList[i - 2], GetCurrPosRatioValue(i)); } } } /// <summary> /// 更新风筒总长度 /// </summary> /// <param name="airductPosList">风筒节点坐标</param> private void UpdateAirDuctLength(List<Vector3> airductPosList) { saveRatioList.Clear(); float length = 0; saveRatioList.Add(0); for (int i = 0; i < airductPosList.Count; i++) { if (i != airductPosList.Count - 1) { length += GetPointDistance(airductPosList[i], airductPosList[i + 1]); saveRatioList.Add(length); } } Length = length; } /// <summary> /// 获取当前点在整体风筒总长度中比例分子 /// </summary> /// <param name="index">第几个点(0开始)</param> /// <returns></returns> private float GetCurrPosRatioValue(int index) { if (index < saveRatioList.Count) { return (saveRatioList[index]); } return -1.0f; } /// <summary> /// 设置起点圆顶点数据 /// </summary> /// <param name="startPos">起点坐标</param> /// <param name="nextPos">下一点坐标</param> private void SetStartVertexData(Vector3 startPos, Vector3 nextPos, Vector3 thirdPos, float currMapValue) { // 首先计算导线点相对场景中心点的偏移坐标 Vector3 position = startPos - center; Vector3 nextPosition = nextPos - center; // 计算direction、right、up向量(注:Unity3d中使用左手坐标系) Vector3 direction = (nextPosition - position).normalized;//z轴 // Vector3 right = Vector3.Cross(Vector3.up, direction).normalized;//x轴,右 Vector3 right = Vector3.Cross((thirdPos-startPos).normalized, direction).normalized;//x轴,右 Vector3 up = Vector3.Cross(direction, -right).normalized;//x和z得到up //if (Vector3.Angle(Vector3.up, direction) <= 1.0f || Vector3.Angle(Vector3.up, direction) >= 179.0f) //{ // up = Vector3.Cross(direction, Vector3.right).normalized; // right = Vector3.Cross(up, direction).normalized; //} GetPointDistance(startPos, nextPos); // 设起点圆定点数据 SetSectionVertexData(position, direction, up, -right, uvPosU, currMapValue); } /// <summary> /// 设置终点圆顶点数据 /// </summary> /// <param name="endPos">终点坐标</param> /// <param name="previousPos">上一点坐标</param> private void SetEndVertexData(Vector3 endPos, Vector3 previousPos, Vector3 thirdPos, float currRatio) { // 首先计算导线点相对场景中心点的偏移坐标 Vector3 position = endPos - center; Vector3 PreviousPosition = previousPos - center; // 计算direction、right、up向量(注:Unity3d中使用左手坐标系) Vector3 direction = (position - PreviousPosition).normalized;//指向下一点(结束)方向向量 Vector3 right = Vector3.Cross((endPos-thirdPos).normalized, direction).normalized; Vector3 up = Vector3.Cross(direction, right).normalized; //if (Vector3.Angle(Vector3.up, direction) <= 1.0f || Vector3.Angle(Vector3.up, direction) >= 179.0f) //{ // up = Vector3.Cross(direction, Vector3.right).normalized; // right = Vector3.Cross(up, direction).normalized; //} //计算U值 uvPosU += (GetPointDistance(position, PreviousPosition) / textureSizeL); SetSectionVertexData(position, direction, up, right, uvPosU, currRatio); } /// <summary> /// 设置断面顶点数据 /// </summary> /// <param name="currentPos">当前点坐标</param> /// <param name="direction">朝向</param> /// <param name="up">向上方向</param> /// <param name="right">向右向</param> /// <param name="u">UV坐标U值</param> private void SetSectionVertexData(Vector3 currentPos, Vector3 direction, Vector3 up, Vector3 right, float u, float currRatio) { // 构建旋转矩阵 Matrix4x4 m = Utils.MakeBasis(right, up, direction); int baseValue = 0; for (float i = 0f; i < 360.0f; i += 36.0f) { //计算顶点 float rad = Mathf.Deg2Rad * i; Vector3 targetPos = currentPos + m.MultiplyPoint3x4(new Vector3(Mathf.Cos(rad) * Radius, Mathf.Sin(rad) * Radius, 0.0f)); //计算V坐标 float v = ((baseValue * 36.0f) / 360.0f); //保存顶点坐标 & 纹理坐标 vertices.Add(targetPos); uvs.Add(new Vector2(u, v)); mapUVList.Add(new Vector2(currRatio, Length)); if (i >= 180) { baseValue -= 2; } else { baseValue += 2; } } } /// <summary> /// 设置中间点顶点数据 /// </summary> /// <param name="currentPos">当前点坐标</param> /// <param name="prevPos">上一点坐标</param> /// <param name="nextPos">下一点坐标</param> /// <param name="isPoint">中间点是否是起始两端水平端点</param> private void SetPrepareVertexData(Vector3 currentPos, Vector3 prevPos, Vector3 nextPos, bool isPoint, float currRatio) { // 首先计算导线点相对场景中心点的偏移坐标 Vector3 prevPosition = prevPos - center; Vector3 position = currentPos - center; Vector3 anitherposition = nextPos - center; // 计算前一段巷道的direction、right、up向量(注:Unity3d中使用左手坐标系) Vector3 prevDirection = (position - prevPosition).normalized; Vector3 prevRight = Vector3.Cross(Vector3.up, prevDirection).normalized; Vector3 prevUp = Vector3.Cross(prevDirection, prevRight).normalized; // 计算后一段巷道的direction、right、up向量(注:Unity3d中使用左手坐标系) Vector3 anitherDirection = (anitherposition - position).normalized; Vector3 anitherlRight = Vector3.Cross(Vector3.up, anitherDirection).normalized; Vector3 anitherUp = Vector3.Cross(anitherDirection, anitherlRight).normalized; float angle = Vector3.Angle(-prevDirection, anitherDirection); // 如果前后两端风筒的夹角大于等于179度则不进行倒角处理 if (angle >= 179.0 || angle <= 1) { //生成断面数据不倒角处理 uvPosU += (GetPointDistance(position, prevPosition) / textureSizeL); SetSectionVertexData(position, prevDirection, prevUp, prevRight, uvPosU, currRatio); return; } // 计算前后段巷道长度 float prevLength = Vector3.Distance(position, prevPosition); float follLength = Vector3.Distance(position, anitherposition); indentationValue = 2 * Radius; //平分向量 Vector3 centerRight = (-prevDirection + anitherDirection).normalized; //圆角所在圆弧半径 var centerRadius = Mathf.Tan(Mathf.Deg2Rad * angle * 0.5f) * indentationValue; //圆角所在圆圆心点 var centerPoint = position + centerRight * (centerRadius / (Mathf.Sin(Mathf.Deg2Rad * angle * 0.5f))); //缩进前后坐标 Vector3 prevPointEnd = position - prevDirection * indentationValue;//左 Vector3 behindPointStart = position + anitherDirection * indentationValue;// 右 Vector3 lastPoint = prevPosition; Vector3 v0 = prevPointEnd - centerPoint; Vector3 v1 = behindPointStart - centerPoint; float t1 = 0.1f; for (float t = 0.0f; t <= 1.0f; t+=0.1f) { Vector3 point = Vector3.Slerp(v0, v1, t) + centerPoint; Vector3 point1 = Vector3.Slerp(v0, v1, t1) + centerPoint; // 计算断面方向 Vector3 direction = (point - lastPoint).normalized; Vector3 right = Vector3.zero; Vector3 up = Vector3.zero; if (isPoint) { if (t == 1.0f) { point1 = anitherposition; } right = Vector3.Cross((point-point1).normalized, direction).normalized; up = Vector3.Cross(direction, right).normalized; } else { right = Vector3.Cross(Vector3.up, direction).normalized; up = Vector3.Cross(direction, right).normalized; if (Vector3.Angle(Vector3.up, direction) <= 1.0f || Vector3.Angle(Vector3.up, direction) >= 179.0f) { up = Vector3.Cross(direction, Vector3.right).normalized; right = Vector3.Cross(up, direction).normalized; } } uvPosU += (GetPointDistance(point, lastPoint) / textureSizeL); // 生成断面顶点数据 SetSectionVertexData(point, direction, up, right, uvPosU, currRatio); t1 += 0.1f; //更新前一个点坐标 lastPoint = point; } } /// <summary> /// 设置索引数据 /// </summary> private void SetIndexData() { var length = (vertices.Count / 10) - 1; for (int i = 0; i < length; i++) { for (int j = 0; j < 10; j++) { if (j < 9) { triangles.Add(i * 10 + (j + 1)); triangles.Add((i + 1) * 10 + j); triangles.Add(i * 10 + j); triangles.Add((i + 1) * 10 + (j + 1)); triangles.Add((i + 1) * 10 + j); triangles.Add(i * 10 + (j + 1)); } else { triangles.Add(i * 10); triangles.Add((i + 1) * 10 + j); triangles.Add(i * 10 + j); triangles.Add((i + 1) * 10); triangles.Add((i + 1) * 10 + j); triangles.Add(i * 10); } } } } /// <summary> /// 求两点距离 /// </summary> /// <param name="prevPos">前一点坐标</param> /// <param name="nextPos">后一点坐标</param> /// <returns></returns> private float GetPointDistance(Vector3 prevPos, Vector3 nextPos) { return Vector3.Distance(prevPos, nextPos); } #region ReadyPosInfoWork /// <summary> /// 通过巷道信息获取风筒节点坐标 /// </summary> private List<Vector3> GetPosListData() { List<Laneway.LeadNode> tempNodeList = new List<Laneway.LeadNode>(); List<Vector3> posList = new List<Vector3>(); //排重(顺不要尾逆不要头(最后一条巷道保留)) for (int i = 0; i < lanewaysList.Count; i++) { int length = ((Laneway)EntityManager.Instance.GetEntity(lanewaysList[i])).GetLeadNodeCount(); switch (buildOrdersList[i]) { case BuildOrder.Positive: for (int j = 0; j < length; j++) { if (i != lanewaysList.Count - 1) { if (j != length - 1) { tempNodeList.Add(GetLanewayNodePos(lanewaysList[i], j)); } } else { tempNodeList.Add(GetLanewayNodePos(lanewaysList[i], j)); } } break; case BuildOrder.Reverse: for (int j = length - 1; j >= 0; j--) { if (i != lanewaysList.Count - 1) { if (j != 0) { tempNodeList.Add(GetLanewayNodePos(lanewaysList[i], j)); } } else { tempNodeList.Add(GetLanewayNodePos(lanewaysList[i], j)); } } break; default: break; } } //设置风筒具体坐标 for (int i = 0; i < tempNodeList.Count; i++) { if (i == 0)//起点 { posList.Add(CalculateStartEndPos(tempNodeList[i], tempNodeList[i + 1], true)); } else if (i == (tempNodeList.Count - 1))//终点 { posList.Add(CalculateStartEndPos(tempNodeList[i], tempNodeList[i - 1], false)); } else//中间点 { posList.Add(CalculatePos(tempNodeList[i], tempNodeList[i - 1])); } } //插入开始结束两端的水平端点 Vector3 direction = (tempNodeList[1].RelativePosition - tempNodeList[0].RelativePosition).normalized; Vector3 right = Vector3.Cross(Vector3.up, direction).normalized; Vector3 up = Vector3.Cross(direction, right).normalized; posList.Insert(1, tempNodeList[0].RelativePosition + up * (baseHeight + VerticalOffset)); Vector3 direction1 = (tempNodeList[tempNodeList.Count - 1].RelativePosition - tempNodeList[tempNodeList.Count - 2].RelativePosition).normalized; Vector3 right1 = Vector3.Cross(Vector3.up, direction1).normalized; Vector3 up1 = Vector3.Cross(direction1, right1).normalized; posList.Insert(posList.Count - 1, tempNodeList[tempNodeList.Count - 1].RelativePosition + up1 * (baseHeight + VerticalOffset)); return posList; } /// <summary> /// 计算风筒中间点坐标 /// </summary> /// <param name="node">当前巷道导线点数据</param> /// <param name="prepNode">上一巷道导线点数据</param> /// <returns></returns> private Vector3 CalculatePos(Laneway.LeadNode node, Laneway.LeadNode prepNode) { Vector3 direction = (node.RelativePosition - prepNode.RelativePosition).normalized; Vector3 right = Vector3.Cross(Vector3.up, direction).normalized; Vector3 up = Vector3.Cross(direction, right).normalized; Vector3 vector3 = node.RelativePosition; vector3 += up * (baseHeight + VerticalOffset); return vector3; } /// <summary> /// 计算风筒两端点坐标 /// </summary> /// <param name="node">当前巷道导线点数据</param> /// <param name="anotherNode">下一巷道导线点数据</param> /// <param name="isStart">是否是开始端点</param> /// <returns></returns> private Vector3 CalculateStartEndPos(Laneway.LeadNode node, Laneway.LeadNode anotherNode, bool isStart) { float width; if (!node.UseSecondWidth)//第一套 { width = (node.LeftWidth + node.RightWidth); } else//第二套 { width = (node.LeftWidth2 + node.RightWidth2); } float radius = Mathf.Sqrt(Mathf.Pow((0.50f * width), 2) + Mathf.Pow((0.50f * width), 2)); float targetHight = (radius - (0.50f * width)) + node.Height; baseHeight = targetHight; Vector3 direction = Vector3.zero; if (isStart) { direction = (anotherNode.RelativePosition - node.RelativePosition).normalized; } else { direction = (node.RelativePosition - anotherNode.RelativePosition).normalized; } Vector3 right = Vector3.Cross(Vector3.up, direction).normalized; Vector3 up = Vector3.Cross(direction, right).normalized; Vector3 CurPos = node.RelativePosition; CurPos += up * targetHight; return CurPos; } /// <summary> /// 初始化Copy巷道数据 /// </summary> /// <param name="lanewaysList">巷道信息集合</param> /// <param name="buildOrdersList">巷道坐标序列(顺逆)</param> private void InitCopyLanewayData(List<string> lanewaysList, List<BuildOrder> buildOrdersList) { lanewayJsonDataList = new List<JsonData>(); this.lanewaysList = new List<string>(); this.buildOrdersList = new List<BuildOrder>(); for (int i = 0; i < lanewaysList.Count; i++) { if (EntityManager.Instance.GetEntity(lanewaysList[i]) != null) { this.lanewaysList.Add(lanewaysList[i]); this.buildOrdersList.Add(buildOrdersList[i]); lanewayJsonDataList.Add(GetAirDuctJsonData(lanewaysList[i], buildOrdersList[i])); } } } /// <summary> /// 根据guid和节点索引获取节点坐标 /// </summary> /// <param name="guid"></param> /// <param name="index"></param> /// <returns></returns> private Laneway.LeadNode GetLanewayNodePos(string guid, int index) { if (EntityManager.Instance.GetEntity(guid) != null) { return ((Laneway)EntityManager.Instance.GetEntity(guid)).GetLeadNode(index); } return null; } /// <summary> /// 生成风筒数据jsonData /// </summary> /// <param name="guid">GUID</param> /// <param name="buildOrder">顺序</param> /// <returns></returns> private JsonData GetAirDuctJsonData(string guid, BuildOrder buildOrder) { return new JsonData { ["guid"] = guid, ["buildOrder"] = (int)buildOrder, }; } /// <summary> /// 校验风筒基本数据坐标 /// </summary> /// <returns>减去中心坐标</returns> private List<Vector3> CorrectPos(List<Vector3> list) { List<Vector3> posList = new List<Vector3>(); Vector3 targetPos = Vector3.zero; for (int i = 0; i < list.Count; i++) { targetPos += list[i]; } Vector3 centerPos = (targetPos / (list.Count)); for (int i = 0; i < list.Count; i++) { posList.Add((list[i] - centerPos)); } return posList; } #endregion #endregion } }