How to negate grid like blocks without unions?

image
image

How would I negate block like parts, without using unions but instead splitting it into other parts as shown above?

1 Like

EDIT: Okay, I posted the second part that actually solves what you were asking for :sweat_smile: What a fun problem! Hope this is of some help.

EDIT: I swapped ∪ for ∩ >.<’ it’s fixed now tho

EDIT: Here's the full script, with added comments and type annotations:
function corner1(part)	return part.Position - part.Size / 2 end function corner2(part)	return part.Position + part.Size / 2 end function minMax(v1, v2)	return math.min(v1, v2), math.max(v1, v2) end function avg(v1, v2)	return (v1 + v2) / 2 end --[[ Given two number ranges (p11, p12) and (p21, p22), return the intersection of those ranges. ]] function numbersIntersection(p11: number, p12: number, p21: number, p22: number): {number}	--Ensure segments (p11, p12) and (p21, p22) go from small to large	--Ensure p11 comes before p12	p11, p12 = minMax(p11, p12)	--Ensure p21 comes before p22	p21, p22 = minMax(p21, p22)	--Ensure (p11, p12) starts before (p21, p22)	if p11 > p21 then	--... by swapping if that's not the case	p11, p12, p21, p22 = p21, p22, p11, p12	end	if p12 < p21 then	return {} --No overlap	elseif p12 == p21 then	return {p12} --Overlaps in a point	elseif p12 > p21 then	if p12 < p22 then	return {p21, p12} --Some overlapping segment	else	return {p21, p22} --Entirity of (p21, p22) is contained in (p11, p12)	end	else	error()	end end --[[ Given two number ranges (p11, p12) and (p21, p22), return the second range removed from the first. ]] function numbersDifference(p11: number, p12: number, p21: number, p22: number): {number}	--Ensure segments (p11, p12) and (p21, p22) go from small to large	--Ensure p11 comes before p12	p11, p12 = minMax(p11, p12)	--Ensure p21 comes before p22	p21, p22 = minMax(p21, p22)	--Nothing or everything gets deleted	local intersection = numbersIntersection(p11, p12, p21, p22)	if #intersection == 0 then --No overlap	return {p11, p12} --Just return first segment	elseif intersection[1] == p11 and intersection[2] == p12 then --Overlap is entire first segment	return {} --Nothing left	end	--Something but not everything gets deleted	if p21 <= p11 then -- (p21, p22) starts before (p11, p12) and ends inside	--Only 1 case because if p22 were >= p12, everything would get deleted and that's checked previously	return {p11, p22, p12}, 1 --First part gets deleted, second part stays	elseif p22 >= p12 then -- (p21, p22) starts inside (p11, p12) and ends outside then	--Only 1 case because if p21 were <= p11, everything would get deleted and that's checked previously	return {p11, p21, p12}, 2 --Second part gets deleted, first part stays then	else --Second segment is inside first segment, so cut out middle part	return {p11, p21, p22, p12}, 2 --Middle gets deleted, other parts stay	end end --[[ Return a non-rotated Part with Size and Position defined by two corners. Optionally set the Size and Position of an existing Part, or use a new Part. ]] function partFromCorners(corner1: Vector3, corner2: Vector3, existingPart: Part?): Part	local part = existingPart or Instance.new("Part")	local diff = corner2 - corner1	local mid = avg(corner1, corner2)	part.Size = Vector3.new(diff.X, diff.Y, diff.Z)	part.Position = Vector3.new(mid.X, mid.Y, mid.Z)	return part end --[[ Given a non-rotated Part, an Axis and a list of points along that axis (as numbers),	return the slices of the Part on the plane perpendicular to the axis and intersecting the points	along the axis, as a list of Parts. ]] function slicePart(part: Part, axis: Enum.Axis, points: {number})	local slices = {}	local cutAxis = Vector3.FromAxis(axis)	local cutPlane = Vector3.new(1, 1, 1) - cutAxis	local sCorner1 = corner1(part)	local sCorner2 = sCorner1 * cutAxis + corner2(part) * cutPlane --Gets zero'd on the cut axis (so it corner2 on cut axis = corner 1 on cut axis)	for i = 1, #points do	sCorner2 += points[i] * cutAxis - sCorner1 * cutAxis --Slide corner2 to point[i] on the cut axis	table.insert(slices, partFromCorners(sCorner1, sCorner2, part:Clone()))	sCorner1 += (sCorner2 - sCorner1) * cutAxis --Slide corner1 to corner2 on the cut axis	end	sCorner2 = corner2(part)	table.insert(slices, partFromCorners(sCorner1, sCorner2, part:Clone()))	return slices end --[[ Given two volumes (as Parts), return the intersection of those volumes (as a Part). Input parts must have no rotation. ]] function volumesIntersection(block1: Part, block2: Part): Part?	local b1c1, b1c2 = corner1(block1), corner2(block1)	local b2c1, b2c2 = corner1(block2), corner2(block2)	local xDifference = numbersDifference(b1c1.X, b1c2.X, b2c1.X, b2c2.X)	local yDifference = numbersDifference(b1c1.Y, b1c2.Y, b2c1.Y, b2c2.Y)	local zDifference = numbersDifference(b1c1.Z, b1c2.Z, b2c1.Z, b2c2.Z)	--If any axis is non-intersecting, the intersection is empty	if #xDifference ~= 2 and #yDifference ~= 2 and #zDifference ~= 2 then return nil end	--If all axes are intersecting, return the 3D intersection	local corner1 = Vector3.new(xDifference[1], yDifference[1], zDifference[1])	local corner2 = Vector3.new(xDifference[2], yDifference[2], zDifference[2])	local difference = block1:Clone()	partFromCorners(corner1, corner2, difference)	return difference end --[[ Given two volumes (as Parts), return the second volume removed from the first volume (as a list of Parts). Input parts must have no rotation. ]] function volumesDifference(block1: Part, block2: Part): {Part}	local parts = {}	-- Compute the differences on each axis *before* slicing on the axes,	--	because we might exit early if one or more axes are non-overlapping	local axisDifferences = {}	for _, axis in pairs(Enum.Axis:GetEnumItems()) do	local A = axis.Name	local points, toDelete = numbersDifference(corner1(block1)[A], corner2(block1)[A], corner1(block2)[A], corner2(block2)[A])	axisDifferences[axis] = {points = points, toDelete = toDelete}	if #points == 2 then --Or equivalently if toDelte == nil, i.e. block2 and block1 do not intersect.	return { block1:Clone() }	end	end	-- Slice along axes after verifying that none are non-interesecting	for _, axis in pairs(Enum.Axis:GetEnumItems()) do	local A = axis.Name	local differences = axisDifferences[axis]	local points: {number}, toDelete = differences.points, differences.toDelete	print(points)	--Slices are to be made between each point, but slicePart expects a list of points to slice AT, not two points to slice BETWEEN.	--Fix this by removing first and last points.	table.remove(points, #points)	table.remove(points, 1)	--Skip if (block1 - block2) == block1, i.e. block2 is completely outside block 1	if #points == 0 then continue end	--Slice block1 according to where it intersects block2	local slices = slicePart(block1, axis, points)	--Return all the slices except the ones that are inside block2	for i = 1, #slices do	if i == toDelete then continue end	table.insert(parts, slices[i])	end	end	return parts end 

Part 1

Problem description

First of all, I’m assuming the blocks will always be axis-aligned (i.e. not rotated).

What you want to find is A - B, where A is the grey block and B is the red one. As you’ve shown it, B fits nicely inside A, but that might not always be the case. We might as well solve the problem in a more general case where only some of B overlaps A, or even where they’re not even touching. In that case, what you want to do is remove from A the volume that is shared between A and B, which can be written as A ∩ B (a symbol from set theory meaning “intersection”). So the first step is to find A ∩ B.

Solution in 1D

It’s helpful to solve it first in 1D and then extend the solution to 3D, because 1D is much simpler and easier to think about. In 1D there are a few cases that must be handled differently.

I've illustrated all the cases below:

Case 1 (no overlap)
image

Case 2 (overlap in a point)
image

Case 3 (overlap in a segment)
image

Case 4 (A completely inside B)
image

You might wonder why there are not cases where A is to the left of B. That’s because is completely symmetric, i.e. A ∩ B = B ∩ A. For example this case…

is the same as case 1 except the colors and labels are switched, but because of that property of it’s the same as case 1.

Since we’re working in 1D the points are just numbers on the number line, so let’s create a function called numbersIntersection that finds A ∩ B where A=(p11, p12) and B=(p21, p22):

function numbersIntersection(p11, p12, p21, p22) -- ... something something -- return {point1 of A ∩ B, point 2 of A ∩ B} end 

The function will return a table containing 0, 1 or 2 points (which are numbers because 1D) that make up A ∩ B.

It's helpful to swap the parameters around to make sure they're always in the order described:
function minMax(v1, v2)	return math.min(v1, v2), math.max(v1, v2) end function numbersIntersection(p11, p12, p21, p22) --Ensure segments (p11, p12) and (p21, p22) go from small to large --Ensure p11 comes before p12 p11, p12 = minMax(p11, p12) --Ensure p21 comes before p22 p21, p22 = minMax(p21, p22) --Ensure (p11, p12) starts before (p21, p22) if p11 > p21 then --... by swapping if that's not the case p11, p12, p21, p22 = p21, p22, p11, p12 end -- ... return something end 

-

And now it's just a matter of returning the right segment in each of the 4 cases:
function numbersIntersection(p11, p12, p21, p22) --Ensure segments (p11, p12) and (p21, p22) go from small to large --Ensure p11 comes before p12 p11, p12 = minMax(p11, p12) --Ensure p21 comes before p22 p21, p22 = minMax(p21, p22) --Ensure (p11, p12) starts before (p21, p22) if p11 > p21 then --... by swapping if that's not the case p11, p12, p21, p22 = p21, p22, p11, p12 end if p12 < p21 then --End of segment 1 comes before start of segment 2 return {} --No overlap elseif p12 == p21 then --End of segment 1 is same as start of segment 2 return {p12} --Overlaps in a point elseif p12 > p21 then --End of segment 1 inside segment 2 if p12 < p22 then --Start of segment 1 before start of segment 2 return {p21, p12} --Some overlapping segment else --Start of segment 1 inside segment 2 return {p21, p22} --Entirity of (p21, p22) is contained in (p11, p12) end end end 

Solution in 2D

The intersection of two 1D segments was (at most) a 1D segment made up of two numbers.

Likewise, the intersection of two 2D rectangles is (at most) a 2D rectangle made up of 2 1D segments at right angles (because we know they’re not rotated), or 4 numbers (start and end on X axis, start and end on Y axis). Once again because we know the rectangles are axis aligned we don’t actually need all 4 points, we can figure out the rest if we just have 2 opposing corners.

The 2D solution illustrated:

The intersection is the rectangle made up of the two segments (projected onto the X and Y axes in this illustration). We can find those segments as the intersections of the vertical and horizontal parts of the rectangles.

Solution in 3D

The intersection of two 3D cuboids is at most a 3D cuboid made up of 3 1D segments at right angles, or 6 numbers. Once again we only need the two opposing corners. We can find those segments as the intersections of the vertical, horizontal and depth-wise parts of the cuboids.

In code that looks like this (full script):
 function corner1(part)	return part.Position - part.Size / 2 end function corner2(part)	return part.Position + part.Size / 2 end function minMax(v1, v2)	return math.min(v1, v2), math.max(v1, v2) end function avg(v1, v2)	return (v1 + v2) / 2 end function numbersIntersection(p11, p12, p21, p22)	--Ensure segments (p11, p12) and (p21, p22) go from small to large	--Ensure p11 comes before p12	p11, p12 = minMax(p11, p12)	--Ensure p21 comes before p22	p21, p22 = minMax(p21, p22)	--Ensure (p11, p12) starts before (p21, p22)	if p11 > p21 then	--... by swapping if that's not the case	p11, p12, p21, p22 = p21, p22, p11, p12	end	if p12 < p21 then	return {} --No overlap	elseif p12 == p21 then	return {p12} --Overlaps in a point	elseif p12 > p21 then	if p12 < p22 then	return {p21, p12} --Some overlapping segment	else	return {p21, p22} --Entirity of (p21, p22) is contained in (p11, p12)	end	end end function partFromCorners(corner1, corner2, part)	local part = part or Instance.new("Part")	local diff = corner2 - corner1	local mid = avg(corner1, corner2)	part.Size = Vector3.new(diff.X, diff.Y, diff.Z)	part.Position = Vector3.new(mid.X, mid.Y, mid.Z)	return part end function volumesIntersection(block1: Part, block2: Part)	local b1c1, b1c2 = corner1(block1), corner2(block1)	local b2c1, b2c2 = corner1(block2), corner2(block2)	local xIntersection = numbersIntersection(b1c1.X, b1c2.X, b2c1.X, b2c2.X)	local yIntersection = numbersIntersection(b1c1.Y, b1c2.Y, b2c1.Y, b2c2.Y)	local zIntersection = numbersIntersection(b1c1.Z, b1c2.Z, b2c1.Z, b2c2.Z)	if #xIntersection == 2 and #yIntersection == 2 and #zIntersection == 2 then	local corner1 = Vector3.new(xIntersection[1], yIntersection[1], zIntersection[1])	local corner2 = Vector3.new(xIntersection[2], yIntersection[2], zIntersection[2])	local intersection= block1:Clone()	partFromCorners(corner1, corner2, intersection)	return intersection	end end 

Part 1 results

Testing it I get this:

image

... with this code:
local a, b = game.Workspace.A, game.Workspace.B local u = volumesIntersection(a, b) u.Name = "A∩B" u.Color = Color3.fromRGB(255, 0, 255) u.Parent = game.Workspace 
4 Likes

Part 2

Problem description

Now that we can calculate A ∩ B, it’s not so difficult to calculate A - B.

Solution in 1D

In the case where there’s no overlap, the result of the operation is to just leave A be, because B doesn’t “take a bite” out of it.

That leaves these 3 cases:

Cases 1 and 2: A is cut into 2 segments, where either the first or last one is deleted.

Case 3: A gets cut into 3 segments where the middle one is deleted.
image

Case 4: A is entirely eaten by B so there are 0 segments left:
image

Solution in 2D

Let’s look at case 1 in 2D:

If we were to think of how we’d apply the 1D solution to figure out the 4 - 1 = 3 rectangles to create in 2D, we’d first cut A into 2 rectangles along one axis, leave one of them be and then cut the other into 2 rectangles on the other axis. This gives us 3 different parts of which 1 must be deleted.

Solution in 3D

To create the parts we need in 3D we use the same logic: cut first on one axis, then another axis, and then the final axis:

image

In this image I first cut the part on the Y axis, then one of the horizontal axes and then the other. So first I turned the blue box that was the entire part into 2 boxes, coloring the top one green. Then I cut the green in two, coloring one of the parts yellow. Then I cut the yellow box in 2 and deleted one of the yellow parts.

Examples

A similar logic applies for case 3 where the middle must be removed. Notice that the 3 cases can be mixed and matched between the 3 axes, e.g.:

Cutting a hole in a part:

image

3 blue segments, middle gets deleted. 3 green segments, middle gets deleted. 0 yellow segments because B completely ate A on that axis.

Cutting a notch into a part:

B only ate one end of A on that axis:

image

1D code

For the same reasons as in part 1, we fix the order of the parameters so they’re always right. Except we don’t swap which segment is which, because A - B ~= B - A.

Swapping
function numbersSub(p11, p12, p21, p22)	--Ensure segments (p11, p12) and (p21, p22) go from small to large	--Ensure p11 comes before p12	p11, p12 = minMax(p11, p12)	--Ensure p21 comes before p22	p21, p22 = minMax(p21, p22) ... end 
Then we handle the situations where everything or nothing gets eaten:
function numbersSub(p11, p12, p21, p22)	--Ensure segments (p11, p12) and (p21, p22) go from small to large	--Ensure p11 comes before p12	p11, p12 = minMax(p11, p12)	--Ensure p21 comes before p22	p21, p22 = minMax(p21, p22)	--Nothing or everything gets deleted	local intersection= numbersIntersection(p11, p12, p21, p22)	if #intersection== 0 then --No overlap	return {p11, p12} --Just return first segment	elseif intersection[1] == p11 and intersection[2] == p12 then --Overlap is entire first segment	return {} --Nothing left	end ... 
and finally we handle the 3 cases:
function numbersSub(p11, p12, p21, p22)	--Ensure segments (p11, p12) and (p21, p22) go from small to large	--Ensure p11 comes before p12	p11, p12 = minMax(p11, p12)	--Ensure p21 comes before p22	p21, p22 = minMax(p21, p22)	--Nothing or everything gets deleted	local intersection= numbersIntersection(p11, p12, p21, p22)	if #intersection== 0 then --No overlap	return {p11, p12} --Just return first segment	elseif intersection[1] == p11 and intersection[2] == p12 then --Overlap is entire first segment	return {} --Nothing left	end	--Something but not everything gets deleted	if p21 <= p11 then -- (p21, p22) starts before (p11, p12) and ends inside	--Only 1 case because if p22 were >= p12, everything would get deleted and that's checked previously	return {p11, p22, p12}, 1 --First part gets deleted, second part stays	elseif p22 >= p12 then -- (p21, p22) starts inside (p11, p12) and ends outside then	--Only 1 case because if p21 were <= p11, everything would get deleted and that's checked previously	return {p11, p21, p12}, 2 --Second part gets deleted, first part stays then	else --Second segment is inside first segment, so cut out middle part	return {p11, p21, p22, p12}, 2 --Middle gets deleted, other parts stay	end end 

3D code

Now we need a way of cutting a block into smaller blocks on any axis. Here's a function that takes a part, an axis to slice along, and points along that axis to slice at:
function slicePart(part: Part, axis: Enum.Axis, points)	local slices = {}	local cutAxis = Vector3.FromAxis(axis)	local cutPlane = Vector3.new(1, 1, 1) - cutAxis	local sCorner1 = corner1(part)	local sCorner2 = sCorner1 * cutAxis + corner2(part) * cutPlane --Gets zero'd on the cut axis (so it corner2 on cut axis = corner 1 on cut axis)	for i = 1, #points do	sCorner2 += points[i] * cutAxis - sCorner1 * cutAxis --Slide corner2 to point[i] on the cut axis	table.insert(slices, partFromCorners(sCorner1, sCorner2, part:Clone()))	sCorner1 += (sCorner2 - sCorner1) * cutAxis --Slide corner1 to corner2 on the cut axis	end	sCorner2 = corner2(part)	table.insert(slices, partFromCorners(sCorner1, sCorner2, part:Clone()))	return slices end 

It keeps track of two corners to create the slices at. They both start off at one end of the part, but still opposite each other (i.e. as opposite corners on one face of the part). The slice is then “walked along” the axis, first moving sCorner2 to the next point, then creating the slice, then moving sCorner1 up to sCorner2 along the cut axis, kind of like a worm or something.

Aaaaand the logic for slicing `A` and deleting the part inside `B` looks like this:
function volumesSub(block1: Part, block2: Part)	local b1c1, b1c2 = corner1(block1), corner2(block1)	local b2c1, b2c2 = corner1(block2), corner2(block2)	local parts = {}	for _, axis in pairs(Enum.Axis:GetEnumItems()) do	local A = axis.Name	local points, toDelete = numbersSub(b1c1[A], b1c2[A], b2c1[A], b2c2[A])	--Slices are to be made between each point, but slicePart expects a list of points to slice AT, not two points to slice BETWEEN.	--Fix this by removing first and last points.	table.remove(points, #points)	table.remove(points, 1)	--Skip if (block1 - block2) == block1, i.e. block2 is completely outside block 1	if #points == 0 then continue end	--Slice block1 according to where it intersects block2	local slices = slicePart(block1, axis, points)	--Return all the slices except the ones that are inside block2	for i = 1, #slices do	if i == toDelete then continue end	table.insert(parts, slices[i])	end	end	return parts end 

Part 2 results

Testing it like this:
local a, b, c, d = game.Workspace.A, game.Workspace.B, game.Workspace.C, game.Workspace.D local u = volumesIntersection(a, b) u.Name = "A∩B" u.Color = Color3.fromRGB(255, 0, 255) u.Parent = game.Workspace local s = volumesSub(c, d) for _, part in pairs(s) do	part.Parent = game.Workspace	part.Transparency = 0 end 
I get these results:

image

image

image

3 Likes

How would I make it so the s parts wont collide with eachother, without re cutting them?. would be highly appreciated :smiley:

image

Ah, looks like a bug :sweat_smile: I’ll take a look when I have time

1 Like

Yeah, there was a bug where it kept reusing the original block to slice each axis, when it should have been using the results of slicing from the previous axis.

Here's a fixed version:
function corner1(part)	return part.Position - part.Size / 2 end function corner2(part)	return part.Position + part.Size / 2 end function minMax(v1, v2)	return math.min(v1, v2), math.max(v1, v2) end function avg(v1, v2)	return (v1 + v2) / 2 end --[[ Given two number ranges (p11, p12) and (p21, p22), return the intersection of those ranges. ]] function numbersIntersection(p11: number, p12: number, p21: number, p22: number): {number}	--Ensure segments (p11, p12) and (p21, p22) go from small to large	--Ensure p11 comes before p12	p11, p12 = minMax(p11, p12)	--Ensure p21 comes before p22	p21, p22 = minMax(p21, p22)	--Ensure (p11, p12) starts before (p21, p22)	if p11 > p21 then	--... by swapping if that's not the case	p11, p12, p21, p22 = p21, p22, p11, p12	end	if p12 < p21 then	return {} --No overlap	elseif p12 == p21 then	return {p12} --Overlaps in a point	elseif p12 > p21 then	if p12 < p22 then	return {p21, p12} --Some overlapping segment	else	return {p21, p22} --Entirity of (p21, p22) is contained in (p11, p12)	end	else	error()	end end --[[ Given two number ranges (p11, p12) and (p21, p22), return the second range removed from the first. ]] function numbersDifference(p11: number, p12: number, p21: number, p22: number): {number}	--Ensure segments (p11, p12) and (p21, p22) go from small to large	--Ensure p11 comes before p12	p11, p12 = minMax(p11, p12)	--Ensure p21 comes before p22	p21, p22 = minMax(p21, p22)	--Nothing or everything gets deleted	local intersection = numbersIntersection(p11, p12, p21, p22)	if #intersection == 0 then --No overlap	return {p11, p12} --Just return first segment	elseif intersection[1] == p11 and intersection[2] == p12 then --Overlap is entire first segment	return {} --Nothing left	end	--Something but not everything gets deleted	if p21 <= p11 then -- (p21, p22) starts before (p11, p12) and ends inside	--Only 1 case because if p22 were >= p12, everything would get deleted and that's checked previously	return {p11, p22, p12}, 1 --First part gets deleted, second part stays	elseif p22 >= p12 then -- (p21, p22) starts inside (p11, p12) and ends outside then	--Only 1 case because if p21 were <= p11, everything would get deleted and that's checked previously	return {p11, p21, p12}, 2 --Second part gets deleted, first part stays then	else --Second segment is inside first segment, so cut out middle part	return {p11, p21, p22, p12}, 2 --Middle gets deleted, other parts stay	end end --[[ Return a non-rotated Part with Size and Position defined by two corners. Optionally set the Size and Position of an existing Part, or use a new Part. ]] function partFromCorners(corner1: Vector3, corner2: Vector3, existingPart: Part?): Part	local part = existingPart or Instance.new("Part")	local diff = corner2 - corner1	local mid = avg(corner1, corner2)	part.Size = Vector3.new(diff.X, diff.Y, diff.Z)	part.Position = Vector3.new(mid.X, mid.Y, mid.Z)	return part end --[[ Given a non-rotated Part, an Axis and a list of points along that axis (as numbers),	return the slices of the Part on the plane perpendicular to the axis and intersecting the points	along the axis, as a list of Parts. ]] function slicePart(part: Part, axis: Enum.Axis, points: {number})	local slices = {}	local cutAxis = Vector3.FromAxis(axis)	local cutPlane = Vector3.new(1, 1, 1) - cutAxis	local sCorner1 = corner1(part)	local sCorner2 = sCorner1 * cutAxis + corner2(part) * cutPlane --Gets zero'd on the cut axis (so it corner2 on cut axis = corner 1 on cut axis)	for i = 1, #points do	sCorner2 += points[i] * cutAxis - sCorner1 * cutAxis --Slide corner2 to point[i] on the cut axis	table.insert(slices, partFromCorners(sCorner1, sCorner2, part:Clone()))	sCorner1 += (sCorner2 - sCorner1) * cutAxis --Slide corner1 to corner2 on the cut axis	end	sCorner2 = corner2(part)	table.insert(slices, partFromCorners(sCorner1, sCorner2, part:Clone()))	return slices end --[[ Given two volumes (as Parts), return the intersection of those volumes (as a Part). Input parts must have no rotation. ]] function volumesIntersection(block1: Part, block2: Part): Part?	local b1c1, b1c2 = corner1(block1), corner2(block1)	local b2c1, b2c2 = corner1(block2), corner2(block2)	local xDifference = numbersDifference(b1c1.X, b1c2.X, b2c1.X, b2c2.X)	local yDifference = numbersDifference(b1c1.Y, b1c2.Y, b2c1.Y, b2c2.Y)	local zDifference = numbersDifference(b1c1.Z, b1c2.Z, b2c1.Z, b2c2.Z)	--If any axis is non-intersecting, the intersection is empty	if #xDifference ~= 2 and #yDifference ~= 2 and #zDifference ~= 2 then return nil end	--If all axes are intersecting, return the 3D intersection	local corner1 = Vector3.new(xDifference[1], yDifference[1], zDifference[1])	local corner2 = Vector3.new(xDifference[2], yDifference[2], zDifference[2])	local difference = block1:Clone()	partFromCorners(corner1, corner2, difference)	return difference end --[[ Given two volumes (as Parts), return the second volume removed from the first volume (as a list of Parts). Input parts must have no rotation. ]] function volumesDifference(block1: Part, block2: Part): {Part}	-- Compute the differences on each axis *before* slicing on the axes,	--	because we might exit early if one or more axes are non-overlapping	local axisDifferences = {}	for _, axis in pairs(Enum.Axis:GetEnumItems()) do	local A = axis.Name	local points, toDelete = numbersDifference(corner1(block1)[A], corner2(block1)[A], corner1(block2)[A], corner2(block2)[A])	if #points == 2 then --Or equivalently if toDelte == nil, i.e. block2 and block1 do not intersect, so (block1 - block2) = block1	return { block1:Clone() }	end	axisDifferences[axis] = {points = points, toDelete = toDelete}	end	-- Slice along axes after verifying that none are non-interesecting	local parts = { }	local partToSlice = block1	for _, axis in pairs(Enum.Axis:GetEnumItems()) do	local A = axis.Name	local differences = axisDifferences[axis]	local points: {number}, toDelete: number = differences.points, differences.toDelete	--Slices are to be made between each point, but slicePart expects a list of points to slice AT, not two points to slice BETWEEN.	--Fix this by removing first and last points.	table.remove(points, #points)	table.remove(points, 1)	--Skip if (block1 - block2) == block1, i.e. block2 is completely outside block 1	if #points == 0 then	continue	end	--Slice block1 according to where it intersects block2	local slices = slicePart(partToSlice, axis, points)	--Return all the slices except the ones that are inside block2	for i = 1, #slices do	if i == toDelete then	partToSlice = slices[i]	else	table.insert(parts, slices[i])	end	end	end	return parts end return {	volumesIntersection = volumesIntersection,	volumesDifference = volumesDifference } 
1 Like

Hello, why doesn’t the volumeintersect work? Could you further explain how it works?