How would I negate block like parts, without using unions but instead splitting it into other parts as shown above?
How would I negate block like parts, without using unions but instead splitting it into other parts as shown above?
EDIT: Okay, I posted the second part that actually solves what you were asking for What a fun problem! Hope this is of some help.
EDIT: I swapped ∪ for ∩ >.<’ it’s fixed now tho
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
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
.
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.
Case 1 (no overlap)
Case 2 (overlap in a point)
Case 3 (overlap in a segment)
Case 4 (A completely inside B)
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
.
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
-
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
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.
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.
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
Testing it I get this:
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
Now that we can calculate A ∩ B
, it’s not so difficult to calculate A - B
.
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.
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.
Case 4: A is entirely eaten by B so there are 0 segments left:
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.
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:
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.
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:
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:
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
.
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
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
...
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
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.
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
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
How would I make it so the s parts wont collide with eachother, without re cutting them?. would be highly appreciated
Ah, looks like a bug I’ll take a look when I have time
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.
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
}
Hello, why doesn’t the volumeintersect work? Could you further explain how it works?