Weird offset in a building system

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!
    I want to create a building system that works with multiple sized blocks and snaps their faces together perfectly
  2. What is the issue? Include screenshots / videos if possible!
    When I have an irregular sized block (regular is 4x4x4, an irregular is 4x4x8 for example) I get weird offsets in position.
  3. What solutions have you tried so far? Did you look for solutions on the Creator Hub?
    I only know that it happens when some of the normal axis are either positive or negative, and none of the creator hub solutions I found helped me. I tried to offset it back if the normal’s Z is negative, but that worked weirdly, sometimes it didnt do anything, sometimes it made it worse.
    After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that it’s easier for people to help you!

Below I listed the main CFrame calculation section that determines the position and the orientation of the placed block depending on some factors.

local cframe = CFrame.new(raycast.Position + (raycast.Normal * partModel.Size/2))
		if raycast.Instance:HasTag("Part") then -- the part tag just determines if its a player placed block
			local normalMax, axi = GetLargestValueFromVector3(raycast.Normal)
			local offset = raycast.Normal * (partModel.Size[axi])
			
			cframe = raycast.Instance.CFrame + offset
			if raycast.Instance.Size ~= partModel.Size then
				offset = ((raycast.Normal * (raycast.Instance.Size[axi]))/2)
					+ ((raycast.Normal * (partModel.Size[axi]))/2)
				cframe = raycast.Instance.CFrame + offset
			end
		end

First off, here’s a better way to write it:

local cframe = CFrame.new(raycast.Position + (raycast.Normal * partModel.Size/2))
	if raycast.Instance:HasTag("Part") then
		local normalMax, axi = GetLargestValueFromVector3(raycast.Normal)
		local offset = raycast.Normal * (partModel.Size[axi])
		
		if raycast.Instance.Size ~= partModel.Size then
			offset = ((raycast.Normal * (raycast.Instance.Size[axi]))/2)
					+ ((raycast.Normal * (partModel.Size[axi]))/2)
		end

		cframe = raycast.Instance.CFrame + offset
	end

Another question, what does GetLargestValueFromVector3() do? Could you send the code in that function, also are you aware that normal vectors aren’t always just (0, 1, 0) or (1, 0, 0) for example? They can also be decimals, like (0.707, 0.707, 0). Correct if I’m wrong, because maybe your system only works with the former because the part models are never rotated in any way.

For the first line, additionally, you multiply the unit vector by partModel.Size/2, but why are you multiplying vectors? Did you mean to multiply raycast.Normal by partModel.Size.X/2 or partModel.Size.Y/2, as in a component of the vector? To get the component you could use the GetLargestValueFromVector3() function if it does what it’s named.

As for your issue, I do not know as to why it occurs, but I have a feeling it has to do with this line:

offset = ((raycast.Normal * (raycast.Instance.Size[axi]))/2)
					+ ((raycast.Normal * (partModel.Size[axi]))/2)

I believe you wrote this line because if the part you’re placing on top isn’t the same size, you can’t use the shortcut of just using the entire size component, because a half plus a half equals a full, but technically you should be able to just use this line all the time, because it works for both cases, so something like this:

local cframe = CFrame.new(raycast.Position + (raycast.Normal * partModel.Size/2))
	if raycast.Instance:HasTag("Part") then
		local normalMax, axi = GetLargestValueFromVector3(raycast.Normal)
		offset = ((raycast.Normal * (raycast.Instance.Size[axi]))/2)
			+ ((raycast.Normal * (partModel.Size[axi]))/2)

		cframe = raycast.Instance.CFrame + offset
	end

Correct me if I’m wrong, I’m not familiar with this code.

Yup, sorry for not fully elaborating. The getlargestvaluefromvector3 function was just something I tried to use to get the highest component that I would use to multiply the normal by. It caused weird issues and I decided to not use both values from it, but I still left it in the code. here is what it did:

local x,y,z = v.X,v.Y,v.Z
	local max = math.max(math.abs(x),math.abs(y),math.abs(z))
	local axi = max == x and "X" or max == y and "Y" or "Z"
	return max, axi

As for this part,

but why are you multiplying vectors? Did you mean to multiply raycast.Normal by partModel.Size.X/2 or partModel.Size.Y/2, as in a component of the vector?

I just did it because I thought that would work best with blocks of different sizes. I later figured I was wrong.
As for everything else in code, its pretty self explanatory. It finds a part from a folder in replicated storage, PivotTo(cframe), and then parents it under a folder and welds it to what you placed it on. I only shared the cframe calculation bit because I believed that is where the problem was, and I still think it is.

1 Like

When you’re making blocks of 4 and 8.
When I take a square of 4, w
44
44

and I turn it into a 8, how do you think it’s done? The size is added uniform (so in all directions).
8448
8448
From the center to the edge of the cube, the edge of a 4x4 cube is 2 away, the edge of a 8x8 is 4 away. This means we have an offset. These two parts have the same position.


One way to solve it is by offsetting by
Vector3.new((GRIDSIZE/X)-1) * GRIDSIZE,(GRIDSIZE/Y)-1) * GRIDSIZE, (GRIDSIZE/Z)-1) * GRIDSIZE)
This offsets it in any direction that it is 8, but not if it’s 4.

1 Like

Offset along the hit normal by half both sizes.

local hit = raycast
local size = partModel.Size
local inst = hit.Instance
local normal = hit.Normal

local axis
if math.abs(normal.X) > 0 then axis = "X"
elseif math.abs(normal.Y) > 0 then axis = "Y"
else axis = "Z" end

local offset = normal * ((inst.Size[axis]/2) + (size[axis]/2))
local cframe = inst.CFrame * CFrame.new(offset)

Arbitrary angles, snapping blocks face-to-face..

local hit = raycast
local inst = hit.Instance
local size = partModel.Size
local normal = inst.CFrame:VectorToObjectSpace(hit.Normal)

local axis
if math.abs(normal.X) > 0.5 then axis = "X"
elseif math.abs(normal.Y) > 0.5 then axis = "Y"
else axis = "Z" end

local offset = Vector3.new(
	(axis=="X" and math.sign(normal.X)*(inst.Size.X/2+size.X/2)) or 0,
	(axis=="Y" and math.sign(normal.Y)*(inst.Size.Y/2+size.Y/2)) or 0,
	(axis=="Z" and math.sign(normal.Z)*(inst.Size.Z/2+size.Z/2)) or 0
)

local cframe = inst.CFrame * CFrame.new(offset)

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