Visual part for building system clipping underground (please help)

Im trying to make this building/placement system work i mean like the visual part that shows you where your gonna place it but it keeps clipping underground with large models (i know its mostly ai thats because i already tried to fix it myself but couldnt get it to work)


local plr = game.Players.LocalPlayer
local tool = script.Parent
local Event = tool:WaitForChild("Place")
local Equipped = false
local CanPlace = false

-- Preview part
local VisPart = Instance.new("Part")
VisPart.Anchored = true
VisPart.CanCollide = false
VisPart.Transparency = 0.5
VisPart.Material = Enum.Material.ForceField
VisPart.Name = "PreviewPart"

-- OverlapParams for collision checks
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Exclude

-- Raycast params for ground check
local rayParams = RaycastParams.new()
rayParams.FilterType = Enum.RaycastFilterType.Exclude
rayParams.FilterDescendantsInstances = {VisPart}

-- Equip
tool.Equipped:Connect(function()
	Equipped = true
	VisPart.Parent = workspace

	-- Match size with stored model in ReplicatedStorage
	local storedModel = game.ReplicatedStorage:FindFirstChild(tool.Name)
	if storedModel and storedModel:FindFirstChild("Collision") then
		VisPart.Size = storedModel.Collision.Size
	else
		VisPart.Size = Vector3.new(4, 4, 4) -- fallback size
	end

	-- Update loop
	while Equipped do
		task.wait()

		local hrp = plr.Character and plr.Character:FindFirstChild("HumanoidRootPart")
		if not hrp then
			continue
		end

		-- Start preview 10 studs forward
		local forwardOffset = 10
		VisPart.CFrame = hrp.CFrame * CFrame.new(0, 0, -forwardOffset)

		-- Push forward if overlapping with player
		local maxPushes = 10 -- up to 100 studs
		local pushes = 0
		repeat
			overlapParams.FilterDescendantsInstances = {VisPart}
			local touching = workspace:GetPartBoundsInBox(VisPart.CFrame, VisPart.Size, overlapParams)

			local collidingWithPlayer = false
			for _, part in ipairs(touching) do
				if plr.Character and part:IsDescendantOf(plr.Character) then
					collidingWithPlayer = true
					break
				end
			end

			if collidingWithPlayer then
				forwardOffset += 10
				VisPart.CFrame = hrp.CFrame * CFrame.new(0, 0, -forwardOffset)
				pushes += 1
			else
				break
			end
		until pushes >= maxPushes

		-- Raycast directly under preview part to check if it's grounded
		local rayOrigin = VisPart.Position
		local rayDirection = Vector3.new(0, -((VisPart.Size.Y / 2) + 0.5), 0) -- a little below the part
		local rayResult = workspace:Raycast(rayOrigin, rayDirection, rayParams)

		local isGrounded = rayResult ~= nil

		-- General collision check
		overlapParams.FilterDescendantsInstances = {VisPart, plr.Character}
		local touching = workspace:GetPartBoundsInBox(VisPart.CFrame, VisPart.Size, overlapParams)

		local blocked = false
		for _, part in ipairs(touching) do
			if part:IsA("BasePart") and part:HasTag("Collidable") then
				blocked = true
				break
			end
		end

		-- Final placement validity
		if not isGrounded or blocked then
			CanPlace = false
			VisPart.BrickColor = BrickColor.new("Really red")
		else
			CanPlace = true
			VisPart.BrickColor = BrickColor.new("Forest green")
		end
	end
end)

-- Unequip
tool.Unequipped:Connect(function()
	Equipped = false
	VisPart.Parent = nil
end)

-- Activate (place)
tool.Activated:Connect(function()
	if CanPlace then
		Event:FireServer(VisPart.CFrame, tool.Name)
	end
end)

this is why. you are using the hrp position to place the vispart so if the Y position of hrp isn’t the same as the half Y size of the vispart it will be either cliping(bigger) or floating(smaller)

use raycast to get the ground position and add the Y offset of the model

local viscframe= hrp.CFrame * CFrame.new(0, 0, -forwardOffset)
local YOffset = VisPart.Size.Y/2
--starts the raycast above the part.
local rayOrigin = viscframe.Position + Vector3.new(0, YOffset*2, 0) 
--raycast to the ground
local rayDirection = Vector3.new(0, -999, 0)
local rayResult = workspace:Raycast(rayOrigin, rayDirection, rayParams)

VisPart.CFrame = CFrame.new(rayResult.Position+YOffset, viscframe.LookVector)


ok so for some reason it broke the rotation with the character

nevermind i managed to think of something that fixed it thanks!

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.