How do you find the side of a part using raycasting?

There is a bug in my droplet system where the droplets always have an Orientation of Vector3.new(0,0,0).
The droplet system works by raycasting at the desired direction and creating a droplet where the ray detects a part.

I can’t find a way to make them land on a part and have the same orientation as the surface they landed on, I tried setting their orientation to the part’s orientation, but that would only work if they landed on the top surface of the part.

6 Likes

This is answering the literal title of your post.

IT’S MATH TIME!

Part of what is returned by a raycasting operation is a normal vector, which is a directional unit vector that faces straight out of whatever face was hit by the raycast.

If you simply want to find the side of the part we hit, we can compare the normal vector we hit to the vectors that make up the face normals of the part and return the face for the given side.

This leaves us a question: how would we compare two vectors to see how similar they are? Well, a property of Vector Dot products will help us out. Given the dot product of two unit vectors, the result will be 1 if the vectors are parallel. So, we can see if the dot product of the raycast result’s normal vector to each face’s normal vector on the part is almost equal to 1. (We check that they are “almost equal” instead of “equal” due to floating point precision errors, you can read more about that here.)

Okay, how do we get the normal vector for each face? Simple, we just do some transformation math. Roblox provides us a constructor for getting the normal from a face here. However, if you used this alone, you would see that these would only work with parts with no rotation. This is because these vectors are oriented in ‘local space,’ or relative to the unrotated part. In order to convert the normal vector to world space, we can use a handy function on the CFrame called “VectorToWorldSpace.” This function will apply the part’s orientation to the vector using complex linear algebra, and get us the normal vector relative to the part’s actual rotation in the world.

In aggregate, here’s how we would do such a thing…


-- Untested!

--[[**
   This function returns the face that we hit on the given part based on
   an input normal. If the normal vector is not within a certain tolerance of
   any face normal on the part, we return nil.

    @param normalVector (Vector3) The normal vector we are comparing to the normals of the faces of the given part.
    @param part (BasePart) The part in question.

    @return (Enum.NormalId) The face we hit.
**--]]
function NormalToFace(normalVector, part)

    local TOLERANCE_VALUE = 1 - 0.001
    local allFaceNormalIds = {
        Enum.NormalId.Front,
        Enum.NormalId.Back,
        Enum.NormalId.Bottom,
        Enum.NormalId.Top,
        Enum.NormalId.Left,
        Enum.NormalId.Right
    }    

    for _, normalId in pairs( allFaceNormalIds ) do
        -- If the two vectors are almost parallel,
        if GetNormalFromFace(part, normalId):Dot(normalVector) > TOLERANCE_VALUE then
            return normalId -- We found it!
        end
    end
    
    return nil -- None found within tolerance.

end

--[[**
    This function returns a vector representing the normal for the given
    face of the given part.

    @param part (BasePart) The part for which to find the normal of the given face.
    @param normalId (Enum.NormalId) The face to find the normal of.

    @returns (Vector3) The normal for the given face.
**--]]
function GetNormalFromFace(part, normalId)
    return part.CFrame:VectorToWorldSpace(Vector3.FromNormalId(normalId))
end

EDIT:
Added missing documentation to first function in code sample.

63 Likes

Thanks for the help, that worked.
I thought rays only had the part and position parameters.

Yeah, it’s neat. You can also see the material of the part!

You can find all of the delicious documentation on the Roblox Dev Hub!

4 Likes

uhh, sorry if to bother and if I sound dumb, but how can I implement this into my game?

I’m unclear exactly what you are asking; the explanation is comprehensive and an example implementation is given in code with in-line documentation.

Could you clarify?

Well I put your script into a part and tried to use a raycast script that I had to patch them together, but I couldn’t figure it out.

sorry for bump lol

if Raycast then
	if Raycast.Normal == GetNormalFromFace(Raycast.Instance, Enum.NormalId.Front) then
		-- Face is the front
	else
		-- Face is not the front
	end
end
1 Like