Help with 1 x 1 x 1 grid snapping!

I’m trying to put together a build tool for my game, but I am stumped on how I can get the building parts to properly snap to each other. When I try to use any part with an odd number size, it does not properly align with the studs. There is a .5 stud offset.

Pasted below is an idea on how I want my tool to function. I’d like to leave a disclaimer and say that this is a very bare-bones template for a building tool, simply for the sake of getting my point across:

tool = script.Parent
equipped = false

camera = workspace.CurrentCamera

--services
players = game:GetService('Players')
userInputService = game:GetService('UserInputService')
runService = game:GetService('RunService')

player = players.LocalPlayer


function equipTool()
	print('Equipped')
	equipped = true

	--display preview
	previewPart = nil
	displayPreview = runService.Heartbeat:Connect(function()
		if previewPart then previewPart:Destroy() end
		previewPart = nil

		local mouse2DPosition = userInputService:GetMouseLocation()
		local viewportRay = camera:ViewportPointToRay(mouse2DPosition.X, mouse2DPosition.Y)

		local mouseToPartRay = workspace:Raycast(viewportRay.Origin, viewportRay.Direction.Unit * 1000)

		if mouseToPartRay then
			local playerTorso = player.Character:FindFirstChild('Torso')
			local humanoid = player.Character:FindFirstChildOfClass('Humanoid')
			if playerTorso and humanoid and humanoid.Health > 0 then
				local distanceToPlayer = (mouseToPartRay.Position - playerTorso.Position).Magnitude
				if distanceToPlayer <= 100 then -- raycast is within proximity

					previewPart = Instance.new('Part')
					previewPart.Parent = workspace
					previewPart.Size = Vector3.new(3,3,3)
					previewPart.Anchored = true
					previewPart.CanCollide = false
					previewPart.CanQuery = false

					--round up values and determine offset
					local offset = mouseToPartRay.Normal * .5
					local roundedX = math.round(mouseToPartRay.Position.X) + (previewPart.Size.X * offset.X) 
					local roundedY = math.round(mouseToPartRay.Position.Y) + (previewPart.Size.Y * offset.Y) 
					local roundedZ = math.round(mouseToPartRay.Position.Z) + (previewPart.Size.Z * offset.Z) 
					previewPart.Position = Vector3.new(roundedX,roundedY,roundedZ)

				end
			end
		end
	end)
end

function unequipTool()
	print('Unequipped')
	if displayPreview then displayPreview:Disconnect() end
	displayPreview = nil
	if previewPart then previewPart:Destroy() end
	previewPart = nil
	equipped = false

end

tool.Equipped:Connect(function()
	equipTool()
end)

tool.Unequipped:Connect(function()
	unequipTool()
end)

This is a local script that’s supposed to go into a tool with an R6 avatar. It works as intended with even size parts, but I can’t manage to align them with other parts (studs parallel to each other) if there is an odd number in play. Forgive me, because I feel like there are dead simple solutions to this, yet they’re not hitting me. Help is appreciated!

image

have you tried math.floor() or converting the mouse position into the math.floor before entering into the math.round()