Detecting which side of a part is touched

I am trying to create a way to apply blood decals to a player if they hit the ground too hard, but only on specific sides of their limbs based on where they hit. I’ve done a similar thing before but with raycasted guns, so i tried to do a similar thing where i would create a raycast in the air to get the face that hit but it didn’t work. Here is the code i am using to apply the decals

function GetNormalFromFace(part, normalId)
		return part.CFrame:VectorToWorldSpace(Vector3.FromNormalId(normalId))
	end

	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

	function gore.LimbBloodSplatter(player, bodypart, normal)
		local decalclone = script.BloodDecals["Decal"..math.random(1, 6)]:Clone()
		decalclone.Parent = bodypart
		decalclone.Transparency = 1

		local face = NormalToFace(normal, bodypart)

		decalclone.Face = face

		tweenservice:Create(decalclone, TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut), {Transparency = 0}):Play()
	end

If this is only for blocks why not just take the maximum component of the normal vector? For example (0.3, -0.4, 0.3) is closest to being the normal of the bottom surface, -Y, since the Y axis has the highest magnitude and is negative.

please explain further, i dont understand

You’re looping through a list of all possible normals on a block, checking the dot product and accepting that as the normal if its “close enough”, basically snapping it to the exact normal. Instead you could do this

function SnapNormal(norm)
	if math.abs(norm.X) > math.max(math.abs(norm.Y), math.abs(norm.Z) then
		return Vector3.xAxis * math.sign(norm.X)
	elseif math.abs(norm.Y) > math.abs(norm.Z) then
		return Vector3.yAxis * math.sign(norm.Y)
	else
		return Vector3.zAxis * math.sign(norm.Z)
	end
end

how would i get the normal though

The input normal? I thought you were already raycasting and thus had a RaycastResult.Normal

it didnt work
idk what went wrong

Raycasting didn’t work? What do you mean?

sorry im playing splatoon
it was retturning an error

here is the error
image

This does not tell me anything as you’ve only posted part of the error (No traceback, the blue part), and have not included the exact code that was run to produce the error. Please attach the entire code and error output so I can determine the line numbers.

1 Like

mb bro

for _, bodypart in pairs(character:GetChildren()) do		
			if bodypart:IsA("BasePart") and not bodypart:FindFirstAncestorOfClass("Accessory") then
				
				bodypart.Touched:Connect(function(touchpart)
					if bodypart.Velocity.Magnitude > 20 and touchpart:FindFirstAncestorOfClass("Model") ~= character then
						local vector = (bodypart.Position + Vector3.new(0, 1, 0) - bodypart.Position)
						local raycast = workspace:Raycast(bodypart.Position + Vector3.new(0, 1, 0), vector * 2, RaycastParams.new())
							
						if raycast then
							goremodule.LimbBloodSplatter(player, bodypart, vector)
							print("mm")
						end
					end
				end)
			end
		end

local face = NormalToFace(normal, bodypart)

is setting face to nil, which is not allowed because it needs to be a Face enum. Your NormalToFace function only works if the normal you give it is already very close to being an exact face normal. You can use the SnapNormal function I showed you to make sure that normal is always an exact face normal, such as (1, 0, 0)

okay cool ill test it later (still playing splatoon)

this code doesnt work
image

There is just a missing parentheses:

if math.abs(norm.X) > math.max(math.abs(norm.Y), math.abs(norm.Z)) then
1 Like

Here is a test script that shows you what is happening:

function SnapNormal(norm)
	if math.abs(norm.X) > math.max(math.abs(norm.Y), math.abs(norm.Z)) then
		return Vector3.xAxis * math.sign(norm.X)
	elseif math.abs(norm.Y) > math.abs(norm.Z) then
		return Vector3.yAxis * math.sign(norm.Y)
	else
		return Vector3.zAxis * math.sign(norm.Z)
	end
end

function VecPretty(vec)
	return string.format("(%+8.3f,%+8.3f,%+8.3f)", vec.X, vec.Y, vec.Z)
end

--Test vector
for i = 1, 10 do
	local tv = Vector3.new(math.random() * 2.0 - 1.0, math.random() * 2.0 - 1.0, math.random() * 2.0 - 1.0).Unit
	local snapped = SnapNormal(tv)
	
	print(VecPretty(tv) .." -snap-> ".. VecPretty(snapped))
end
1 Like

is this something i can feed into the system as a direction or is it incorrectly written?

local direction = ((bodypart.Position + Vector3.new(0, 1, 0)) - bodypart.Position).Unit

The raycast is only returning nil and baseplate when i use this

This doesn’t make any sense, direction will always be (0, 1, 0)