Grid placement inaccuracy

I have a working placement system that has a grid which isn’t fully functional
Basically I take model.PrimaryPart.Position to check if it’s inside the plot, however this takes the model’s very center and you are still able to place even if the model is out of bounds and this is what happens:

What I want:

How can I avoid taking the model’s center position and get it’s all 4 sides or solve this somehow else?

I think you should explain more about the approach you are using for this placement system.
Raycasting?
In those pictures, its based on the wall or based on the floor?
You tried using an offset substracting the half of the size of the part you are placing or the size of the wall?

Add or subtract half the size in each axis to get the X, Y and Z coordinates of the center of each face, and check if those are within the bounds.

1 Like

Ok so this is currently what I’ve tried and works pretty well except for positive Z axis ?
(PlotBounds.Negative and PlotBounds.Positive are Vector3Values)

local modelSize = currentModel:GetExtentsSize()

local bottomLeftCorner = currentModel.PrimaryPart.Position - Vector3.new(modelSize.X / 2, 0, modelSize.Z / 2)
local bottomRightCorner = currentModel.PrimaryPart.Position - Vector3.new(-modelSize.X / 2, 0, modelSize.Z / 2)
local topLeftCorner = currentModel.PrimaryPart.Position + Vector3.new(modelSize.X / 2, 0, -modelSize.Z / 2)
local topRightCorner = currentModel.PrimaryPart.Position + Vector3.new(-modelSize.X / 2, 0, -modelSize.Z / 2)

if bottomLeftCorner.X < PlotBounds.Negative.Value.X or
	bottomLeftCorner.Z < PlotBounds.Negative.Value.Z or
	bottomRightCorner.X > PlotBounds.Positive.Value.X or
	bottomRightCorner.Z > PlotBounds.Positive.Value.Z or
	topLeftCorner.X < PlotBounds.Negative.Value.X or
	topLeftCorner.Z > PlotBounds.Positive.Value.Z or
	topRightCorner.X > PlotBounds.Positive.Value.X or
	topRightCorner.Z > PlotBounds.Positive.Value.Z then
	canPlace = false
else
    canPlace = true
end

is there a more efficient way to this?

Read my post please. It’s based on the model.PrimaryPart.Position which is the center of the model and it’s checked if it’s within the bounds of the plot with Vector3

Try using the Model’s bounding box

local cframe, size = Model:GetBoundingBox()
-- CFrame is Position and Orientation
-- Size is... well, size. (Vector3)
1 Like

Not sure if this is correct, but it works:

local CF, Size = currentModel:GetBoundingBox()
				local bottomLeftCorner = CF + Vector3.new(Size.X * -0.5, 0, Size.Z * -0.5)
				local topRightCorner = CF + Vector3.new(Size.X * 0.5, 0, Size.Z * 0.5)
				
				if bottomLeftCorner.X < PlotBounds.Negative.Value.X or
					bottomLeftCorner.Z < PlotBounds.Negative.Value.Z or
					topRightCorner.X > PlotBounds.Positive.Value.X or
					topRightCorner.Z > PlotBounds.Positive.Value.Z then
					canPlace = false
				else
					canPlace = true
				end

Thanks for the help

Yes, especially if you want it to work no matter how the parts are rotated (although still at 90 degree intervals):

local DOT_TO_DIAGONAL = Vector3.xAxis:Dot((Vector3.xAxis + Vector3.yAxis + Vector3.zAxis).Unit)

--[[
Given a part and a world face (or a direction in world space, represented by a NormalId), return the face
of the part that most closely corresponds to that world face.
]]
function getPartFace(part: BasePart, worldFace: Enum.NormalId): Enum.NormalId
	local worldNormal = Vector3.FromNormalId(worldFace)
	for _, partFace in ipairs(Enum.NormalId:GetEnumItems()) do
		local partNormal = part.CFrame:VectorToWorldSpace( Vector3.FromNormalId(partFace) )
		local isClosest = partNormal:Dot(worldNormal) >= DOT_TO_DIAGONAL
		if isClosest then 
			return partFace 
		end
	end
	error() --Unreachable
end

--[[
Given a part and one of it's faces, return the center of the face of that part.
]]
function getPartFaceCenter(part: BasePart, face: Enum.NormalId): Vector3
	local objectTranslation =  Vector3.fromNormalId(face) * part.Size * 0.5
	return part.CFrame * objectTranslation
end

--[[
Checks if the inner part is fully contained by the outer part.
Works for parts that are axis-aligned or rotated at right angles to the axes.
]]
function isPartFullyInPart(outer: Part, inner: Part): boolean
	local outerCF, innerCF = outer.CFrame, inner.CFrame
	for _, worldFace in ipairs(Enum.NormalId:GetEnumItems()) do
		local outerFace = getPartFace(outer, worldFace)
		local innerFace = getPartFace(inner, worldFace)
		
		local faceAxis = Vector3.FromNormalId(outerFace)
		local outerDisplacementOnAxis = ((getPartFaceCenter(outer, outerFace) - outerCF.Position) * faceAxis):Dot(faceAxis)
		local innerDisplacementOnAxis = ((getPartFaceCenter(inner, innerFace) - outerCF.Position) * faceAxis):Dot(faceAxis)
		
		local outsideOnAxis = math.abs(innerDisplacementOnAxis) > math.abs(outerDisplacementOnAxis) 
		local sameDirection = math.sign(outerDisplacementOnAxis) == math.sign(innerDisplacementOnAxis)
		
		if sameDirection and outsideOnAxis then
			return false
		end
	end
	
	return true
end

It essentially does the same as your math, but since the parts might be rotated it can’t know if it should compare (X to X) or (X to Y) or (X to Z) and whatever other combinations are possible. So it uses some vector math instead to figure out which face on each part corresponds to each of the directions. And it uses GetEnumItems to loop over all 6 directions.

3 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.