可弯曲软管的动态生成
最近在做一个化学VR实验室项目,其中要求用一个橡胶软管连接两个导管,两个导管可以任意移动旋转,而连接它们的橡胶软管需要跟随做弯曲和拉伸
美术给了一个直的软管模型
如何实现软管的弯曲和拉伸呢,想起了仰慕已久的Mega-Fiers插件
研究使用了半天,Mega-Fiers插件确实能弯曲和拉伸软管模型,但是
1.Mega-Fiers插件只对模型顶点做位置变化,不会增加和删除模型顶点,所以在对软管局部做角度偏大的弯曲时,会有明显的折痕(这个软管模型有2k+的顶点,增加模型横向顶点数折痕会有缓解)
2.Mega-Fiers插件很难对同一模型用多个形变组件组合出想要的效果,我试了很久没试出怎么组合出软管两头弯曲加中段拉伸的效果,更不要说在程序运行时动态组合效果了
3.Mega-Fiers插件无法满足一些精确的控制要求,比如无法让弯曲后的软管口和导管口无缝衔接
对静态模型做形变不能达到要求,于是想是否能运行时动态生成软管模型,研究了一下,发现确实可行,设计思路如下
1.如下图,以A点为一个导管口端点,DirA方向为导管延伸方向,在DirA方向上取一个合适的距离A-CA,B点为另一个导管口端点取同样的距离B-CB,以CA->CB方向取同样的距离CA-EA,以CB->CA为方向取同样距离CB-EB,CAB为CA-CB中心点
2.以A和EA点为一段贝塞尔曲线的顶点,CA为其控制点,获取到一串贝塞尔曲线点,同样以B和EB点为顶点,CB为控制点获取一串点,以EA,EB为顶点,CAB为控制点获取一串点
3.在以当前曲线点到下一个曲线点的方向为法线,穿过当前曲线点的平面上,以当前曲线点为圆心获取一圈圆的顶点
4.以获取到的所有圆的顶点为软管的模型顶点,再设置三角面和法线信息,最终生成软管模型的网格
中间一段是直线,可以只生成三个曲线点就可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | using UnityEngine; using System.Collections.Generic; public struct BezierLineSegment { public Vector3 fromPoint; public Vector3 toPoint; public Vector3 controlPoint; public Quaternion fromDir; public Quaternion toDir; public void CalculateDir() { fromDir = Quaternion.FromToRotation(Vector3.forward, controlPoint - fromPoint); toDir = Quaternion.FromToRotation(Vector3.forward, toPoint - controlPoint); } public bool IsStraight() { return fromDir == toDir; } } [ExecuteInEditMode] public class BezierPipe : MonoBehaviour { public float cornerScale = 1f; [Range(1,100)] public int cornerStep = 10; [Range(2,100)] public int circleStep = 10; public float r = 0.1f; public Transform point1; public Vector3 point1Dir = Vector3.up; public Transform point2; public Vector3 point2Dir = Vector3.up; public bool update = false ; public Mesh mesh; List<Vector3> verts = new List<Vector3>(); List< int > triangles = new List< int >(); private MeshCollider mc; // Use this for initialization void Start () { mesh = new Mesh(); mesh.name = "Pipe" ; MeshFilter mf = GetComponent<MeshFilter>(); if (mf != null ) { mf.sharedMesh = mesh; } mc = GetComponent<MeshCollider>(); if (mc != null ) { mc.sharedMesh = mesh; } BuildMesh(); } // Update is called once per frame void Update() { if (update) { BuildMesh(); } } void GetCirclePoint(Vector3 pos, Quaternion dir, bool draw) { for ( int a = 0; a <= circleStep; a++) { float p = 2 * Mathf.PI * a / circleStep; Vector3 cp = new Vector3(r * Mathf.Cos(p), r * Mathf.Sin(p), 0); cp = dir* cp + pos; //if(draw) // Gizmos.DrawSphere(cp, 0.0005f); cp = transform.worldToLocalMatrix.MultiplyPoint(cp); //cp += transform.position; verts.Add(cp); } } void SetTriangles() { triangles.Clear(); for ( int i = 0; i < verts.Count - circleStep - 2; i ++) { triangles.Add(i); triangles.Add(i+1); triangles.Add(i+circleStep + 1); triangles.Add(i+circleStep + 1); triangles.Add(i+1); triangles.Add(i+circleStep + 2); } mesh.triangles = triangles.ToArray(); } public void BuildMesh() { if (point1 != null && point2 != null ) { verts.Clear(); float scale = cornerScale; float length = (point1.position - point2.position).magnitude/4; if (scale > length) { scale = length; } BezierLineSegment[] segments = new BezierLineSegment[3]; segments[0].fromPoint = point1.position; point1Dir.Normalize(); segments[0].controlPoint = point1.position + point1.rotation * point1Dir * scale; segments[2].toPoint = point2.position; point2Dir.Normalize(); segments[2].controlPoint = point2.position + point2.rotation * point2Dir * scale; segments[1].controlPoint = (segments[0].controlPoint + segments[2].controlPoint) / 2; segments[0].toPoint = segments[1].fromPoint = segments[0].controlPoint + (segments[1].controlPoint - segments[0].controlPoint).normalized * scale; segments[1].toPoint = segments[2].fromPoint = segments[2].controlPoint + (segments[1].controlPoint - segments[2].controlPoint).normalized * scale; transform.position = segments[1].controlPoint; segments[1].CalculateDir(); transform.rotation = segments[1].fromDir; // Debug.Log (transform.eulerAngles); foreach ( var segment in segments) { segment.CalculateDir(); if (segment.IsStraight()) { GetCirclePoint(segment.fromPoint, segment.fromDir, true ); GetCirclePoint(segment.controlPoint, segment.fromDir, false ); GetCirclePoint(segment.toPoint, segment.toDir, true ); //Gizmos.DrawLine(segment.fromPoint, segment.controlPoint); //Gizmos.DrawLine(segment.controlPoint, segment.toPoint); } else { GetCirclePoint(segment.fromPoint, segment.fromDir, true ); Vector3 p1 = segment.fromPoint; for ( int s = 1; s < cornerStep; s++) { float t = ( float ) s/cornerStep; Vector3 p2 = Bezier.GetPoint(segment.fromPoint, segment.controlPoint, segment.toPoint, t); //Quaternion dir = Quaternion.FromToRotation(Vector3.forward, p2 - p1); Quaternion dir = Quaternion.Lerp(segment.fromDir, segment.toDir, t); GetCirclePoint(p2,dir, false ); //Gizmos.DrawLine(p1, p2); p1 = p2; } GetCirclePoint(segment.toPoint, segment.toDir, true ); //Gizmos.DrawLine(p1,segment.toPoint); } } mesh.Clear(); mesh.vertices = verts.ToArray(); SetTriangles(); mesh.RecalculateNormals(); if (mc != null ) { mc.sharedMesh = mesh; } } } } |
CornerScale即A-CA,CA-EA,B-CB,CB-EB的长度,越长软管曲线部分越长,直线部分越短,运行时CornerScale将不大于两个端点间距离的四分之一
CornerStep即A-EA间贝塞尔曲线要取的曲线点数,越高软管弯曲部分越顺滑
CircleStep即绕曲线点圆的顶点数,越高软管横截面越圆
R即绕曲线点圆的半径,即软管的粗度
两个Point即两个导管的头,两个Dir即两个导管头在未旋转时的延伸方向
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!