This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 15610 - Definition for rotate3d() doesn't match implementations
Summary: Definition for rotate3d() doesn't match implementations
Status: CLOSED FIXED
Alias: None
Product: CSS
Classification: Unclassified
Component: Transforms (show other bugs)
Version: unspecified
Hardware: All All
: P2 normal
Target Milestone: ---
Assignee: Dirk Schulze
QA Contact: This bug has no owner yet - up for the taking
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2012-01-18 17:33 UTC by Aryeh Gregor
Modified: 2012-02-14 17:13 UTC (History)
5 users (show)

See Also:


Attachments
rotate3d (6.00 KB, image/png)
2012-02-12 02:49 UTC, Dirk Schulze
Details

Description Aryeh Gregor 2012-01-18 17:33:48 UTC
"""
This function is equivalent to matrix3d(1 + (1-cos(angle))*(x*x-1), -z*sin(angle)+(1-cos(angle))*x*y, y*sin(angle)+(1-cos(angle))*x*z, 0, z*sin(angle)+(1-cos(angle))*x*y, 1 + (1-cos(angle))*(y*y-1), -x*sin(angle)+(1-cos(angle))*y*z, 0, -y*sin(angle)+(1-cos(angle))*x*z, x*sin(angle)+(1-cos(angle))*y*z, 1 + (1-cos(angle))*(z*z-1), 0, 0, 0, 0, 1).
"""
http://dev.w3.org/csswg/css3-3d-transforms/#transform-functions

rotate3d(0, 0, 1, 90deg) should therefore be equivalent to

matrix3d(0,-1, 0, 0,
         1, 0, 0, 0,
         0, 0, 1, 0,
         0, 0, 0, 1)

Test-case 1:

data:text/html,<!doctype html>
<div style="height:50px;width:50px;
border-top:1px solid black;
transform:rotate3d(0,0,1,90deg)"></div>

Test-case 2:

data:text/html,<!doctype html>
<div style="height:50px;width:50px;
border-top:1px solid black;
transform:matrix3d(
0,-1, 0, 0,
1, 0, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1)"></div>

Per spec these should render the same.  In Firefox 12.0a1 and Chrome 17 dev, however (with prefixes added), they render differently: the first has the border turned to the right, and the second has it turned to the left.  Implementations have the angle negated with respect to the spec, which works out to the same as negating sin(angle).  So the spec should say:

"""
This function is equivalent to matrix3d(1 + (1-cos(angle))*(x*x-1), z*sin(angle)+(1-cos(angle))*x*y, -y*sin(angle)+(1-cos(angle))*x*z, 0, -z*sin(angle)+(1-cos(angle))*x*y, 1 + (1-cos(angle))*(y*y-1), x*sin(angle)+(1-cos(angle))*y*z, 0, y*sin(angle)+(1-cos(angle))*x*z, -x*sin(angle)+(1-cos(angle))*y*z, 1 + (1-cos(angle))*(z*z-1), 0, 0, 0, 0, 1).
"""

where I've just replaced sin(angle) with -sin(angle) consistently.
Comment 1 Simon Fraser 2012-01-31 21:40:21 UTC
This seems pretty different from what WebKit actually implements:

    angle /= 2.0f;
    double sinA = sin(angle);
    double cosA = cos(angle);
    double sinA2 = sinA * sinA;

        double x2 = x*x;
        double y2 = y*y;
        double z2 = z*z;
    
        mat.m_matrix[0][0] = 1.0f - 2.0f * (y2 + z2) * sinA2;
        mat.m_matrix[0][1] = 2.0f * (x * y * sinA2 + z * sinA * cosA);
        mat.m_matrix[0][2] = 2.0f * (x * z * sinA2 - y * sinA * cosA);
        mat.m_matrix[1][0] = 2.0f * (y * x * sinA2 - z * sinA * cosA);
        mat.m_matrix[1][1] = 1.0f - 2.0f * (z2 + x2) * sinA2;
        mat.m_matrix[1][2] = 2.0f * (y * z * sinA2 + x * sinA * cosA);
        mat.m_matrix[2][0] = 2.0f * (z * x * sinA2 + y * sinA * cosA);
        mat.m_matrix[2][1] = 2.0f * (z * y * sinA2 - x * sinA * cosA);
        mat.m_matrix[2][2] = 1.0f - 2.0f * (x2 + y2) * sinA2;
        mat.m_matrix[0][3] = mat.m_matrix[1][3] = mat.m_matrix[2][3] = 0.0f;
        mat.m_matrix[3][0] = mat.m_matrix[3][1] = mat.m_matrix[3][2] = 0.0f;
        mat.m_matrix[3][3] = 1.0f;
Comment 2 Aryeh Gregor 2012-02-01 17:07:26 UTC
Remember your trig identities.  :)  E.g., for the first entry, the spec says

  1 + (1-cos(angle))*(x*x-1)

while WebKit says

  1.0f - 2.0f * (y2 + z2) * sinA2
= 1 - 2*(y*y + z*z) * sin(angle/2)^2.

sin(t)^2 = (1 - cos(2t))/2 <http://en.wikipedia.org/wiki/Half-angle_formula#Power-reduction_formula>, so this is the same as

  1 - (y*y + z*z) * (1 - cos(angle)).

(x, y, z) is normalized, so x*x + y*y + z*z = 1, which means y*y + z*z = 1 - x*x, and WebKit's formula is the same as

  1 - (1 - x*x) * (1 - cos(angle))

which if we distribute the first minus sign across (1 - x*x), becomes

  1 + (x*x - 1) * (1 - cos(angle))

which is the same as the spec.


If you look at the terms involving sin(), you can see that WebKit does do the opposite of the spec, as my black-box testing discovered and as I explained in comment #0:

  2.0f * (x * y * sinA2 + z * sinA * cosA)
= 2*x*y*sin(angle/2)^2 + 2*z*sin(angle/2)*cos(angle/2)
= x*y*(1 - cos(angle)) + 2*z*sin(angle/2)*cos(angle/2)
    (power reduction in first term)
= x*y*(1 - cos(angle)) + z*sin(angle)
    (double-angle in second term).

Note that it's z*sin(angle), as I say in the bottom of comment #0, not -z*sin(angle) as the spec says.  The same goes for the other entries.  sin(angle) needs to be negated in every case.


FWIW, it looks like Mozilla independently realized the spec didn't match WebKit when developing their 3D transform implementation, and made their implementation match WebKit instead of the spec for compat with web content:

https://bugzilla.mozilla.org/show_bug.cgi?id=704468
Comment 3 Chris Marrin 2012-02-01 18:24:06 UTC
...
> FWIW, it looks like Mozilla independently realized the spec didn't match WebKit
> when developing their 3D transform implementation, and made their
> implementation match WebKit instead of the spec for compat with web content:
> 
> https://bugzilla.mozilla.org/show_bug.cgi?id=704468

So, the spec is describing the left-hand rule for rotations, which is not very common. WebKit (and now FF) implement the right-hand rule, which is the more common way of expressing the direction. So I'd say we should change the spec to match the two implementations. 

OBTW, I'm betting Chrome matches the other two, unless they've made some changes in Chrome specific parts of the code. So changing the spec would match all the major transform implementations.
Comment 4 Chris Marrin 2012-02-01 18:25:14 UTC
This is all probably a fallout of our changing the spec a while back from row-major to column-major notation. Changing the spec certainly seems like the right solution
Comment 5 Dirk Schulze 2012-02-12 02:49:50 UTC
Created attachment 1077 [details]
rotate3d

Can some one verify that this is the correct matrix that we want? It is the code that Chris applied to this bug.

But the matrix on SVG Transform[1] looks different to the attached matrix as well as to the matrix on CSS Transforms.

[1] http://www.w3.org/TR/2009/WD-SVG-Transforms-20090320/#threed-rotate-definition
Comment 6 Dirk Schulze 2012-02-13 03:57:07 UTC
I added a matrix to the mathematical description section, that matches the current behavior of WebKit. If all agree with the math, we can close this bug now.
Comment 7 Aryeh Gregor 2012-02-13 16:42:33 UTC
There are two mistakes in the new version:

1) alpha needs to be replaced by alpha/2.  This is the "angle /= 2.0f;" line in WebKit.

2) The first matrix entry has x^2 + z^2, but it should by y^2 + z^2 like WebKit.

Otherwise it's correct.
Comment 8 Dirk Schulze 2012-02-13 16:47:39 UTC
Thanks a lot, I'll change that asap. I will also add a comment that alpha must be in radian for the current rotate3d matrix. This might be the difference to SVG Transforms. I will check this as well.
Comment 9 Dirk Schulze 2012-02-14 17:00:03 UTC
I corrected the code with the commit 2012/02/13. Closing bug now. Feel free to reopen it if you find other issues with rotate3d.
Comment 10 Aryeh Gregor 2012-02-14 17:13:38 UTC
Looks good to me.  Thanks!