Detailed explanation of Unity Mesh cutting algorithm

Preface
When we develop games, we often have some special gameplay, etc., which need to involve Mesh cutting. For example, 3D cutting fruit, digging a hole in the city wall of the map, today I will share with you a mesh cutting algorithm to help you solve the problem of mesh cutting that needs to be used in the project. This article mainly explains Mesh cutting from the following aspects.

right! There is a game development exchange group that gathers a group of zero-based beginners who love to learn Unity, and there are also some technical leaders who are engaged in Unity development. You are welcome to exchange and learn from source material acquisition.

(figure 1)

How to accept the player's touch operation and generate a cutting surface

When doing model cutting, we first need to generate a cutting plane according to the player's touch operation. As shown in Figure 1, according to the player's black line, we need to generate a cutting plane based on the black line. The main steps are as follows:

(1) Obtain the screen coordinates of the starting point, and combine the 3D camera to transfer the screen coordinates of the starting point to the viewport coordinate system of the camera.

if (!dragging && Input.GetMouseButtonDown(0)) {
    start = cam.ScreenToViewportPoint(Input.mousePosition);
    dragging = true;

}

(2) Obtain the screen coordinates of the end point, and combine with the 3D camera to transfer the key screen coordinates to the camera's viewport coordinate system.

if (dragging && Input.GetMouseButtonUp(0)) {
        // Finished dragging. We draw the line segment
        end = cam.ScreenToViewportPoint(Input.mousePosition);
        dragging = false;
}

(3) Based on the camera, generate two rays from the starting point and the end point, and calculate the intersection of the rays and the Near plane of the camera.

var startRay = cam.ViewportPointToRay(start);
var endRay = cam.ViewportPointToRay(end);
var startPoint = startRay.GetPoint(cam.nearClipPlane),
var endPoint = endRay.GetPoint(cam.nearClipPlane),

(4) After there are two points, there is a line, and there is also a direction to determine a plane (the line moves in the direction, and it becomes a plane). For this direction, we take the direction of the endRay ray as the vector A.

(figure 2)

(image 3)

var planeTangent = (end - start).normalized;
if (planeTangent == Vector3.zero)
    planeTangent = Vector3.right;
var normalVec = Vector3.Cross(depth, planeTangent);

According to start and normalVec, we can determine our cutting plane, which is the plane passing the start point, and the upward direction vector is normalVec.

The main data composition in the 3D model Mesh object

Before explaining the Mesh cutting algorithm in detail, let me explain to you the main parts of the mesh data of a model. When a new mesh is generated, we will know what each part of the data represents. The mesh data in Unity is generated into the Mesh object. A Mesh object mainly contains the following important data:

Triangle vertex index data : The triangle data corresponding to each face in the Mesh object, and the triangle is based on the vertex index. For example, face A consists of three vertices A1, A2, A3, here is the vertex index, A1 is the index, and the vertex data is obtained from the vertex data, and the normal is obtained from the A1 index to the normal data. In Unity, the above data can be obtained through the interface function of the Mesh object, as follows:

mesh.GetVertices(ogVertices);
mesh.GetTriangles(ogTriangles, 0);
mesh.GetNormals(ogNormals);
mesh.GetUVs(0, ogUvs);

Detailed explanation of model Mesh cutting algorithm steps

After determining the cutting surface, let's analyze the main steps of the Mesh cutting algorithm:

var localNormal = ((Vector3)(obj.transform.localToWorldMatrix.transpose * normal)).normalized;
var localPoint = obj.transform.InverseTransformPoint(point);

Then according to the localPoint, the normal vector is localNormal, to generate a Plane plane object. The plane objects here are relative to the model's coordinate system.

var slicePlane = new Plane();
slicePlane.SetNormalAndPosition(localNormal , localPoint);

Step3:  Determine whether the following Mesh and the plane intersect. If the Mesh and the plane have no focus at all, the algorithm ends, and there is no need to divide the Mesh;

if (!Intersections.BoundPlaneIntersect(mesh, ref slice))
    return false;

Step4:  Obtain the mesh data in the original Mesh, and clean up the forward and reverse mesh data sets stored after cutting.

mesh.GetVertices(ogVertices);
mesh.GetTriangles(ogTriangles, 0);
mesh.GetNormals(ogNormals);
mesh.GetUVs(0, ogUvs);

PositiveMesh.Clear();
NegativeMesh.Clear();
addedPairs.Clear();

Step5:  Traverse all the vertices to see which vertices are on the upper part of the cutting surface and which vertices are on the lower part of the cutting surface, and place them into PositiveMesh and NegativeMesh separately.

for(int i = 0; i < ogVertices.Count; ++i) {
    if (slice.GetDistanceToPoint(ogVertices[i]) >= 0)
        PositiveMesh.AddVertex(ogVertices, ogNormals, ogUvs, i); 
    else
        NegativeMesh.AddVertex(ogVertices, ogNormals, ogUvs, i);
}

Step6:  Calculate the intersection of the cutting surface and the object, and prepare for generating a new surface;

// 3. Separate triangles and cut those that intersect the plane
for (int i = 0; i < ogTriangles.Count; i += 3)
{
    if (intersect.TrianglePlaneIntersect(ogVertices, ogUvs, ogTriangles, i, ref slice, PositiveMesh, NegativeMesh, intersectPair))
        addedPairs.AddRange(intersectPair);
}

Step7:  Generate a new surface based on the newly added points

private void FillBoundaryFace(List<Vector3> added)
{
        // 1. Reorder added so in order ot their occurence along the perimeter.
        MeshUtils.ReorderList(added);

        // 2. Find actual face vertices
        var face = FindRealPolygon(added);

        // 3. Create triangle fans
        int t_fwd = 0,
            t_bwd = face.Count - 1,
            t_new = 1;
        bool incr_fwd = true;

        while (t_new != t_fwd && t_new != t_bwd)
        {
            AddTriangle(face, t_bwd, t_fwd, t_new);

            if (incr_fwd) t_fwd = t_new;
            else t_bwd = t_new;

            incr_fwd = !incr_fwd;
            t_new = incr_fwd ? t_fwd + 1 : t_bwd - 1;
        }
}

(Figure 4)

Step8:  Instantiate a new object, copy the two Meshes after cutting, one to the original node, and one to the newly created node.

// Create new Sliced object with the other mesh

GameObject newObject = Instantiate(obj, ObjectContainer);

newObject.transform.SetPositionAndRotation(obj.transform.position, obj.transform.rotation);

var newObjMesh = newObject.GetComponent<MeshFilter>().mesh;



// Put the bigger mesh in the original object

// TODO: Enable collider generation (either the exact mesh or compute smallest enclosing sphere)

ReplaceMesh(mesh, biggerMesh);

ReplaceMesh(newObjMesh, smallerMesh);

Guess you like

Origin blog.csdn.net/voidinit/article/details/126417560