Unity中动态绘制圆柱体
问题背景
上次写了动态绘制立方体,这最近又来了新功能,绘制圆柱(风筒),要求是给了很多节点,根据节点去动态绘制风筒,风筒就是圆柱连接而成的,可以理解为管道,还有就是拐角处注意倒角,圆润过度过来。
实现原理
动态绘制圆柱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 = ; 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)点去创建。
具体代码:
/// <summary>
/// 设置起点圆顶点数据
/// </summary>
/// <param name="startPos">起点坐标</param>
/// <param name="nextPos">下一点坐标</param>
private void SetStartVertexData(Vector3 startPos, Vector3 nextPos)
{
// 首先计算导线点相对场景中心点的偏移坐标
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 up = Vector3.Cross(direction, right).normalized;//x和z得到up
// 设起点圆定点数据
SetSectionVertexData(position, direction, up, right, uvPosU);
} /// <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);
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);
}
}
这里解释下,过程是这样的,在本地坐标构建圆,通过旋转矩阵变换到世界坐标。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具体逻辑如下:
/// <summary>
/// 通过坐标轴构建矩阵
/// </summary>
/// <param name="xAxis">x轴</param>
/// <param name="yAxis">y轴</param>
/// <param name="zAxis">z轴</param>
/// <returns>4x4矩阵</returns>
public static Matrix4x4 MakeBasis(Vector3 xAxis, Vector3 yAxis, Vector3 zAxis)
{
Matrix4x4 mat = Matrix4x4.identity; mat.m00 = xAxis.x;
mat.m10 = xAxis.y;
mat.m20 = xAxis.z; mat.m01 = yAxis.x;
mat.m11 = yAxis.y;
mat.m21 = yAxis.z; mat.m02 = zAxis.x;
mat.m12 = zAxis.y;
mat.m22 = zAxis.z; return mat;
}
矩阵就不解释了,自己看吧。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。
到这就将两种设置顶点坐标方式说完了。还有就是拐角处圆的构建,需要用贝塞尔曲线差值出来。
代码:
/// <summary>
/// 设置中间点顶点数据
/// </summary>
/// <param name="currentPos"></param>
/// <param name="prevPos"></param>
/// <param name="nextPos"></param>
private void SetPrepareVertexData(Vector3 currentPos, Vector3 prevPos, Vector3 nextPos)
{
// 首先计算导线点相对场景中心点的偏移坐标
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); if (angle >= 179.0)
{
//生成断面数据不倒角处理
uvPosU += (GetPointDistance(position, prevPosition) / textureSizeL);
SetSectionVertexData(position, prevDirection, prevUp, prevRight, uvPosU);
return;
}
//倒角处理 //前后两段风筒长度
float PrevLength = Vector3.Distance(position, prevPosition);
float anithorLength = Vector3.Distance(position, anitherposition); indentationValue = PrevLength > anithorLength ? (anithorLength * 0.25f) : (PrevLength * 0.25f);//缩进为短风筒的1/4 // 计算缩进后的位置
Vector3 prevEnd = position - prevDirection * indentationValue;//左
Vector3 behindStart = position + anitherDirection * indentationValue;// 右 uvPosU += (GetPointDistance(prevPosition, prevEnd) / textureSizeL);
// 生成前段结束断面顶点数据
SetSectionVertexData(prevEnd, prevDirection, prevUp, prevRight, uvPosU); // 生成中间倒角断面顶点数据
//插值0-1
float timer = 0.1f;
Vector3 prevpos = prevEnd;
for (float i = 1.0f; i <= 9.0f; i++)
{
Vector3 pos = Utils.CalculateCubicBezierPoint(timer, prevEnd, position, behindStart);
// 计算断面方向
Vector3 direction = (pos - prevpos).normalized;
Vector3 right = Vector3.Cross(Vector3.up, direction).normalized;
Vector3 up = Vector3.Cross(direction, right).normalized;
uvPosU += (GetPointDistance(pos, prevpos) / textureSizeL);
// 生成断面顶点数据
SetSectionVertexData(pos, direction, up, right, uvPosU);
//递增插值时间
timer += 0.1f;
//更新前一个点坐标
prevpos = pos;
}
// 生成后段起始断面顶点数据
SetSectionVertexData(behindStart, anitherDirection, anitherUp, anitherlRight, ++uvPosU);
}
1到9插10个圆。需要找拐角处前后两个点分别为开始和结束插值点,Utils.CalculateCubicBezierPoint(timer, prevEnd, position, behindStart);timer为每次插值(可理解为时刻)。此函数(三阶贝塞尔曲线)具体逻辑:
/// <summary>
/// 计算三阶贝塞尔曲线点坐标
/// </summary>
/// <param name="t">时刻(0.0~1.0)</param>
/// <param name="p0">起点坐标</param>
/// <param name="p1">中间点坐标</param>
/// <param name="p2">终点坐标</param>
/// <returns>坐标点</returns>
public static Vector3 CalculateCubicBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2)
{
float u = 1.0f - t;
float tt = t * t;
float uu = u * u; return (p0 * uu + p1 * * u * t + p2 * tt);
}
这样准备顶点坐标就结束了。
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 = ;
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 > )
{
baseValue -= ;
}
else
{
baseValue += ;
}
}
}
这float v = ((baseValue * 36.0f) / 360.0f);算法需要按要求设置。
3.构建索引
构建索引就要按照定点顺序去构建,连接圆与圆之间的曲面,索引值也是累加的比如这里十个点,第一个圆是0-9/第二个10-19/以此类推。
/// <summary>
/// 设置索引数据
/// </summary>
private void SetIndexData()
{
int faceCount = (vertices.Count / devide) - ;
int baseValue;
for (int i = ; i < faceCount; i++)
{
baseValue = ;
for (int j = ; j <= ; j++)
{
if (j < )
{
triangles.Add(i * + baseValue);
triangles.Add(i * + + baseValue);
triangles.Add(i * + baseValue + ); triangles.Add(i * + baseValue);
triangles.Add(i * + baseValue + );
triangles.Add(i * + + baseValue);
}
else
{
triangles.Add(i * + baseValue);
triangles.Add(i * + );
triangles.Add(i * + baseValue + ); triangles.Add(i * + baseValue);
triangles.Add(i * );
triangles.Add(i * + );
}
baseValue++;
}
}
}
注意构建顺序不然顺逆时针容易反了。
4.构建实体
这里解释最后一步了,也是最简单的一步了,不解释上代码:
/// <summary>
/// 绘制风筒实体
/// </summary>
/// <param name="posList">风筒节点数据集合</param>
/// <param name="radius">风筒半径</param>
public void DrawAirDuct(List<Vector3> posList, float radius)
{
this.radius = radius;
PrepareVertexData(posList);
SetIndexData();
Mesh mesh = new Mesh
{
vertices = vertices.ToArray(),
triangles = triangles.ToArray(),
uv = uvs.ToArray(),
};
mesh.RecalculateNormals();
GameObject airDuctObj = new GameObject(name);
airDuctObj.AddComponent<MeshFilter>().mesh = mesh;
airDuctObj.AddComponent<MeshRenderer>().material = new Material(Resources.Load<Material>("Materials/Mine/LanewayColor")); // 添加碰撞器MeshCollider
airDuctObj.AddComponent<MeshCollider>();
}
切记别忘了重新计算法线mesh.RecalculateNormals();这才出预期效果。
完整代码:
using System;
using System.Collections.Generic;
using UnityEngine; namespace Tx3d.Framework
{
/// <summary>
/// 绘制风筒实体
/// </summary>
public class AirDuct : Entity
{
#region Fields //计算圆角的缩进值
private float indentationValue; //圆划分为多少等份
private int devide = ; //中心点坐标
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 radius; //贴图长度缩放尺寸
private float textureSizeL = 10.24f; //uv坐标的u坐标值
private float uvPosU = ; #endregion #region Public Methods /// <summary>
/// 风筒实体
/// </summary>
/// <param name="name">实体名</param>
public AirDuct(string name) : base(name)
{ } /// <summary>
/// 绘制风筒实体
/// </summary>
/// <param name="posList">风筒节点数据集合</param>
/// <param name="radius">风筒半径</param>
public void DrawAirDuct(List<Vector3> posList, float radius)
{
this.radius = radius;
PrepareVertexData(posList);
SetIndexData();
Mesh mesh = new Mesh
{
vertices = vertices.ToArray(),
triangles = triangles.ToArray(),
uv = uvs.ToArray(),
};
mesh.RecalculateNormals();
GameObject airDuctObj = new GameObject(name);
airDuctObj.AddComponent<MeshFilter>().mesh = mesh;
airDuctObj.AddComponent<MeshRenderer>().material = new Material(Resources.Load<Material>("Materials/Mine/LanewayColor")); // 添加碰撞器MeshCollider
airDuctObj.AddComponent<MeshCollider>();
} /// <summary>
/// 释放风筒实体
/// </summary>
public override void Dispose()
{ } /// <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)
{
var collider = gameObject.GetComponent<MeshCollider>();
return collider.Raycast(ray, out hit, maxDistance);
} /// <summary>
/// 体积查询 <see cref="Entity.VolumeQuery(PlaneBoundedVolume)"/>
/// </summary>
/// <param name="volume">查询体</param>
/// <returns>查询成功返回true,否则返回false</returns>
public override bool VolumeQuery(PlaneBoundedVolume volume)
{
throw new NotImplementedException();
} #endregion #region Private Methods /// <summary>
/// 准备顶点数据
/// </summary>
private void PrepareVertexData(List<Vector3> posList)
{
for (int i = ; i < posList.Count; i++)
{
//起点圆面
if (i == )
{
SetStartVertexData(posList[i], posList[i + ]);
}
else if (i != posList.Count - )
{
////中间点(求缩进点设置圆角)
SetPrepareVertexData(posList[i], posList[i - ], posList[i + ]);
}
else
{
//终点圆面
SetEndVertexData(posList[i], posList[i - ]);
}
}
} /// <summary>
/// 设置起点圆顶点数据
/// </summary>
/// <param name="startPos">起点坐标</param>
/// <param name="nextPos">下一点坐标</param>
private void SetStartVertexData(Vector3 startPos, Vector3 nextPos)
{
// 首先计算导线点相对场景中心点的偏移坐标
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 up = Vector3.Cross(direction, right).normalized;//x和z得到up
// 设起点圆定点数据
SetSectionVertexData(position, direction, up, right, uvPosU);
} /// <summary>
/// 设置终点圆顶点数据
/// </summary>
/// <param name="endPos">终点坐标</param>
/// <param name="previousPos">上一点坐标</param>
private void SetEndVertexData(Vector3 endPos, Vector3 previousPos)
{
// 首先计算导线点相对场景中心点的偏移坐标
Vector3 position = endPos - center;
Vector3 PreviousPosition = previousPos - center; // 计算direction、right、up向量(注:Unity3d中使用左手坐标系)
Vector3 direction = (position - PreviousPosition).normalized;//指向下一点(结束)方向向量
Vector3 right = Vector3.Cross(Vector3.up, direction).normalized;
Vector3 up = Vector3.Cross(direction, right).normalized;
//计算U值
uvPosU += (GetPointDistance(position, PreviousPosition) / textureSizeL);
SetSectionVertexData(position, direction, up, right, uvPosU);
} /// <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 = ;
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 > )
{
baseValue -= ;
}
else
{
baseValue += ;
}
}
} /// <summary>
/// 设置中间点顶点数据
/// </summary>
/// <param name="currentPos"></param>
/// <param name="prevPos"></param>
/// <param name="nextPos"></param>
private void SetPrepareVertexData(Vector3 currentPos, Vector3 prevPos, Vector3 nextPos)
{
// 首先计算导线点相对场景中心点的偏移坐标
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); if (angle >= 179.0)
{
//生成断面数据不倒角处理
uvPosU += (GetPointDistance(position, prevPosition) / textureSizeL);
SetSectionVertexData(position, prevDirection, prevUp, prevRight, uvPosU);
return;
}
//倒角处理 //前后两段风筒长度
float PrevLength = Vector3.Distance(position, prevPosition);
float anithorLength = Vector3.Distance(position, anitherposition); indentationValue = PrevLength > anithorLength ? (anithorLength * 0.25f) : (PrevLength * 0.25f);//缩进为短风筒的1/4 // 计算缩进后的位置
Vector3 prevEnd = position - prevDirection * indentationValue;//左
Vector3 behindStart = position + anitherDirection * indentationValue;// 右 uvPosU += (GetPointDistance(prevPosition, prevEnd) / textureSizeL);
// 生成前段结束断面顶点数据
SetSectionVertexData(prevEnd, prevDirection, prevUp, prevRight, uvPosU); // 生成中间倒角断面顶点数据
//插值0-1
float timer = 0.1f;
Vector3 prevpos = prevEnd;
for (float i = 1.0f; i <= 9.0f; i++)
{
Vector3 pos = Utils.CalculateCubicBezierPoint(timer, prevEnd, position, behindStart);
// 计算断面方向
Vector3 direction = (pos - prevpos).normalized;
Vector3 right = Vector3.Cross(Vector3.up, direction).normalized;
Vector3 up = Vector3.Cross(direction, right).normalized;
uvPosU += (GetPointDistance(pos, prevpos) / textureSizeL);
// 生成断面顶点数据
SetSectionVertexData(pos, direction, up, right, uvPosU);
//递增插值时间
timer += 0.1f;
//更新前一个点坐标
prevpos = pos;
}
// 生成后段起始断面顶点数据
SetSectionVertexData(behindStart, anitherDirection, anitherUp, anitherlRight, ++uvPosU);
} /// <summary>
/// 设置索引数据
/// </summary>
private void SetIndexData()
{
int faceCount = (vertices.Count / devide) - ;
int baseValue;
for (int i = ; i < faceCount; i++)
{
baseValue = ;
for (int j = ; j <= ; j++)
{
if (j < )
{
triangles.Add(i * + baseValue);
triangles.Add(i * + + baseValue);
triangles.Add(i * + baseValue + ); triangles.Add(i * + baseValue);
triangles.Add(i * + baseValue + );
triangles.Add(i * + + baseValue);
}
else
{
triangles.Add(i * + baseValue);
triangles.Add(i * + );
triangles.Add(i * + baseValue + ); triangles.Add(i * + baseValue);
triangles.Add(i * );
triangles.Add(i * + );
}
baseValue++;
}
}
} /// <summary>
/// 求两点距离
/// </summary>
/// <param name="prevPos">前一点坐标</param>
/// <param name="nextPos">后一点坐标</param>
/// <returns></returns>
private float GetPointDistance(Vector3 prevPos, Vector3 nextPos)
{
return Vector3.Distance(prevPos, nextPos);
} #endregion
}
}
这里定了五个测试点,
效果如下:
到这基本结束了,但是这期间计算思考过程还是还是有点东西的,这玩意也是会的不难难的不会,做过一次了就好办了,这里给没搞过的小朋友启发一下。
渴望指正交流。
最新:
最近在扭曲问题上羁绊住了,记录下,原因是在创建本地正交基时有特殊情况没考虑进去,就是方向与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 ? : );
}
}
} /// <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 = ; //风筒Mesh过滤器
private MeshFilter airDuctMeshFilter; //风筒Mesh渲染器
private MeshRenderer airDuctMeshRenderer; //风筒MeshCollider
private MeshCollider airDuctMeshCollider; //本地缓存巷道信息
private List<string> lanewaysList; //本地缓存巷道序列信息
private List<BuildOrder> buildOrdersList; //基本纵向偏移量(底面到顶板)
private float baseHeight = ; //贴图渐变集合数据
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 = ; i < airductPosList.Count - ; i++)
{
var start = Utils.WorldToScreenPointProjected(CameraControllerManager.Instance.MainCamera, airductPosList[i]);
var end = Utils.WorldToScreenPointProjected(CameraControllerManager.Instance.MainCamera, airductPosList[i + ]); // 判断起点或终点是否落在矩形框范围内
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 = -;
endIndex = -; // 标识是否投影到巷道上
bool isProjectedTo = false; // 最小投影距离
var minProjectionDistance = float.PositiveInfinity; for (int i = ; i < askPosList.Count - ; i++)
{ var startPosition = askPosList[i];
var endPosition = askPosList[i + ]; // 计算风筒长度
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 + ;
}
}
}
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 < || 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 < || index >= lanewaysList.Count)
{
return string.Empty;
}
return lanewaysList[index];
} /// <summary>
/// (初始)获取风筒对应的巷道走向
/// </summary>
/// <param name="index">索引</param>
/// <returns>构建顺序</returns>
public BuildOrder GetAirDuctBuildOrder(int index)
{
if (index < || 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 = ; i < airductPosList.Count; i++)
{
if (i != || i != airductPosList.Count - )
{
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(), );
mesh.SetUVs(, uvs);
mesh.SetUVs(, 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 ? : );
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 = ; i < posList.Count; i++)
{
//起点圆面
if (i == )
{
SetStartVertexData(posList[i], posList[i + ], posList[i + ], GetCurrPosRatioValue(i));
}
else if (i != posList.Count - )
{
//中间点(求缩进点设置圆角)
if (i == || i == posList.Count - )
{
SetPrepareVertexData(posList[i], posList[i - ], posList[i + ], true, GetCurrPosRatioValue(i));
}
else
{
SetPrepareVertexData(posList[i], posList[i - ], posList[i + ], false, GetCurrPosRatioValue(i));
}
}
else
{
//终点圆面
SetEndVertexData(posList[i], posList[i - ], posList[i - ], GetCurrPosRatioValue(i));
}
}
} /// <summary>
/// 更新风筒总长度
/// </summary>
/// <param name="airductPosList">风筒节点坐标</param>
private void UpdateAirDuctLength(List<Vector3> airductPosList)
{
saveRatioList.Clear();
float length = ;
saveRatioList.Add();
for (int i = ; i < airductPosList.Count; i++)
{
if (i != airductPosList.Count - )
{
length += GetPointDistance(airductPosList[i], airductPosList[i + ]);
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 = ;
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 >= )
{
baseValue -= ;
}
else
{
baseValue += ;
}
}
} /// <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 <= )
{
//生成断面数据不倒角处理
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 = * 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 / ) - ; for (int i = ; i < length; i++)
{
for (int j = ; j < ; j++)
{
if (j < )
{
triangles.Add(i * + (j + ));
triangles.Add((i + ) * + j);
triangles.Add(i * + j); triangles.Add((i + ) * + (j + ));
triangles.Add((i + ) * + j);
triangles.Add(i * + (j + ));
}
else
{
triangles.Add(i * );
triangles.Add((i + ) * + j);
triangles.Add(i * + j); triangles.Add((i + ) * );
triangles.Add((i + ) * + j);
triangles.Add(i * );
}
}
} } /// <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 = ; i < lanewaysList.Count; i++)
{
int length = ((Laneway)EntityManager.Instance.GetEntity(lanewaysList[i])).GetLeadNodeCount();
switch (buildOrdersList[i])
{
case BuildOrder.Positive:
for (int j = ; j < length; j++)
{
if (i != lanewaysList.Count - )
{
if (j != length - )
{
tempNodeList.Add(GetLanewayNodePos(lanewaysList[i], j));
}
}
else
{
tempNodeList.Add(GetLanewayNodePos(lanewaysList[i], j));
}
}
break;
case BuildOrder.Reverse:
for (int j = length - ; j >= ; j--)
{
if (i != lanewaysList.Count - )
{
if (j != )
{
tempNodeList.Add(GetLanewayNodePos(lanewaysList[i], j));
}
}
else
{
tempNodeList.Add(GetLanewayNodePos(lanewaysList[i], j));
}
}
break;
default:
break;
}
} //设置风筒具体坐标
for (int i = ; i < tempNodeList.Count; i++)
{
if (i == )//起点
{
posList.Add(CalculateStartEndPos(tempNodeList[i], tempNodeList[i + ], true));
}
else if (i == (tempNodeList.Count - ))//终点
{
posList.Add(CalculateStartEndPos(tempNodeList[i], tempNodeList[i - ], false));
}
else//中间点
{
posList.Add(CalculatePos(tempNodeList[i], tempNodeList[i - ]));
}
}
//插入开始结束两端的水平端点
Vector3 direction = (tempNodeList[].RelativePosition - tempNodeList[].RelativePosition).normalized;
Vector3 right = Vector3.Cross(Vector3.up, direction).normalized;
Vector3 up = Vector3.Cross(direction, right).normalized;
posList.Insert(, tempNodeList[].RelativePosition + up * (baseHeight + VerticalOffset)); Vector3 direction1 = (tempNodeList[tempNodeList.Count - ].RelativePosition - tempNodeList[tempNodeList.Count - ].RelativePosition).normalized;
Vector3 right1 = Vector3.Cross(Vector3.up, direction1).normalized;
Vector3 up1 = Vector3.Cross(direction1, right1).normalized;
posList.Insert(posList.Count - , tempNodeList[tempNodeList.Count - ].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), ) + Mathf.Pow((0.50f * width), ));
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 = ; 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 = ; i < list.Count; i++)
{
targetPos += list[i];
}
Vector3 centerPos = (targetPos / (list.Count));
for (int i = ; i < list.Count; i++)
{
posList.Add((list[i] - centerPos));
}
return posList;
} #endregion #endregion }
}
Unity中动态绘制圆柱体的更多相关文章
- Unity中动态创建Mesh
什么是Mesh? Mesh是指的模型的网格,3D模型是由多边形拼接而成,而多边形实际上又是由多个三角形拼接而成的.即一个3D模型的表面其实是由多个彼此相连的三角面构成.三维空间中,构成这些三角形的点和 ...
- Unity 中动态修改碰撞框(位置,大小,方向)
在Unity中,玩家处于不同的状态,要求的碰撞框的 位置/大小/方向 会有所改变,怎么动态的修改碰撞框呢? 下面是Capsure Collider(胶囊体)的修改: CapsuleCollider.d ...
- Unity中 动态加载 Resources.Load()和Asset Bundle 的区别
版权声明:本文为博主原创文章,未经博主允许不得转载. 初学Unity的过程中,会发现打包发布程序后,unity会自动将场景需要引用到的资源打包到安装包里,没有到的不会跟进去.我们在编辑器里看到的Ass ...
- Unity中OnGUI绘制贪吃蛇
Square.cs : public class Square : MonoBehaviour { public int row, col; public Rect rect; public Text ...
- unity中动态生成网格
以下是绘制正方形面片的一个例子,方便之后查阅: 效果如图所示: 红轴为x方向,蓝轴为z方向. 代码如下: using System.Collections; using System.Collecti ...
- 关于Unity中如何代码动态修改天空盒
在Unity中动态修改天空盒有两种方法: 一.为每个Texture建立天空盒材质球,需要更换时直接将对应材质球作为天空盒,缺点是建立的材质球太多 private void ChangeSkybox(M ...
- 在Unity中使用UGUI修改Mesh绘制几何图形
在商店看到这样一个例子,表示很有兴趣,他们说是用UGUI做的.我想,像这种可以随便变形的图形,我第一个就想到了网格变形. 做法1: 细心的朋友应该会发现,每个UGUI可见元素,都有一个‘Canvas ...
- Unity NGUI中动态添加和删除sprite
(以后,参考链接和作者将在文章首部给出,转载请保留此部分内容) 参考链接:http://www.narkii.com/club/thread-299977-1.html,作者:纳金网 比巴卜: 参考链 ...
- 【Unity】Unity中资源动态载入的两种方式之AssetsBundle
首先要说的是,我们的project中有2个脚本.各自是: Build(编辑器类脚本.无需挂载到不论什么物体).可是必需要把Build脚本放到Editor目录中 Load脚本,挂载到摄像机上<pr ...
随机推荐
- 3.Jmeter 快速入门教程(三-1) --添加响应断言(即loadrunner中所指的检查点)
上一节课,我们创建了一个测试场景,并进行了少量vuser的负载测试. 有时候我们执行了测试,但是发现并不是所有事务都执行成功了. 那是因为我们只是发起了测试,但并没有对每次请求测试的返回作校验. 所以 ...
- MockServer
基于Flask实现的一个简易Mock平台,使用标准json结构体编写Mock Api https://github.com/yinquanwang/MockServer Key Features ...
- ASP.NET MVC 学习笔记之面向切面编程与过滤器
AOP(面向切面)是一种架构思想,用于把公共的逻辑放到一个单独的地方,这样就不用每个地方都写重复的代码了.比如程序中发生异常,不用每个地方都try…catch 只要在Golbal的Applicatio ...
- 【转载】vue install报错run `npm audit fix` to fix them, or `npm audit` for details html
原链接https://www.jianshu.com/p/60591cfc6952 执行npm install 出现如下提醒 added 253 packages from 162 contribut ...
- 2018-5-28-WPF-popup置顶
title author date CreateTime categories WPF popup置顶 lindexi 2018-05-28 09:58:53 +0800 2018-2-13 17:2 ...
- HDU-4747 二分+线段树
题意:给出长度为n的序列,问任两个区间的mex运算结果的总和. 解法:直接讲线段树做法:我们注意到mex(1,1),mex(1,2),mex(1,3)...mex(1,i)的结果是单调不减的,那么我们 ...
- javascript与jquery删除元素节点
今天工作的时候遇到一个删除的问题,研究了下发现是没有很好的区分js和jquery的删除方法,在此澄清一下 工作的代码如下 // 删除图片 $("#js_takePhotoWrap" ...
- Linux编译C语言程序
1.首先安装gcc包,运行C++程序,安装gcc-c++ 包 如果没有安装的自行进行安装 2.编辑C语言程序, 打印乘法口诀表 [root@Db1 c]# vim chengfa.c 在编辑界面中,输 ...
- ElasticSearch---初识
1.概述 1.1 ElasticSearch是一个 基于Lucene 的 搜索服务器: 1.2 ElasticSearch 提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful ...
- Yii2 使用十一 在设置enablePrettyUrl时候,defaultAction的设置方法
启用美化Url的功能 'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, 'enableS ...