Interesting question. There’s a lot to unpack here.
First, let’s clarify the goal. We want to take two parts and find the minimum bounding box (not axis-align enforced) that contains both parts. More generally, find the minimum bounding box that contains an arbitrary point cloud.
There are two steps to answering this question:
- Write a function that will generate a bounding box given an arbitrary orientation
- Write a function that will pick the optimal orientation to minimize the bounding box.
I’m pretty confident I can answer #1 on the spot, but #2 is a bit more difficult.
Let’s talk about #1 for now:
When we do an axis-aligned bounding box (AABB) we simply get the max and min components of our point cloud in world space and then subtract.
local function getCorners(cframe: CFrame, size2: Vector3): {Vector3}
local corners = {}
for i = 0, 7 do
corners[i + 1] = cframe * (size2 * Vector3.new(
2 * (math.floor(i / 4) % 2) - 1,
2 * (math.floor(i / 2) % 2) - 1,
2 * (i % 2) - 1
))
end
return corners
end
local function getPointCloud(parts: {BasePart}): {Vector3}
local cloud = {}
for _, part in parts do
local corners = getCorners(part.CFrame, part.Size / 2)
for _, corner in corners do
table.insert(cloud, corner)
end
end
return cloud
end
local function getAABB(parts: {BasePart}): (CFrame, Vector3)
local cloud = getPointCloud(parts)
local maxV, minV = cloud[1], cloud[1]
for i = 2, #cloud do
local point = cloud[i]
maxV = maxV:Max(point)
minV = minV:Min(point)
end
return CFrame.new((maxV + minV) / 2), maxV - minV
end
To extend this to accept an arbitrary rotation we approach it the same way except we treat the target orientation as the axis-aligned space. After we’ve figured out our min and max we can convert back to real world space to find the center for calculating the CFrame.
local function getOBB(oreintation: CFrame, parts: {BasePart}): (CFrame, Vector3)
oreintation = oreintation.Rotation -- want X, Y, Z to be 0, 0, 0
local cloud = getPointCloud(parts)
for i = 1, #cloud do
cloud[i] = oreintation:PointToObjectSpace(cloud[i])
end
local maxV, minV = cloud[1], cloud[1]
for i = 2, #cloud do
local point = cloud[i]
maxV = maxV:Max(point)
minV = minV:Min(point)
end
local wMaxV = oreintation:PointToWorldSpace(maxV)
local wMinV = oreintation:PointToWorldSpace(minV)
return CFrame.new((wMaxV + wMinV) / 2) * oreintation, maxV - minV
end
Using the green part to set an arbitrary orientation in the above video
All that leaves is #2 which I’d need to think about, but hopefully this can help get you started.