This is renewed version of An Introductory Guide to Splitting Meshes
a more in-depth tutorial about the algorithm behind it, but more readable, less algebras and easier
Splitting a Triangle
Every mesh is made out of triangles, so before cutting full objects, we first need to learn how to cut a single triangle with a plane
Here’s the idea
- A triangle has three points
- Get the positive, negative sides of the points
- Positive means if the point is above the plane
- Negative means if the point is below the plane
The code below does exactly that
local function cutTriangle(trix, plane, positive)
-- the plane point is the plane position
-- the plane's "normal" (its top direction of the plane), Imagine the plane size is ..., 0.001, ...
local point, normal = plane.Position, plane.CFrame.UpVector
local dist, side = {}, {}
for i = 1, 3 do
-- get the distance of the point to the plane = dot((vertex - planePoint), planeNormal)
-- If positive → above the plane, negative → below the plane
dist[i] = vector.dot(trix[i] - point, normal)
-- Store which side the point is on, depending on positive params of the function
-- I don't use >= and <= because if the plane in the exact point as the vertex,
-- it could glitch out or something
side[i] = if positive then dist[i] > 0 else dist[i] < 0
end
end
Cutting Triangle Edges
Now that we know which side of the plane each vertex is on, the next step is to walk along each edge of the triangle and decide what to keep
Here’s the idea
- A triangle has 3 edges
- For each edge, look at its two vertices (
p1andp2) - If a vertex is on the correct side, we keep it
- If the edge crosses the plane (the vertices are on opposite sides), then the plane slices through it, we compute the intersection point
- By doing this for all 3 edges, we collect a new set of points that represent the cut
The next code would be
local points = {}
for i = 1, 3 do
-- j is the next vertex in the loop (1→2, 2→3, 3→1)
local j = i % 3 + 1
-- p1 and p2 are the two vertices forming the current edge
local p1, p2 = trix[i], trix[j]
-- d1 and d2 are their distances from the plane
local d1, d2 = dist[i], dist[j]
-- s1 and s2 tell if p1/p2 are inside the side we want
local s1, s2 = side[i], side[j]
-- If this vertex is on the correct side, keep it
if s1 then
points[#points + 1] = p1
end
-- If the edge crosses the plane ( invalid side )
-- calculate the intersection point and keep it
if s1 ~= s2 then
-- Formula: intersection = p1:Lerp(p2, d1 / (d1 - d2))
-- This finds the exact cut point along the edge
points[#points + 1] = p1:Lerp(p2, d1 / (d1 - d2))
end
end
Creating Triangles
After walking all 3 edges, we end up with a new set of points
This set represents the shape of the clipped triangle
There are only two possible outcomes
- 3 points : Still a triangle (just reshaped)
- 4 points : The cut turned into a quad. We can’t draw quad, Just turns them into 2 triangles instead!
The last code would be
local result = {}
if #points == 3 then
-- Case 1: Already a triangle
result[1] = { points[1], points[2], points[3] }
elseif #points == 4 then
-- Case 2: Quad → split into 2 triangles
-- Fan triangulation works: (1,2,3) and (1,3,4)
result[1] = { points[1], points[2], points[3] }
result[2] = { points[1], points[3], points[4] }
end
return result
Putting It All Together
We now have all the building blocks
- Classify points (above/below plane)
- Walk edges to collect valid vertices and intersections
- Rebuild new triangles from the resulting points
Let’s combine everything into one clean function
local function cutTriangle(trix, plane, positive)
local point, normal = plane.Position, plane.CFrame.UpVector
local dist, side, points = {}, {}, {}
for i = 1, 3 do
dist[i] = vector.dot(trix[i] - point, normal)
side[i] = if positive then dist[i] >= 0 else dist[i] <= 0
end
for i = 1, 3 do
local j = i % 3 + 1
local p1, p2 = trix[i], trix[j]
local d1, d2 = dist[i], dist[j]
local s1, s2 = side[i], side[j]
if s1 then points[#points + 1] = p1 end
if s1 ~= s2 then
if d1 ~= d2 then -- could be NAN
points[#points + 1] = vector.lerp(p1, p2, d1 / (d1 - d2))
else
points[#points + 1] = p1
end
end
end
if #points == 3 then
return {points}
elseif #points == 4 then
return {{points[1], points[2], points[3]}, {points[1], points[3], points[4]}}
end
return {} -- no triangles
end
After all that, the final result looks promising.
You can test it with yourself or lazy to learning the tutorial, download this file to test split a triangle
You can use 3D Triangles by @EgoMoose to filling the gaps of triangles too, Example usage:
while task.wait() do
triangleRenderer.BeginFrame()
local trix = { workspace[1].Position, workspace[2].Position, workspace[3].Position }
local positive = cutTriangle(trix, plane, true)
local negative = cutTriangle(trix, plane, false)
if positive then -- get points above plane
for _, tri in positive do
triangleRenderer.DrawTriangle(tri[1], tri[2], tri[3], Color3.new(1, 0, 0))
end
end
if negative then -- get points below plane, like the other side of the plane
for _, tri in negative do
triangleRenderer.DrawTriangle(tri[1], tri[2], tri[3], Color3.new(0, 0, 1))
end
end
triangleRenderer.EndFrame()
end



