How to make it so that my placement script works on diagonal/vertical surfaces?

Okay, so I have this placement system that works almost flawlessly on horizontal and unrotated surfaces, but if the surface is either verticle or diagonal (rotated), problems arise.

local e = false
local tb = Instance.new("Model")
local tbb = Instance.new("Part",tb)
tb.PrimaryPart=tbb
tbb.Anchored=true
local mou = game.Players.LocalPlayer:GetMouse()
game:GetService("RunService").RenderStepped:Connect(function()
    local rcp = RaycastParams.new()
    rcp.FilterType=Enum.RaycastFilterType.Blacklist
    rcp.FilterDescendantsInstances={game.Players.LocalPlayer.Character,tb}
    local ray = workspace:Raycast(workspace.CurrentCamera.CFrame.Position,(mou.Hit.p-workspace.CurrentCamera.CFrame.Position).Unit*500,rcp)
    if ray==nil then
        tb.Parent=nil
    else
        tb.Parent=workspace
        local i1 = Vector3.new(ray.Position.X,ray.Position.Y,ray.Position.Z)+ray.Normal.Unit*(tb.PrimaryPart.Size/Vector3.new(2,2,2))
        local intended = CFrame.new(i1)
        local p1 = intended.Position+intended.LookVector.Unit*(tb.PrimaryPart.Size.Z/2)+intended.UpVector.Unit*(tb.PrimaryPart.Size.Y/2)+intended.RightVector.Unit*(tb.PrimaryPart.Size.X/2)
        local p2 = intended.Position-intended.LookVector.Unit*(tb.PrimaryPart.Size.Z/2)-intended.UpVector.Unit*(tb.PrimaryPart.Size.Y/2)-intended.RightVector.Unit*(tb.PrimaryPart.Size.X/2)
        print(Vector3.new(math.abs(p1.X-p2.X),math.abs(p1.Y-p2.Y),math.abs(p1.Z-p2.Z)))
        print(intended.Position)
        print(ray.Instance.Position.X-ray.Instance.Size.X/2+math.abs(p1.X-p2.X)/2)
        print(ray.Instance.Position.X+ray.Instance.Size.X/2-math.abs(p1.X-p2.X)/2)
        print(ray.Instance.Position.Z-ray.Instance.Size.Z/2+math.abs(p1.Z-p2.Z)/2)
        print(ray.Instance.Position.Z+ray.Instance.Size.Z/2-math.abs(p1.Z-p2.Z)/2)
        print(math.clamp(intended.Position.X,ray.Instance.Position.X-ray.Instance.Size.X/2+math.abs(p1.X-p2.X)/2,ray.Instance.Position.X+ray.Instance.Size.X/2-math.abs(p1.X-p2.X)/2))
        local intended2
        if math.abs(ray.Normal.Unit.Y)==1 then
            intended2 = CFrame.new(Vector3.new(math.clamp(intended.Position.X,ray.Instance.Position.X-ray.Instance.Size.X/2+math.abs(p1.X-p2.X)/2,ray.Instance.Position.X+ray.Instance.Size.X/2-math.abs(p1.X-p2.X)/2),intended.Position.Y,math.clamp(intended.Position.Z,ray.Instance.Position.Z-ray.Instance.Size.Z/2+math.abs(p1.Z-p2.Z)/2,ray.Instance.Position.Z+ray.Instance.Size.Z/2-math.abs(p1.Z-p2.Z)/2)))
        elseif math.abs(ray.Normal.Unit.X)==1 then
            intended2 = CFrame.new(Vector3.new(intended.Position.X,math.clamp(intended.Position.Y,ray.Instance.Position.Y-ray.Instance.Size.Y/2+math.abs(p1.Y-p2.Y)/2,ray.Instance.Position.Y+ray.Instance.Size.Y/2-math.abs(p1.Y-p2.Y)/2),math.clamp(intended.Position.Z,ray.Instance.Position.Z-ray.Instance.Size.Z/2+math.abs(p1.Z-p2.Z)/2,ray.Instance.Position.Z+ray.Instance.Size.Z/2-math.abs(p1.Z-p2.Z)/2)))
        elseif math.abs(ray.Normal.Unit.Z)==1 then
            intended2 = CFrame.new(Vector3.new(math.clamp(intended.Position.X,ray.Instance.Position.X-ray.Instance.Size.X/2+math.abs(p1.X-p2.X)/2,ray.Instance.Position.X+ray.Instance.Size.X/2-math.abs(p1.X-p2.X)/2),math.clamp(intended.Position.Y,ray.Instance.Position.Y-ray.Instance.Size.Y/2+math.abs(p1.Y-p2.Y)/2,ray.Instance.Position.Y+ray.Instance.Size.Y/2-math.abs(p1.Y-p2.Y)/2),intended.Position.Z))
        else
            intended2=intended
        end
        local r = 45
        intended2=intended2*CFrame.fromEulerAnglesXYZ(math.rad(ray.Instance.Orientation.X),math.rad(ray.Instance.Orientation.Y),math.rad(ray.Instance.Orientation.Z))
        game:GetService("TweenService"):Create(tb.PrimaryPart,TweenInfo.new(.1/3,Enum.EasingStyle.Quad,Enum.EasingDirection.InOut),{CFrame=intended2}):Play()
    end
end)

The first problem is that I have no idea how to make surface normal boundaries/clamping for rotated surfaces, both horizontal and vertical.

The second problem is that my part’s front/back face is always parallel to the surface’s front/back face, and my part’s right/left face is always parallel to the surface’s right/left face, and vice versa.


Instead, I want my part’s front face to always be facing the surface normal touching the part IF THE SURFACE NORMAL IS VERTICAL, NOT HORIZONTAL. , like so

The third problem is that the distance between the surface normal touching the part, to the part, is incorrectly calculated if the surface is diagonal or vertical.

If you still don’t understand, here’s a video of me displaying all the problems I mentioned above.

I don’t want hacky or downright terrible solutions. I want a solution that’s efficient, loyal to the initial code, and works 100% of the time. I don’t want copy and pasted code either. I want a thorough explanation, so in the future I can look back and make more of these placement scripts from memory.

You might like this: Articles/Furniture Placement System.md at master · EgoMoose/Articles · GitHub

Also here’s a slightly cleaned up version of your code just for the sake of others

local e = false
local tb = Instance.new("Model")
local tbb = Instance.new("Part",tb)
tb.PrimaryPart=tbb
tbb.Anchored=true

local mou = game.Players.LocalPlayer:GetMouse()

game:GetService("RunService").RenderStepped:Connect(function()
	local rcp = RaycastParams.new()
	rcp.FilterType = Enum.RaycastFilterType.Blacklist
	rcp.FilterDescendantsInstances = {game.Players.LocalPlayer.Character,tb}
	
	local ray = workspace:Raycast(
		workspace.CurrentCamera.CFrame.Position,
		mou.Hit.p-workspace.CurrentCamera.CFrame.Position,
		rcp)
	
	if ray == nil then
		tb.Parent = nil
	else
		tb.Parent = workspace
		
		local i1 = ray.Position + ray.Normal.Unit * (tb.PrimaryPart.Size/Vector3.new(2,2,2))
		
		local intended = CFrame.new(i1)
		
		local p1 = intended.Position + (intended.LookVector + intended.UpVector + intended.RightVector) * tb.PrimaryPart.Size / 2
		local p2 = intended.Position - (intended.LookVector + intended.UpVector + intended.RightVector) * tb.PrimaryPart.Size / 2

		local intended2
		
		if math.abs(ray.Normal.Unit.Y)==1 then
			intended2 = CFrame.new(
				Vector3.new(
					math.clamp(intended.Position.X,
						ray.Instance.Position.X-ray.Instance.Size.X/2+math.abs(p1.X-p2.X)/2,
						ray.Instance.Position.X+ray.Instance.Size.X/2-math.abs(p1.X-p2.X)/2),
					intended.Position.Y,
					math.clamp(intended.Position.Z,
						ray.Instance.Position.Z-ray.Instance.Size.Z/2+math.abs(p1.Z-p2.Z)/2,
						ray.Instance.Position.Z+ray.Instance.Size.Z/2-math.abs(p1.Z-p2.Z)/2)
				)
			)
		elseif math.abs(ray.Normal.Unit.X)==1 then
			intended2 = CFrame.new(
				Vector3.new(
					intended.Position.X,
					math.clamp(intended.Position.Y,
						ray.Instance.Position.Y-ray.Instance.Size.Y/2+math.abs(p1.Y-p2.Y)/2,
						ray.Instance.Position.Y+ray.Instance.Size.Y/2-math.abs(p1.Y-p2.Y)/2),
					math.clamp(intended.Position.Z,
						ray.Instance.Position.Z-ray.Instance.Size.Z/2+math.abs(p1.Z-p2.Z)/2,
						ray.Instance.Position.Z+ray.Instance.Size.Z/2-math.abs(p1.Z-p2.Z)/2)
				)
			)
		elseif math.abs(ray.Normal.Unit.Z)==1 then
			intended2 = CFrame.new(
				Vector3.new(
					math.clamp(intended.Position.X,
						ray.Instance.Position.X-ray.Instance.Size.X/2+math.abs(p1.X-p2.X)/2,
						ray.Instance.Position.X+ray.Instance.Size.X/2-math.abs(p1.X-p2.X)/2),
					math.clamp(intended.Position.Y,
						ray.Instance.Position.Y-ray.Instance.Size.Y/2+math.abs(p1.Y-p2.Y)/2,
						ray.Instance.Position.Y+ray.Instance.Size.Y/2-math.abs(p1.Y-p2.Y)/2),
					intended.Position.Z
				)
			)
		else
			intended2=intended
		end

		local r = 45
		intended2 = intended2 * CFrame.fromEulerAnglesXYZ(
			math.rad(ray.Instance.Orientation.X),
			math.rad(ray.Instance.Orientation.Y),
			math.rad(ray.Instance.Orientation.Z))
		
		game:GetService("TweenService"):Create(tb.PrimaryPart,TweenInfo.new(.1/3,Enum.EasingStyle.Quad,Enum.EasingDirection.InOut),{CFrame=intended2}):Play()
	end
end)

I also cleaned up some of your math but not much. This is just so I and others can figure out what’s going on.

2 Likes