Placement system detecting unwanted collisions on rotated plots?

Hello! I am working on an update for my module Placement Service (a placement system module). I am facing a issue with the module on rotated plots. The collision system detects collisions even if the model being placed is on the grid units adjacent to another model (only if the placed model and plot are rotated). What doesn’t make sense to me is it doesn’t happen as often with smaller grid units (1, 2), and it doesn’t happen over the entire part. I don’t know why this is happening as from my testing, there should be no collision even though it’s detecting something. My best guess is that somehow the placed part is being slightly rotated which would make it so only a portion of the model detects collisions, but I’ve compared the rotation of the placed model and the one being placed and it matches. There are no other objects that are interfering with the collisions either. The server code doesn’t change the position or rotation on placement so there is no issue there either. Although both the server and client detect a collision from my testing.

Here is a video showing the issue:

Maybe I’m just missing something, so here is my code where I think the issue is:

Here is the collisions function I am using

local function checkHitbox()
	if object and collisions then
		if range then
			setCurrentState(5)
		else
			setCurrentState(1)
		end

		local collisionPoints: {BasePart} = workspace:GetPartsInPart(hitbox)

		-- Checks if there is collision on any object that is not a child of the object and is not a child of the player
		for i = 1, #collisionPoints, 1 do
			if collisionPoints[i].CanTouch and not collisionPoints[i]:IsDescendantOf(object) and not collisionPoints[i]:IsDescendantOf(character) and collisionPoints[i] ~= plot then
				setCurrentState(3)

				if preferSignals then
					collided:Fire(collisionPoints[i])
				end

				break
			end
		end

		return
	end
end

Here is where the position is calculated:

-- Calculates the position of the object
local function calculateItemLocation(last, final: boolean): CFrame
	local x: number, z: number
	local cx: number, cz: number
	local sizeX: number, sizeZ: number = primary.Size.X*0.5, primary.Size.Z*0.5
	local finalC: CFrame
	
	if not currentRot then
		sizeX = primary.Size.Z*0.5
		sizeZ = primary.Size.X*0.5
	end
	
	if moveByGrid then
		cx = sizeX - floor(sizeX/GRID_UNIT)*GRID_UNIT
		cz = sizeZ - floor(sizeZ/GRID_UNIT)*GRID_UNIT
	else
		cx = sizeX
		cz = sizeZ
	end
	
	local cam: Camera = workspace.CurrentCamera
	local camPos: Vector3 = cam.CFrame.Position
	local unit: Ray
	local ray
	local nilRay

	if isMobile then
		ray = workspace:Raycast(camPos, cam.CFrame.LookVector*maxRange, raycastParams)
		nilRay = camPos + cam.CFrame.LookVector*(maxRange + plot.Size.X*0.5 + plot.Size.Z*0.5)
	else
		unit = cam:ScreenPointToRay(mouse.X, mouse.Y, 1)
		ray = workspace:Raycast(unit.Origin, unit.Direction*maxRange, raycastParams)
		nilRay = unit.Origin + unit.Direction*(maxRange + plot.Size.X*0.5 + plot.Size.Z*0.5)
	end

	if ray then
		x, z = ray.Position.X - cx, ray.Position.Z - cz
		
		if stackable then
			target = ray.Instance
		else
			target = plot
		end
	else
		x, z = nilRay.X - cx, nilRay.Z - cz
		target = plot
	end
	
	y = calculateYPos(plot.Position.Y, plot.Size.Y, primary.Size.Y)
	
	-- Changes y depending on mouse target
	if stackable and target and (target:IsDescendantOf(placedObjects) or target == plot) then
		if ray and ray.Normal then
			if not isMobile and cframe(ray.Normal):VectorToWorldSpace(Vector3.FromNormalId(Enum.NormalId.Top)):Dot(ray.Normal) > 0 then
				y = calculateYPos(target.Position.Y, target.Size.Y, primary.Size.Y)
			elseif isMobile then
				y = calculateYPos(target.Position.Y, target.Size.Y, primary.Size.Y)
			else
				y = ray.Instance.Position.Y
			end
		end	
	end
	
	-- Clamps y to a max height above the plot position
	y = clamp(y, initialY, maxHeight + initialY)
	
	local pltCFrame: CFrame = plot.CFrame
	
	if moveByGrid then
		-- Calculates the correct position
		local rel: CFrame = pltCFrame:Inverse()*cframe(x, 0, z)*cframe(cx, 0, cz)
		local snappedRel: CFrame = snapCFrame(rel)*cframe(cx, 0, cz)
		
		if not removePlotDependencies then
			snappedRel = bounds(snappedRel, sizeX, sizeZ)
		end
		
		finalC = pltCFrame*snappedRel
	else
		finalC = pltCFrame:Inverse()*cframe(x, 0, z)*cframe(cx, 0, cz)
		
		if not removePlotDependencies then
			finalC = bounds(finalC, sizeX, sizeZ)
		end
		
		finalC = pltCFrame*finalC
	end
	
	-- For placement or no intepolation
	if final or not interpolation then
		return (finalC*cframe(0, y - plot.Position.Y, 0))*anglesXYZ(0, rot*pi/180, 0)
	end
	
	return (finalC*cframe(0, y - plot.Position.Y, 0))*anglesXYZ(0, rot*pi/180, 0)*calcAngle(last, finalC)
end

Here is a demo place to test the issue out or see the full source code of the module used (and anything else that could be causing this issue):
placementbug.rbxl (71.5 KB)
(make sure when testing to rotate the object)

Thank you to all who help, it is greatly appreciated. If I learn any new information, I’ll update this post with that.

Anyways, thanks!

Hi! Sorry for the reply earlier :sweat_smile:

You need to floor the entire result, not the size divided by grid only.

if moveByGrid then
		cx = sizeX - floor(sizeX/GRID_UNIT*GRID_UNIT)
		cz = sizeZ - floor(sizeZ/GRID_UNIT*GRID_UNIT)
	else
		cx = sizeX
		cz = sizeZ
	end

Worked for me. Let me know if you have any issues

1 Like

Hi! Thanks for the reply. Unfortunately, this did not solve the issue. The issue is very strange as it only happens when you rotate an object. I am guessing you didn’t rotate the which made it appear that it was working. I’ve added a note as I didn’t make that very clear in my post. Thanks anyways!

All good :+1: