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();這才出預期效果。

完整代碼:

  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 }
View Code

 

這里定了五個測試點,

效果如下:

到這基本結束了,但是這期間計算思考過程還是還是有點東西的,這玩意也是會的不難難的不會,做過一次了就好辦了,這里給沒搞過的小朋友啟發一下。

渴望指正交流。

 

 

最新:

最近在扭曲問題上羈絆住了,記錄下,原因是在創建本地正交基時有特殊情況沒考慮進去,就是方向與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

    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM