Animation playback does not yield accurate rotations

When playing back an animation using the Roblox animation system, rotations are not properly displayed. They are somewhat close, but sometimes their rotation seems to get “stuck” and later on in the animation “snap” into a correct orientation. The position does seem to get interpolated properly.

To illustrate the issue, here is an example of an animation exhibiting the issue. The animation is 1 second long and there are 61 keyframes spread evenly (obviously way more keyframes than required, but it really shows that something goes really wrong as the result transform is not anywhere in-between the transform of two keyframes).

First the animation is shown by simply setting the CFrame of the joint through code each frame (not using the Roblox animation system). Then a Roblox animation is generated from the same keyframes. Note that the rotation “snaps” at some point instead of interpolating smoothly! It is rather noticeable and extremely annoying, especially on high framerates.

https://streamable.com/i8yjv

Reproduction file:
animissue.rbxl (14.4 KB)

(same animation as in the video)

This also happens when using fewer keyframes, by the way.

4 Likes

I took your keyframesequence and removed the position component from all of the poses.

for i, v in pairs(game.Workspace.KeyframeSequence:GetChildren()) do
v.HumanoidRootPart.Ax1.CFrame = v.HumanoidRootPart.Ax1.CFrame - (v.HumanoidRootPart.Ax1.CFrame.p)
end

The rotation seems to be perfectly smooth. Are you sure this isn’t an optical illusion caused by the sudden change in positional velocity?

That actually severely amplifies the visibility of the problem. Did you change from my CFrame-code to the Roblox-Animation-code in game.ServerScriptService.Script?

Adding

for i, v in pairs(game.Workspace.KeyframeSequence:GetChildren()) do
    v.Time = v.Time * 4
end

adds some clarification too.

Ah derp, yeah that would explain that. I definitely see the issue now.

What code did you use to generate this KFS?

It’s generated through some plugin that converts blender keyframes to Roblox KeyframeSequences, so it’s not really a small repro. The code does generate each keyframe sequentially, though (first the KF at t = 0, then the next one etc).
I’ll try to compose some small independent repro script.

1 Like

Couldn’t really reproduce it using “simple” code, but this generates a new KFS from a set of hardcoded cframes.

local KSP = game:GetService("KeyframeSequenceProvider");
local RS = game:GetService("RunService");

--local kfs = game.Workspace.KeyframeSequence;

local kfjump = 1;
local framesperkf = kfjump * 2;
local robloxanim = true;

local cfs = {
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.970000029, -0.230000004, 0, 0.230000004, 0.970000029),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.980000019, -0.219999999, 0, 0.219999999, 0.980000019),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.980000019, -0.209999993, 0, 0.209999993, 0.980000019),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.980000019, -0.200000003, 0, 0.200000003, 0.980000019),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.980000019, -0.189999998, 0, 0.189999998, 0.980000019),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.980000019, -0.180000007, 0, 0.180000007, 0.980000019),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.980000019, -0.170000002, 0, 0.170000002, 0.980000019),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.99000001, -0.170000002, 0, 0.170000002, 0.99000001),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.99000001, -0.159999996, 0, 0.159999996, 0.99000001),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.99000001, -0.150000006, 0, 0.150000006, 0.99000001),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.99000001, -0.140000001, 0, 0.140000001, 0.99000001),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.99000001, -0.129999995, 0, 0.129999995, 0.99000001),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.99000001, -0.119999997, 0, 0.119999997, 0.99000001),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.99000001, -0.109999999, 0, 0.109999999, 0.99000001),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 1, -0.100000001, 0, 0.100000001, 1),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 1, -0.0900000036, 0, 0.0900000036, 1),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 1, -0.0799999982, 0, 0.0799999982, 1),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 1, -0.0700000003, 0, 0.0700000003, 1),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 1, -0.0599999987, 0, 0.0599999987, 1),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 1, -0.0500000007, 0, 0.0500000007, 1),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 1, -0.0399999991, 0, 0.0399999991, 1),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 1, -0.0299999993, 0, 0.0299999993, 1),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 1, -0.0199999996, 0, 0.0199999996, 1),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 1, -0.00999999978, 0, 0.00999999978, 1),
	CFrame.new(0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1),
};

local kfs = Instance.new("KeyframeSequence");
for cfidx, cf in pairs(cfs) do
	if ((cfidx - 1) % kfjump == 0) then
		local kf = Instance.new("Keyframe", kfs);
		kf.Time = (cfidx - 1) / (60 / framesperkf);
		
		local hrp = Instance.new("Pose", kf);
		hrp.Name = "HumanoidRootPart";
		
		local ax1 = Instance.new("Pose", hrp);
		ax1.Name = "Ax1";
		ax1.CFrame = cf;
	end
end

local asset = KSP:RegisterKeyframeSequence(kfs);
local rig = game.Workspace.Rig;

local animation = Instance.new("Animation");
animation.AnimationId = asset;

if (robloxanim) then
	-- Use as animation
	activeAnimTrack = rig.Humanoid:LoadAnimation(animation);
	activeAnimTrack:Play();
else
	-- Simply iterating each KF
	while (true) do
		for kfidx, kf in pairs(kfs:GetChildren()) do
			local tr = kf.HumanoidRootPart.Ax1.CFrame;
			rig.Axle1.PrimaryPart.CFrame = tr;
			for i = 1, framesperkf do
				RS.RenderStepped:wait();
			end
		end
	end
end

Try increasing kfjump to 2 and 4 too to reduce the amount of keyframes used, even then rotation messes up.

Figured out the cause, I think I am to blame here :stuck_out_tongue: .

Normalizing each rotation vector solves the weird rotations. Their magnitude was off by a small amount from 1.

for cfidx, cf in pairs(cfs) do
	local c = {cf:components()};
	for axis = 0, 2 do
		local normvec = Vector3.new(c[4+axis], c[7+axis], c[10+axis]).unit;
		c[4+axis], c[7+axis], c[10+axis] = normvec.X, normvec.Y, normvec.Z;
	end
	cfs[cfidx] = CFrame.new(unpack(c));
end

Apologies for the inconvenience and thanks for the effort!

1 Like

Thanks for following up! No worries

1 Like

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