I’m working on a sandbox game where players can spawn various objects and move them using a move tool. The move tool calls workspace:JoinToOutsiders() once they finished moving them to automatically generate welds.

I am working on a “motor” object. The object has a HingeConstraint inside of it and an Attachment, and it waits until any other part gets welded to it. As soon as it gets welded, it creates an Attachment inside the newly-welded part and sets the HingeConstraint up. However the HingeConstraint only works when the part is welded on the correct face.

My question is: How do I get the face the new part is welded to?

First I came up with this abomination:

if
Weld.C0.Position.Z == first.Size.Z/2
and Weld.C1.Position.Z == -second.Size.Z/2
then
print("front")
elseif
Weld.C0.Position.Z == -first.Size.Z/2
and Weld.C1.Position.Z == second.Size.Z/2
then
print("back")
elseif
Weld.C0.Position.X == first.Size.X/2
and Weld.C1.Position.X == -second.Size.X/2
then
print("left")
elseif
Weld.C0.Position.X == -first.Size.X/2
and Weld.C1.Position.X == second.Size.X/2
then
print("right")
elseif
Weld.C0.Position.Y == -first.Size.Y/2
and Weld.C1.Position.Y == second.Size.Y/2
then
print("top")
elseif
Weld.C0.Position.Y == first.Size.Y/2
and Weld.C1.Position.Y == -second.Size.Y/2
then
print("bottom")
else
warn("unknown")
end

This works but after some testing I realized that it doesn’t account for rotation, so any kind of rotation makes this return an incorrect value and honestly I’m kind of lost at this point. I can’t think of a way to make it work with rotation. I think it could have something to do with converting the Weld’s CFrames to WorldPosition and doing some math then but I have no idea how.

If anyone has experience with this please help, thanks!

local function getWeldedFace(weld)
-- Get the parts involved in the weld
local part0 = weld.Part0
local part1 = weld.Part1
-- Convert Weld CFrames to World Positions
local weld0WorldPos = part0.CFrame:PointToWorldSpace(weld.C0.Position)
local weld1WorldPos = part1.CFrame:PointToWorldSpace(weld.C1.Position)
-- Calculate the relative position of the weld on each part
local relativePos0 = part0.CFrame:pointToObjectSpace(weld0WorldPos)
local relativePos1 = part1.CFrame:pointToObjectSpace(weld1WorldPos)
-- Determine the face based on relative position
local function getFace(part, relativePos)
local size = part.Size / 2
if math.abs(relativePos.X - size.X) < 0.01 then
return "Right"
elseif math.abs(relativePos.X + size.X) < 0.01 then
return "Left"
elseif math.abs(relativePos.Y - size.Y) < 0.01 then
return "Top"
elseif math.abs(relativePos.Y + size.Y) < 0.01 then
return "Bottom"
elseif math.abs(relativePos.Z - size.Z) < 0.01 then
return "Front"
elseif math.abs(relativePos.Z + size.Z) < 0.01 then
return "Back"
else
return "Unknown"
end
end
local face0 = getFace(part0, relativePos0)
local face1 = getFace(part1, relativePos1)
return face0, face1
end
-- Example usage
local weld = -- your weld instance here
local face0, face1 = getWeldedFace(weld)
print("Part0 is welded on its " .. face0 .. " face.")
print("Part1 is welded on its " .. face1 .. " face.")

After a while of experimenting with CFrames I came up with this solution, which works flawlessly:

local function GetNormals(part: BasePart)
return {
[Enum.NormalId.Front] = part.CFrame.LookVector,
[Enum.NormalId.Back] = -part.CFrame.LookVector,
[Enum.NormalId.Right] = part.CFrame.RightVector,
[Enum.NormalId.Left] = -part.CFrame.RightVector,
[Enum.NormalId.Top] = part.CFrame.UpVector,
[Enum.NormalId.Bottom] = -part.CFrame.UpVector
}
end
local function ClosestFace(part: BasePart, direction: Vector3)
local Normals = GetNormals(part)
local ClosestDot = -math.huge
local Result
for Face, Normal in next, Normals do
local Dot = Normal:Dot(direction)
if Dot > ClosestDot then
ClosestDot = Dot
Result = Face
end
end
return Result
end

You can then use the ClosestFace function like this:

local Face: Enum.NormalId = ClosestFace(part1, (part1.CFrame.p - part2.Position).Unit)