How to rotate the CFrame of a bone in World Space

I want to be able to rotate the CFrame of a bone like this, in the Y axis relative to the world:

However anything that I try always results in the bone being rotated in its local Z axis, never the world Y axis


This is what I’ve tried so far:

local rot = CFrame.Angles(0,0,math.rad(40)) 
bone.WorldCFrame = bone.WorldCFrame:ToWorldSpace(rot)
local rot = CFrame.Angles(0,0,math.rad(40))
bone.WorldCFrame *= rot
local rot = CFrame.Angles(0,0,math.rad(40)) 
bone.CFrame = bone.CFrame:ToWorldSpace(rot)

All of the snippets above produce the exact same result where the bone gets rotated in one of its local axis, instead of the world Y axis.

I’d appreciate if someone could help me out here, thanks

1 Like

Probably you can create a other piece (this piece must be hidden, with CanCollide in false and transparency in 1) with the local axis identical to the world axis, next weld the ‘bone’ piece to the new piece, and then attempt modify the orientation of the new piece, this should make ‘bone’ move in that same orientation (world axis).

I’ve been facing the same issue, and it seems this wasn’t solved yet.
I’m also trying to create a creature system and the head of the skinned mesh simply doesn’t want to rotate on the world axis.

I’ve tried a few solutions that were found on the dev forums :

Part.CFrame = CFrame.Angles( Rx, Ry, Rz ) * ( Part.CFrame - Part.CFrame.Position ) + Part.CFrame.Position
local rot = CFrame.Angles(math.pi/4,0,0)
Part.CFrame=(rot*(Part.CFrame-Part.CFrame.p))+Part.CFrame.p

While these seem to work for part’s cframes, it doesn’t for bones. I believe it’s related to some specific behavior with attachments. Either way, this really needs a solution.

Bones follow a similar CFrame logic to Motor6D, execpt the C0 and C1 is replaced with Attachment0 and Attachment1 CFrame property, and the Part0 and Part1 is replaced with the parent parts the attachments are attached to.

--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

Now you should be able to repeat with bones

boneAttach0.Parent.CFrame * boneAttach0.CFrame  = boneAttach1.CFrame* boneAttach1.CFrame

--Solve for a CFrame property
boneAttach0.CFrame =  ...

Yep that should work for Part.CFrame, you just need to convert it to bones.

local newRotationWorldCFrame = (rot*(Part.CFrame-Part.CFrame.p))+Part.CFrame.p
boneAttach0.CFrame = boneAttach0.Parent.CFrame:Inverse()*newRotationWorldCFrame* boneAttach1.CFrame

I’m slightly confused on the meaning of boneAttach0 and boneAttach1.
I’m guessing boneAttach0 represents the bone we’re actively rotating, but so what is boneAttach1 ?
I don’t see it being a property of bones.

boneAttach1 is the other bone that the bone0 is attached to. It could be the child of the bone0 as the bone1 is dependent on the bone0.

I see, thanks for your time.
I’ve tried applying the new code snippet in my case but it seems like it keeps rotating on the local axis…
Here’s the code :

local rot = CFrame.Angles(0,math.rad(90),0)
local newRotationWorldCFrame = (rot*(Neck.CFrame-Neck.CFrame.p))+Neck.CFrame.p
Neck.CFrame = Neck.Parent.CFrame:Inverse()*newRotationWorldCFrame* Neck.def_c_head_joint.CFrame

Here, “Neck” represents the bone I’m trying to rotate and “def_c_head_joint” is the child of it. (though this is still confusing to me, what if I’m trying to move the last bone ?)
Either way, this is the result :


And here it is, with the rotation applied to world space :

image

As you can see, 2 very different results. I still have a hard time working around with CFrames sometimes, so excuse any blatant mistakes.

No worries I have also never worked with bones although the math is similar to Motor6ds. (I dont plan on using fbx and bones for my game)

You can dm me the model and I could try figuring it out for your case scenario.

Make sure the Neck.CFrame is the head CFrame in world space so Neck.WorldCFrame perhaps.

I probably overcomplicated it.

You could just do it like this:

local neckBone : Bone = script.Parent

while true do
	task.wait()
	local rot = CFrame.Angles(0,0.1,0)
--Same rotation as doing WorldSpace axis rotation through ctrl+L
	local newRotationWorldSpace = rot*neckBone.WorldCFrame.Rotation+neckBone.WorldCFrame.Position
	neckBone.WorldCFrame = newRotationWorldSpace
end

You probably need :Inverse() if you want to use .CFrame instead of .WorldCFrame like the above post for optimization.

Something like this which has the same effect

local neckBone : Bone = script.Parent

while true do
	task.wait()
	local rot = CFrame.Angles(0,0.1,0)
	local newRotationWorldSpace = rot*neckBone.WorldCFrame.Rotation
	local test = neckBone.Parent.WorldCFrame:Inverse()
	neckBone.CFrame = (test*newRotationWorldSpace).Rotation + neckBone.CFrame.Position
end

The confusing part is that Bones can be children of Parts or other Bones, so sometimes using .WorldCFrame is mandatory for attachments whereas for parts you can just use .CFrame which is equivalent.

Unlike Attachment instances, Bones can be children of other Bones in addition to Parts. When parented to another Bone the child bone’s world position will be relative to the parent Bone’s position. Bones form an explicit hierarchy.

3 Likes

This seems to work just fine.
Thanks for your help ! I would mark this as the solution if I could.

1 Like

Hi
I’m trying to do a similar thing, but have the rotation be in the rootpart’s y-direction instead of world space. Also with a fixed offset relative to the default position of the bone.
Here’s my code:

local defaultCFrame = bone.WorldCFrame

local function getWorldOrientation(bone, offset)
	local rotBase = rootPart.CFrame * CFrame.Angles(0, math.rad(offset), 0)
	local newRotationWorldSpace = rotBase * defaultCFrame.Rotation
	local test = bone.Parent.WorldCFrame:Inverse()
	local destCFrame = (test*newRotationWorldSpace).Rotation + bone.CFrame.Position
	
	local rx, ry, rz = destCFrame:ToOrientation()
	local dx, dy, dz = math.deg(rx), math.deg(ry), math.deg(rz)
	return Vector3.new(dx, dy, dz)
end

-- Rotate 'bone' with 45° along the rootpart's y-axis
bone.Orientation = getWorldOrientation(bone, 45)

This behaves exactly like a want apart from one minor thing: it works only if my model’s rootpart is aligned with the world root on startup. I want it to work in whatever orientation the rootpart is.

Thanks for your help :+1:

1 Like

Hey there, I believe you should move the world CFrame inside the function as it will update as the model rotates. It probably only works initially for that reason.

2 Likes

Using

local function getWorldOrientation(bone, offset, defaultCFrame)
	local rotBase = rootPart.CFrame * CFrame.Angles(0, math.rad(offset), 0)
	local newRotationWorldSpace = rotBase * bone.WorldCFrame.Rotation
	
	-- Convert from world space to bone local space
	local test = bone.Parent.WorldCFrame:Inverse()
	local destCFrame = (test * newRotationWorldSpace).Rotation
	-- Get Orientation from CFrame
	local rx, ry, rz = destCFrame:ToOrientation()
	local dx, dy, dz = math.deg(rx), math.deg(ry), math.deg(rz)
	return Vector3.new(dx, dy, dz)
end

while true do
	task.wait()
	bone.Orientation = getWorldOrientation(bone, 45, defaultCFrame)
end

makes it spin around unfortunately. What I want is for the 45° to be a fixed y-rotation offset along the rootpart’s axis referenced against the bone’s original CFrame.

The code in the previous post works when rotating the rootpart, but it only works if the rootpart was originally aligned with the world root.
I want to make it work with an arbitrary rotation at the time ‘defaultCFrame’ is set.

1 Like