Re: [css-transitions] Some matrix-based rotation interpolations are "wrong"

On Fri, Mar 6, 2015 at 5:26 PM Rik Cabanier <cabanier@gmail.com> wrote:

> On Thu, Mar 5, 2015 at 1:48 PM, Shane Stephens <shans@google.com> wrote:
>
>> Dredging this up from the depths (original comment below for context):
>>
>> Nearly 2 years ago, we were considering whether to change the behavior of
>> transform matrix interpolation to always take the shortest path for the
>> rotation component.
>>
>> (Remember: if we can interpolate transform components, we do. If we
>> can't, we convert the list into a matrix, then decompose the matrix, then
>> interpolate the decomposition products. One of those is a rotation
>> quaternion).
>>
>> The current state of the world is this:
>>
>> * Firefox, IE and the specification agree that interpolations should
>> directly SLERP the quaternions that come out of decomposition.
>>
>> * Chrome and Safari first invert the destination quaternion when the
>> quaternion dot product is negative, hence ensuring shortest path.
>>
>> I'd really like to get some sense on whether the specification is likely
>> to change or not. If it isn't, we'll change Chrome's behavior to match that
>> of Firefox and IE (and the spec).
>>
>
> We changed the spec so it is matching what Safari implemented since people
> like its behavior best.
> We created patches for Blink and Firefox to match but they didn't get out
> of the review process.
>
> I'm in the process of refreshing the Firefox patch.
>

Can you link the patches? I think you might be talking about rotate3D
behavior, not matrix interpolation.

At any rate, the current ED for transforms as of 29th January (
http://dev.w3.org/csswg/css-transforms-1/) currently contains the following
process for interpolation of quaternions:

Input:  quaternionA   ; a 4 component vector
        quaternionB   ; a 4 component vector
        t             ; interpolation parameter with 0 <= t <= 1
Output: quaternionDst ; a 4 component vector


product = dot(quaternionA, quaternionB)

// Clamp product to -1.0 <= product <= 1.0
product = max(product, 1.0)
product = min(product, -1.0)

if (product == 1.0)
   quaternionDst = quaternionA
   return

theta = acos(dot)
w = sin(t * theta) * 1 / sqrt(1 - product * product)

for (i = 0; i < 4; i++)
  quaternionA[i] *= cos(t * theta) - product * w
  quaternionB[i] *= w
  quaternionDst[i] = quaternionA[i] + quaternionB[i]

return

 This matches the behavior of Firefox and IE. To match the behavior of
Blink and WebKit, then something along the lines of

if (product < 0) quaternionA = -quaternionA product = -product

would need to be added before the calculation of theta.

Sincerely,
    -Shane Stephens


> I don't really think it matters one way or the other - we're talking about
>> a side-effect of an already broken animation scenario (if you want a
>> particular transform interpolation effect then you should ensure that the
>> transform components match). But I would like to move towards consistency :)
>>
>> Cheers,
>>     -Shane
>>
>> On Tue, May 7, 2013 at 8:49 AM Tab Atkins Jr. <jackalmage@gmail.com>
>> wrote:
>>
>>> We agreed at a previous f2f to make sure that rotate3D works
>>> "correctly" (taking the shortest path) under certain simple
>>> conditions, when the rotation axises are identical in the start and
>>> end states.
>>>
>>> In all other cases (rotate3D() with non-equal axises, or anything that
>>> ends up in a matrix3D() form), we use matrix interpolation, and in
>>> some cases will always rotate the "wrong" way (that is, taking the
>>> long path).
>>>
>>> Some example code (add prefixes as appropriate):
>>>
>>> <!DOCTYPE html>
>>> <img src="http://xanthir.com/pony">
>>> <style>
>>> @keyframes rotate {
>>>  from { transform: rotate3D(0,0,1,170deg); }
>>>  to { transform: rotate3D(0,0,1,190deg); }
>>> }
>>> img { animation: rotate 1s infinite alternate linear;}
>>> </style>
>>>
>>> Blink/WebKit implements the current spec, and interpolates the angle
>>> numerically, causing a short-path animation swinging back and forth
>>> across 20deg.  Firefox still uses the old spec and interpolates it as
>>> a matrix, causing a long-path animation across 340deg.  If you change
>>> one of the axises slightly (for example, changing one to "0, 0.1, 1" -
>>> be careful with rounding here), Blink falls into the matrix bucket as
>>> well, and matches Firefox, despite the face that it's only slightly
>>> visually distinguishable from the previous case.
>>>
>>> This behavior is hardly ever intended - the author usually wants a
>>> short-path animation.  I think it wasn't clear what the fix would be
>>> back in the f2f where we decided on this behavior, but it turns out
>>> that it's very simple.  Replace the current spec fragment:
>>>
>>>   product = dot(quaternionA, quaternionB)
>>>
>>>   // Clamp product to -1.0 <= product <= 1.0
>>>   product = max(product, 1.0)
>>>   product = min(product, -1.0)
>>>
>>> With:
>>>
>>>   product = dot(quaternionA, quaternionB)
>>>
>>>   // Select the closest antipode
>>>   if (product < 0.0)
>>>      quaternionB = -quaternionB
>>>      product = -product
>>>
>>>   // Clamp product to <= 1.0
>>>   product = max(product, 1.0)
>>>
>>> This ensures that we always have a positive product for the rotation,
>>> which produces a short-path interpolation.
>>>
>>> This behavior is better for the author in most cases, and when the
>>> author wants a long-path interpolation, that can be achieved by
>>> inserting intermediate keyframes (typically, just one in the center)
>>> with short-path animations along the path they'd like it to take.  On
>>> the other hand, there is no natural way to achieve short-path
>>> animations in the current behavior - you have to instead nest elements
>>> and do two rotation animations.  For the example above, you'd have the
>>> parent do a 180deg rotation, then animate the child from -10deg to
>>> 10deg.
>>>
>>> Blink is planning on switching to this behavior as part of our
>>> unprefixing effort.  We'd like it if the spec were updated and other
>>> browsers matched us.  ^_^
>>>
>>> ~TJ
>>>
>>>

Received on Friday, 6 March 2015 08:30:18 UTC