unity 线绳管道纯代码创建方法

本文详细介绍了如何在Unity中利用代码生成具有内切圆处理的3D管道模型。通过在路径点放置横截面并处理拐点,使用内切圆避免了拐点处截面的压缩变形,确保了截面的正确朝向。主要涉及路径线转换、网格点创建和网格生成等关键步骤,适合于Unity开发者进行3D场景构建和管线模拟。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

unity 线绳管道纯代码创建方法

实现效果

原理思路

管线是由多边形横截面和路径线组成,只要在路径点上放置横截面就可以得到管线的网格点,通过网格点就可以生成管线模型。

以上的思路在生成只有两个节点的路径时,是没有问题,但是在生成有拐点的路径时,就会出现拐点的截面朝向问题,不管横截面朝向前一点或者后一点都会压缩变形。所以针对拐点必须分段处理,这里引用了内切圆的方法,在前后两个线段直接加入内切圆,将原先的拐点替换为两个切点和切圆上的三点,从而形成的新路径。

如上图,原始路径为a→b→c;取内切圆后,路径改为a→e→f→g→h→i→c;这样截面的朝向刚好为内切圆的切线方向。

核心代码

截面圆的建立

这里可以设置段数和半径;

        #region 横切圆创建
        /// <summary>
        /// 得到管线横切圆
        /// </summary>
        /// <param name="Count">段数</param>
        /// <param name="R">半径</param>
        /// <returns></returns>
        Vector3[] CircularSection(int Count, float R)
        {
            Vector3[] vector3s = new Vector3[Count];
            float angle = 360 / Count;
            Vector3 vector3 = new Vector3(R, 0, 0);
            for (int i = 0; i < Count; i++)
            {
                //根据角度得到圆的分布点
                vector3s[i] = vector3.ToAngle(angle * i, Vector3.zero, Vector3.forward);
            }
            return vector3s;
        }
        #endregion

路径线的转换

得到的路径线是没有做内切处理的,这里将路径拐点转换为内切圆;

#region 得到3d线的中心路径
        /// <summary>
        /// 设置中心路径
        /// </summary>
        /// <param name="createPoint">路径点</param>
        /// <param name="createPoint">拐点半径</param>
        /// <returns></returns>
        List<PipePoint> SetPipePoint(Vector3[] createPoint,float  elbowR)
        {
            List<PipePoint> pipePoints = new List<PipePoint>();
            int length = createPoint.Length;
            for (int i = 0; i < length; i++)
            {
                if (i == 0)
                {
                    AddPipePoints(createPoint[i], createPoint[i + 1] - createPoint[i], ref pipePoints);
                }
                else if (i == length - 1)
                {
                    AddPipePoints(createPoint[i], createPoint[i] - createPoint[i - 1], ref pipePoints);
                }
                else
                {
                    GetElbowPoint(createPoint[i], createPoint[i - 1], createPoint[i + 1], elbowR, ref pipePoints);
                }
            }
            return pipePoints;
        }
        /// <summary>
        /// 增加管线点
        /// </summary>
        /// <param name="location"></param>
        /// <param name="direction"></param>
        void AddPipePoints(Vector3 location, Vector3 direction, ref List<PipePoint> pipePoints)
        {
            PipePoint pipePoint = new PipePoint();
            pipePoint.Location = location;
            pipePoint.Direction = direction;
            pipePoints.Add(pipePoint);
        }
        /// <summary>
        /// 得到内切点
        /// </summary>
        /// <param name="focus"></param>
        /// <param name="front"></param>
        /// <param name="back"></param>
        /// <param name="r">内切圆半径</param>
        void GetElbowPoint(Vector3 focus, Vector3 front, Vector3 back, float r, ref List<PipePoint> pipePoints)
        {
            //拐点前后向量
            Vector3 frontVec = focus - front;
            Vector3 backVec = back - focus;
            //得到前后切点
            Vector3 tangencyFront = ((frontVec.magnitude - r) * frontVec.normalized) + front;
            Vector3 tangencyBack = (r * backVec.normalized) + focus;
            //得到内切圆圆心
            Vector3 circulPoint = GetCirculPoint(focus, tangencyFront, tangencyBack);
            //得到拐点分段
            Vector3[] circulSection = GetCirculSection(focus, tangencyFront, tangencyBack, circulPoint);
            //得到两个切点向量的法线
            Vector3 normal = Vector3.Cross(frontVec, backVec).normalized;
            //增加管线点
            AddPipePoints(tangencyFront, GetDirection(tangencyFront, circulPoint, normal), ref pipePoints);
            int length = circulSection.Length;
            for (int i = 0; i < length; i++)
            {
                AddPipePoints(circulSection[i], GetDirection(circulSection[i], circulPoint, normal), ref pipePoints);
            }
            AddPipePoints(tangencyBack, GetDirection(tangencyBack, circulPoint, normal), ref pipePoints);
        }
        /// <summary>
        /// 得到切点在内切圆上的切线方向
        /// </summary>
        /// <param name="self"></param>
        /// <param name="circulPoint"></param>
        /// <param name="normal"></param>
        /// <returns></returns>
        Vector3 GetDirection(Vector3 self, Vector3 circulPoint, Vector3 normal)
        {
            Vector3 vector = circulPoint - self;
            return Vector3.Cross(vector, normal).normalized;
        }
        /// <summary>
        /// 得到管线拐点内切圆分段点
        /// </summary>
        /// <param name="focus">交点</param>
        /// <param name="tangency1">切点1</param>
        /// <param name="tangency2">切点2</param>
        /// <param name="circulPoint">切圆圆心</param>
        /// <returns></returns>
        Vector3[] GetCirculSection(Vector3 focus, Vector3 tangency1, Vector3 tangency2, Vector3 circulPoint)
        {
            Vector3 vector0 = tangency1 - circulPoint;
            Vector3 vector4 = tangency2 - circulPoint;
            float dis = vector0.magnitude;
            Vector3 vector2 = (vector0 + vector4).normalized * dis;
            Vector3 vector1 = (vector0 + vector2).normalized * dis;
            Vector3 vector3 = (vector4 + vector2).normalized * dis;
            Vector3[] vector3s = new Vector3[3];
            vector3s[0] = vector1 + circulPoint;
            vector3s[1] = vector2 + circulPoint;
            vector3s[2] = vector3 + circulPoint;
            return vector3s;
        }
        /// <summary>
        /// 得到管线拐点内切圆圆心
        /// </summary>
        /// <param name="focus">拐点</param>
        /// <param name="tangency1">切点1</param>
        /// <param name="tangency2">切点2</param>
        /// <returns></returns>
        Vector3 GetCirculPoint(Vector3 focus, Vector3 tangency1, Vector3 tangency2)
        {
            Vector3 vector1 = tangency1 - focus;
            Vector3 vector2 = tangency2 - focus;
            Vector3 vector0 = (vector1 + vector2).normalized;
            float angle = Vector3.Angle(vector1, vector0);
            float dis = vector1.magnitude / Mathf.Cos(angle * Mathf.Deg2Rad);
            return (vector0 * dis) + focus;
        }
        #endregion

创建网格点

将横截面和路径点结合,创建网格点

      #region 创建网格点
        Vector3[] CreateMeshPoint(List<PipePoint> pipePoint, Vector3[] circular)
        {
            int length = pipePoint.Count;
            int circularCount = circular.Length;
            Vector3[] meshPoint = new Vector3[length * circularCount];
            for (int i = 0; i < length; i++)
            {
                for (int j = 0; j < circularCount; j++)
                {
                    meshPoint[(i * circularCount) + j] = circular[j].FromToMoveRotation(pipePoint[i].Location, pipePoint[i].Direction);
                }
            }
            return meshPoint;
        }
        #endregion

网格创建

    #region 网格创建
        Mesh CreatMesh(List<PipePoint> pipePoints, Vector3[] meshPoint, Vector3[] circular)
        {
            Mesh mesh = new Mesh();
            int circularCount = circular.Length;
            mesh.vertices = meshPoint;
            mesh.triangles = GetTriangles(pipePoints, circularCount);
            mesh.uv = GetUV(pipePoints, circular);
            mesh.RecalculateNormals();
            mesh.RecalculateBounds();
            return mesh;
        }
        Vector2[] GetUV(List<PipePoint> pipePoints, Vector3[] circular)
        {
            int length = pipePoints.Count;
            int circularCount = circular.Length;
            Vector2[] uvs = new Vector2[(circularCount * length)];
            float lineDis = 0;
            float circularDis = Vector3.Distance(circular[0], circular[1]);
            int k = 0;
            for (int i = 0; i < length; i++)
            {
                if (i != 0)
                {
                    lineDis += Vector3.Distance(pipePoints[i].Location, pipePoints[i - 1].Location);
                }
                for (int j = 0; j < circularCount; j++)
                {
                    Vector2 vector2;
                    if (j%2!=0)
                    {
                         vector2 = new Vector2(circularDis, lineDis);
                    }
                    else
                    {
                         vector2 = new Vector2(0, lineDis);
                    }
                  
                    uvs[k] = vector2;
                    k+=1;
                }
            }
            return uvs;
        }
        int[] GetTriangles(List<PipePoint> pipePoints, int Count)
        {
            int length = pipePoints.Count;
            int[] triangles = new int[(Count * (length - 1)) * 6];
            int k = 0;
            for (int i = 0; i < length - 1; i++)
            {
                for (int j = 0; j < Count; j++)
                {
                    if (j == Count - 1)
                    {
                        triangles[k] = (i * Count) + j;
                        triangles[k + 1] = (i * Count) + 0;
                        triangles[k + 2] = ((i + 1) * Count) + 0;
                        triangles[k + 3] = (i * Count) + j;
                        triangles[k + 4] = ((i + 1) * Count) + 0;
                        triangles[k + 5] = ((i + 1) * Count) + j;
                    }
                    else
                    {
                        triangles[k] = (i * Count) + j;
                        triangles[k + 1] = (i * Count) + j + 1;
                        triangles[k + 2] = ((i + 1) * Count) + j + 1;
                        triangles[k + 3] = (i * Count) + j;
                        triangles[k + 4] = ((i + 1) * Count) + j + 1;
                        triangles[k + 5] = ((i + 1) * Count) + j;
                    }
                    k += 6;
                }
            }
            return triangles;
        }
        #endregion

最后创建线

       public GameObject CreateLine(Vector3[] createPoint, Material material, int circularCount, float circularR, float elbowR)
        {
            GameObject game = new GameObject();
            MeshFilter filter = game.AddComponent<MeshFilter>();
            Vector3[] circul = CircularSection(circularCount, circularR);
            List<PipePoint> pipePoints = SetPipePoint(createPoint, elbowR);
            Vector3[] meshPoint = CreateMeshPoint(pipePoints, circul);
            filter.mesh = CreatMesh(pipePoints, meshPoint, circul);
            MeshRenderer renderer = game.AddComponent<MeshRenderer>();
            renderer.sharedMaterial = material;
            return game;
        }

Demo下载地址

### 关于 `AddPipePoints` 方法Unity 中,如果要实现线绳管道创建并结合贝塞尔曲线的功能,通常会涉及多个自定义类和方法来管理路径点(即 PipePoint 列表)。虽然未提供具体的 `AddPipePoints` 定义,但从上下文中可以推测其功能可能是向某个列表中添加新的路径点对象。 以下是基于引用内容以及常见实践的一个可能实现方式: #### 可能的 `AddPipePoints` 方法定义 此方法的作用是将一组路径点加入到现有的路径集合中。假设我们有一个名为 `PipeManager` 的类用于管理这些路径点,则该方法可如下实现: ```csharp public class PipeManager { public List<PipePoint> pipePoints = new List<PipePoint>(); /// <summary> /// 向路径点列表中添加新点。 /// </summary> /// <param name="location">位置</param> /// <param name="direction">方向</param> public void AddPipePoints(Vector3 location, Vector3 direction) { PipePoint newPoint = new PipePoint(); newPoint.Location = location; newPoint.Direction = direction; // 添加至列表 pipePoints.Add(newPoint); } } // 假设 PipePoint 是一个简单的数据结构 [System.Serializable] public class PipePoint { public Vector3 Location; // 路径点的位置 public Vector3 Direction; // 路径的方向矢量 } ``` 上述代码展示了如何通过 `AddPipePoints` 将一个新的路径点添加到 `pipePoints` 列表中[^1]。这一步骤对于后续调用 `CreateMeshPoint` 来构建网格至关重要[^2]。 --- #### 结合贝塞尔曲线动态生成路径点 为了更灵活地控制路径形状,可以通过贝塞尔曲线计算中间插值点,并将其作为输入传递给 `AddPipePoints` 方法。例如: ```csharp private void GenerateBezierPath() { List<Vector3> bezierPoints = CalculateBezierCurve(controlPoints); foreach (Vector3 point in bezierPoints) { // 计算每个点的方向(可以根据前后两点差值得到) Vector3 direction = GetDirectionForPoint(point, controlPoints); // 添加路径点 manager.AddPipePoints(point, direction); } } /// <summary> /// 根据控制点计算贝塞尔曲线上的点集。 /// </summary> List<Vector3> CalculateBezierCurve(List<Vector3> controlPoints) { List<Vector3> result = new List<Vector3>(); float tStep = 0.05f; for (float t = 0; t <= 1; t += tStep) { Vector3 interpolatedPoint = BezierInterpolation(t, controlPoints); result.Add(interpolatedPoint); } return result; } /// <summary> /// 获取某一点对应的方向。 /// </summary> Vector3 GetDirectionForPoint(Vector3 currentPoint, List<Vector3> points) { int index = points.IndexOf(currentPoint); if (index > 0 && index < points.Count - 1) { return (points[index + 1] - points[index - 1]).normalized; } else { return Vector3.forward; // 默认方向 } } /// <summary> /// 贝塞尔插值函数。 /// </summary> Vector3 BezierInterpolation(float t, List<Vector3> controlPoints) { int n = controlPoints.Count - 1; Vector3 result = Vector3.zero; for (int i = 0; i <= n; i++) { float coefficient = BinomialCoefficient(n, i) * Mathf.Pow(1 - t, n - i) * Mathf.Pow(t, i); result += coefficient * controlPoints[i]; } return result; } /// <summary> /// 组合计数器系数。 /// </summary> float BinomialCoefficient(int n, int k) { return Factorial(n) / (Factorial(k) * Factorial(n - k)); } /// <summary> /// 阶乘辅助函数。 /// </summary> float Factorial(int value) { float result = 1; for (int i = 1; i <= value; i++) { result *= i; } return result; } ``` 以上代码片段实现了从控制点生成平滑贝塞尔曲线的过程,并利用 `AddPipePoints` 动态扩展路径点列表。 --- ### 总结 - `AddPipePoints` 方法的核心作用在于维护路径点的数据结构,便于后续渲染逻辑处理。 - 如果需要支持复杂的路径形态,建议引入贝塞尔曲线算法以增强灵活性。 - 上述代码仅为一种可能实现方案;具体细节需根据实际项目需求调整。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小生云木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值