How to fix my math.clamp() so objects when being moved stayed in a plot's area?

So here’s my problem in my build system… I want to make it so when you move your mouse across the plot the object stays on the plot’s part and doesn’t hangover. Currently, when you move your mouse it keeps the model on the plot but half of the model is not.

I’ve asked multiple times and haven’t gotten an answer that fixed the problem. I’ve tried math.clamp() and it just made it so the object stays at the center of the plot and you can’t move it.

Here’s the script:

local function SnapToGrid(vector3)


math.clamp( Round( vector3.X, Grid ), Plot.Position.X - 20, Plot.Position.X + 20),

Round(vector3.Y, 0),

math.clamp( Round( vector3.Z, Grid ), Plot.Position.Z - 20, Plot.Position.Z + 20),




Searching through your long script, this is the only code I could find that is related to your problem with math.clamp.

This is a microcosm on your code’s unreadability as I can’t tell what’s happening so here are some questions:

  1. What are you inputting into the function? Is Vector 3 the unchanged mouse position which you are snapping to the grid

  2. What is a Plot? assuming from the .Position call it seems to be a base part and Why are you subtracting an arbitrary number of +20 and -20? What is the logic behind this, plus there are no comments here at all to indicate whats happening.

Plus, I see code redundancies when you set an event for each prop to MouseButton1Click.

From this, I recommend learning Object-Oriented Programming so you can group up all the props into one Prop placement object:

Beyond these code readability issues, I recommend just boiling down the issue to psuedo code, no need to worry about the actually mechanics of how to use math.clamp.

This would be something like this:

if furniture is outside the plot then

Pick a point on your plot so that when the
furniture is placed at this point the furniture
will not overhang


do nothing, the furniture is inside the plot and is fine


Yeah, you gotta fix your code formatting, or no one will want to read it :laughing: Including yourself in a week. Use the proper about of tabs, and don’t do that double-line-break-all-the-time thing. Use double line-breaks to separate statements that are less related, and have single line-breaks for more related statements.

Also, don’t just dump an entire 360 LoC script on us, post the relevant parts of the code that you need help with. If it’s the entire script you don’t understand, then at least disclose that you’ve just grabbed it somewhere and didn’t write it yourself, and spend some time analyzing it so that you can ask specific questions.

And I just want to voice my opinion on the whole OOP thing that was suggested, I don’t think that’s a good idea. This is not a case where OOP makes anything better, because none of the issues are related to code encapsulation or inheritance. If you want help making your code less repetitive and easier to maintain, post a comment or a new post :slight_smile:

1 Like

First, this script is written by me but I asked a couple people on the Dev Forum for various problems like the 2 by 2 Grid and now the Math.Clamp which someone suggested a couple weeks ago. I shortened the script to just the math.Clamp() and what I want is so the object can only be moved on the plot and so it does not hang over but I can’t figure out how to find a min and max of a basepart(plot) because the position is just the middle of the plot not the perimeter. Hope you can help…

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**hxs, ym*hys, zm*hzs)
	return cornerCfs

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
		if math.abs(relZ) > zLimit and ((negZ and extraZ < largestExtraZ) or (not negZ and extraZ > largestExtraZ)) then
			largestExtraZ = extraZ
	return largestExtraX, largestExtraZ

-- 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*(, 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)
		local newPartCf = gridCenter*(, 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
				if math.abs(extraZ) > math.abs(largestExtraZ) then
					largestExtraZ = extraZ
		-- 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)
		-- 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*(, 0, -relCf.Z+zOffset))
		return newPrimaryCf
1 Like