Detecting corners

Hi! For my game, I plan to have a corner peeking mechanic. Because there are going to be many maps, I don’t want to have to manually indicate every corner, so I’m looking to have some sort of code to detect where a corner in a hallway is so that its automatic and I can go from there.

But I’m not sure how to do that. My only idea was to use raycasts but I feel like, although it might work, it’s very inefficient. If there’s anything else that might be more suited to detecting this sort of thing, could somebody point me in its direction?

2 Likes

I think using the collection system that came out might help. And for each wall you could get It size and use It to calculate the corner positions and then check If player is near that position

1 Like

Well I’d be best if you naturally “map” this positions out because it can become quick tedious very quickly, but it would obviously be automated by some code of some sort.

But for edge/corner detection what we do is.

  • Send a ray in any direction to a surface to get the normal.
  • After getting the normal we make another raycast aligned to the face and do some funny vector math to get the rightvector/upvector aligned to the part and this ray is only limited to the part. And it should be placed far enough too.

EDIT: I stole some code and played around with it and it works pretty good.

local function mark(cf: CFrame, color: Color3)
	local part = Instance.new("Part")
	part.Anchored = true
	part.CanCollide = false
	part.Material = Enum.Material.Neon
	part.Size = Vector3.one * 0.5
	part.Transparency = 0.5
	part.Color = color or part.Color
	part.CFrame = cf
	part.Parent = workspace
	
	game.Debris:AddItem(part, 1)
end

type FaceData = {
	["Front" | "Back" | "Left" | "Right" | "Top" | "Bottom"]: { -- normalId works but i dont like enums..
		Length: number,
		CFrame: CFrame,
	}
}

local AXIS_VECTORS: { [string]: Vector3 } = {
	["Right"] = Vector3.xAxis,
	["Left"] = -Vector3.xAxis,
	["Top"] = Vector3.yAxis,
	["Bottom"] = -Vector3.yAxis,
	["Back"] = Vector3.zAxis,
	["Front"] = -Vector3.zAxis,
}

local function getFaceNameFromNormal(v: Vector3): string?
	for normalId, axis in AXIS_VECTORS do
		if axis == v then
			return normalId
		end
	end
	return nil
end

local function axis(axis: Vector3, size: Vector3): number
	return 
		math.abs(axis.X) * size.X +
		math.abs(axis.Y) * size.Y +
		math.abs(axis.Z) * size.Z
end

local function getHitFaceName(hit: BasePart, worldNormal: Vector3): string
	local localNormal = hit.CFrame:VectorToObjectSpace(worldNormal)
	local best, bestDot = "Top", -math.huge
	
	for normalId, axis in AXIS_VECTORS do
		local d = axis:Dot(localNormal)
		if d > bestDot then
			bestDot = d
			best = normalId
		end
	end
	
	return best
end

local function getFaceEdgesFromRaycast(raycastResult: RaycastResult): FaceData
	local results: FaceData = {}
	local hit, worldNormal = raycastResult.Instance, raycastResult.Normal
	local halfSize = hit.Size * 0.5

	local n = AXIS_VECTORS[getHitFaceName(hit, worldNormal)]

	local u, v
	if n.X ~= 0 then
		u, v = Vector3.yAxis, Vector3.zAxis
	elseif n.Y ~= 0 then
		u, v = Vector3.xAxis, Vector3.zAxis
	else
		u, v = Vector3.xAxis, Vector3.yAxis
	end

	local nExtent, uExtent, vExtent =
		axis(n, halfSize), axis(u, halfSize), axis(v, halfSize)

	local faceCenter = n * nExtent

	local c1 = faceCenter + u * uExtent + v * vExtent
	local c2 = faceCenter + u * uExtent - v * vExtent
	local c3 = faceCenter - u * uExtent - v * vExtent
	local c4 = faceCenter - u * uExtent + v * vExtent
	
	local edges = {
		{ from = c1, to = c2, borders = getFaceNameFromNormal(u) },
		{ from = c2, to = c3, borders = getFaceNameFromNormal(-v) },
		{ from = c3, to = c4, borders = getFaceNameFromNormal(-u) },
		{ from = c4, to = c1, borders = getFaceNameFromNormal(v) },
	}

	for _, edge in edges do
		local localMid = (edge.from + edge.to) / 2
		local localDir = edge.to - edge.from
		local length = localDir.Magnitude

		local localCFrame = CFrame.lookAt(localMid, localMid + localDir.Unit, n)
		local worldCFrame = hit.CFrame * localCFrame

		results[edge.borders] = {
			Length = length,
			CFrame = worldCFrame,
		}

		mark(worldCFrame, Color3.new(0, 1, 0))
	end

	return results
end

while true do
	local result = workspace:Raycast(Vector3.zero, Vector3.new(0, 0, 1000))
	if result then
		print(getFaceEdgesFromRaycast(result))
	end
	
	task.wait(1)
end

3 Likes

My inclination would be to just do a few raycasts in the direction you’re facing, and check for the hit depth to left and right of center being higher than in front of you. I’d err on the side of accepting a false positive, if the worst thing that will happen just allowing you to peek when in some places you wouldn’t expect to.

If it’s really critical to gameplay to only have the peek option for specific corner geometries, than the only solutions are manual tagging or more comprehensive tests that use more raycasts or shapecasts. But i’d make something quick and dirty first and just see if it’s good enough.

2 Likes

What I ended up doing was very similar to this, with having three raycasts sent out from the player’s HumanoidRootPart (one directly forward, one slightly to the left, one slightly to the right). They have limits on how far they reach.

The way I currently have it set up is that if the left raycast or right raycast is nil while the other two aren’t, then it detects a corner on the nil side. This seems to work for my purposes right now, but it’s possible that I’ll have to edit it in the future if issues come up.

Thank you to everyone who contributed to this thread! I appreciate the help.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.