Finding Difference of Height Based On Angle Rotation

I am trying to make a part rotate side ways but move the CFrame up so that it doesn’t collide with the ground.

I need somebody to explain the math in order to do this.

Eg.

If a part was rotated 45 degrees, how would I figure out the distance with math and move it up so it doesn’t collide with the ground?

Converting this:
image
To this:
image

Can’t you just use :MoveTo? It has this special property:

Assuming you know the Y level of the floor already, all you need to find is the lowest extents of the part in the Y direction, i.e. the height of the lowest corner. There are a bunch of ways to do this, one simple one is to just take the minimum of all 8 part corners y locations, like so:

local part = game.Workspace.Part
local cf = part.CFrame
local halfSize = 0.5 * part.Size
local minY = math.huge
for x = -1,1,2 do
	for y = -1,1,2 do
		for z = -1,1,2 do 
			local p = cf * CFrame.new(halfSize*Vector3.new(x,y,z))
			minY = math.min(minY, p.Y)
		end
	end
end
print(minY)

I wrote this as nested for loops so that it you can more easily see that it’s going over all 8 corners of the part by doing part position +/- all the combinations of half the size. But you could actually hard-code all the offsets into one long 8-argument call to math.min() with no for loops needed at all.

Also, if you already know which corner is the lowest, based on constraints you’re putting on the rotations, then you might have just one corner to check. The code above assumes you don’t know which corner of the part is lowest.

1 Like

I don’t understand what’s happening here, your for loop would only run one time? Could you also explain the math (halfSize*Vector3.new(x,y,z)) as well as minY = math.min(minY, p.Y)

I’ve just never seen looping through vertices of a part. How does this get the vertices’ height or extent?

This would only work for models though

Also, just to be clear, I want to change both rotation and position, the MoveTo method that you have suggested would not work as the only parameter it takes is a Vector3 position.

1 Like

Sure, I can explain.

halfSize is a Vector3, which you can see is just half the part size. So for the standard 4x1x2 block, it’s just the vector (2, 0.5, 1). Half the size in each dimension is how far it is from the center of the part (Part.CFrame.Position) to the sides (the faces) in each of the cardinal directions. The corners of the part, of which there are 8, are all vertices that are shared by 3 sides of the part, so you need to go in all 3 directions to get to a corner. For example, to get on one corner, you start at the center of the part and go half the width in the direction of the part.CFrame.RightVector, half the height in the direction of its UpVector, and half its depth in the direction of its LookVector. Since in each direction, you have 2 possible ways to go, you end up with 8 combinations +/- Right, Up, and Look.

The loops are iterating through all the combinations of +1 and -1, e.g. (1,1,1), (1,1,-1), (1,-1,-1) etc, all 8 of them, multiplied then by half the part size, to reach all 8 corners in this manner.

CFrame.new(halfSize*Vector3.new(x,y,z)) is a CFrame that is a pure translation, representing the vector described above. The rotation matrix part of this CFrame is left as an identity matrix (no added rotation).

part.CFrame * TranslationCFrame gives you a CFrame that has the original orientation of the part (part’s rotation is maintained unmodified), translated by the translation CFrame, in the coordinate frame of the part. For example:

part.CFrame * CFrame.new( 2, 0, 0 ) gives you a CFrame that is the same as doing:
part.CFrame + 2 * part.CFrame.RightVector

just with a matrix multiply instead of a vector addition. You can do it either way, I’m just more used to thinking about working just with matrices since I do so much work on animation systems at my day job.

minY = math.min(minY, p.Y)

This variable, minY, is keeping track of the lowest Y position seen so far. As each of the 8 part corner positions is calculated, the value that is kept in minY is whichever is lower, the current value of minY so far, or the current corner being tested’s position Y value.

Lastly, some people might be wondering why there is no trig being done here (no sine or cosine). This is because–having moved the part already–the expensive sine and cosine calls have already been done for you by the game engine, when it computed the rotation matrix needed to rotate the part! You already have the results, so you can do what you need to do with just cheap vector and matrix multiplies or additions, without having to redundantly do the trig calls (which are even slower from Lua).

2 Likes

Thanks! This helped me understand the math well, how would I do this if I didn’t know the Y position of the ground? Also, you explained RightVector, UpVector, and LookVector, which I’ve never really understood. Is this just a vector with the direction of right, up, and front respectively with a magnitude of 1? Could you write the first piece of code with those vectors so I could understand where they could be replaced/used? Where could I find documentation for the computation of the rotation matrices?

Thank you for explaining!

That is a simple, easy to understand solution. However, it contains quite a bit of redundant calculations (calculating every corner vector instead of just the lowest and highest ones, and calculating the whole vectors when we are only interested in the y coordinates).

Alternatively, you can calculate the y-axis distance from the lowest corner to the highest corner, divide that by two and add the floor y.

On each axis of the part, there are two opposite directions in which you can move. When we start from the center of the part and move part.Size.X / 2 studs on the x-axis of the part, part.Size.Y studs on the y-axis of the part and part.Size.Z studs on the z-axis of the part, we get to one of the corners. By picking the movement directions such that we always pick the direction that goes up on the global y-axis, we get to the top corner. So we want to pick the vectors whose y-coordinates are non-negative.

In this case, since all that we are interested in is the y coordinate, we don’t actually need to choose any vectors. Instead, it’s enough to choose the correct component value. In the case of the part’s x-axis, the direction vector options are part.CFrame.RightVector and -part.CFrame.RightVector. Thus, the y component options are part.CFrame.RightVector.Y and -part.CFrame.RightVector.Y. math.abs(part.CFrame.RightVector.Y) will give us the desired non-negative option. This same principle applies to the other two axes.

Here’s the code. In the code, I don’t divide each size component by two but instead just do one division by two.

local function updatePartYPosition(floorY, part)
	local distanceBetweenMinYAndMaxY = math.abs(part.CFrame.RightVector.Y * part.Size.X)  + math.abs(part.CFrame.UpVector.Y * part.Size.Y) + math.abs(part.CFrame.LookVector.Y * part.Size.Z)
	part.Position = Vector3.new(
		part.Position.X,
		floorY + distanceBetweenMinYAndMaxY / 2,
		part.Position.Z
	)
end
2 Likes

Very nice and clean. Roblox’s API has a way that you can pull out just those Y values you need too, elimatinating some hidden allocations you get when extracting column vectors and referencing the part.Size. It might not be obvious, but wherever you can factor 3D multiply-adds into a dot product, it will usually be noticably faster than the expanded Lua expression too. So if you had to shift up thousands of parts, you could rewrite this like:

local function getPartYPosition(floorY, part)
	local x,_,z,_,_,_,xy,yy,zy = part.CFrame:GetComponents()
	local v = 0.5 * Vector3.new(math.abs(xy),math.abs(yy),math.abs(zy))
	return Vector3.new(x, floorY + v:Dot(part.Size), z)
end

On most architectures, this should be roughly twice as fast as accessing RightVector, UpVector, LookVector + part.Size multiple times.

2 Likes