Here’s what I made earlier when someone had a similar problem. Now I just added a snap to grid -thing to it. I believe it’ll solve your problem. It treats every part as a block in the calculations. It’s not very well optimised, but I don’t think it’ll cause performance issues unless you use models that contain a lot of parts. And for those models, you could actually use a single collision box part, which would prevent possible performance problems.
The function that you are supposed to call is makeSureObjIsInGrid
.
local function getCornerCfs(cf, size)
local cornerCfs = table.create(8)
local hxs, hys, hzs = size.X/2, size.Y/2, size.Z/2
local i = 0
-- repeat for each corner of the part using the loop and multipliers in it
for ix = 1, 2 do
local xm =(-1)^ix
for iy = 1, 2 do
local ym =(-1)^iy
for iz = 1, 2 do
local zm =(-1)^iz
i += 1
cornerCfs[i] = cf*CFrame.new(xm*hxs, ym*hys, zm*hzs)
end
end
end
return cornerCfs
end
local function getExtra(cf, size, gridCenterInverse, xLimit, zLimit) -- this is a separate function to avoid code repetition
local hxs, hys, hzs = size.X/2, size.Y/2, size.Z/2
local largestExtraX, largestExtraZ = 0, 0
-- repeat for each corner of the part using the loop and multipliers in it
local cornerCfs = getCornerCfs(cf, size)
for i, cornerCf in ipairs(cornerCfs) do
local cornerX, cornerZ = cornerCf.X, cornerCf.Z
-- getting what the offset of this spesific corner of the part would be to the GRID_CENTER
-- If the object given to makeSureobjIsInGrid is a model, this is
-- the CFrame the corner would have if the PrimaryPart of the model would be set to the CFrame given to
-- the makeSureObjIsInGrid
-- if this is a single part not in a model, then the corner's Cframe this checks is the cframe where the corner would be if
-- the CFrame given to makeSureObjIsInGrid would be the CFrame of the part
local relCf = gridCenterInverse*cornerCf
local relX, relZ = relCf.X, relCf.Z
local negX, negZ = relX < 0, relZ < 0
-- this is how much the corner is too far away from the GRID_CENTER on the local x and z axes of the GRID_CENTER
local extraX, extraZ = relX-(negX and -xLimit or xLimit), relZ-(negZ and -zLimit or zLimit)
-- here the script checks if it is further away than any corner checked before
-- and if it is, the largest value will be updated
if math.abs(relX) > xLimit and ((negX and extraX < largestExtraX) or (not negX and extraX > largestExtraX)) then
largestExtraX = extraX
end
if math.abs(relZ) > zLimit and ((negZ and extraZ < largestExtraZ) or (not negZ and extraZ > largestExtraZ)) then
largestExtraZ = extraZ
end
end
return largestExtraX, largestExtraZ
end
-- When using this for models, the cf should be the CFrame that the model's PrimaryPart would be set to if that CFrame was already valid
-- the gridcenter must be a CFrame, the sizes are the sizes of the grid on the local x- and z- axes of the gridCenter
local function makeSureObjIsInGrid(obj, cf, gridCenter, gridXSize, gridZSize, gridUnit)
local gridCenterInverse, xLimit, zLimit = gridCenter:Inverse(), gridXSize/2, gridZSize/2
if obj:IsA("BasePart") then
local extraX, extraZ = getExtra(cf, obj.Size, gridCenterInverse, xLimit, zLimit)
-- relative CFrame of the part on the local axis of GRID_CENTER
-- (gridCenter is kind of like treated as the center of the world)
local relCf = gridCenterInverse*cf
--local newPartCf = gridCenter*(relCf-Vector3.new(extraX, 0, extraZ))
local xOffset, zOffset = relCf.X-extraX, relCf.Z-extraZ
if gridUnit then
local absXOffset, absZOffset = math.abs(xOffset), math.abs(zOffset)
xOffset = math.sign(xOffset)*(absXOffset-absXOffset%gridUnit)
zOffset = math.sign(zOffset)*(absZOffset-absZOffset%gridUnit)
end
local newPartCf = gridCenter*(relCf+Vector3.new(-relCf.X+xOffset, 0, -relCf.Z+zOffset))
return newPartCf
elseif obj:IsA("Model") then -- you'll probably use models when you make furniture
-- make sure that if you use models the PrimaryPart is set. Otherwise this will error.
local realPrimaryPartCfInverse = obj:GetPrimaryPartCFrame():Inverse()
local largestExtraX, largestExtraZ = 0, 0 -- variable names may be missleading, the extra values can also be negative
for i, v in ipairs(obj:GetDescendants()) do
if v:IsA("BasePart") then
local realPartCf = v.CFrame
-- giving the getExtra function a CFrame that is relative to the cf the same way as
-- the current CFrame of the part is relative to the current PrimaryPartCFrame of the model
local extraX, extraZ = getExtra(cf*(realPrimaryPartCfInverse*realPartCf), v.Size, gridCenterInverse, xLimit, zLimit)
local negX, negZ = extraX < 0, extraZ < 0
-- changing the largestValues if distance from edge is larger than any distance found until this
-- and updating maxValueif it is
if math.abs(extraX) > math.abs(largestExtraX) then
largestExtraX = extraX
end
if math.abs(extraZ) > math.abs(largestExtraZ) then
largestExtraZ = extraZ
end
end
end
-- offset between the two cframes on the local axis of the center CFrame of the grid
local relCf = gridCenterInverse*cf
local xOffset, zOffset = relCf.X-largestExtraX, relCf.Z-largestExtraZ
if gridUnit then
local absXOffset, absZOffset = math.abs(xOffset), math.abs(zOffset)
xOffset = math.sign(xOffset)*(absXOffset-absXOffset%gridUnit)
zOffset = math.sign(zOffset)*(absZOffset-absZOffset%gridUnit)
end
-- This will be set as the CFrame of the PrimaryPart. It's calculated by using the relative offset (relCf)
-- and substracting the extras from that and then kind of moving the GRID_CENTER on it's own axis with the resulting CFrame.
local newPrimaryCf = gridCenter*(relCf+Vector3.new(-relCf.X+xOffset, 0, -relCf.Z+zOffset))
return newPrimaryCf
end
end