Problem with surface normals

Heya!!!

Currently im developing a game where each player gets the task to build a limb for a giant monster.

In the game the player gets a lot of freedom to customize their limb however they want. Theres just 1 problem… The method i use to retrieve the “Surface normal” of a part to position surface guis on doesnt work that well on cylinders/spheres.

Yes i could technically use the “Mouse.TargetSurface” to solve this issue but that also has a problem. Theres objects in the workspace that needs to be ignored. I cant just set “Mouse.TargetFilter” right now because that would break most of the game.

As you can see from these videos. The method i currently use works perfectly on cubes but on others well… not that well.

When hovering over a cube it prints out the normals numbers as ints.
image

But on others such as a sphere/cylinder it prints them as floats
image

Cube:

Cylinder/Sphere

Any help would be realy appreciated!! because as you can guess. Customization is the key component in the game so if i cant get this fixed then the game is basically dead.

Code i currently use to retreieve the surface normal of a part.

function Funcs:GetMouseTarget()
	local Params = RaycastParams.new()
	local Building = workspace.Assets.Building
	local Mouse = game.Players.LocalPlayer:GetMouse()
	local UnitRay = Mouse.UnitRay
	local Faces = {
		[Vector3.new(0,1,0)] = Enum.NormalId.Top,
		[Vector3.new(0,-1,0)] = Enum.NormalId.Bottom,
		[Vector3.new(0,0,-1)] = Enum.NormalId.Front,
		[Vector3.new(0,0,1)] = Enum.NormalId.Back,
		[Vector3.new(-1,0,0)] = Enum.NormalId.Left,
		[Vector3.new(1,0,0)] = Enum.NormalId.Right,
	}
	
	Params.FilterDescendantsInstances = {game.Players.LocalPlayer.Character,Building.Radius,Building.Objects.PrimaryPart,Building.Parent.Debris}
	Params.FilterType = Enum.RaycastFilterType.Exclude
	
	local A = workspace:Raycast(UnitRay.Origin,UnitRay.Direction*1000,Params)
	local Face = Faces[A.Normal] or Enum.NormalId.Front -- Returns "Front" as default to make sure it doesnt return nil.
	
	
	return A.Instance,A.Position,Face
end

Try setting all the parts to 0,0,0 for orientation?
You might have to “rotate” the vector based on the object’s orientation.

1 Like

This happens because raycasting respects the geometry of the part the ray is cast onto. If you need a specific NormalId there are a couple options:

1- use mouse.TargetSurface, but be aware that the mouse doesn’t always guarantee support for devices like phones.
2- use the dot product on your “goal” normal and the vector that corresponds to a NormalId, whichever dot product is the largest would be the surface your cursor is closest to.

local userInputService = game:GetService('UserInputService')

local normalIds = Enum.NormalId:GetEnumItems()

local normalVectors = {}

for _, normalId in normalIds do
	normalVectors[normalId] = Vector3.FromNormalId(normalId)
end

local function getClosestNormalId(vector: Vector3)
	local closestDot, closestNormalId = -math.huge, nil
	
	for _, normalId in normalIds do
		local otherVector = normalVectors[normalId]
		
		local dot = vector:Dot(otherVector) -- how close vector is to otherVector
		
		if closestDot < dot then
			closestDot, closestNormalId = dot, normalId
		end
	end
	
	return closestNormalId
end

while true do
	local mouseLocation = userInputService:GetMouseLocation()
	
	local ray = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
	
	local raycast = workspace:Raycast(ray.Origin, ray.Direction * 1000)
	
	if raycast then
		local resultCFrame = (raycast.Instance :: BasePart).CFrame
		
		local normalRelative = resultCFrame:VectorToObjectSpace(raycast.Normal).Unit -- this will be the normal relative to the object, so it should have the length of approx. 1 taking into account floating point imprecision. 
		-- ^you should be able to remove the index for the unit vector, however if you want it for contingency it shouldn't be a hindrance for performance
		
		local closestNormalId = getClosestNormalId(normalRelative)
		
		script.Parent.TextLabel.Text = `Mouse is over {closestNormalId.Name} surface.`
	end
	
	task.wait()
end

1 Like

You my man… Is a lifesaver!!! world/object space and math is like my achilles heel.

2 Likes

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