Basics of CFrame multiplication and Welds/Motor6Ds/Joint Instances

Hello I’m dthecoolest, I’m mainly known for my CFrame work on the devforum specifically inverse kinematics with the CCDIK controller. Surprisingly there isn’t a lot of motor6d or weld tutorial on the devforum so I’ll be here to try and fix that.

Contents

Prerequisite knowledge. 1

CFrame Multiplication what is it?. 2

Understanding welds and Motor6Ds. 3

World CFrame relative to Motor6D/Weld space. 4

Common CFrame Tips and Advice. 5

CFrame Rotation in WorldSpace. 6

CFrame.Angles ~= CFrame.fromOrientation, and how I learned to love CFrame.fromOrientation. 7

CFrame aligning vectors. 8

Prerequisite knowledge

I recommend watching Alvinblox tutorial as it will teach you the basics of what a CFrame is (position and orientation) and the main points.

  1. you can combine CFrames using multiplication and that CFrame.Angles uses radians.
Part.CFrame = CFrame.new(5,5,5) * CFrame.Angles(0, math.rad(180), 0)
  1. And that you can “add” onto a CFrame via multiplication
Part.CFrame = Part.CFrame * CFrame.Angles(0, math.rad(180), 0)
  1. The LookVector, RightVector, and UpVector which describe where the part is looking as a unit vector with the length/magnitude equal to 1.

I will try my best to expand upon that with one weird quirk about CFrame multiplication and its implications.

CFrame Multiplication what is it?

One of the main CFrame article is this one on CFrame math’s and multiplication and you see all the complicated math and you are wondering what does it mean?

Honestly, we can basically ignore the maths and instead focus on the results by what we observe when performing CFrame operations which leads to the most important rule there is.

When multiplying CFrames, the Axis in which the translational or rotational motion occurs is relative to the previous CFrame being multiplied with.

Coincidentally this is the same as CFrame:ToWorldSpace() which is also just CFrame multiplication and the description provide in Understanding CFrames article.

The CFrame:ToWorldSpace() function transforms an object’s CFrame — respecting its own local orientation — to a new world orientation

The best way to visualize this property is through attachments as an attachments WorldCFrame uses CFrame multiplication as seen below.

worldCFrame = attachment.Parent.CFrame * attachment.CFrame
--or
Attachment.WorldCFrame = PartWithAttachment.CFrame * attachment.CFrame

This WorldCFrame describes where the attachment is located relative to origin like a parts CFrame.

Additionally, additional knowledge for those not familiar with Attachments is the Axis and Secondary Axis. Basically, the attachments secondary axis represents the Y axis (UpVector) and the Axis represents the X axis (RightVector).

To observe how CFrame multiplication is relative based upon the previous CFrame we can simply insert an attachment in a part and rotate it and observe where the attachment will end up keeping in mind the attachment WorldCFrame formula.

Here is an example with an attachment with position (5, 5, -5) and notice how the attachment is located diagonally from the top left corner of the face.

Then by turning the head +45 degrees on the Y axis:

Observations:

  • As noticed the positioning of the (5, 5, -5) attachment remains the same relative to the part remaining at the top left corner of the face diagonally outwards.
  • Additionally, the X and Y axis of the attachment still maintain the same relative to the part’s RightVector and UpVector showing the rotation is the same as the part.

Consequently, this shows that the axis in which the (5, 5, -5) translation is relative to the parts CFrame axis which leads the attachment being positioned the same diagonal position from the part’s corner. This also creates a point and orientation known as a “CFrame offset” which will be an important term to take note.

Another method to visualize the importance of order of operations is a basic rotation script when the part is tilted.

local part = script.Parent

while true do
	local dt = task.wait()
	local radiansPerSecond = math.pi/4
	local increment = dt*radiansPerSecond
	part.CFrame = part.CFrame*CFrame.Angles(0,increment,0)
end

First as noticed in the video the part is rotating along it’s local Y axis, due to the fact that Y axis is based on part.CFrame which is being multiplied by.

Now compare this with the rotation happening before adding on the part’s CFrame

local part = script.Parent

while true do
	local dt = task.wait()
	local radiansPerSecond = math.pi/2
	local increment = dt*radiansPerSecond
	
	local rotation = CFrame.Angles(0,increment,0)*part.CFrame
	
	part.CFrame = rotation
end

As noted in the video the rotation is happening along the global Y axis instead of the parts local Y axis. Why is this happening?

Well as we noted down the previous CFrame that is being multiplied by is the axis of the rotation, so what is the axis here? The answer is that the axis is CFrame.new() which is the origin CFrame as can be seen below:

local rotation = CFrame.Angles(0,increment,0)*part.CFrame

--CFrame.new() * anyCFrame = anyCFrame, identity CFrame multiplication property
--Also CFrame.new() is located at the origin (0, 0, 0) with orientation (0, 0, 0)
local rotation =CFrame.new()* CFrame.Angles(0,increment,0)*part.CFrame

However, this method also messes up with the translational axis which can be undesirable creating a sort of orbital motion effect.

Consequently, it’s recommended to do the operations separately, firstly perform the rotation relative to the world space axis, then add the parts original position:

local part = script.Parent

while true do
	local dt = task.wait()
	local radiansPerSecond = math.pi/2
	local increment = dt*radiansPerSecond
	
	local rotation = CFrame.Angles(0,increment,0)*part.CFrame
	
	part.CFrame = rotation.Rotation + part.Position
end

Understanding welds and Motor6Ds

Opening the Devforum API Reference on welds will give you this info.

While the weld is Active, it maintains the part positions such that:

part1.CFrame * C1 == Part0.CFrame * C0

What does this mean?

Using what we know of CFrame multiplication and attachment world CFrame one can notice the equation is very similar to an attachment WorldCFrame equation.

Attachment.WorldCFrame = Part.CFrame * Part.Attachment.CFrame

Where Part is the Part0 and C1 is a constant “CFrame offset” CFrame just like an attachment’s CFrame.

Consequently, by substituting the attachment WorldCFrame equation into a weld equation we can see what weld equation is implying.

part1.CFrame * C1 == Part0.CFrame * C0

--Replace C1 and C0, with a constant Attachment CFrame

Part1.CFrame * Part1.Attachment.CFrame == Part0.CFrame * Part0.Attachment.CFrame

--Replace PartWithAttachment.CFrame * attachment.CFrame with Attachment.WorldCFrame

Part1.Attachment.WorldCFrame == Part0.Attachment.WorldCFrame

From this equation we can finally deduce what a weld does which maintains the equation such that these two points created by the CFrame offsets are equal.

To visualize it we can use the new Rigid Constraint CFrame offsets points can be visualized as attachments WorldCFrame as previously discussed:

Yep, that’s all a weld is.

And you can even do it by script using the original weld Instance:

local weld = Instance.new("Weld")

weld.C0 = attachment0.CFrame

weld.Part0 = attachment0.Parent

weld.C1 = attachment1.CFrame

weld.Part1 = attachment1.Parent

weld.Parent = attachment1.Parent --any parent under workspace to make the weld active

World CFrame relative to Motor6D/Weld space

Ok, now that we have understood what a weld can do, so now the question is how do we manipulate it via script without breaking the weld? The answer is to modify the offsets C0 and C1. Unfortunately this goes into CFrame:Inverse() territory which is a bit more complex and not accounted for in this tutorial however I will just point out the math properties during the derivation

--Starting from the weld equation

part1.CFrame * C1 == Part0.CFrame * C0

--Apply Part0.CFrame:Inverse() to the left hand side of both equations

Part0.CFrame:Inverse()*part1.CFrame * C1 = Part0.CFrame:Inverse()*Part0.CFrame * C0

--Apply CFrame:Inverse() * CFrame = CFrame.new(), CFrame Inverse property
Part0.CFrame:Inverse()*part1.CFrame * C1 = CFrame.new() * C0

--CFrame.new() *anyCFrame = anyCFrame, Identity CFrame property
Part0.CFrame:Inverse()*part1.CFrame * C1 =  C0
--Rearrange equation, swap sides
C0 = Part0.CFrame:Inverse()*part1.CFrame * C1

--We want part1 CFrame to equal some CFrame like CFrame.lookAt()

C0 = Part0.CFrame:Inverse()*CFrame.lookAt(part1, part2) * C1

Using this knowledge, we have derived one very useful CFrame formula.

Here is what it looks like in function form and an example when applies to a common Motor6D rig like R6:

local motor = script.Parent
local target = workspace.Target

local function worldCFrameRotationToC0ObjectSpace(motor6DJoint,worldCFrame)
	local part1CF = motor6DJoint.Part1.CFrame
	local part0 = motor6DJoint.Part0
	local c1Store = motor6DJoint.C1
	local c0Store = motor6DJoint.C0
	local relativeToPart0 = part0.CFrame:inverse() * worldCFrame * c1Store
	
	local goalC0CFrame = relativeToPart0
		
	return goalC0CFrame
end

while true do
	task.wait()
	motor.C0 = worldCFrameRotationToC0ObjectSpace(motor, target.CFrame)
end

Wow it functions as if it’s just a regular part and you are simply setting the parts CFrame!

However usually you don’t want limbs detached so we maintain the original position component and input in our new calculated CFrame rotation. So these are the changes to the function:

local function worldCFrameRotationToC0ObjectSpace(motor6DJoint,worldCFrame)
	local part1CF = motor6DJoint.Part1.CFrame
	local part0 = motor6DJoint.Part0
	local c1Store = motor6DJoint.C1
	local c0Store = motor6DJoint.C0
	local relativeToPart0 = part0.CFrame:inverse() * worldCFrame * c1Store

	local goalC0CFrame = relativeToPart0.Rotation+c0Store.Position--New orientation but keep old C0 joint position
			
	return goalC0CFrame
end

As seen this implies we can use WorldSpace CFrames for it! Stuff like CFrame.lookAt will work now which is especially useful for inverse kinematics, some arm pointing, and head look at script.

As a bonus thanks to some advice is the derivation for C1 which is more interesting as the goal CFrame is the one being inversed instead of the Part0 for the C0 solution:

Part0.CFrame * C0 = part1.CFrame * C1
weld.Part1.CFrame:inverse() * weld.Part0.CFrame * weld.C0 = weld.C1
--Subsitute part1 with some goalCFrame, to make part1 go to goal target
goalCFrame:Inverse() * motor6DJoint.Part0.CFrame * motor6DJoint.C0

local function worldCFrameRotationToC1(motor6DJoint, worldCFrame)
	local part0 = motor6DJoint.Part0
	local c0Store = motor6DJoint.C0
			
	return worldCFrame:Inverse() * part0.CFrame * c0Store 
end

Common CFrame Tips and Advice

CFrame Rotation in WorldSpace

To show case the importance of order of operations within CFrames is this method to rotate a CFrame in worldspace instead of the parts local axis. Here is

CFrame.Angles ~= CFrame.fromOrientation, and how I learned to love CFrame.fromOrientation

Simply put, CFrame.Angles is not equal to orientation which will lead to some unintended bugs and that Orientation is really easy to work with since it’s the default angle measurement of Roblox Studio.

Additionally there is a really neat property with CFrame.lookAt and CFrame:ToOrientation() and that’s a CFrame generated by CFrame.lookAt() doesn’t have any Z axis component when converted to orientation which I used in my turret controller. However :ToObjectSpace() was needed for certain scenarios especially when the turret is rotated.

CFrame aligning vectors

Usually, we want to align a CFrame such that it’s UpVector matches another arbitrary vector for example if we want to align a part to a surface normal vector.

One common method that is seen is the CFrame.lookAt method

CFrame.lookAt(pos1, pos1 + newDirection)

However, the above CFrame method forcefully also generates the rest of the vectors such as the right vector and up vector and doesn’t allow for much control unless you manually mess around with the third parameter the “UpVector”

The second option you could use, is @EgoMooses advanced CFrame technique like in this which does the job of alignment post.

Here are some more other resources which I found usefull when studying this type of problem. One with a problem statement, and one with a explanation of surface normal plus the CFrame look at technique.

Conclusion


Thanks for reading!
Please let me know if I have missed anything in this tutorial or if some points could be clarified further.

I believe there’s a lot more such as using CFrame:Inverse() or CFrame:ToObjectSpace() however that should be pretty lengthy and deserves it’s own tutorial.

81 Likes
How do I make a part parallel to the surface it touches?
CFrame of Part relative to Motor6d
Sliding mechanism help (R6)
Where to start on custom movement system
How do I manipulate offset CFrames in the world space?
Rotate a CFrame orientation like a vector
Convert LookVector/Surface Normal to rotation
How to make skinned character look toward mouse (Y axis)
Trying to work out how to turn tank turret
How do I resize a part with attachments?
Keeping world position of object the same while welded to an object that can move independently
How do you rotate a CFrame on the global axis?
How to always rotate part 180 degrees even if it has already been rotated on another axis
[Help with Script] Character's workspace CFrame to Keyframe CFrame
Making a motor6d point in direction of mouse
How do I weld a part to a player's foot
CFrame: Revolve Part Around a Position
Question about Motor6Ds
Issue with CFrames and body alignment
Trouble with C0
(HELP!) How do I set the C0 of a Motor6D to go to a position of another part?
Arm C0 Rotation relative to camera and compensating torso rotation
Correctly translate world position to motor position accounting for both C0 and C1
Motor6d CFrame.lookAt issues
Viewmodel with gun position relative to a tool's position to the arm
How to make armor for a rpg or something
Make a proper rotate system
How would i place Part A on a face of Part B with CFrame math
Possible to convert C0 to C1?
Convert C0 to C1
Convert C0 into C1
How do I understand CFrame better?
non-Melee Hitbox Issue
How can I fix this weird rotation problem?
Keeping a character's rotation the same whilst changing the position with cframe
Rotate Welded Parts Based on their CFrames?
Use Motor6D C0 for BodyGyro CFrame component?
Rotate Weld.C0 on Specific Axis

Finnaly I found this post ! Thank you so mush

1 Like

How do you make this work for Motor.C1?

1 Like

Absolutely amazing, the section on CFrame.Angles ~= CFrame.LookVector was very helpful

1 Like

I believe you have achieved it in the scripting support post with a bit of help, but I’ll update the post since it’s a bit more different than C0 within the weld section.

this post is most confusing post ever

1 Like

Yep sorry realize now that it’s a wall of text that’s confusing.

I would recommend just looking at the videos which show the properties of CFrame and what happens if you :Inverse() or apply CFrames in a different order.

Perhaps a show not tell tutorial would be better next time in the future.

--These two are different
part.CFrame * rotationCFrame
rotationCFrame * part.CFrame 

Or better yet you can try and experiment it yourself in studio which was how I came up with the examples and learnt.

1 Like