How to make an object unable to go outside of a part's surface

Hello, I need help making an object that isn’t able to go outside a part’s surface. I’ve tried using some clamp methods such as:

local posX = math.clamp(BPortal.BPortal.Position.X, hit.Position.X - hit.Size.X/2, hit.Position.X + hit.Size.X/2)
local posY = math.clamp(BPortal.BPortal.Position.Y, hit.Position.Y - hit.Size.Y/2, hit.Position.Y + hit.Size.Y/2)
local posZ = math.clamp(BPortal.BPortal.Position.Z, hit.Position.Z - hit.Size.Z/2, hit.Position.Z + hit.Size.Z/2)

BPortal.BPortal.Position = Vector3.new(posX, posY, posZ)

BPortal.BPortal is the part i want to not be able to go out of hit's surface

What that method does is snap the BPortal to the center of hit, and doesn’t work

So i’m trying to achieve this:

portalcorrect

but not this:

portalclipping

or this:

portalclipping2

So for example, this is what should happen when you place the BPortal in the very top right or bottom right corner of the part:

workingrr

3 Likes

So there’s quite a lot to this question, but it’s a very good one imo. There are a number of problems we’ll have to solve here which I’ll outline below.

Keep in mind in all of this I am assuming we’re using brick shaped parts. If you’re not you can’t easily calculate surface size and thus clamping becomes difficult. In those cases I recommend ignoring clamping.

Getting a surface’s orientation from a surface normal.

I’ll leave this section relatively bare because I’ve discussed this before. See this thread for more information about why this function works and does what it does.

local function vec3Func(f, ...)
	local x, y, z = {}, {}, {}
	for i, v in next, {...} do
		x[i], y[i], z[i] = v.x, v.y, v.z
	end
	return Vector3.new(f(unpack(x)), f(unpack(y)), f(unpack(z)))
end

local function getRotationBetween(u, v, axis)
    local dot, uxv = u:Dot(v), u:Cross(v)
    if (dot < -0.99999) then return CFrame.fromAxisAngle(axis, math.pi) end
    return CFrame.new(0, 0, 0, uxv.x, uxv.y, uxv.z, 1 + dot)
end

local function getSurfaceCF(part, lnormal)
	local pcf, size2 = part.CFrame, part.Size/2
	local transition = getRotationBetween(UNIT_Z, lnormal, UNIT_Y)
	local size = vec3Func(math.abs, transition:VectorToWorldSpace(part.Size))
	return (pcf * transition) * CFrame.new(size/2 * Vector3.new(0, 0, 1)), size
end

local target = mouse.Target
local normal = -- the normal of the surface your mouse is over
local rotation, surfaceSize = getSurfaceCF(target, target.CFrame:VectorToObjectSpace(normal))

Clamping to the surface for any orientation of our portal.

Now once you have the surface CFrame and surface Size it’s quite simple to clamp an object to the surface given you know the size of the part relative to its rotation. So if you don’t apply any extra rotation to the portal object you’d just clamp based on portal.Size. However, since we want the portal to rotate dependent on how are camera is looking at the surface we’ll need to be able to handle an arbitrary rotation.

We do this by finding that arbitrary rotation relative to the surface CFrame and then finding the bounding box size of object we’re placing. We can then clamp with this size value instead.

local corners = getCornersFromCFrameSize(surfCF, portal.Size) -- function gets corners from cframe and size
local max = vec3Func(math.max, unpack(corners))
local min = vec3Func(math.min, unpack(corners))
local pSize = max - min -- this is our world bounds size

We could then handle our clamping logic:

if (pSize.x > surfaceSize.x or pSize.y > surfaceSize.y) then
	-- can't place item (won't fit)
	return
end

-- clamping
local pos = rotation:PointToObjectSpace(mouse.Hit.p)
pos = rotation:PointToWorldSpace(Vector3.new(
	math.clamp(pos.x, -surfaceSize.x/2 + pSize.x/2, surfaceSize.x/2 - pSize.x/2),
	math.clamp(pos.y, -surfaceSize.y/2 + pSize.y/2, surfaceSize.y/2 - pSize.y/2),
	pos.z
))

Finding the correct orientation for our portal given the camera.

So the last question is to find the correct surfCF that would rotate our portal relative to our camera. This is simple enough because the actual camera’s rotation already encompasses most of this information.

We only really care about the spin relative to the surface though, not any vertical tilt. To ensure we only grab that information we’ll flatten the right and up vector to the surface and rebuild the CFrame. Think of this of getting the shadow of the UpVector and RightVector on the surface and then using those two vectors to get the rotation.

local camCF = rotation:ToObjectSpace(workspace.CurrentCamera.CFrame)
local rVec = (camCF.RightVector * XY_VEC).Unit
local uVec = UNIT_Z:Cross(rVec).Unit
local surfCF = CFrame.fromMatrix(ZERO, rVec, uVec)

Conclusion

Now we can put all that together and create a clamped CFrame for our part.

Portal placement.rbxl (30.6 KB)

Good luck! Enjoy :+1:

22 Likes

Oh
My
God.

THE EgoMoose.

THE Legend

Thank you so so much, this will work perfectly.

8 Likes

After playing around with it i realized three problems:

One: Portals can clip through objects not on the surface they were placed on

Two: Portals don’t like wedges, but this wont really be an issue as i can just use rotated parts

Three: Portals have to be placed precisely to fit onto certain parts

I do feel like i’m asking for too much here, Sorry

EDIT: Issue 1 can be kinda fixed by unioning the parts i want to work,


but then other problems arise:

1 Like

For issue 1 I don’t think you’ll find an efficient way to clamp. What I’d do personally is use use a region3 or something of that nature to find out if you’re bumping into something and if so don’t place it.

Issue 3 is intentional. The portal doesn’t fit in any other way than that precise position/rotation so that’s why it doesn’t work.

Issue 2 was assumed from the get go:

2 Likes