Averaging CFrames (or averaging matrices) for animation blending

I am making a custom animation system, so I need to find the average of CFrames so I can blend animations together. I assume I can use the same formula that is used for finding averages of real numbers, but how would I apply that to matrices? I am quite inexperienced in manipulating matrices and I would like a bit of extra help on this.

If there’s a solution that works fine with Euler angles, I would be open to that too (as long as it avoids gimbal lock).

If you only need to find the average of two CFrames use CF1:Lerp(CF2, .5)

5 Likes

It works if I only have two animations playing, but I need a solution to average multiple CFrames in case I need to blend more than two animations.

Just adding up the Vector3 positions and rotations of each CFrame and dividing them by how many animations are playing does work (not sure if gimbal lock is going to present a huge problem in the future) but I’m not marking this as a solution in case anyone knows how to average matrices.

Sorry for bumping, but I came across the same problem and I believe I have found a nice non-matrix solution using lerp.

local function weightedAverageCFrame(weightedCFrames: {{cFrame: CFrame,weight: number}}): CFrame
    local average = CFrame.new()
    local totalWeight = 0
    for _,weightedCFrame in weightedCFrames do
        local currentCFrame = weightedCFrame.cFrame
        local currentWeight = weightedCFrame.weight
        totalWeight += currentWeight 
        average = average:Lerp(currentCFrame,currentWeight/totalWeight)
    end
    return average
end

This takes a table of CFrame-weight dictionary pairs, and sequentially lerps the average with each CFrame using increasingly discounted alpha.

How I think it works is that Lerping with alpha currentWeight/totalWeight effectively “multiplies” the average by (1-currentWeight/totalWeight) and adds it with the currentCFrame “multiplied by” currentWeight, as per analogous definitions of lerp in other objects like vectors.

So, in a non-rigorous sense,

average = average*(1-currentWeight/totalWeight) + currentCFrame*(currentWeight/totalWeight)

But

(1-currentWeight/totalWeight)
= (totalWeight/totalWeight-currentWeight/totalWeight) 
= (totalWeight-currentWeight)/totalWeight
= previousTotalWeight/totalWeight

Hence

average = average*(previousTotalWeight/totalWeight) + currentCFrame*(currentWeight/totalWeight)
average = (average*previousTotalWeight + currentCFrame*currentWeight)/totalWeight

Thus, we are dividing the weighted sum by the total weight, which gives us the weighted mean. The logic is sound for vectors, so I hope Lerp is sufficiently well defined on CFrames for this to work. In practice, this seems to be the case.

In the case where all weights are equal, this simplifies to

local function averageCFrame(cFrames: {CFrame}): CFrame
    local average = CFrame.new()
    for i,cFrame in cFrames do
        average = average:Lerp(cFrame,1/i)
    end
    return average
end

This one just takes a table of CFrames.