Help with block placement

So I have finished making a block placement system but now I ran into a problem, the problem is that it is very laggy looping through the BlocksFolder everytime. So I want to rewrite it so it just snaps into a grid instead of getting the closest part and setting the position there.
Any help please.

local function findClosestPart()
	local currentPart = nil
	local currentClosestDir = nil
	local currentClosestMag = nil

	local mouseRay = currentCamera:ScreenPointToRay(mouse.X, mouse.Y)

	for i,v in ipairs(BlocksFolder:GetDescendants()) do
		if v:IsA("BasePart") then
			local mouse3DPos = mouseRay:ClosestPoint(v.Position)
			local magnitude = (v.Position - mouse3DPos).Magnitude

			if not currentClosestMag or magnitude < currentClosestMag then
				currentClosestMag = magnitude
				currentPart = v
			end
		end
	end

	if currentPart and currentClosestMag <= closestPartMaxDistance then
		local surfaceOffset = Vector3.new(3, 3, 3) -- Offset to determine surface positions

		local surfaceIDs = {
			Left = currentPart.Position - Vector3.new(surfaceOffset.X, 0, 0),
			Right = currentPart.Position + Vector3.new(surfaceOffset.X, 0, 0),
			Front = currentPart.Position - Vector3.new(0, 0, surfaceOffset.Z),
			Back = currentPart.Position + Vector3.new(0, 0, surfaceOffset.Z),

			DiagonalFrontLeft = currentPart.Position + Vector3.new(-surfaceOffset.X, 0, surfaceOffset.Z),
			DiagonalFrontRight = currentPart.Position + Vector3.new(surfaceOffset.X, 0, surfaceOffset.Z),
			DiagonalBackLeft = currentPart.Position + Vector3.new(-surfaceOffset.X, 0, -surfaceOffset.Z),
			DiagonalBackRight = currentPart.Position + Vector3.new(surfaceOffset.X, 0, -surfaceOffset.Z),
		}

		if mouse.Target == nil then
			local closestMag = nil
			local rayDir = mouseRay.Direction
			local rayOrigin = mouseRay.Origin

			for surfaceId, pos in pairs(surfaceIDs) do
				local dir = (pos - rayOrigin).Unit
				local magnitude = (rayDir - dir).Magnitude

				if not closestMag or magnitude < closestMag then
					closestMag = magnitude
					currentClosestDir = surfaceId
				end
			end
		else
			local mouse3DPos = mouseRay:ClosestPoint(currentPart.Position)
			local closestMag = nil

			for surfaceId, pos in pairs(surfaceIDs) do
				local magnitude = (mouse3DPos - pos).Magnitude

				if not closestMag or magnitude < closestMag then
					closestMag = magnitude
					currentClosestDir = surfaceId
				end
			end
		end

		if currentClosestDir then
			return currentPart, currentClosestDir
		end
	end
end

local function display()
	local mouseTarget = mouse.TargetSurface
	local HumanoidRootPart = character:FindFirstChild("HumanoidRootPart") or character.PrimaryPart

	if HumanoidRootPart then
		if mouse.Target then
			if (mouse.Target.Position - HumanoidRootPart.Position).Magnitude <= maxPlaceBlockRange then
				blockPrediction = false
				displayBlock.CFrame = CFrame.new(mouse.Target.Position)

				local newPlacementCFrame = nil

				if mouseTarget == Enum.NormalId.Top then
					newPlacementCFrame = CFrame.new(mouse.Target.Position + Vector3.new(0, 3, 0))
				elseif mouseTarget == Enum.NormalId.Bottom then
					newPlacementCFrame = CFrame.new(mouse.Target.Position - Vector3.new(0, 3, 0))
				elseif mouseTarget == Enum.NormalId.Left then
					newPlacementCFrame = CFrame.new(mouse.Target.Position - Vector3.new(3, 0, 0))
				elseif mouseTarget == Enum.NormalId.Right then
					newPlacementCFrame = CFrame.new(mouse.Target.Position + Vector3.new(3, 0, 0))
				elseif mouseTarget == Enum.NormalId.Front then
					newPlacementCFrame = CFrame.new(mouse.Target.Position - Vector3.new(0, 0, 3))
				elseif mouseTarget == Enum.NormalId.Back then
					newPlacementCFrame = CFrame.new(mouse.Target.Position + Vector3.new(0, 0, 3))
				end

				placementCFrame = newPlacementCFrame
			else
				local closestPart, direction = findClosestPart()
				local offsetVector = Vector3.new()

				blockPrediction = true
				currentClosestPart = closestPart

				if closestPart and direction then
					if tostring(direction) == "Left" then
						offsetVector = Vector3.new(-3, 0, 0)
					elseif tostring(direction) == "Right" then
						offsetVector = Vector3.new(3, 0, 0)
					elseif tostring(direction) == "Front" then
						offsetVector = Vector3.new(0, 0, -3)
					elseif tostring(direction) == "Back" then
						offsetVector = Vector3.new(0, 0, 3)

					elseif tostring(direction) == "DiagonalFrontLeft" then
						offsetVector = Vector3.new(-3, 0, 3)
					elseif tostring(direction) == "DiagonalFrontRight" then
						offsetVector = Vector3.new(3, 0, 3)
					elseif tostring(direction) == "DiagonalBackLeft" then
						offsetVector = Vector3.new(-3, 0, -3)
					elseif tostring(direction) == "DiagonalBackRight" then
						offsetVector = Vector3.new(3, 0, -3)
					end

					local targetPos = closestPart.Position + offsetVector

					displayBlock.CFrame = CFrame.new(targetPos)
					placementCFrame = displayBlock.CFrame

					closestPart = nil
					direction = nil
				end
			end
		else
			local closestPart, direction = findClosestPart()
			local offsetVector = Vector3.new()

			blockPrediction = true
			currentClosestPart = closestPart

			if closestPart and direction then
				if tostring(direction) == "Left" then
					offsetVector = Vector3.new(-3, 0, 0)
				elseif tostring(direction) == "Right" then
					offsetVector = Vector3.new(3, 0, 0)
				elseif tostring(direction) == "Front" then
					offsetVector = Vector3.new(0, 0, -3)
				elseif tostring(direction) == "Back" then
					offsetVector = Vector3.new(0, 0, 3)

				elseif tostring(direction) == "DiagonalFrontLeft" then
					offsetVector = Vector3.new(-3, 0, 3)
				elseif tostring(direction) == "DiagonalFrontRight" then
					offsetVector = Vector3.new(3, 0, 3)
				elseif tostring(direction) == "DiagonalBackLeft" then
					offsetVector = Vector3.new(-3, 0, -3)
				elseif tostring(direction) == "DiagonalBackRight" then
					offsetVector = Vector3.new(3, 0, -3)
				end

				local targetPos = closestPart.Position + offsetVector

				displayBlock.CFrame = CFrame.new(targetPos)
				placementCFrame = displayBlock.CFrame

				closestPart = nil
				direction = nil
			end
		end
	end
end
2 Likes

Use this module, please search on the devforums before posting.

function CalcCanvas(CanvasPart)
	local canvasSize = CanvasPart.Size

	-- want to create CFrame such that cf.lookVector == self.CanvasPart.CFrame.upVector
	-- do this by using object space and build the CFrame
	local back = Vector3.new(0, -1, 0)
	local top = Vector3.new(0, 0, -1)
	local right = Vector3.new(-1, 0, 0)

	-- convert to world space
	local cf = CanvasPart.CFrame * CFrame.fromMatrix(-back*canvasSize/2, right, top, back)
	-- use object space vectors to find the width and height
	local size = Vector2.new((canvasSize * right).magnitude, (canvasSize * top).magnitude)

	return cf, size
end

function CalcPlacementCFrame(model, position, rotation, CanvasPart)
	-- use other method to get info about the surface
	local cf, size = CalcCanvas(CanvasPart)

	-- rotate the size so that we can properly constrain to the surface
	local modelSize 
	if model:IsA("Model") then
		modelSize = CFrame.fromEulerAnglesYXZ(0, rotation, 0) *  model:GetExtentsSize() --model.PrimaryPart.Size
	else
		modelSize = model.Size
	end
	modelSize = Vector3.new(math.abs(modelSize.x), math.abs(modelSize.y), math.abs(modelSize.z))

	-- get the position relative to the surface's CFrame
	local lpos = cf:pointToObjectSpace(position);
	-- the max bounds the model can be from the surface's center
	local size2 = (size - Vector2.new(modelSize.x, modelSize.z))/2

	-- constrain the position using size2
	local x = math.clamp(lpos.x, -size2.x, size2.x);
	local y = math.clamp(lpos.y, -size2.y, size2.y);

	local g = 1
	if (g > 0) then
		x = math.sign(x)*((math.abs(x) - math.abs(x) % g) + (size2.x % g))
		y = math.sign(y)*((math.abs(y) - math.abs(y) % g) + (size2.y % g))
	end


	-- create and return the CFrame
	return cf * CFrame.new(x, y, -modelSize.y/2) * CFrame.Angles(-math.pi/2 + tilt, -math.pi/2+rotation, 0)
end
1 Like

Tried it but what if the player mouse target is nil? What would I do?

function CalculateCanvasFrame(CanvasPart)
	local canvasSize = CanvasPart.Size

	local back = Vector3.new(0, -1, 0)
	local top = Vector3.new(0, 0, -1)
	local right = Vector3.new(-1, 0, 0)

	local canvasCFrame = CanvasPart.CFrame * CFrame.fromMatrix(-back * canvasSize / 2, right, top, back)
	local canvasDimensions = Vector2.new((canvasSize * right).Magnitude, (canvasSize * top).Magnitude)

	return canvasCFrame, canvasDimensions
end

function CalculatePlacementCFrame(model, position, rotation, CanvasPart)
	local canvasCFrame, canvasDimensions = CalculateCanvasFrame(CanvasPart)
	local modelSize

	if model:IsA("Model") then
		modelSize = CFrame.fromEulerAnglesYXZ(0, rotation, 0) * model:GetExtentsSize()
	else
		modelSize = model.Size
	end

	modelSize = Vector3.new(math.abs(modelSize.X), math.abs(modelSize.Y), math.abs(modelSize.Z))

	local localPosition = canvasCFrame:PointToObjectSpace(position)
	local modelBounds = (canvasDimensions - Vector2.new(modelSize.X, modelSize.Z)) / 2

	local x = math.clamp(localPosition.X, -modelBounds.X, modelBounds.X)
	local y = math.clamp(localPosition.Y, -modelBounds.Y, modelBounds.Y)

	local gridSpacing = 1

	if gridSpacing > 0 then
		x = math.sign(x) * ((math.abs(x) - math.abs(x) % gridSpacing) + (modelBounds.X % gridSpacing))
		y = math.sign(y) * ((math.abs(y) - math.abs(y) % gridSpacing) + (modelBounds.Y % gridSpacing))
	end

	return canvasCFrame * CFrame.new(x, y, -modelSize.Y / 2) * CFrame.Angles(-math.pi / 2, -math.pi / 2 + rotation, 0)
end

local function display()
	local mouseTarget = mouse.TargetSurface
	local HumanoidRootPart = character:FindFirstChild("HumanoidRootPart") or character.PrimaryPart

	if HumanoidRootPart then
		local canvasPart = mouse.Target
		local position = displayBlock.Position
		local rotation = 0

		local canvasCFrame, canvasDimensions = CalculateCanvasFrame(canvasPart)
		local newPlacementCFrame = CalculatePlacementCFrame(displayBlock, position, rotation, canvasPart)

		blockPrediction = false
		displayBlock.CFrame = CFrame.new(mouse.Target.Position)

		if mouseTarget == Enum.NormalId.Top then
			newPlacementCFrame = CFrame.new(mouse.Target.Position + Vector3.new(0, 3, 0))
		elseif mouseTarget == Enum.NormalId.Bottom then
			newPlacementCFrame = CFrame.new(mouse.Target.Position - Vector3.new(0, 3, 0))
		elseif mouseTarget == Enum.NormalId.Left then
			newPlacementCFrame = CFrame.new(mouse.Target.Position - Vector3.new(3, 0, 0))
		elseif mouseTarget == Enum.NormalId.Right then
			newPlacementCFrame = CFrame.new(mouse.Target.Position + Vector3.new(3, 0, 0))
		elseif mouseTarget == Enum.NormalId.Front then
			newPlacementCFrame = CFrame.new(mouse.Target.Position - Vector3.new(0, 0, 3))
		elseif mouseTarget == Enum.NormalId.Back then
			newPlacementCFrame = CFrame.new(mouse.Target.Position + Vector3.new(0, 0, 3))
		end

		placementCFrame = newPlacementCFrame

		local closestPart, direction = findClosestPart()
		local offsetVector = Vector3.new()

		blockPrediction = true
		currentClosestPart = closestPart

		if closestPart and direction then
			if tostring(direction) == "Left" then
				offsetVector = Vector3.new(-3, 0, 0)
			elseif tostring(direction) == "Right" then
				offsetVector = Vector3.new(3, 0, 0)
			elseif tostring(direction) == "Front" then
				offsetVector = Vector3.new(0, 0, -3)
			elseif tostring(direction) == "Back" then
				offsetVector = Vector3.new(0, 0, 3)
			elseif tostring(direction) == "DiagonalFrontLeft" then
				offsetVector = Vector3.new(-3, 0, 3)
			elseif tostring(direction) == "DiagonalFrontRight" then
				offsetVector = Vector3.new(3, 0, 3)
			elseif tostring(direction) == "DiagonalBackLeft" then
				offsetVector = Vector3.new(-3, 0, -3)
			elseif tostring(direction) == "DiagonalBackRight" then
				offsetVector = Vector3.new(3, 0, -3)
			end

			local targetPos = closestPart.Position + offsetVector

			displayBlock.CFrame = CFrame.new(targetPos)
			placementCFrame = displayBlock.CFrame

			closestPart = nil
			direction = nil
		end
	end
end
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local PlacementEvent = ReplicatedStorage.PlacementEvent

local RenderStepped = nil;
local Player = Players.LocalPlayer
PlacementEvent.OnClientEvent:Connect(function()
      if RenderStepped ~= nil then
           RenderStepped:Disconnect()
           RenderStepped = nil;
      end
     local mouse = Player:GetMouse()
     RenderStepped = RunService.RenderStepped:Connect(function()
          if mouse.Target ~= nil then
               --Calculate placement CFrame, etc
          end
     end)
end)

just detect if it hit something or not.

I also need block prediction, so how would I do that?

what is block prediction?? i’m not too good at developing sorry.

Its like the Bedwars block prediction when you hover your mouse over the void, it will find the closest block and use the mouse position to check what face to place it in.

This part:

local function findClosestPart()
	local currentPart = nil
	local currentClosestDir = nil
	local currentClosestMag = nil

	local mouseRay = currentCamera:ScreenPointToRay(mouse.X, mouse.Y)

	for i,v in ipairs(BlocksFolder:GetDescendants()) do
		if v:IsA("BasePart") then
			local mouse3DPos = mouseRay:ClosestPoint(v.Position)
			local magnitude = (v.Position - mouse3DPos).Magnitude

			if not currentClosestMag or magnitude < currentClosestMag then
				currentClosestMag = magnitude
				currentPart = v
			end
		end
	end

	if currentPart and currentClosestMag <= closestPartMaxDistance then
		local surfaceOffset = Vector3.new(3, 3, 3) -- Offset to determine surface positions

		local surfaceIDs = {
			Left = currentPart.Position - Vector3.new(surfaceOffset.X, 0, 0),
			Right = currentPart.Position + Vector3.new(surfaceOffset.X, 0, 0),
			Front = currentPart.Position - Vector3.new(0, 0, surfaceOffset.Z),
			Back = currentPart.Position + Vector3.new(0, 0, surfaceOffset.Z),

			DiagonalFrontLeft = currentPart.Position + Vector3.new(-surfaceOffset.X, 0, surfaceOffset.Z),
			DiagonalFrontRight = currentPart.Position + Vector3.new(surfaceOffset.X, 0, surfaceOffset.Z),
			DiagonalBackLeft = currentPart.Position + Vector3.new(-surfaceOffset.X, 0, -surfaceOffset.Z),
			DiagonalBackRight = currentPart.Position + Vector3.new(surfaceOffset.X, 0, -surfaceOffset.Z),
		}

		if mouse.Target == nil then
			local closestMag = nil
			local rayDir = mouseRay.Direction
			local rayOrigin = mouseRay.Origin

			for surfaceId, pos in pairs(surfaceIDs) do
				local dir = (pos - rayOrigin).Unit
				local magnitude = (rayDir - dir).Magnitude

				if not closestMag or magnitude < closestMag then
					closestMag = magnitude
					currentClosestDir = surfaceId
				end
			end
		else
			local mouse3DPos = mouseRay:ClosestPoint(currentPart.Position)
			local closestMag = nil

			for surfaceId, pos in pairs(surfaceIDs) do
				local magnitude = (mouse3DPos - pos).Magnitude

				if not closestMag or magnitude < closestMag then
					closestMag = magnitude
					currentClosestDir = surfaceId
				end
			end
		end

		if currentClosestDir then
			return currentPart, currentClosestDir
		end
	end
end

You’d have to look here, I don’t know if the grid system would cover it, or if he actually made prediction.

But I want to create my own placement system.

Then why are you posting on the devforums?

I’m not saying you have to copy word for word his code, you can learn from it though.

I need help rewriting my old code so that it uses a grid placement system without doing this:

if tostring(direction) == "Left" then
	offsetVector = Vector3.new(-3, 0, 0)
end

Because he… Wants help? I don’t see the issue. There are plenty of times where modules exist but I want to make my own system

Any help please? I don’t know how to snap into grid.