How To Clamp An Object To Boundaries

You can write your topic however you want, but you need to answer these questions:
In this post I want to understand the fundamentals of clamping an object to boundaries. I have looked at other devforum posts and I understand a bit on math.clamp.

The issue I have is that when I clamp the object to boundaries, my grid system stops working. I think that when I run the clamp function, it immediately overrides my grid function that I pasted down below.

I tried looking for solutions on the devforum, and some helped me get closer to the solution.

If anyone could help give me an understand on how to intervene math.clamp into my code, that would be greatly appreciated. :slight_smile:

function mouseTrack()
	if MouseTarget then
		
		
		
		local PosX = math.floor(Mouse.Hit.X/GridSize + 0.5)*GridSize
		local PosY = math.floor(Mouse.Hit.Y/GridSize + 0.5)*GridSize
		local PosZ = math.floor(Mouse.Hit.Z/GridSize + 0.5)*GridSize

		MouseTarget.BasePart.Position = Vector3.new(PosX, 1, PosZ)
		MouseTarget.Position = Vector3.new(PosX, 1.2, PosZ)

	end
end

If you were wondering, “BasePart” was my variable for the part under the object to indicate whether the object is good to place…

I don’t see any math.clamp functions in your code so it’s hard for me to see what’s going on, however I’ll do my best to explain how you might approach this again.

Clamp will not override a value unless that value is out of the “range” passed into the function. One thing to remember is that on Roblox and most (if not all) other game engines, the position is taken from the center of objects. To clamp an object to the bounds you have set, you need to understand how to get the corners of the object. What you can do is take the position (which will be by default, is the center) and add or subtract half of the x or z size depending on the corner you want to get to position of. Here’s an image I made quickly to show what I mean:

What you need to do, is clamp the calculated position to each corner. If you are interested in some source code on how this is done, here is the function my module Placement Service uses to calculate position and clamp bounds:

Code
-- Clamps the x and z positions so they cannot leave the plot
local function bounds(c: CFrame, cx: number, cz: number): CFrame
	local pos: CFrame = plot.CFrame
	local xBound: number = (plot.Size.X*0.5) - cx
	local zBound: number = (plot.Size.Z*0.5) - cz
	
	local newX: number = clamp(c.X, -xBound, xBound)
	local newZ: number = clamp(c.Z, -zBound, zBound)
	
	local newCFrame: CFrame = cframe(newX, 0, newZ)
	
	return newCFrame
end

-- Returns a rounded cframe to the nearest grid unit
local function snapCFrame(c: CFrame): CFrame
	local offsetX: number = (plot.Size.X % (2*GRID_UNIT))*0.5
	local offsetZ: number = (plot.Size.Z % (2*GRID_UNIT))*0.5	
	local newX: number = round(c.X/GRID_UNIT)*GRID_UNIT - offsetX
	local newZ: number = round(c.Z/GRID_UNIT)*GRID_UNIT - offsetZ
	local newCFrame: CFrame = cframe(newX, 0, newZ)

	return newCFrame
end

-- Calculates the position of the object
local function calculateItemLocation()
	if currentRot then
		cx = primary.Size.X*0.5
		cz = primary.Size.Z*0.5

		x, z = mouse.Hit.X - cx, mouse.Hit.Z - cz
	else
		cx = primary.Size.Z*0.5
		cz = primary.Size.X*0.5

		x, z = mouse.Hit.X - cx, mouse.Hit.Z - cz
	end

	-- Clamps y to a max height above the plot position
	y = clamp(y, initialY, maxHeight + initialY)

	-- Changes y depending on mouse target
	if stackable and mouse.Target and mouse.Target:IsDescendantOf(placedObjects) or mouse.Target == plot then
		y = calculateYPos(mouse.Target.Position.Y, mouse.Target.Size.Y, primary.Size.Y)
	end

	if moveByGrid then
		-- Calculates the correct position
		local pltCFrame = cframe(plot.CFrame.X, plot.CFrame.Y, plot.CFrame.Z)
		pos = cframe(x, 0, z)
		pos = snapCFrame(pltCFrame:Inverse()*pos)
		finalC = pos*pltCFrame*cframe(cx, 0, cz)
	else
		finalC = cframe(x, y, z)*cframe(cx, 0, cz)
	end

	finalC = bounds(finalC)

	return finalC	
end

As you can see, all I do is calculate the position first, then set the finalC variable to the clamped return value (passing in the unclamped CFrame to the function). The basic steps done by the clamp function are:

  • Find each corner
  • Create new CFrame with clamped values
  • Return new CFrame

One thing to note is that I am also adding/subtracting the primary part’s size/2 (cx, cz). The reason for this is the same as above. Since the position is taken from the center, (unless the pivot is changed) the object would clamp to the corners but be half way out of the surface. To fix this, we can simply compensate for this by adding/subtracting half of the model/part size (X and Z) depending on each corner.

If I am misunderstanding your issue, I will need more details and code samples with the clamp function attempts to give further assistance.

Hope this helps!

5 Likes

Not sure if you know but you could save a few lines there by doing something like this unless if I misunderstood something in your writing but instead of having that large if statement there you can simply do this:

local Min = -(PlotSize - PlotPosition) + ModelSize
local Max = (PlotSize + PlotPosition) - ModelSize 
	
local X = math.clamp(HitPosition.X, Min.X, Max.X)
local Y = ModelSize.Y
local Z = math.clamp(HitPosition.Z, Min.Z, Max.Z)
1 Like

The reason the if statement is there is when the user rotates, I need to compensate for the length of the x and z swapping basically. Cause when the model rotates, it doesn’t shift the entire coordinate plane with it meaning the x will become the z and z the x.

Good point, sorry for misreading

1 Like

Thanks everyone for the help, I ended up figuring out a way to prevent it from going outside the boundaries!

1 Like