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);