目录
1.模型切割
2.代码
1.模型切割
如图,对3D模型的Mesh网格进行切割,会经过若干个三角面。而切割后,将会产生新的面来组成左右两边的物体。
要记录每个顶点与顶点下标,新的面要顺时针绘制,
2.代码
using System.Collections.Generic;
using UnityEngine;public class CutMode : MonoBehaviour
{private Material cutMaterial;private Vector3 _startPos;private Vector3 _endPos;private Vector3 _hitPos;private Vector3 _dir, _upDir, _planeNormal;private Mesh _mesh;private Transform _hitTrans;private MeshFilter _leftMeshFilter;/// 三角形三个顶点的坐标信息缓存(世界坐标)private Vector3[] _triangleTemp = new Vector3[3];/// 三角形三个点乘结果的缓存private float[] _resultTemp = new float[3];//左侧(和平面法向量同侧)模型数据private List<Vector3> _leftVertices = new List<Vector3>();private List<int> _leftTriangles = new List<int>();private List<Vector3> _leftNormals = new List<Vector3>();public List<Vector2> uvs_left;/// key:原模型顶点下标 value:现模型的顶点下标private Dictionary<int, int> _leftIndexMapping = new Dictionary<int, int>();//右侧(和平面法向量反向)模型数据private List<Vector3> _rightVertices = new List<Vector3>();private List<int> _rightTriangles = new List<int>();private List<Vector3> _rightNormals = new List<Vector3>();public List<Vector2> uvs_right;/// key:原模型顶点下标 value:现模型的顶点下标private Dictionary<int, int> _rightIndexMapping = new Dictionary<int, int>();/// 切面上新生成的顶点private List<Vector3> _rectionVertexs = new List<Vector3>();private void Update(){if (Input.GetMouseButtonDown(0)){_startPos = Input.mousePosition;}if (Input.GetMouseButtonUp(0)){_endPos = Input.mousePosition;Ray();}}private void Ray(){//两点间的中心点var center = (_endPos + _startPos) * 0.5f;var ray = Camera.main.ScreenPointToRay(center);RaycastHit hit;if (Physics.Raycast(ray, out hit)){_hitTrans = hit.transform;if (_hitTrans.tag.Equals("cutObj")){Debug.Log("我切到了" + _hitTrans.name);cutMaterial = _hitTrans.GetComponent<MeshRenderer>().materials[0];}else{return;}_hitPos = hit.point;_leftMeshFilter = _hitTrans.GetComponent<MeshFilter>();_mesh = hit.transform.GetComponent<MeshFilter>().mesh;//相机到物体的方向向量 Vector3.normalized归一化向量_dir = (hit.point - Camera.main.transform.position).normalized;//垂直于_dir的方向向量 Vector3.Dot(v1,v2)点乘--->计算v1在v2上的投影长度(标量)--法向量_upDir = (-_dir * Vector3.Dot(Vector3.up, _dir) + Vector3.up).normalized;//平面 Vector3.Cross叉乘_planeNormal = Vector3.Cross(_dir, _upDir);//计算滑动方向与角度Vector3 sildeDir = _endPos - _startPos;Vector3 baseDir = sildeDir.y < 0 ? -Vector3.up : Vector3.up;float angle = Vector3.Angle(sildeDir, baseDir);if (sildeDir.y < 0){angle = sildeDir.x > 0 ? angle : -angle;}else{angle = sildeDir.x > 0 ? -angle : angle;}//角度转弧度angle *= Mathf.Deg2Rad;//sin cos 需传入的参数为弧度_upDir = _upDir * Mathf.Cos(angle) + _planeNormal * Mathf.Sin(angle);_planeNormal = Vector3.Cross(_dir, _upDir);Cut();}else{_hitPos = Vector3.zero;_mesh = null;}}private void Cut(){if (_mesh == null)return;ClearData();CalculateVertexInfo();GenerateSectionInfo();GenerateMesh();}private void ClearData(){_leftVertices.Clear();_leftTriangles.Clear();_leftNormals.Clear();_leftIndexMapping.Clear();_rightNormals.Clear();_rightTriangles.Clear();_rightVertices.Clear();_rightIndexMapping.Clear();_rectionVertexs.Clear();}private void GenerateMesh(){GenerateLeftMesh();GenerateRightMesh();}private void GenerateLeftMesh(){Mesh mesh = new Mesh();mesh.name = "leftMesh";mesh.vertices = _leftVertices.ToArray();mesh.triangles = _leftTriangles.ToArray();mesh.normals = _leftNormals.ToArray();//ToDo mesh.uv = _leftMeshFilter.mesh = mesh;}private int newObjNum = 0;private void GenerateRightMesh(){Mesh mesh = new Mesh();mesh.name = "rightMesh";mesh.vertices = _rightVertices.ToArray();mesh.normals = _rightNormals.ToArray();mesh.triangles = _rightTriangles.ToArray();//ToDo mesh.uv = GameObject newGo = new GameObject();newGo.name = "newObj" + newObjNum;newGo.transform.tag = "cutObj";newGo.transform.position = _hitTrans.position;newGo.transform.rotation = _hitTrans.rotation;newGo.AddComponent<MeshFilter>().mesh = mesh;//newGo.AddComponent<MeshRenderer>().material = _hitTrans.GetComponent<MeshRenderer>().material;newGo.AddComponent<MeshRenderer>().material = cutMaterial;newGo.AddComponent<Rigidbody>();newGo.AddComponent<MeshCollider>().convex = true;newObjNum++;}/// <summary>/// 分别计算并存储切开的两个部分的顶点信息/// </summary>private void CalculateVertexInfo(){var triangles = _mesh.triangles;for (int i = 0; i < triangles.Length; i += 3){//三个顶点在原triangles中下标是 i i+1 i+2GetDotResult(i, triangles);if (_resultTemp[0] >= 0 && _resultTemp[1] >= 0 && _resultTemp[2] >= 0){//左侧SaveOldVertex(i, true);}else if (_resultTemp[0] <= 0 && _resultTemp[1] <= 0 && _resultTemp[2] <= 0){//右侧SaveOldVertex(i, false);}else{//被切割的三角形部分int differentIndex = GetDifferentSidePointIndex();//当前点在triangles的下标int p0_Index = i + differentIndex;int p1_index = (differentIndex + 1) % 3 + i;//先算出c1点进行存储SavePointOnSection(_mesh.triangles[p0_Index], _mesh.triangles[p1_index]);int p2_index = (differentIndex + 2) % 3 + i;//再算出c2点进行存储SavePointOnSection(_mesh.triangles[p0_Index], _mesh.triangles[p2_index]);SaveCutTriangleVertex(_resultTemp[differentIndex], p0_Index, p1_index, p2_index);}}}private void SaveCutTriangleVertex(float result, int p0, int p1, int p2){if (result >= 0){SaveOldVertex(p0, _leftVertices, _leftNormals, _leftIndexMapping);SaveSectionVertexWithOnePoint(p0, _leftTriangles, _leftVertices, _leftNormals, _leftIndexMapping);SaveOldVertex(p1, _rightVertices, _rightNormals, _rightIndexMapping);SaveOldVertex(p2, _rightVertices, _rightNormals, _rightIndexMapping);SaveSectionVertexWithTwoPoint(p1, p2, _rightTriangles, _rightVertices, _rightNormals, _rightIndexMapping);}else{SaveOldVertex(p0, _rightVertices, _rightNormals, _rightIndexMapping);SaveSectionVertexWithOnePoint(p0, _rightTriangles, _rightVertices, _rightNormals, _rightIndexMapping);SaveOldVertex(p1, _leftVertices, _leftNormals, _leftIndexMapping);SaveOldVertex(p2, _leftVertices, _leftNormals, _leftIndexMapping);SaveSectionVertexWithTwoPoint(p1, p2, _leftTriangles, _leftVertices, _leftNormals, _leftIndexMapping);}}private void SaveSectionVertexWithOnePoint(int index,List<int> curTriangles,List<Vector3> curVertices,List<Vector3> curNormals,Dictionary<int, int> indexMapping){int vertexIndex = _mesh.triangles[index];//存储c1curVertices.Add(_rectionVertexs[_rectionVertexs.Count - 2]);//存储c2curVertices.Add(_rectionVertexs[_rectionVertexs.Count - 1]);curNormals.Add(_mesh.normals[vertexIndex]);curNormals.Add(_mesh.normals[vertexIndex]);curTriangles.Add(indexMapping[vertexIndex]);curTriangles.Add(curVertices.Count - 2);curTriangles.Add(curVertices.Count - 1);}private void SaveSectionVertexWithTwoPoint(int index1,int index2,List<int> curTriangles,List<Vector3> curVertices,List<Vector3> curNormals,Dictionary<int, int> indexMapping){int vertexIndex1 = _mesh.triangles[index1];int vertexIndex2 = _mesh.triangles[index2];//存储c1curVertices.Add(_rectionVertexs[_rectionVertexs.Count - 2]);//存储c2curVertices.Add(_rectionVertexs[_rectionVertexs.Count - 1]);curNormals.Add(_mesh.normals[vertexIndex1]);curNormals.Add(_mesh.normals[vertexIndex2]);//c1-p1-p2curTriangles.Add(curVertices.Count - 2);curTriangles.Add(indexMapping[vertexIndex1]);curTriangles.Add(indexMapping[vertexIndex2]);//p2-c2-c1curTriangles.Add(indexMapping[vertexIndex2]);curTriangles.Add(curVertices.Count - 1);curTriangles.Add(curVertices.Count - 2);}/// <summary>/// 返回值是对应点在_resultTemp中的下标/// </summary>private int GetDifferentSidePointIndex(){List<int> temp1 = new List<int>(2);List<int> temp2 = new List<int>(2);for (int i = 0; i < _resultTemp.Length; i++){if (_resultTemp[i] > 0){temp1.Add(i);}else{temp2.Add(i);}}if (temp1.Count == 1){return temp1[0];}else{return temp2[0];}}//参数是 原模型vertices下标private void SavePointOnSection(int index1, int index2){Vector3 side = _mesh.vertices[index2] - _mesh.vertices[index1];//方向向量 --- 本地坐标系转世界坐标系Vector3 dir = _hitTrans.TransformDirection(side.normalized);Vector3 startPos = _hitTrans.TransformPoint(_mesh.vertices[index1]);float lengthOnNormal = Vector3.Dot(_hitPos, _planeNormal) - Vector3.Dot(startPos, _planeNormal);float length = lengthOnNormal / Vector3.Dot(dir, _planeNormal);Vector3 target = startPos + dir * length;_rectionVertexs.Add(_hitTrans.InverseTransformPoint(target));}private void GetDotResult(int index, int[] triangles){for (int i = 0; i < _triangleTemp.Length; i++){_triangleTemp[i] = _hitTrans.TransformPoint(_mesh.vertices[triangles[index + i]]);_resultTemp[i] = Vector3.Dot(_planeNormal, _triangleTemp[i] - _hitPos);}}private void SaveOldVertex(int index, bool isLeft){if (isLeft){SaveTriangleVertex(index, _leftTriangles, _leftVertices, _leftNormals, _leftIndexMapping);}else{SaveTriangleVertex(index, _rightTriangles, _rightVertices, _rightNormals, _rightIndexMapping);}}private void SaveTriangleVertex(int index,List<int> curTriangles,List<Vector3> curVertices,List<Vector3> curNormals,Dictionary<int, int> indexMapping){for (int i = 0; i < 3; i++){SaveOldVertex(index + i, curVertices, curNormals, indexMapping);curTriangles.Add(indexMapping[_mesh.triangles[index + i]]);}}private void SaveOldVertex(int index,List<Vector3> curVertices,List<Vector3> curNormals,Dictionary<int, int> indexMapping){int vertexIndex = _mesh.triangles[index];if (!indexMapping.ContainsKey(vertexIndex)){curVertices.Add(_mesh.vertices[vertexIndex]);curNormals.Add(_mesh.normals[vertexIndex]);indexMapping.Add(vertexIndex, curVertices.Count - 1);}}//生成切面信息private void GenerateSectionInfo(){Vector3 center = (_rectionVertexs[0] + _rectionVertexs[_rectionVertexs.Count / 2]) * 0.5f;Vector3 centerNormal = _hitTrans.InverseTransformDirection(_planeNormal);SaveSectionCenter(center, centerNormal);int leftCenterIndex = _leftVertices.Count - 1;int rightCenterIndex = _rightVertices.Count - 1;for (int i = 0; i < _rectionVertexs.Count; i += 2){Vector3 v1 = _rectionVertexs[i];Vector3 v2 = _rectionVertexs[i + 1];Vector3 normal = Vector3.Cross(v1 - center, v2 - center);SaveSectionVertexInfo(i, -centerNormal, _leftVertices, _leftNormals);SaveLeftSectionTriangle(_planeNormal, normal, leftCenterIndex, _leftTriangles, _leftVertices);SaveSectionVertexInfo(i, centerNormal, _rightVertices, _rightNormals);SaveRightSectionTriangle(_planeNormal, normal, rightCenterIndex, _rightTriangles, _rightVertices);}}private void SaveLeftSectionTriangle(Vector3 planeNormal, Vector3 normal, int centerIndex, List<int> triangles, List<Vector3> vertices){if (Vector3.Dot(planeNormal, normal) < 0){//左侧切面 三角形法向量方向和planeNormal方向相反,才能正常显示// 0 1 2triangles.Add(centerIndex);triangles.Add(vertices.Count - 2);triangles.Add(vertices.Count - 1);}else{// 0 2 1triangles.Add(centerIndex);triangles.Add(vertices.Count - 1);triangles.Add(vertices.Count - 2);}}private void SaveRightSectionTriangle(Vector3 planeNormal, Vector3 normal, int centerIndex, List<int> triangles, List<Vector3> vertices){if (Vector3.Dot(planeNormal, normal) > 0){//右侧切面 三角形法向量方向和planeNormal方向相同,才能正常显示// 0 1 2triangles.Add(centerIndex);triangles.Add(vertices.Count - 2);triangles.Add(vertices.Count - 1);}else{// 0 2 1triangles.Add(centerIndex);triangles.Add(vertices.Count - 1);triangles.Add(vertices.Count - 2);}}private void SaveSectionVertexInfo(int index, Vector3 normal, List<Vector3> vertices, List<Vector3> normals){vertices.Add(_rectionVertexs[index]);vertices.Add(_rectionVertexs[index + 1]);normals.Add(normal);normals.Add(normal);}private void SaveSectionCenter(Vector3 center, Vector3 normal){_leftVertices.Add(center);_leftNormals.Add(-normal);_rightVertices.Add(center);_rightNormals.Add(normal);}
}