Mathematical question?

When I first started learning scripting on roblox I thought of this:

Part.CFrame = Part.CFrame*CFrame.Angles(0,1,0)

As a rule, you just need to remember it and don’t think of it logically. So I asked myself today:

Why exactly this part of the code rotates locally and not globally

3 Likes

try using CFrame.Angles instead

1 Like

If the question is related to replication(other clients/the server don’t see the part rotated but your character does) this is because the code runs in a local script. Instead, if you want the part to rotate for the server and all the clients in it you need to run that line in a regular server script.

1 Like

I was meaning local in mathematical way

2 Likes

Do Part.CFrame = CFrame.new(Part.Position) * CFrame.Angles(0, 1, 0). This way you apply a new CFrame with the same position but new rotation.

1 Like

They’re not asking how to set a global rotation, they’re asking why this: Part.CFrame = Part.CFrame*CFrame.Angles(0,1,0) rotates locally.


@towerscripter1386
I don’t really know the answer myself but Roblox CFrames are essential a 3x3 rotation matrix. It’s based on concepts in Linear Algebra, so you may want to look at that.
YouTube playlist that may help: https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab

1 Like

I’m going to do the best I can to answer this question, so let me know if I’m wrong.

Transformations like rotation are represented by transformation matrices. When you apply transformations to an object, it combines the new transformation with the existing one, resulting in a cumulative effect. The CFrame represents the transformation of said object in its local space, which is local to the object’s orientation and position.

So when you multiply Part.CFrame by CFrame.Angles(0, 1, 0), you’re basically adding a 1 degree rotation to the local Y-axis to the existing transformation of said part; this means it’ll rotate 1 degree around its own Y-axis, not the global Y-axis.

If you were to do Part.CFrame = CFrame.Angles(0, 1, 0) * Part.CFrame, that would rotate it 1 degree relative to the world space, not the part. However, since you did it the opposite way, it rotates relative to itself.

Hope that makes even a little bit of sense! :smiley:

6 Likes

The only way to really fully understand what’s going on here is to go through the exercise of writing out two CFrames as 4x4 homogeneous coordinates matrices, doing the matrix multiplication on paper, and seeing what happens when you change the multiplication order.

See: Help understanding what happens when I multiply 2 CFrames

Another way to look at is like this: you have a CFrame R that is only a rotation (and not identity), the translation vector part is 0,0,0. You also have some vector3 that is v that has x,y,z components which you can think of as like an arrow pointing from (0,0,0) to some position (x,y,z). Because R is a 3x3 matrix, and v has just 3 elements, the only way you can multiply them is by treating v as a 1x3 column vector and doing:

R * v

This rotates the vector v. If you think of v as representing a position, originally x,y,z, then that position is now somewhere new, x’,y’,z’. If you instead think of v as being a direction, well now it’s pointing to x’, y’, z’, a different direction.

When you work out the full multiplcation for R * Part.CFrame, what you see is that the calculations for the columns of the resulting CFrame can be written just like the matrix-vector multiplcation above, you get:

new CFrame.RightVector = R * CFrame.RightVector
new CFrame.UpVector = R * CFrame.UpVector
new CFrame.LookVector = R * CFrame.LookVector
new CFrame.Position = R * CFrame.Position

Basically, the multiplication by rotation matrix R on the left causes R to rotate each column of the CFrame’s orientation matrix, including the “4th” column, which is the Part.CFrame.Position vector. So, orientation of the part changes, but so does the position (position rotates around the world origin just like the other vectors)

When you reverse the order of the multiplication, it’s instead like the orientation matrix of Part.CFrame rotates each column of R, which are the right, up, and -look vectors of R. R has no translation, so the final product CFrame retains Part.CFrame’s position and overall appears like a local rotation of the Part.

Again, you really need to do this out long-hand to completely understand it. You’ll see which components from R and Part.CFrame contribute to the resulting new CFrame’s columns, and why TransformCFrame * Part.CFrame applies a parent-space (which is world space for parts) rotation and translation to the part, and why Part.CFrame * TransformCFrame applies like a local-space rotation and translation.

5 Likes

I would like to add something, because the most important thing was not mentioned.

The answer is: because in roblox you can only rotate BaseParts globally. You can’t do it locally. So you usually do Part.CFrame*Cframe.Angles(…) to simulate local rotation.

CFrames behave cumulatively. When two CFrames are multiplied, they are applied one after the other. In this case Part.CFrame*CFrame.Angles(0,1,0) means, move and/or rotate Part.CFrame times something (which is the current position and rotation of Part), and then rotate it ‘CFrame.Angles(0,1,0)’ degrees. All that information is in the new CFrame which we use in some BasePart (in this case in the same Part.CFrame). The result is a local rotation, but it is applied as a global displacement/rotation.

On the one hand, although it looks complicated, it is quite efficient and we don’t have to worry about performance. On the other hand, it is the only way to do it, unless mathematics is your second passion in life.

This is just a semantics issue. Part.CFrame always stores an orientation and position in world space, but when one talks about the act of rotating something, i.e. applying a transformation, it can be done in the world-space coordinate frame, or in the local frame of the already transformed part. This isn’t simulating a local rotation, it is a local rotation about the part’s current position.

1 Like

in this I disagree. The big difference is that no relative transformations are stored, unlike other systems like blender or maya.
You said Part.CFrame always stores an orientation and position in world space. So what you do in this case is: 1) calculate the current transformation, 2) apply a transformation to the current transformation, 3) apply the new transformation. If a transformation could be done locally there would be no need to perform the calculation of the current transformation.

Yes it is. But remember what OP asked:

He asked for the exact reason, so semantics do matter.

1 Like

I’m not sure I follow what you’re saying. Part.CFrame is storing “the current transformation” so I’m not sure what your step 1 is calculating.

If you have some CFrame “T” that represents a transformation:

Part.CFrame = T * Part.CFrame applies T as a world-space transformation
and
Part.CFrame = Part.CFrame * T applies that same transform interpreted in the local frame of the part.

It’s one CFrame multiplication plus storing the result back to Part.CFrame either way you order it.

It’s true that other tools support transforms being inherited through parent-child hierarchy, but affect whether or not you can apply a transform locally, only how things are stored. Some Roblox instances store parent-relative transforms, like Attachments and Bones, but the multiplication order thing still applies, it’s just more calculations if you want to get the world-space CFrames for those types instances when any of their ancestors’ transforms change.

In this case it is fortuitous. But in general it is not. For example, imagine you have a moving train, inside is a child dragging a trolley, on the trolley is a clock with hands turning constantly. Now, let’s say you can’t use anything but CFrames to do all the animations (the moving train, the moving trolley, the turning of the clock hands). So how do you know where the clock hands should be? since their transformation is relative to the trolley and the trolley’s transformation is relative to the train, in roblox, you inevitably have to do a calculation to get the current position of the hands. in maya, for example, this is as simple as setting the local transformation of the object in question.

In roblox it would be like this:

clockHand.CFrame = Train.CFrame * Trolley.CFrame * clockHand.CFrame * CFrame.Angles(0, 90, 0)

in Maya:

select 'clockHand'; // select the clock hand object
xform -r -ro 0 90 0;  // xform is used to make the transformation, -r means relative, -ro is rotation.

notice the difference, in Maya I don’t need to know the trolley or the train. The advantage of local transformation.

Oops. Of course there are non-BaseParts things in roblox that have local support, but, well, I was thinking of BaseParts only.

Except that you, the scripter don’t ever have to do this math. Even the Roblox engine doesn’t do it this way. Sure, all of the parts’ CFrames are exposed through the Lua API as world space positions, but that’s not how they’re updated in the C++ engine code. Like if you want things on the train to move in sync with the train, you weld them together, at which point Roblox’s engine stores them as a rigid assembly, with optimization and caching of transforms relative to the assembly root part. This is done so that incremental changes to an assembly don’t cause it to vibrate apart over time from floating point issues.

If you just want to animate the hands of a clock, you could still do this with just a single CFrame post-multiply, even from Lua. Like so:

clockHand.CFrame = clockHand.CFrame * CFrame.fromAxisAngle(localRotationAxis, dt * math.pi/30)

Even if you want to set the position of a clock hand absolutely, to a specific time, you’re still just doing it in the local space of the clock itself, since changing the Transform property of the Motor6D that connects the clock body to the moving hand is how you’d animate this with CFrames, and the angle of that transform is 1:1 with the local direction of the hand. e.g. if an identity CFrame puts the hands at 12 O’Clock, then that is true no matter where the train is or what direction it’s headed. You never need to multiply out the whole chain of transforms.

You could code a clock on a train by manually multiplying all of the CFrames of the hierarchy from the workspace root all the way up to the hands of the clock, but in Roblox, the only way to have all the parts stay in sync would be to do it purely on the client, on renderstep. Performance would be terrible and it wouldn’t work with Roblox physics engine at all.

So, in a nutshell, you can do local transforms when you set things up correctly to be animated. Roblox and Maya may present transforms differently to the end user through property editors and API, but that doesn’t necessarily reflect how transforms are actually stored or manipulated in the underlying code.

In regard to this, I think we might be using different definitions for a “local” transform. When I say local transform, what I’m talking about is transforming a part intrinsically, aka “object space”; like a “local X-axis rotation of 30 degrees” means rotating the part 30 degrees about it’s own x-axis, which is Part.CFrame.RightVector. That’s what the post-multiply gets you.

I’m not that familiar with Maya scripting, but my guess would be that your line of code is doing a parent-relative transform. i.e. a 90-degree Y rotation about the clockHand’s parent’s y-axis, not the y-axis of the clock hand itself. It is true that you can’t do this with just a Part in Roblox unless there is a weld/motor establishing a transform hierarchy between two parts. But you use a motor6D or some other kind of constraint when animating Parts, either with published animations or with code, so it’s not a practical concern.

1 Like

A good analysis, although I do not share it entirely. Anyway, let’s not lose the thread. That was just a fictitious example to explain my point.

In reality they do. A game engine tries to give developers the lowest possible level so that they can get the most out of the hardware. A CFrame is a structure in memory, there is nothing more efficient than that. the same with CFrame operations, it is very difficult for an optimization to simplify them.

But, beyond that, this global and local transformations are user stuff. the engine is not interested in how the user actually created the transformation operations. In that case we are talking about how the user makes use of the engine. And in the case of roblox it only supports global transformations, leaving the user to implement his own methods or techniques to deal with a problem. For example, the code that OP posted, or even something crazier than a local transformation.

It’s actually the same thing (as far as I know). Again, it was just an example of what it would be like if roblox supported local transformations. Easier from the user’s point of view (the engine doesn’t care, as expected).

It doesn’t though, that’s the point I’ve been trying to get across. Part orientation and position are exposed as world-space transforms for unconnected parts, but if you’re animating assemblies, you’re setting motor transforms, which are parent-relative w.r.t. the Part1 connected to them making it the same as those relative Maya transforms. Global, parent-space, objects-space… there’s a method to do all of these in Roblox as a single CFrame multiply or assignment.

Consider this though: Roblox’s animation system presents animation transforms to the user as Pose.CFrames, but internally, all the interpolation and blending is done with quaternions and Vector3s. Rotations are stored in memory as quaterions using 3 integer values. CFrames are sent between server and client as integer quaternions too. All because it’s more efficient than CFrames, both in terms of memory and number of operations to mutliply them. On the GPU though, all Bone CFrames are still matrices, because GPUs are optimized for basically just doing loads of dot products.

A game engine tries to give developers the lowest possible level so that they can get the most out of the hardware. A CFrame is a structure in memory, there is nothing more efficient than that. the same with CFrame operations, it is very difficult for an optimization to simplify them.

Quaternions are way more optimized than a standard rotation matrix. It’s also way more stable in computing. Kind of sad that roblox does rotations based on rotation matrix rather than quaternions

Quaternions make sense when you have to compose long chains of rotations, something that’s typical in evaluating animations, but not many other places in game engines.

Matrices more than make up for their shortcomings by keeping the data in an immediately-usable format, for both game code and the GPU. Anyone who writes code that uses CFrames knows that you often need the vector components, e.g. using LookVector to know which way something is facing, or which way to raycast. This is just reading a column from a CFrame, but if you keep orientations stored as quaternions, you have to calculate the axes you need. Recovering just a single axis from a quaternion is nearly 3 times as many multiplication and addition operations as a full CFrame * CFrame multiplication. And going the other way–storing a matrix to quaternion–requires square roots! In other words, the overhead of going to and from quaternions often negates their efficiency benefits.

Yep, that’s a sure thing about them. Despite how easy they are at computing, They get easily overwhelmed when it comes to stuff like directional raycasting