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