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.

``````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.