Brick Placement on Any Surface

Hello,
I am trying to make a brick placement program based on where the player clicks. First it displays the brick semi-transparent and only locally, then when the player clicks, a RemoteEvent is fired to set the position once and for all. The part in question is a 2x2x2 brick, with welds on all sides. Currently, the script does not place it on any surface at any orientation, though that was the attempt I was going for.

function CalcPlacementCFrame(mouse, position)

local shiftPos = Vector3.FromNormalId(mouse.TargetSurface)

local x = position.X

local y = position.Y

local z = position.Z

x = math.sign(x)*((math.abs(x) - math.abs(x) % 1) + (1 % 1))

y = math.sign(y)*((math.abs(y) - math.abs(y) % 1) + (1 % 1))

z = math.sign(z)*((math.abs(z) - math.abs(z) % 1) + (1 % 1))

shiftPos = shiftPos + Vector3.new(x,y,z)

return CFrame.new(shiftPos, shiftPos-Vector3.FromNormalId(mouse.TargetSurface))

end


                        local brick = gBB:Clone()
			brick.Parent = workspace
			brick.Transparency = 0.5
			local keepMoving = true
			local mouse  = lplr:GetMouse()
			mouse.TargetFilter = brick
			brick.CanCollide = false
			brick.Anchored = true
			--[[local x = mouse.Hit.p.X
			local y = mouse.Hit.p.Y
			local z = mouse.Hit.p.Z
			x = math.sign(x)*((math.abs(x) - math.abs(x) % 1) + (1 % 1))
			y = math.sign(y)*((math.abs(y) - math.abs(y) % 1) + (1 % 1))
			z = math.sign(z)*((math.abs(z) - math.abs(z) % 1) + (1 % 1))
			local position = Vector3.new(x,y,z)]]
			local br_cf = CalcPlacementCFrame(mouse, mouse.Hit.p)
			brick.CFrame = br_cf
			local mEvent = mouse.Button1Down:Connect(function()
				print("Button 1 pressed")
				keepMoving = false
			end)
			while keepMoving == true do
				if ((lplr.Character.HumanoidRootPart.Position - mouse.Hit.p).magnitude <= 20) then
					print("moving brick")
					--[[x = mouse.Hit.p.X
					y = mouse.Hit.p.Y
					z = mouse.Hit.p.Z
					x = math.sign(x)*((math.abs(x) - math.abs(x) % 1) + (1 % 1))
					y = math.sign(y)*((math.abs(y) - math.abs(y) % 1) + (1 % 1))
					z = math.sign(z)*((math.abs(z) - math.abs(z) % 1) + (1 % 1))
					position = Vector3.new(x,y,z)]]
					br_cf = CalcPlacementCFrame(mouse.Target, mouse, brick, mouse.Hit.p, 0)
					--brick.Position = position
					--print(position)
					brick.CFrame = br_cf
				end
				wait()
			end
			script.Parent:WaitForChild("placeBrick"):FireServer(br_cf)
			mEvent:Disconnect()
			brick:Destroy()

I apologize for the odd formatting, it is my first post so I am learning how to work it.
Anyway, this works to an extent. It doesn’t place the brick properly on slanted surfaces, and it doesn’t create the welds it should, actually non at all.
Any ideas on making this work as I envisioned?

1 Like

Can we have the server-side script please?

And possibly a file so we can test this more readily.

Agree, to offer specific recommendation to the question(s) here we need to see the server script, since that it where the desired part and welds would be created.

If the server script is not placing and welding the placed part, have you confirmed that the server script is at least receiving the FireServer event and data payload… and then successfully calling an appropriate function to execute on that event?

For the purpose of experimenting and testing (or even other intents), you can create a single LocalScript that creates does ALL of your work to produce a placed part and weld locally. This only affects the client workspace, but would necessarily include all the functions needed. When that works, you can copy the required functions into a server script and add in the RemoteEvent handling.

Meanwhile, you may be interested in this tutorial from @EgoMoose, although it restricts placement to “Canvas objects” and so does not address the “place anywhere on any surface” need directly. However, it does a good job of explaining and organizing the concepts involved, and how to go about structuring the scripts involved. Also, as discussed in another thread, this tutorial internally operates on a 2D XY plane (regardless of how that plane is oriented in 3D world space!), and is only a tutorial.

2 Likes

@skwoginc @xuefei123
I know the server side works because the system works, just not the positioning, which is done client-side.
Here’s the server script:

    local gBB = game:GetService("ReplicatedStorage"):WaitForChild("Generic_Build_Brick")
    script.Parent:WaitForChild("placeBrick").OnServerEvent:Connect(function(plr, --[[position]] cframe)
        local brick = gBB:Clone()
        --brick.Position = position
        brick.Position = cframe.Position
        print("Placing brick at position: " .. tostring(cframe))
        brick.Anchored = true
        brick.Parent = workspace
        wait()
        brick.Anchored = false
    end)

Here’s a simple client-side script

local gBB = game:GetService("ReplicatedStorage"):WaitForChild("Generic_Build_Brick")
local uis = game:GetService("UserInputService")
local lplr = game.Players.LocalPlayer


function CalcPlacementCFrame(mouse, position)
	local mCF =  mouse.Hit
	local x = position.X
	local y = position.Y
	local z = position.Z
	x = math.sign(x)*((math.abs(x) - math.abs(x) % 1) + (1 % 1))
	y = math.sign(y)*((math.abs(y) - math.abs(y) % 1) + (1 % 1))
	z = math.sign(z)*((math.abs(z) - math.abs(z) % 1) + (1 % 1))
	local gridVector = Vector3.new(x,y,z) + Vector3.FromNormalId(Enum.NormalId.Front)
	mCF = mCF - (mCF - gridVector).Position
	mCF = mCF * CFrame.new(Vector3.FromNormalId(Enum.NormalId.Back))
	return CFrame.new(gridVector, mCF.Position)
end

uis.InputEnded:Connect(function(inp)
	if inp.UserInputType == Enum.UserInputType.Keyboard then
		if inp.KeyCode == Enum.KeyCode.Q then
			local brick = gBB:Clone()
			brick.Parent = workspace
			brick.Transparency = 0.5
			local keepMoving = true
			local mouse  = lplr:GetMouse()
			mouse.TargetFilter = brick
			brick.CanCollide = false
			brick.Anchored = true
			--[[local x = mouse.Hit.p.X
			local y = mouse.Hit.p.Y
			local z = mouse.Hit.p.Z
			x = math.sign(x)*((math.abs(x) - math.abs(x) % 1) + (1 % 1))
			y = math.sign(y)*((math.abs(y) - math.abs(y) % 1) + (1 % 1))
			z = math.sign(z)*((math.abs(z) - math.abs(z) % 1) + (1 % 1))
			local position = Vector3.new(x,y,z)]]
			local br_cf = CalcPlacementCFrame(mouse, mouse.Hit.p)
			brick.Position = br_cf.Position
			local mEvent = mouse.Button1Down:Connect(function() --ends placement
				print("Button 1 pressed")
				keepMoving = false
			end)
			while keepMoving == true do
				if ((lplr.Character.HumanoidRootPart.Position - mouse.Hit.p).magnitude <= 20) then
					print("moving brick")
					--[[x = mouse.Hit.p.X
					y = mouse.Hit.p.Y
					z = mouse.Hit.p.Z
					x = math.sign(x)*((math.abs(x) - math.abs(x) % 1) + (1 % 1))
					y = math.sign(y)*((math.abs(y) - math.abs(y) % 1) + (1 % 1))
					z = math.sign(z)*((math.abs(z) - math.abs(z) % 1) + (1 % 1))
					position = Vector3.new(x,y,z)]]
					br_cf = CalcPlacementCFrame(mouse,mouse.Hit.p)
					--brick.Position = position
					--print(position)
					brick.Position = br_cf.Position
				end
				wait()
			end
			script.Parent:WaitForChild("placeBrick"):FireServer(br_cf)
			mEvent:Disconnect()
			brick:Destroy() --removes the local brick
		end
	end
end)

Also, that system is what I used initially until I realized it wouldn’t do rotation based on any surface, and only on a specific surface, I needed a system that would place anywhere and have the proper rotation. Right now it doesn’t preserve rotation, I removed that temporarily to just at minimum get proper placement relative to surfaces

In the server Script, change:

brick.Position = cframe.Position

to:

brick.CFrame = cframe

… in order to apply whatever CFrame is sent from the client to the part being cloned and placed on the server. Your posted server Script only applies the Position.

I know, it still doesn’t work. I did make a note of that. It doesn’t even position properly. I removed rotation temporarily to jsut get the positioning right. Parts go inside the surface I am trying to palce on, or overlap one another, or are half in and half out of hte part I am trying to place on.

Ah… another issue often referred to as clipping. To check for clipping you can use:

BasePart.Touched to Check When Something Touches a Part
FindPartOnRay or FindPartOnRayWithIgnoreList
IsRegion3Empty or IsRegion3EmptyWithIgnoreList

If any of these find parts such that your placed part would be clipping the other part, you can simply return or return false and do not allow the placement. This check can optionally (and I would argue should) be done in LocalScript on the client first, and it should always be done on the server (regardless of client checks) as the source of truth to validate placement.

Wherever and however you store “placementAllowed”, placement should be NOT allowed by default and remain not allowed until the server confirms that a valid position and rotation with no clipping is in fact allowed. Then you can flip that bit and clone the part at the given CFrame (for position and rotation).

That doesn’t solve my problem.
My problem remains it won’t even place OUTSIDE of the part it is clipping through.
I’m not inexperienced with this stuff.
I am just stumped as to make the part being placed, be placed correctly. The issue, no matter what bounds checking I do, remains that when the cursor hovers over a spot, the part I want to place won’t go on the OUTSIDE of the surface I want to place on. It will place halfway in, or maybe if I am lucky, on the outside correctly, or unlucky, completely inside the part of the surface I am hovering over. Think of it as exactly this, a part that moves according to the Hit property of the mouse, with the TargetFilter being that part, so it goes everywhere the mouse points, overlapping anything and everything. I am trying to make it so that the part offsets at the position of that CFrame, but not overlap into the part the mouse is pointing at. First and foremost I want to get that Vector3 that would permit the part being outside the surface. From there I can use the CFrame.new(position, lookAt position) constructor to form the rotation to the position property of the mouse’s hit.p, then translate on the lookVector until the surfaces are touching.

I am just having issues getting the point at which the brick would not overlap into what I suppose is the Mouse’s Target.

That’s what the placementCframe function was attempting to do, but it wasn’t getting it perfectly. It just won’t do it right 100% of the time. I would say, maximum, 1/25 surfaces done sit get it right.

Does that help clarify it?

Feel like that’s a different problem than the description at the beginning of this thread.

Sounds a lot like Studio placing a part with Collisions enabled, except in your own in game Lua.

An externally oriented solution could be approached from outside of existing parts, where the new part to be placed is in the workspace and follows the mouse at all times, until a collision is detected, at which point the part remains fixed in its last known good position even while the mouse keeps moving… until the mouse moves to a good position with no collision.

An internally oriented solution could be to check for clipping parts, find the position and size of the clipped part (or position and vector to the center of the clipping surface point, then reposition the new part to be placed at a distance of half the new part’s size outside of the clipping part along that vector.

Out of time now.