I Recreated the Corner Clip Glitch [Free]

How to use:

Take this and place it inside of StarterPlayer → StarterCharacterScripts, and you’re ready to go!

Why make another one?

Having seen a few other people do this already, I still wanted to figure it out for myself and improve upon other resources. It was a fun, but surprisingly complex challenge. More detail about how I approached the problem is below.

The inner workings:

In short, this works by running a set of checks every physics step.

  • Is the player rotating quickly enough?
  • What parts is the player touching?
  • Can the player collide with these parts?
  • Do these parts have corners? (Are they spherical or cylindrical?)
  • Is the player touching a corner?
  • Is the player clipping inside the corner?
  • How do we teleport the player?

Is the player rotating quickly enough?

I decided to calculate this using frame differences. If we check the player’s orientation on one physics step, and then we compare its angle with the next, we can calculate a speed.

function System.GetRotationSpeed()
	return System.RootPart.CFrame.LookVector:Angle(System.LastLookDirection)
end

I want the required speed to be very high, since the typical corner clip would require the player to shift-lock or enter first person. So, I have set my required speed to an angle close to 180 degrees. This means that the player must complete a near half rotation in a single frame.

System.RotationSpeedRequirement = math.pi * 0.9

Comparing the speeds, we can reject if the speed is too low.

if System.GetRotationSpeed() < System.RotationSpeedRequirement then
	return
end

What parts is the player touching?

This is practically built-in.

for _, part in pairs(System.RootPart:GetTouchingParts()) do
	...
end

However, it is important to note that this won’t work until one of the two touched parts have a TouchInterest. To do this, I connected the player’s Touched event to some function. In my case, I left it empty.

System.RootPart.Touched:Connect(function() end)

Talk about some janky code.

Can the player collide with these parts?

This is also built-in, even taking collision groups into consideration.

System.RootPart:CanCollideWith(part)

This will only work if the player has collision on the checked part. I’m checking the HumanoidRootPart, and its collision is only disabled with R6. So, I have to force it on.

System.RootPart.CanCollide = true

Do these parts have corners?

Basic check for non-cornery parts…

part.Shape == Enum.PartType.Ball or part.Shape == Enum.PartType.Cylinder

Is the player touching a corner?

This is a tricky question to answer. We have to know where the corners are, and we have to know whether the player is making contact.

Calculating corners is always a fun challenge. I decided to make some points in a little cube, then multiply those points by the part size.

function System.GetCorners(cframe, size)
	local corners = {}
	
	for x = -1, 1, 2 do
		for y = -1, 1, 2 do
			for z = -1, 1, 2 do
				local corner = Vector3.new(x, y, z) * 0.5
				corner = cframe:PointToWorldSpace(corner * size)
				
				table.insert(corners, corner)
			end
		end
	end
	
	return corners
end

I multiplied by 0.5 because I need the cube to be 1x1x1 for size multiplication to work. If each point is halfway out from the center, their distances end up being 1.

I am going to get the corners of a given part’s CFrame and Size. I sort the corners by distance to only consider the closest one. Now for the important part, I compare the corner with a given point and flatten the y-axis. This means that a top corner and bottom corner will be treated the same. In fact, this allows me to treat corner points like full edges.

function System.IsTouchingCorner(cframe, size, position)
	local corners = System.GetCorners(cframe, size)
	
	table.sort(corners, function(a, b)
		local distanceA = (a - position).Magnitude
		local distanceB = (b - position).Magnitude
		
		return distanceA < distanceB
	end)

	local corner = corners[1]
	
	local difference = corner - position
	difference -= difference * Vector3.yAxis
	
	return difference.Magnitude < System.CornerDistance
end

Finally, I read the magnitude to check the distance to the corner.

This works because I am no longer taking height difference into consideration. It is like looking at a part from above: the player is touching a corner regardless of height from this perspective.

Like the rest of this post, this also has a caveat. This assumes that the part has not been flipped on its side. Since we are flattening it on the y-axis, a sideways part would be checking the wrong edges.

To fix this, you need to spend 5 hours trying different methods only to realize nothing worked because of a typo. I present to you, GetWorldPart.

function System.GetWorldPart(part)
	local upVector = System.GetWorldUpVector(part)
	local upVectorWorld = part.CFrame:VectorToWorldSpace(upVector)
	
	local rightVectorWorld = System.FindCrossVector(part, upVectorWorld)
	
	local cframe = CFrame.fromMatrix(part.Position, rightVectorWorld, upVectorWorld)

	local rightVectorRelative = part.CFrame:VectorToObjectSpace(cframe.RightVector)
	local upVectorRelative = part.CFrame:VectorToObjectSpace(cframe.UpVector)
	local lookVectorRelative = part.CFrame:VectorToObjectSpace(cframe.LookVector)
	
	local size = Vector3.zero

	size += (part.Size * rightVectorRelative).Magnitude * Vector3.xAxis
	size += (part.Size * upVectorRelative).Magnitude * Vector3.yAxis
	size += (part.Size * lookVectorRelative).Magnitude * Vector3.zAxis
	
	size = Vector3.new(
		math.abs(size.X),
		math.abs(size.Y),
		math.abs(size.Z)
	)
	
	return cframe, size
end

The idea is to rotate a part so that the upmost face becomes the actual top face, maintaining the size after the rotation. Normally any face can be the top, like the front face for example. In this example, it would return a representation of a rotated part such that the top face takes the place of the front face. The size would also be modified in a way that should look no different to the eye, despite the rotation.

I start by finding the top-most face. I sort all of the part’s faces by angle to the up direction, so that the first result should be the face with the lowest angle.

function System.GetWorldUpVector(part)
	local worldUp = Vector3.yAxis
	local localUp = part.CFrame:VectorToObjectSpace(worldUp)

	local faces = Enum.NormalId:GetEnumItems()
	local directions = {}

	for _, face in pairs(faces) do
		table.insert(directions, Vector3.fromNormalId(face))
	end

	table.sort(directions, function(a, b)
		return a:Angle(localUp) < b:Angle(localUp)
	end)

	return directions[1]
end

Then, I find out which unit vector is most likely the cross product given the up vector. This is a similar process to before, but this time, I am finding the vector with the closest angle to 90 degrees.

function System.FindCrossVector(part, upVector)
	local goalAngle = math.pi * 0.5
	
	local vectors = {
		part.CFrame.RightVector,
		part.CFrame.UpVector,
		part.CFrame.LookVector
	}
	
	table.sort(vectors, function(a, b)
		local angleDifferenceA = math.abs(goalAngle - a:Angle(upVector))
		local angleDifferenceB = math.abs(goalAngle - b:Angle(upVector))
		
		return angleDifferenceA < angleDifferenceB
	end)
	
	return vectors[1]
end

I chose this method over calculating it manually to preserve rotation. If I were to calculate the cross product using the up vector, I would be left guessing how the part might be rotated on that axis.

Now back to GetWorldPart, I create a new CFrame using the found vectors.

local cframe = CFrame.fromMatrix(part.Position, rightVectorWorld, upVectorWorld)

Not only is this the new position and orientation needed to fix the corner problem, but this can be used to calculate a new part size that will also be useful later for determining how far to teleport the player.

Using these new vectors, I get the part’s size in each direction, and then add them manually to the axes I need. When I am creating this new size, I do not necessarily know the new XYZ order, so I pull it by multiplying the sizes by directions.

local rightVectorRelative = part.CFrame:VectorToObjectSpace(cframe.RightVector)
local upVectorRelative = part.CFrame:VectorToObjectSpace(cframe.UpVector)
local lookVectorRelative = part.CFrame:VectorToObjectSpace(cframe.LookVector)
	
local size = Vector3.zero

size += (part.Size * rightVectorRelative).Magnitude * Vector3.xAxis
size += (part.Size * upVectorRelative).Magnitude * Vector3.yAxis
size += (part.Size * lookVectorRelative).Magnitude * Vector3.zAxis

Finally, I ensure that the new size is positive. When working with directions, you may sometimes find yourself using negative vectors.

size = Vector3.new(
	math.abs(size.X),
	math.abs(size.Y),
	math.abs(size.Z)
)

Back to the original question, is the player touching a corner? Now, we can do the same amount of work as before, and just call IsTouchingCorner with the new CFrame and Size we received from GetWorldPart. Thus, fixing the part rotation problem and only considering the most-vertical edges.

Is the player clipping inside the corner?

This is some classic collision math. Of course, I had to be a bit special and do it with fewer conditional statements.

function System.IsInsidePart(cframe, size, position)
	local relativePosition = cframe:PointToObjectSpace(position)
	local boundsPosition = relativePosition / size
	
	local total = math.abs(boundsPosition.X) + math.abs(boundsPosition.Y) + math.abs(boundsPosition.Z)
	
	local minimumClippingTotal = 1.5
	
	return total < minimumClippingTotal
end

By getting a position relative to the given part bounds, we can determine if the position is inside the box if each axis adds up to a specific number. The number in specific depends on the part size, so I divided by that size to undo it. At most, these axes should add up to 1.5. I know this because in an imaginary 1x1x1 box like we are working with, a given point can be at most 0.5 distance from the center in every direction before it reaches a corner.

How do we teleport the player?

Unfortunately, I don’t have nice code for you here. And it’s a shame that after all that blissful math, I couldn’t avoid doing teleportation manually. Maybe I’m just not seeing the clean method, but you let me know.

Anyway, here is what I came up with.

function System.TeleportOut(cframe, size, position)
	local relativePosition = cframe:PointToObjectSpace(position)
	local boundsPosition = relativePosition / size
	
	local direction = (boundsPosition - boundsPosition * Vector3.yAxis).Unit
	
	local teleportDirection = System.GetClosestDirection(direction)

	teleportDirection = Vector3.yAxis:Cross(teleportDirection)

	local teleportDistance = (size * teleportDirection).Magnitude
	local worldDirection = cframe:VectorToWorldSpace(teleportDirection)
	
	if relativePosition.X < 0 then
		teleportDistance *= -1
	end

	if relativePosition.Z < 0 then
		teleportDistance *= -1
	end

	if relativePosition.X > 0 and relativePosition.Z < 0 and teleportDirection.X < 0 then
		teleportDistance *= -1
	end

	if relativePosition.X < 0 and relativePosition.Z > 0 and teleportDirection.X > 0 then
		teleportDistance *= -1
	end

	if relativePosition.X < 0 and relativePosition.Z < 0 and teleportDirection.X < 0 then
		teleportDistance *= -1
	end

	if relativePosition.X > 0 and relativePosition.Z > 0 and teleportDirection.X > 0 then
		teleportDistance *= -1
	end
	
	System.RootPart.CFrame += worldDirection.Unit * teleportDistance
end

Given part bounds (CFrame and Size), I use a relative position to determine which direction to teleport. First, I get the direction to the part’s center, then I find the closest relative direction to that. I flatten it on the y-axis, since I never want to teleport up or down.

I have a set of predetermined teleport directions, since I don’t want to consider vertical teleportation.

System.TeleportDirections = {
	Vector3.xAxis,
	Vector3.xAxis * -1,
	Vector3.zAxis,
	Vector3.zAxis * -1
}

function System.GetClosestDirection(direction)
	table.sort(System.TeleportDirections, function(a, b)
		return a:Angle(direction) < b:Angle(direction)
	end)

	return System.TeleportDirections[1]
end

This method uses another angle sort to ensure that the first TeleportDirection is the one closest to a given direction.

Using that TeleportDirection, which is in object space, I cross it with the local up-vector to find a new direction that should slide along the wall instead of into it. The TeleportDirection actually represents which face is closest to the given position. And by using a cross product, I am able to find a direction perpendicular to that face.

And absolutely finally, I find the part’s size in that direction to determine how far to teleport in the world.

local teleportDistance = (size * teleportDirection).Magnitude
local worldDirection = cframe:VectorToWorldSpace(teleportDirection)

Now here is the annoying, quick and dirty part. I did a lot of trial-and-error testing to find edge cases where the direction was inverted. This was a result of calculating the cross vector, since there isn’t really any code determining whether to go forwards or backwards, left or right. So with some lazy debugging, here is the logic I threw together.

if relativePosition.X < 0 then
	teleportDistance *= -1
end

if relativePosition.Z < 0 then
	teleportDistance *= -1
end

if relativePosition.X > 0 and relativePosition.Z < 0 and teleportDirection.X < 0 then
	teleportDistance *= -1
end

if relativePosition.X < 0 and relativePosition.Z > 0 and teleportDirection.X > 0 then
	teleportDistance *= -1
end

if relativePosition.X < 0 and relativePosition.Z < 0 and teleportDirection.X < 0 then
	teleportDistance *= -1
end

if relativePosition.X > 0 and relativePosition.Z > 0 and teleportDirection.X > 0 then
	teleportDistance *= -1
end

System.RootPart.CFrame += worldDirection.Unit * teleportDistance

With that out of the way, that’s all I have to say.

Final thoughts?

Does this work in your games? Do you have any issues or suggestions? Let me know in a reply. Refer to the linked YouTube video at the top for extra information on limitations and settings.

26 Likes