[csswg-drafts] [css-transforms-2] The ratio of quaternionA in Slerp algorithm (#9338)

BorisChiou has just created a new issue for https://github.com/w3c/csswg-drafts:

== [css-transforms-2] The ratio of quaternionA in Slerp algorithm ==
Per spec in [css-transforms-2], in the section of [Interpolation of decomposed 3D matrix values](https://drafts.csswg.org/css-transforms-2/#interpolation-of-decomposed-3d-matrix-values):
```
w = sin(t * theta) / sqrt(1 - product * product)
quaternionA[i] *= cos(t * theta) - product * w
quaternionB[i] *= w
quaternionDst[i] = quaternionA[i] + quaternionB[i]
```
The ratio of `quaternionA` is `cos(t * theta) - product * w`. (note: `w` is the ratio of `quaternionB`.)

This is correct In theory. However, its floating-point number calculation may produce some imprecise values.
For example, in this [wpt](https://github.com/web-platform-tests/wpt/blob/2a7dfe2c61e56614d7dac52149015230f16d536d/css/css-transforms/animation/transform-interpolation-005.html#L218-L229):
```
test_interpolation({
  property: 'transform',
  from: 'matrix3d(0.571428571428571, -0.625, -0.8333333333333346, -0.66666666666669, 0.5, -0.1875, -0.8125, 0.3125, 0.34375, -1, 0.8333333333333327, 1.34375, -1.34375, 1, -0.9375, 1)',
  to: 'none'
}, [
  ...
  {at: 1, expect: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)'}, // note: this will be serialized as matrix(1, 0, 0, 1, 0, 0)
  ...
]);
```
At 100%, the interpolated value should be serialized as `matrix(1, 0, 0, 1, 0, 0)`. However, in Gecko, when applying the slerp algorithm, the ratio of quaternionA is not equal to 0 exactly. It's something like `-2.220446049250313e-16`, which is equal to zero approximately. So the recomposed matrix is:
```
matrix3d(
  1.0, 9.355591e-17, 2.0598274e-16, 0.0,
  -9.355591e-17, 1.0, 8.233775e-17, 0.0,
  -2.0598274e-16, -8.233775e-17, 1.0, 0.0,
  0.0, 0.0, 0.0, 1.0
)
```
Therefore, this test is failed in Gecko because the interpolated result is not a 2d matrix, though these components are equal to zero approximately with some tolerances.

However, I notice Blink and WebKit use another formula.
[Blink](https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/geometry/quaternion.cc;l=100;drc=eef4762779d05708d5dfc7d5fe4ea16288069a35):
```
double half_angle = std::acos(cos_half_angle);
double scaleA = std::sin((1 - t) * half_angle) / sin_half_angle; // The ratio of quaternionA
double scaleB = std::sin(t * half_angle) / sin_half_angle;
return (scaleA * from) + (scaleB * to);
```

[WebKit](https://github.com/WebKit/WebKit/blob/bb19a861180a394e2c3a3d56e34bd051b1c56fe5/Source/WebCore/platform/graphics/transforms/Quaternion.cpp#L60-L64):
```
double halfAngle = std::acos(cosHalfAngle);
double scale = std::sin((1 - t) * halfAngle) / sinHalfAngle; // The ratio of quaternionA
double invscale = std::sin(t * halfAngle) / sinHalfAngle;
return { copy.x * scale + other.x * invscale, copy.y * scale + other.y * invscale, copy.z * scale + other.z * invscale, copy.w * scale + other.w * invscale };
```

Obviously, the ratio of quaternionA in Blink and WebKit is:
```
sin((1 - t) * halfAngle) / sin(halfAngle)
```
This formula is from https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/index.htm

After I applied this formula in Gecko, and the result of the ratio of quaternionA become `0` exactly in this test case, obviously. So the recomposed matrix can be serialized as 2d matrix successfully (i.e. there is no number approaching zero. They are exactly zeros.)

So perhaps we should update the spec to use this formula to avoid potential different behaviors among browsers and potential imprecision issues from floating-point number calculation?

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/9338 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Monday, 11 September 2023 20:04:07 UTC