How would i snap a mesh onto another?

i want to be able to snap the model onto the other one and detect if its inside it to return false so it can be placed on top of it or inside of it


i have my own placement system but is there any good tutorials on this or something you would recommend?

1 Like

Here is a rough outline of how I would do this:

1. Find closest pair of snaps (the green attachments)

Get a list of the snaps of what’s being placed. Get a list of the snaps of everything else (or just everything within a certain distance).

local placingSnaps = -- List of snap attachments of what's being placed

-- You could get this by looping through existing models when the placement starts
local otherSnaps = -- List of snap attachments of everything else 

-- Skip if not enough snaps to matter
if #placingSnaps < 1 or #otherSnaps < 1 then return end

local closestSnap = placingSnaps[1]
local closestOtherSnap = otherSnaps[1]
local closestDistance = math.huge
for _, snap in ipairs(placingSnaps) do
     for _, otherSnap in ipairs(otherSnaps) do
          local snapPosition = snap.Position
          local otherSnapPosition = otherSnap.Position
local currentDistance = (snapPosition - otherSnapPosition).Magnitude
         -- If the currentDistance is smaller than closestDistance, set the closestSnap,  closestOtherSnap, and closestDistance
     end
end

2. Only if the closest snap is less than a certain threshold

-- Enforce snapping threshold
local SNAP_THRESHOLD = 0.4
if not closestDistance <= SNAP_THRESHOLD then return end

3. Snap the models together

-- Snap the model being placed
local placingSnap = closestSnap
local otherSnap = closestOtherSnap

local placingSnapCFrame = placingSnap.CFrame
local otherSnapCFrame = otherSnap.CFrame

local placingModel = placingSnap:FindFirstAncestorOfClass("Model")
local placingModelCFrame = placingModel.WorldPivot

local modelRelativeToSnap = otherSnapCFrame:ToObjectSpace(placingModelCFrame)
local finalCFrame = otherSnapCFrame:ToWorldSpace(modelRelativeToSnap)

placingModel:PivotTo(finalCFrame)

Let me know if that snapping math works. I can fix it for you if I got something backwards. Make sure to have your attachments oriented properly (i.e. the same way relative to the model, otherwise they will add rotation).

local function Snap(Object: Model)
	if not Object.PrimaryPart then return end
	local placingSnaps = {}
	
	local otherSnaps = {}
	
	for _, soil in CollectionService:GetTagged("Soil") do
		for _, attach in soil:GetChildren() do
			if attach:IsA("Attachment") then
				table.insert(otherSnaps, attach)
			end
		end
	end
	
	for _, attach in Object.PrimaryPart:GetChildren() do
		if attach:IsA("Attachment") then
			table.insert(placingSnaps, attach)
		end
	end
	
	if #placingSnaps < 1 or #otherSnaps < 1 then return end
	
	local closestSnap = placingSnaps[1]
	local closestOtherSnap = otherSnaps[1]
	local closestDistance = math.huge
	
	for _, snap in placingSnaps do
		for _, otherSnap in otherSnaps do
			local snapPos = snap.Position
			local otherSnapPos = otherSnap.Position
			
			local currentDistance = (snapPos - otherSnap.Position).Magnitude
		end
	end
	
	local placingSnap = closestSnap
	local otherSnap = closestOtherSnap
	
	local placingSnapCFrame = placingSnap.CFrame
	local otherSnapCFrame = otherSnap.CFrame
	
	local placingModelCFrame = placingSnap:FindFirstChildWhichIsA("Model")
	
	local modelRelativeToSnap = otherSnapCFrame:ToObjectSpace(placingModelCFrame)
	local finalCFrame = otherSnapCFrame:ToWorldSpace(modelRelativeToSnap)
	
	return finalCFrame
end

it returns nil

RunService.RenderStepped:Connect(function()
	if Object == nil then return end
	if Object.PrimaryPart == nil then return end
	if #CurrentPlaced == MaxItems then Object:Destroy() Object = nil return end
	
	local location : RaycastResult = raycast()
	
	local finalCFrame = Snap(Object)	
	print(finalCFrame)
	
	if location then
		
		local snapping = location.Position + Vector3.new(0, 0.1, 0) - Vector3.new(location.Position.X % 1, location.Position.Y % 1, location.Position.Z % 1)
		Object:PivotTo(CFrame.new(snapping))
	end
end)

did i do something wrong? Object isn’t nil

Try running these test versions that will print some debugging information:

local function Snap(Object: Model)
	if not Object.PrimaryPart then
		print("No model primary part!")
		return
	end
	local placingSnaps = {}
	
	local otherSnaps = {}
	
	for _, soil in CollectionService:GetTagged("Soil") do
		for _, attach in soil:GetChildren() do
			if attach:IsA("Attachment") then
				table.insert(otherSnaps, attach)
			end
		end
	end
	
	for _, attach in Object.PrimaryPart:GetChildren() do
		if attach:IsA("Attachment") then
			table.insert(placingSnaps, attach)
		end
	end
	
	if #placingSnaps < 1 or #otherSnaps < 1 then
		print("Not enough snaps, no possible pair!")
		return
	end
	
	local closestSnap = placingSnaps[1]
	local closestOtherSnap = otherSnaps[1]
	local closestDistance = math.huge
	
	for _, snap in placingSnaps do
		for _, otherSnap in otherSnaps do
			local snapPos = snap.Position
			local otherSnapPos = otherSnap.Position
			
			local currentDistance = (snapPos - otherSnap.Position).Magnitude
			if currentDistance < closestDistance then
				closestDistance = currentDistance
				closestSnap = snap
				closestOtherSnap = otherSnap
			end 
		end
	end
	
	local SNAP_THRESHOLD = 2 -- How close snaps need to be to snap together
	if closestDistance < SNAP_THRESHOLD then
		print("Snaps are too far apart!")
		return
	end

	local placingSnap = closestSnap
	local otherSnap = closestOtherSnap
	
	local placingSnapCFrame = placingSnap.CFrame
	local otherSnapCFrame = otherSnap.CFrame
	
	local placingModelCFrame = placingSnap:FindAncestorWhichIsA("Model")
	
	local modelRelativeToSnap = otherSnapCFrame:ToObjectSpace(placingModelCFrame)
	local finalCFrame = otherSnapCFrame:ToWorldSpace(modelRelativeToSnap)
	
	return finalCFrame
end

I added code for a maximum snapping distance (i.e. wont try to snap with something far away) and switched the placing model search to look for the first ancestor instead of child.

For the second piece of code:

RunService.RenderStepped:Connect(function()
	if Object == nil then return end
	if Object.PrimaryPart == nil then return end
	if #CurrentPlaced == MaxItems then Object:Destroy() Object = nil return end
	
	local location : RaycastResult = raycast()
	
	if location then
		local snapping = location.Position + Vector3.new(0, 0.1, 0) - Vector3.new(location.Position.X % 1, location.Position.Y % 1, location.Position.Z % 1)
		Object:PivotTo(CFrame.new(snapping))
	end

	local snapCFrame = Snap(Object) -- Assumes that the object is at location, so we need to set that first
	if snapCFrame then
		print("Snap CFrame!")
		Object:PivotTo(CFrame.new(snapCFrame))
	else
		print("No snap CFrame!")
	end
end)

I switched the snap function to run after the location setting, since the snap function uses the location of the object. I also added an if statement for the snapping, since if Snap returns nil it means the object shouldn’t be snapped.

Please LMK how that works, and if it doesn’t, what’s printed in the output (you could comment out the no snap and snap prints to make the other logs easier to see).

closing this post as i have fixed the issue

Just out of curiosity, did the code work?

no i just added my previous snapping inside of the raycast function

1 Like

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