目录
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+2
GetDotResult(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];
//存储c1
curVertices.Add(_rectionVertexs[_rectionVertexs.Count - 2]);
//存储c2
curVertices.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];
//存储c1
curVertices.Add(_rectionVertexs[_rectionVertexs.Count - 2]);
//存储c2
curVertices.Add(_rectionVertexs[_rectionVertexs.Count - 1]);
curNormals.Add(_mesh.normals[vertexIndex1]);
curNormals.Add(_mesh.normals[vertexIndex2]);
//c1-p1-p2
curTriangles.Add(curVertices.Count - 2);
curTriangles.Add(indexMapping[vertexIndex1]);
curTriangles.Add(indexMapping[vertexIndex2]);
//p2-c2-c1
curTriangles.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 2
triangles.Add(centerIndex);
triangles.Add(vertices.Count - 2);
triangles.Add(vertices.Count - 1);
}
else
{
// 0 2 1
triangles.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 2
triangles.Add(centerIndex);
triangles.Add(vertices.Count - 2);
triangles.Add(vertices.Count - 1);
}
else
{
// 0 2 1
triangles.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);
}
}