Check if cylinder is completely enclosed in another part

Hi, I need help with checking if a cylinder part is completely inside another part that is a block shape. I want an efficient way that doesn’t use volumes. Currently I am thinking to get the two endpoints of the cylinder and find where the radii intersect with the surrounding box, but I do not know how to go about this. I know that a potential solution is to check several points located on the edge of the circle formed by the radius, but I want to see if there is a better approach.

Cylinder can be in any orientation and size

In this scenario, the cylinder can be thought of as a cuboid or part. I cannot think of any scenario where a cylinder could fit but not a cuboid with the same dimensions, and vice versa. Then, get the 8 vertices of the cuboid and check if it within the region defined by the large cuboid.

Edit: Actually, there is a case where a cylinder would fit, but not a cuboid. If you do not need absolute precision, I would just assume the cylinder to be a cuboid and do some simple math from there. If you need absolute precision, the algorithm will likely be pretty complex. I doubt there is an efficient solution, so I recommend using a heuristic to compute in a reasonable time.

1 Like

Hello. I do apologize for necroposting but seeing as how this doesn’t have a solution and I do have one, I will provide it.

The solution is to generate an approximation of vertices that represent the cylinder, then check to see if all of those vertices are contained in the larger part.

I made this function which does this for you. It supports any Part shape against any other Part shape with rotations of both the boundary and the part you are checking.

Here is the thread (you can increase the resolution of the vertices to get more accuracy as you need):

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.
image

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.
image

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)
1 Like