Implementing Dynamic Mesh Slicing Based on Touch Input in Unity

Handling a mesh slicing operation includes generating a cut plane from user interaction, understanding the mesh data structures, and executing the core algorithm. The following sections cover these aspects.

Computing a Cut Plane from Screen Input

To define the cutting plane, convert two screen points into a plane relative to the camera. The steps extract coordinates and direction vectors.

First, capture the start point when a touch or click begins. Convert the screen location into the camera’s viewport space.

bool isActive;
Vector3 viewStart;
if (!isActive && Input.GetMouseButtonDown(0))
{
    viewStart = cam.ScreenToViewportPoint(Input.mousePosition);
    isActive = true;
}

When the drag finishes, record the end position similar.

Vector3 viewEnd;
if (isActive && Input.GetMouseButtonUp(0))
{
    viewEnd = cam.ScreenToViewportPoint(Input.mousePosition);
    isActive = false;
}

Generate rays from both viewport points and compute their intersections with the near clipping plane.

var rayStart = cam.ViewportPointToRay(viewStart);
var rayEnd = cam.ViewportPointToRay(viewEnd);
var nearPtStart = rayStart.GetPoint(cam.nearClipPlane);
var nearPtEnd = rayEnd.GetPoint(cam.nearClipPlane);

A line connects the two near-plane points. The direction of the rayEnd ray provides a depth vector. Taking the cross product of (nearPtEnd - nearPtStart) with the depth vector yields a perpendicular up vector that, together with nearPtStart, defines the slice plane.

var tangent = (nearPtEnd - nearPtStart).normalized;
if (tangent == Vector3.zero)
    tangent = Vector3.right;
var upDirection = Vector3.Cross(rayEnd.direction, tangent);

Key Mesh Components in Unity

A Mesh object stores vertices, normals, UV coordinates, and triangle index arrays. Triangles reefrence vertices by index. Fetch the existing data before modification:

mesh.GetVertices(originalVerts);
mesh.GetTriangles(originalTris, 0);
mesh.GetNormals(originalNorms);
mesh.GetUVs(0, originalUVs);

Slice Algorithm in Detail

Step 1: Transform the plane into local space. Take the world‑space plane point and normal and convert them to the model’s coordinate system.

var localNormal = ((Vector3)(targetTransform.localToWorldMatrix.transpose * worldNormal)).normalized;
var localPoint = targetTransform.InverseTransformPoint(worldPoint);

Create a Plane object for intersection tests.

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

Step 2: Classify orientation. Points on one side of the plane belong to the positive mesh; those on the other side belong to the negative mesh.

Step 3: Bounds check. If the mesh bounds do not intersect the plane, abort the operation.

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

Step 4: Initialize collections. Retrieve original data and prepare structures for the two resulting meshes.

mesh.GetVertices(originalVerts);
mesh.GetTriangles(originalTris, 0);
mesh.GetNormals(originalNorms);
mesh.GetUVs(0, originalUVs);

positiveSet.Clear();
negativeSet.Clear();
intersectionHistory.Clear();

Step 5: Partition vertices. Iterate through every vertex and assign it according to signed distance.

for (int i = 0; i < originalVerts.Count; ++i)
{
    if (cuttingPlane.GetDistanceToPoint(originalVerts[i]) >= 0)
        positiveSet.AddVertex(originalVerts, originalNorms, originalUVs, i);
    else
        negativeSet.AddVertex(originalVerts, originalNorms, originalUVs, i);
}

Step 6: Handle intersected triangles. Walk through each triangle, detect plane intersection, and split geometry according. If a triangle is cut, store the new intersection pair.

for (int triIndex = 0; triIndex < originalTris.Count; triIndex += 3)
{
    if (intersect.TrianglePlaneIntersect(originalVerts, originalUVs, originalTris, triIndex, 
        ref cuttingPlane, positiveSet, negativeSet, currentPairs))
    {
        intersectionHistory.AddRange(currentPairs);
    }
}

Step 7: Generate cap geometry. After collecting the open boundary points, order them and triangulate the filled region.

private void ConstructCap(List<Vector3> boundary)
{
    MeshUtils.ReorderList(boundary);
    var polygon = SimplifyToPolygon(boundary);

    int forward = 0;
    int backward = polygon.Count - 1;
    int nextIndex = 1;
    bool advanceForward = true;

    while (nextIndex != forward && nextIndex != backward)
    {
        RegisterTriangle(polygon, backward, forward, nextIndex);
        if (advanceForward)
            forward = nextIndex;
        else
            backward = nextIndex;

        advanceForward = !advanceForward;
        nextIndex = advanceForward ? forward + 1 : backward - 1;
    }
}

Step 8: Apply split meshes. Instantiate a new GameObject, then assign the larger mesh to the original object and the smaller one to the new object.

GameObject newObj = Instantiate(originalObject, containerTransform);
newObj.transform.SetPositionAndRotation(originalObject.transform.position, originalObject.transform.rotation);
var newMeshFilter = newObj.GetComponent<MeshFilter>();

ReplaceMesh(originalMesh, largerResult);
ReplaceMesh(newMeshFilter.mesh, smallerResult);

Tags: unity Mesh Slicing Computational Geometry game development C#

Posted on Tue, 13 Oct 2026 16:25:08 +0000 by Thresher