How To Find Vector2 Position Relative To Vector3 Intersection Point?

I’m trying to create this script where a SurfaceGui attaches to a specific Vector3 point. This point is important because it is where the intersection (or collision) occurs when a part hits another part.

Everything works fine except I’m trying get it to stick where it lands. (Blue marked area).

Is there anyway this is possible?

Current first prototyped code:

local RayCastParams = RaycastParams.new()
local NormalIDs = {
    [Vector3.FromNormalId(Enum.NormalId.Top)] = "Top",
    [Vector3.FromNormalId(Enum.NormalId.Bottom)] = "Bottom",
    [Vector3.FromNormalId(Enum.NormalId.Front)] = "Front",
    [Vector3.FromNormalId(Enum.NormalId.Back)] = "Back",
    [Vector3.FromNormalId(Enum.NormalId.Right)] = "Right",
    [Vector3.FromNormalId(Enum.NormalId.Left)] = "Left",
}
local Part = script.Parent.BloodyPart
local Connection = nil

Connection = Part.Touched:Connect(function(Hit)
    if not Connection or Hit.Transparency ~= 0 or not Hit.CanCollide or Hit.Name == "Baseplate" then return end
    --print(Hit)
    Connection:Disconnect() 
    local UI = Part.SurfaceGui
    local x = math.clamp(math.random(Hit.Size.X,Hit.Size.X*50),10,200)
    local y = math.clamp(math.random(Hit.Size.Y,Hit.Size.Y*50),10,200)
    local AimAtCF = CFrame.new(Part.Position,Hit.Position)
    
    local raycastResult = workspace:Raycast(AimAtCF.Position,AimAtCF.LookVector*50, RayCastParams)
    
    if not raycastResult then return end

    UI.Face = Enum.NormalId[NormalIDs[raycastResult.Normal]]

    UI.Frame.Size = UDim2.new(.1,x,.1,y)
    UI.Frame.Rotation = math.random(-360,360)
    UI.Parent = Hit

    Part:Destroy()
end)

RayCastParams.FilterType = Enum.RaycastFilterType.Whitelist
RayCastParams.FilterDescendantsInstances = {workspace}
RayCastParams.IgnoreWater = true

Which is the Part and which is the Hit in your video? Edit: Oh I guess Part is the falling part because it’s destroyed.

The way I see it, the problem is broken down like this:

  1. Figure out which face you hit
  2. Project the 3D position of the bloody part onto the plane of that surface
  3. Convert the 3D projected point to a 2D point on the given SurfaceGui

That doesn’t seem too bad. I’ll take a crack at it.

Edit: The first part:

local function NearestNormalId(part: BasePart, pos: Vector3): Enum.NormalId
	local relative = part.CFrame:PointToObjectSpace(pos) / part.Size
	
	local x, y, z = relative.X, relative.Y, relative.Z
	local ax, ay, az = math.abs(x), math.abs(y), math.abs(z)
	
	if ax > ay and ax > az then
		return x > 0 and Enum.NormalId.Right or Enum.NormalId.Left
	elseif ay > ax and ay > az then
		return y > 0 and Enum.NormalId.Top or Enum.NormalId.Bottom
	else
		return z > 0 and Enum.NormalId.Back or Enum.NormalId.Front
	end
end
1 Like

I managed to throw together a hacky revised script for this using your method and it works perfectly if I use a proxy/dummy part to stick to that exact position.

Though originally I aimed to prevent having to use a separate part. But I just realized SurfaceGuis do not wrap around BaseParts like Decals do, and since Decals aren’t X,Y positional it would defeat my original goal.

local RayCastParams = RaycastParams.new()
local NormalSizeConstraints = {
    [Enum.NormalId.Top] = function(Part) return Vector3.new(Part.Size.X,0,Part.Size.Z) end,
    [Enum.NormalId.Bottom] = function(Part) return Vector3.new(Part.Size.X,0,Part.Size.Z) end,
    [Enum.NormalId.Front] = function(Part) return Vector3.new(Part.Size.X,Part.Size.Y,0) end,
    [Enum.NormalId.Back] = function(Part) return Vector3.new(Part.Size.X,Part.Size.Y,0) end,
    [Enum.NormalId.Left] = function(Part) return Vector3.new(0,Part.Size.Y,Part.Size.Z) end,
    [Enum.NormalId.Right] = function(Part) return Vector3.new(0,Part.Size.Y,Part.Size.Z) end,
}
local NormalOpposites = {
    [Enum.NormalId.Top] = Enum.NormalId.Bottom,
    [Enum.NormalId.Bottom] = Enum.NormalId.Top,
    [Enum.NormalId.Front] = Enum.NormalId.Back,
    [Enum.NormalId.Back] = Enum.NormalId.Front,
    [Enum.NormalId.Left] = Enum.NormalId.Right,
    [Enum.NormalId.Right] = Enum.NormalId.Left,
}
local FaceList = {
    "LookVector","UpVector","RightVector"
}
local Part = script.Parent.BloodyPart
local Connection = nil

local function CheckPart(Hit)
    if Hit.Transparency ~= 0 or Hit.Size.X < 1 or Hit.Size.Y < 1 or Hit.Size.Z < 1 then warn("Invalid Part") return end

    return true
end

local function NearestNormalId(part,pos)
    local relative = part.CFrame:PointToObjectSpace(pos) / part.Size

    local x, y, z = relative.X, relative.Y, relative.Z
    local ax, ay, az = math.abs(x), math.abs(y), math.abs(z)

    if ax > ay and ax > az then
        return x > 0 and Enum.NormalId.Right or Enum.NormalId.Left
    elseif ay > ax and ay > az then
        return y > 0 and Enum.NormalId.Top or Enum.NormalId.Bottom
    else
        return z > 0 and Enum.NormalId.Back or Enum.NormalId.Front
    end
end

Connection = Part.Touched:Connect(function(Hit)
    if not Connection or Hit.Transparency ~= 0 or not Hit.CanCollide then return end
    local Results = {}
    for _,VectorStr in pairs(FaceList) do
        local Result = workspace:Raycast(Part.Position,Part.CFrame[VectorStr],RayCastParams)
        if Result then
            table.insert(Results,Result)
        end
    end
    for _,VectorStr in pairs(FaceList) do
        local Result = workspace:Raycast(Part.Position,-Part.CFrame[VectorStr],RayCastParams)
        if Result then
            table.insert(Results,Result)
        end
    end
    if #Results <= 0 then return end
    local ChosenCast,Distance = nil,math.huge
    for _,Result in pairs(Results) do
        if CheckPart(Result.Instance) and Result.Distance <= Distance then Distance = Result.Distance ChosenCast = Result end
    end
    if not ChosenCast then return end
    Hit = ChosenCast.Instance
    Connection:Disconnect() 
    
    local NormalEnum = NearestNormalId(Hit,ChosenCast.Position)

    local Vector = NormalSizeConstraints[NormalEnum](Hit)
    Vector = Vector3.new(math.clamp(math.random(Vector.X*.1,Vector.X*.5),math.min(Vector.X,1),5),
        math.clamp(math.random(Vector.Y*.1,Vector.Y*.5),math.min(Vector.Y,1),5),
        math.clamp(math.random(Vector.Z*.1,Vector.Z*.5),math.min(Vector.Z,1),5)
    )
    Part.AssemblyLinearVelocity = Vector3.new(0,0,0)
    Part.Size = Vector
    Part.Orientation = ChosenCast.Instance.Orientation
    Part.Position = ChosenCast.Position
    Part.WeldConstraint.Part1 = Hit

    Part.Blood1.Face = NormalEnum
    Part.Blood2.Face = NormalOpposites[NormalEnum]
    Part.Blood1.Enabled = true
    Part.Blood2.Enabled = true
    Part.Blood1.Frame.Rotation = math.random(-360,360)
    Part.Blood2.Frame.Rotation = math.random(-360,360)
    Part.Parent = Hit
end)

RayCastParams.FilterType = Enum.RaycastFilterType.Whitelist
RayCastParams.FilterDescendantsInstances = {workspace}
RayCastParams.IgnoreWater = true

In conclusion thank you so much, this is just what I was hoping for. Cheers!