unity模型任意无限切割插件
概述
3d模型的任意切割一直是游戏开发里的一个很大的问题,模型切割的关键点就只有生成横切面的新顶点以及切口纹理的缝合,理论上解决了这两点,就近乎可以做到以假乱真的程度了。本篇文章就这两点进行描述
详细
一、准备工作
解压缩后得到ClipDemo.unitypackage文件,将此文件导入unity5.45中,双击main场景,运行即可。运行后可以看到一个球体,对着球体拖动鼠标做切割动作,可以看到Hierarchy面板生成多个new Model,即为切割生成的模型,可以在Scene中拖动这些物体,可以看到是切割后的物体。压缩包内容如下:
二、程序实现
如上图所示,当切割模型时,对于切面上的三角面,无非是如图中3种情况(正好切在三角形的某个顶点上几乎不可能,不过也可以考虑在内,这里就不画出来了),所以每个三角形正好被切到的时候,其自身内部应该生成新的顶点(图中-1,-2点)。生成处新的顶点之后,我们需要将原来的一个三角形重新分割为如图绿色的数字标志的三个三角形,也就是原来一个三角形被分为三个三角形。代码如下:
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 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | MeshFilter mf = this .gameObject.GetComponent<MeshFilter>(); //顶点数组转顶点容器 List<Vector3> verticeList = new List<Vector3>(); int verticeCount = mf.mesh.vertices.Length; for ( int verticeIndex = 0; verticeIndex < verticeCount; ++verticeIndex) { verticeList.Add(mf.mesh.vertices[verticeIndex]); } //三角形数组转三角形容器 List< int > triangleList = new List< int >(); int triangleCount = mf.mesh.triangles.Length; for ( int triangleIndex = 0; triangleIndex < triangleCount; ++triangleIndex) { triangleList.Add(mf.mesh.triangles[triangleIndex]); } //uv坐标数组转uv坐标容器 List<Vector2> uvList = new List<Vector2>(); int uvCount = mf.mesh.uv.Length; for ( int uvIndex = 0; uvIndex < uvCount; ++uvIndex) { uvList.Add(mf.mesh.uv[uvIndex]); } //顶点颜色数组转顶点颜色容器 List<Vector3> normalList = new List<Vector3>(); int normalCount = mf.mesh.normals.Length; for ( int normalIndex = 0; normalIndex < normalCount; ++normalIndex) { normalList.Add(mf.mesh.normals[normalIndex]); } //检查每个三角面,是否存在两个顶点连接正好在直线上 for ( int triangleIndex = 0; triangleIndex < triangleList.Count;) { int trianglePoint0 = triangleList[triangleIndex]; int trianglePoint1 = triangleList[triangleIndex + 1]; int trianglePoint2 = triangleList[triangleIndex + 2]; Vector3 point0 = verticeList[trianglePoint0]; Vector3 point1 = verticeList[trianglePoint1]; Vector3 point2 = verticeList[trianglePoint2]; float planeY = 0.3f; //0-1,1-2相连线段被切割 if ((point0.y - planeY)* (point1.y - planeY) < 0 && (point1.y - planeY) * (point2.y - planeY) < 0) { //截断0-1之间的顶点 float k01 = (point1.y - point0.y) / (planeY - point0.y); float newPointX01 = (point1.x - point0.x) / k01 + point0.x; float newPointZ01 = (point1.z - point0.z) / k01 + point0.z; Vector3 newPoint0_1 = new Vector3(newPointX01, planeY, newPointZ01); verticeList.Add(newPoint0_1); //uv if (uvList.Count > 0) { Vector2 uv0 = uvList[trianglePoint0]; Vector2 uv1 = uvList[trianglePoint1]; float newUV_x = (uv1.x - uv0.x) / k01 + uv0.x; float newUV_y = (uv1.y - uv0.y) / k01 + uv0.y; uvList.Add( new Vector2(newUV_x, newUV_y)); } //法向量 Vector3 normalX0 = normalList[trianglePoint0]; Vector3 normalX1 = normalList[trianglePoint1]; Vector3 normalX2 = normalList[trianglePoint2]; float newNoramlX01 = (normalX1.x - normalX0.x) / k01 + normalX0.x; float newNoramlY01 = (normalX1.y - normalX0.y) / k01 + normalX0.y; float newNoramlZ01 = (normalX1.z - normalX0.z) / k01 + normalX0.z; normalList.Add( new Vector3(newNoramlX01, newNoramlY01, newNoramlZ01)); //截断1-2之间的顶点 float k12 = (point2.y - point1.y) / (planeY - point1.y); float newPointX12 = (point2.x - point1.x) / k12 + point1.x; float newPointZ12 = (point2.z - point1.z) / k12 + point1.z; Vector3 newPoint1_2 = new Vector3(newPointX12, planeY, newPointZ12); verticeList.Add(newPoint1_2); if (uvList.Count > 0) { Vector2 uv1 = uvList[trianglePoint1]; Vector2 uv2 = uvList[trianglePoint2]; float newUV_x = (uv2.x - uv1.x) / k12 + uv1.x; float newUV_y = (uv2.y - uv1.y) / k12 + uv1.y; uvList.Add( new Vector2(newUV_x, newUV_y)); } //法向量 float newNoramlX12 = (normalX2.x - normalX1.x) / k12 + normalX1.x; float newNoramlY12 = (normalX2.y - normalX1.y) / k12 + normalX1.y; float newNoramlZ12 = (normalX2.z - normalX1.z) / k12 + normalX1.z; normalList.Add( new Vector3(newNoramlX12, newNoramlY12, newNoramlZ12)); int newVerticeCount = verticeList.Count; //插入顶点索引,以此构建新三角形 triangleList.Insert(triangleIndex + 1, newVerticeCount - 2); triangleList.Insert(triangleIndex + 2, newVerticeCount - 1); triangleList.Insert(triangleIndex + 3, newVerticeCount - 1); triangleList.Insert(triangleIndex + 4, newVerticeCount - 2); triangleList.Insert(triangleIndex + 6, trianglePoint0); triangleList.Insert(triangleIndex + 7, newVerticeCount - 1); } //1-2,2-0相连线段被切割 else if ((point1.y - planeY) * (point2.y - planeY) < 0 && (point2.y - planeY) * (point0.y - planeY) < 0) { //截断1-2之间的顶点 float k12 = (point2.y - point1.y) / (planeY - point1.y); float newPointX12 = (point2.x - point1.x) / k12 + point1.x; float newPointZ12 = (point2.z - point1.z) / k12 + point1.z; Vector3 newPoint1_2 = new Vector3(newPointX12, planeY, newPointZ12); verticeList.Add(newPoint1_2); if (uvList.Count > 0) { Vector2 uv1 = uvList[trianglePoint1]; Vector2 uv2 = uvList[trianglePoint2]; float newUV_x = (uv2.x - uv1.x) / k12 + uv1.x; float newUV_y = (uv2.y - uv1.y) / k12 + uv1.y; uvList.Add( new Vector2(newUV_x, newUV_y)); } //法向量 Vector3 normalX0 = normalList[trianglePoint0]; Vector3 normalX1 = normalList[trianglePoint1]; Vector3 normalX2 = normalList[trianglePoint2]; float newNoramlX12 = (normalX2.x - normalX1.x) / k12 + normalX1.x; float newNoramlY12 = (normalX2.y - normalX1.y) / k12 + normalX1.y; float newNoramlZ12 = (normalX2.z - normalX1.z) / k12 + normalX1.z; normalList.Add( new Vector3(newNoramlX12, newNoramlY12, newNoramlZ12)); //截断0-2之间的顶点 float k02 = (point2.y - point0.y) / (planeY - point0.y); float newPointX02 = (point2.x - point0.x) / k02 + point0.x; float newPointZ02 = (point2.z - point0.z) / k02 + point0.z; Vector3 newPoint0_2 = new Vector3(newPointX02, planeY, newPointZ02); verticeList.Add(newPoint0_2); //uv if (uvList.Count > 0) { Vector2 uv0 = uvList[trianglePoint0]; Vector2 uv2 = uvList[trianglePoint2]; float newUV_x = (uv2.x - uv0.x) / k02 + uv0.x; float newUV_y = (uv2.y - uv0.y) / k02 + uv0.y; uvList.Add( new Vector2(newUV_x, newUV_y)); } //法向量 float newNoramlX02 = (normalX1.x - normalX0.x) / k02 + normalX0.x; float newNoramlY02 = (normalX1.y - normalX0.y) / k02 + normalX0.y; float newNoramlZ02 = (normalX1.z - normalX0.z) / k02 + normalX0.z; normalList.Add( new Vector3(newNoramlX02, newNoramlY02, newNoramlZ02)); int newVerticeCount = verticeList.Count; //插入顶点索引,以此构建新三角形 //{0} //{1} triangleList.Insert(triangleIndex + 2, newVerticeCount - 2); triangleList.Insert(triangleIndex + 3, newVerticeCount - 1); triangleList.Insert(triangleIndex + 4, newVerticeCount - 2); //{2} triangleList.Insert(triangleIndex + 6, newVerticeCount - 1); triangleList.Insert(triangleIndex + 7, trianglePoint0); triangleList.Insert(triangleIndex + 8, newVerticeCount - 2); } //0-1,2-0相连线段被切割 else if ((point0.y - planeY) * (point1.y - planeY) < 0 && (point2.y - planeY) * (point0.y - planeY) < 0) { //截断0-1之间的顶点 float k01 = (point1.y - point0.y) / (planeY - point0.y); float newPointX01 = (point1.x - point0.x) / k01 + point0.x; float newPointZ01 = (point1.z - point0.z) / k01 + point0.z; Vector3 newPoint0_1 = new Vector3(newPointX01, planeY, newPointZ01); verticeList.Add(newPoint0_1); //uv if (uvList.Count > 0) { Vector2 uv0 = uvList[trianglePoint0]; Vector2 uv1 = uvList[trianglePoint1]; float newUV_x = (uv1.x - uv0.x) / k01 + uv0.x; float newUV_y = (uv1.y - uv0.y) / k01 + uv0.y; uvList.Add( new Vector2(newUV_x, newUV_y)); } //法向量 Vector3 normalX0 = normalList[trianglePoint0]; Vector3 normalX1 = normalList[trianglePoint1]; Vector3 normalX2 = normalList[trianglePoint2]; float newNoramlX01 = (normalX1.x - normalX0.x) / k01 + normalX0.x; float newNoramlY01 = (normalX1.y - normalX0.y) / k01 + normalX0.y; float newNoramlZ01 = (normalX1.z - normalX0.z) / k01 + normalX0.z; normalList.Add( new Vector3(newNoramlX01, newNoramlY01, newNoramlZ01)); //截断0-2之间的顶点 float k02 = (point2.y - point0.y) / (planeY - point0.y); float newPointX02 = (point2.x - point0.x) / k02 + point0.x; float newPointZ02 = (point2.z - point0.z) / k02 + point0.z; Vector3 newPoint0_2 = new Vector3(newPointX02, planeY, newPointZ02); verticeList.Add(newPoint0_2); //uv if (uvList.Count > 0) { Vector2 uv0 = uvList[trianglePoint0]; Vector2 uv2 = uvList[trianglePoint2]; float newUV_x = (uv2.x - uv0.x) / k02 + uv0.x; float newUV_y = (uv2.y - uv0.y) / k02 + uv0.y; uvList.Add( new Vector2(newUV_x, newUV_y)); } //法向量 float newNoramlX02 = (normalX1.x - normalX0.x) / k02 + normalX0.x; float newNoramlY02 = (normalX1.y - normalX0.y) / k02 + normalX0.y; float newNoramlZ02 = (normalX1.z - normalX0.z) / k02 + normalX0.z; normalList.Add( new Vector3(newNoramlX02, newNoramlY02, newNoramlZ02)); int newVerticeCount = verticeList.Count; //插入顶点索引,以此构建新三角形 //{0} triangleList.Insert(triangleIndex + 1, newVerticeCount - 2); triangleList.Insert(triangleIndex + 2, newVerticeCount - 1); triangleList.Insert(triangleIndex + 3, newVerticeCount - 2); //{1} //{2} triangleList.Insert(triangleIndex + 6, trianglePoint2); triangleList.Insert(triangleIndex + 7, newVerticeCount - 1); triangleList.Insert(triangleIndex + 8, newVerticeCount - 2); } //只有0-1被切 else if ((point0.y - planeY) * (point1.y - planeY) < 0) { Debug.Log( "只有01被切" ); } //只有1-2被切 else if ((point1.y - planeY) * (point2.y - planeY) < 0) { Debug.Log( "只有12被切" ); } //只有2-0被切 else if ((point2.y - planeY) * (point0.y - planeY) < 0) { Debug.Log( "只有02被切" ); } triangleIndex += 3; } //筛选出切割面两侧的顶点索引 List< int > triangles1 = new List< int >(); List< int > triangles2 = new List< int >(); for ( int triangleIndex = 0; triangleIndex < triangleList.Count; triangleIndex += 3) { int trianglePoint0 = triangleList[triangleIndex]; int trianglePoint1 = triangleList[triangleIndex + 1]; int trianglePoint2 = triangleList[triangleIndex + 2]; Vector3 point0 = verticeList[trianglePoint0]; Vector3 point1 = verticeList[trianglePoint1]; Vector3 point2 = verticeList[trianglePoint2]; //切割面 float planeY = 0.3f; if (point0.y > planeY || point1.y > planeY || point2.y > planeY) { triangles1.Add(trianglePoint0); triangles1.Add(trianglePoint1); triangles1.Add(trianglePoint2); } else { triangles2.Add(trianglePoint0); triangles2.Add(trianglePoint1); triangles2.Add(trianglePoint2); } } //缝合切口 //for (int verticeIndex = verticeCount; verticeIndex < verticeList.Count - 2; ++verticeIndex) //{ // triangles1.Add(verticeIndex + 2); // triangles1.Add(verticeIndex); // triangles1.Add(verticeCount); // triangles2.Add(verticeCount); // triangles2.Add(verticeIndex); // triangles2.Add(verticeIndex + 2); //} mf.mesh.vertices = verticeList.ToArray(); mf.mesh.triangles = triangles1.ToArray(); if (uvList.Count > 0) { mf.mesh.uv = uvList.ToArray(); } mf.mesh.normals = normalList.ToArray(); //分割模型 GameObject newModel = new GameObject( "New Model" ); MeshFilter meshFilter = newModel.AddComponent<MeshFilter>(); meshFilter.mesh.vertices = mf.mesh.vertices; meshFilter.mesh.triangles = triangles2.ToArray(); meshFilter.mesh.uv = mf.mesh.uv; meshFilter.mesh.normals = mf.mesh.normals; Renderer newRenderer = newModel.AddComponent<MeshRenderer>(); newRenderer.material = this .gameObject.GetComponent<MeshRenderer>().material; |
切出来的模型新生成的顶点是无序的,但是我们可以连接任意两个无序顶点定为参考向量,然后其他任意顶点与参考向量中的起点连接形成新的向量,求得这两个向量之间的夹角,利用这个夹角大小来排序,如图所示:
顶点的排序算法如下:
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 | //重新排序新生成的顶点,按照角度 List<SortAngle> SortAngleList = new List<SortAngle>(); for ( int verticeIndex = verticeCount + 1; verticeIndex < verticeList.Count; verticeIndex++) { //计算角度,以0-1为参照 Vector3 vec1to0 = verticeList[verticeCount + 1] - verticeList[verticeCount]; Vector3 indexTo0 = verticeList[verticeIndex] - verticeList[verticeCount]; float moIndexto0 = indexTo0.magnitude; float mo1to0 = vec1to0.magnitude; float dotRes = Vector3.Dot(indexTo0, vec1to0); if (moIndexto0 == 0.0f) { continue ; } float angle = Mathf.Acos(dotRes / (mo1to0 * moIndexto0)); //Vector3.Angle(indexTo0.normalized, vec1to0.normalized); bool isExis = false ; for ( int i = 0; i < SortAngleList.Count; ++i) { //同样角度,距离近的被剔除 if (Mathf.Abs(SortAngleList[i].Angle * 180.0f / Mathf.PI - angle * 180.0f / Mathf.PI) < 0.1f) { float dis1 = Vector3.Distance(verticeList[SortAngleList[i].Index], verticeList[verticeCount]); float dis2 = Vector3.Distance(verticeList[verticeIndex], verticeList[verticeCount]); if (dis2 >= dis1) { SortAngleList[i].Index = verticeIndex; } isExis = true ; break ; } } if (!isExis) { //Debug.Log(angle); SortAngle sortAngle = new SortAngle(); sortAngle.Index = verticeIndex; sortAngle.Angle = angle; SortAngleList.Add(sortAngle); } } SortAngleList.Sort(); |
三、运行效果
四、项目截图
注:本文著作权归作者,由demo大师发表,拒绝转载,转载需要作者授权
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步