Here’s a way that I think should give accurate results (within the limits of floating point accuracy of course). I can’t think of any reason why this wouldn’t work and based on my testing so far, it does seem to work.
Firstly, we can simplify this by doing the check for one axis of the block at a time. If the cylinder is at least partially outside on at least one axis of the block, then it is at least partially outside the block. It is completely inside only if it’s completely inside on every axis of the block.
Now, for a given axis, we can lower this to a one-dimensional comparison problem. In order to do so, we want to find out the length of the cylinder on this axis. With this I mean twice the distance between the cylinder center and one of the points that are furthest away from the cylinder center in the direction of this axis.
In the image below, this length is twice the sum of the lengths of the blue and red dashed lines. For each colored line segment, there’s a text of the same color that tells how the length of that segment can be represented using the variables in the code. The angle α is the angle between the block face normal and the normal of a circular face of the cylinder.
If the direction of the face normal was opposite from what it is in the image, the angle between the vectors would be 180° - α and the cosine would be -cos(α). To get the correct length, we need to multiply the cylinder’s length along its own center axis with the absolute value of the cosine and the cylinder’s diameter (diameter of the circular faces) with the absolute value of the sine of the angle and them sum these together. That’s where the variable names cosineAbs and sineAbs come from. Actually, the unsigned angle between vectors is always in the interval [0, 180°] so its sine is always non-negative by definition. So sineAbs could actually be called just sine.
Calculating the length of the blue line segment in the way shown in the image works because the cylinder is symmetrical about its own center axis and thus its rotation around its own axis doesn’t matter which means that the RightVector tells everything relevant about its orientation.
Now, how do we calculate the sine and cosine? The dot product of two vectors is the cosine of the angle between them multiplied by their lengths.

So for two unit vectors, the dot product is just the cosine. Because cosine and sine are coordinates on the unit circle, the sum of their squares is 1 according the the Pythagorean theorem. From this, we get two alternative solutions for the sine.

Because these solutions are opposites of each other, we get the absolute value of sine by picking the non-negative solution. And because the square of a number and the square of its opposite are the same, it doesn’t matter whether we use the cosine or its absolute value when calculating the sine.
After calculating the length, we just do a simple one-dimensional comparison to see whether the cylinder is completely inside the block in this direction.
Here’s the code. It contains the relevant function and also some code for testing.
--!strict
local RunService = game:GetService("RunService")
local function isCylinderInsideBlock(block: Part, cylinder: Part): boolean
if block.Shape ~= Enum.PartType.Block or cylinder.Shape ~= Enum.PartType.Cylinder then
error("At least one of the given parts has incorrect shape.")
end
for _, axis: Enum.Axis in Enum.Axis:GetEnumItems() do
local blockFaceNormalInBlockSpace: Vector3 = Vector3.FromAxis(axis)
local blockFaceNormalInWorldSpace: Vector3 = block.CFrame.Rotation * blockFaceNormalInBlockSpace
--== coordinates when the block face normal is used as a positive axis vector ==--
local blockCenterCoord: number = block.Position:Dot(blockFaceNormalInWorldSpace)
local cylinderCenterCoord: number = cylinder.Position:Dot(blockFaceNormalInWorldSpace)
--== sizes in the direction of the block face normal ==--
-- block
local blockSize: number = block.Size:Dot(blockFaceNormalInBlockSpace)
-- cylinder
-- cosineAbs and sineAbs are the absolute values of the cosine and sine of
-- the angle between the cylinder circular face normal and the block face normal.
local cylinderCircularFaceNormalInWorldSpace: Vector3 = cylinder.CFrame.RightVector
local cosineAbs: number = math.abs(cylinderCircularFaceNormalInWorldSpace:Dot(blockFaceNormalInWorldSpace))
local sineAbs: number = math.sqrt(1 - cosineAbs * cosineAbs)
local cylinderLength: number, cylinderDiameter: number = cylinder.Size.X, math.min(cylinder.Size.Y, cylinder.Size.Z)
local cylinderSize: number = cylinderLength * cosineAbs + cylinderDiameter * sineAbs
--== Checks ==--
local outsideOnNegativeAxisSide: boolean = cylinderCenterCoord - .5 * cylinderSize < blockCenterCoord - .5 * blockSize
local outsideOnPositiveAxisSide: boolean = cylinderCenterCoord + .5 * cylinderSize > blockCenterCoord + .5 * blockSize
if outsideOnNegativeAxisSide or outsideOnPositiveAxisSide then
return false
end
end
return true
end
local cylinderColorWhenCompletelyInside: Color3 = Color3.new(0, 1, 0)
local cylinderColorWhenNotCompletelyInside: Color3 = Color3.new(1, 0, 0)
local block: Part = Instance.new("Part")
block.Name = "EnclosingBlock"
block.Transparency = .25
block.Shape = Enum.PartType.Block
block.Anchored = true
block.Size = Vector3.new(20, 10, 30)
block.Position = .5 * block.Size + Vector3.new(10, 0, 5)
block.Parent = workspace
local cylinder: Part = Instance.new("Part")
cylinder.Name = "EnclosedCylinder"
cylinder.Shape = Enum.PartType.Cylinder
cylinder.Anchored = true
cylinder.Size = Vector3.new(5, 3, 3)
cylinder.Parent = workspace
RunService.PostSimulation:Connect(function()
local color: Color3 =
if isCylinderInsideBlock(block, cylinder)
then cylinderColorWhenCompletelyInside
else cylinderColorWhenNotCompletelyInside
cylinder.Color = color
end)