w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.42k stars 650 forks source link

[css-transforms] New quaternion CSS transform option #9243

Open infusion opened 1 year ago

infusion commented 1 year ago

Hello,

CSS3D transforms offer a lot of options to apply the desired transform, from Euler angles, to matrix3D or even skews. As I am the author of Quaternion.js ( https://github.com/rawify/Quaternion.js ) and working with quaternions for years, I propose the following syntax to CSS:

transform: quat(w, x, y, z);

As an alternative name, quaternion(w, x, y, z) would also be possible. I was searching for discussions in this direction already, but only saw you're trying to decompose rotation matrices for spherical interpolation. I think adding quaternions explicitly would make clear that one can spherically interpolate orientations.

I think it is also consequent to add this syntax since other working groups discuss the introduction of quaternions as well, especially for device orientations or like WebXR's XRRigidTransform.

Also in terms of the gimbal lock problem, it would make sense to be able to apply a rotation in a compact way (with only 4 instead of 6 parameters for a rotation).

I think engines could also benefit from execution speed when only rotations are applied, since the formula to rotate a vector using a quaternion can be improved quite a lot, as I derived/proved here: https://raw.org/proof/vector-rotation-using-quaternions/

What do you guys think of this proposal?

Robert Eisele

tabatkins commented 1 year ago

It's been too long since I dug into quaternions - are these purely (3d) rotations, or can they correspond to other transforms, like translates? (Given https://drafts.csswg.org/css-transforms-2/#decomposing-a-3d-matrix I suspect it's a rotation and a scale, since the decomposition separates out the other things, leaving just rotate and scale to be expressed via the quaternion.)

For my own edification (and that of others who have a passing, at best, familiarity with the quaternion math), could you give an example of it in practice, comparing with the equivalent rotate3d()?

Assuming that the quat() normalizes to a matrix3d() like all the other functions, would the decomposition text currently in the spec correctly extract out the quaternion unchanged, so you still get the expected quaternion-based rotation?

infusion commented 1 year ago

Quaternions encode rotations only when they are normalized. Then the 4 dimensional vector looks like [cos(phi), v * sin(phi). Intuitively they encode a 3 dimensional (unit) vector that points into a certain direction and the angle around this vector. There is an extension called dual-quaternions, which also add a translational part besides the rotation in a 8 dimensional vector. I think the math there is getting a bit too heavy so that a broad audience is maybe scared - what I think many people are already of the matrix() / matrix3d() value.

Sure, you can use exactly the formula I already mentioned. For example to rotate 45° around the X axis, you would enter:

[w, [x, y, z]] = [cos(45°), [1, 0, 0] * sin(45°)]

which is a bit tedious in practice, but it opens a lot of benefits, some of them were already mentioned.

BTW, implementing a "trackball" is also super simple using quaternions. Maybe something I implemented here with Trackball.js would be possible to implement (almost) natively in the future.

Decomposing a quat out of a rotation3d() matrix is no problem. Basically you do the same steps as usually, 1) remove tx, ty, tz from the matrix, 2) calculate the norm of the rows to get the scaling and 3) divide the matrix by these scalings to get the rotation matrix. This rotation matrix can then be translated to a quaternion, like I did here https://github.com/rawify/Quaternion.js/blob/master/quaternion.js#L1391 . Please note, the typical implementation you'll find on the internet scales the quaternions directly for a unit quaternion, which makes the code a bit unreadable imho.

I'm always available to help if you're a little rusty with the math. But I think you worked already on this decomposition as far as I understood here https://github.com/w3c/csswg-drafts/issues/3710

schenney-chromium commented 1 year ago

IIRC the primary advantage of quaternions is that they offer a more intuitive and compact interpolation method for rotations, as compared to matrices. That is a q_1 + (1-a) q_2 interpolates the axis and angle smoothly. Hence their use in 3D animation systems.

tabatkins commented 1 year ago

Yeah I understand the benefit of quaternions for animation (at least roughly; again, I haven't had a reason to dig into the math properly but I do watch a lot of math YouTube, which is basically the same thing ^_^), but that's already handled implicitly by the spec; assuming the transform lists don't perfectly match so you interpolate via matrix, we then extract the equivalent quaternions (and other aspects) from the matrixes and interpolate each of those independently.

The requested change here is allowing quaternions as an input method for transforms; my question is what benefit this brings to authors.

For example to rotate 45° around the X axis, you would enter:

So the comparison is rotate3d(1, 0, 0, 45deg) vs quat(cos(45deg), sin(45deg), 0, 0), right? Could you elaborate more on what makes this more convenient in some circumstances? I check the TrackBall.js page; you mention that being able to express rotations directly as quaternions would simplify your code significantly there (presumably referring to the sample code at the bottom), but could you give an example?

(I'm pushing back like this, btw, because convenience sugar is a dime a dozen. We're not afraid to add sugar, and do so regularly, but it needs to be reasonably justified that it's actually solving problems authors have, rather than just being something that's theoretically or rarely useful to some people. New abilities are often easier to justify in that regard; sugar can take a little more push before it gets over the finish line. We've rejected a lot of perfectly reasonable numeric stuff from the V&U spec, for example, because even tho they're perfectly well-defined, simple, and can probably be useful sometimes, they can't be justified as useful often enough to be worth defining for everyone.)

infusion commented 1 year ago

The reason I'm bringing this up is not the spherical interpolation part. As you said, it is already handled in the spec. And don't worry about the pushback. I think there was this study from MIT which compared classical brain-storming methods against how Linux kernel hackers push back new ideas. This way people who brought up new ideas always have to find new arguments to justify something new, which in the end brings much more quality into products.

Anyway, I think the quat()/quaternion() extension has the highest value as a raw interface to rotations, not to tinker around with it manually. I think it's not just syntactic sugar, since Euler angles for example have the problem of gimbal locks (that you can rotate two axes in such a way that the third angle doesn't have any effect anymore).

Since more and more APIs adapting quaternions, such as WebXR and I think there are discussions to get devicemotion/deviceorientation event data also as quat, it would be a natural extension to pass the data directly to CSS. As an overview:

To me, the quaternion() sits between rotate3d() and matrix3d() where you only pass 4 numbers and open the gates to other APIs.

tabatkins commented 1 year ago

I think it's not just syntactic sugar, since Euler angles for example have the problem of gimbal locks (that you can rotate two axes in such a way that the third angle doesn't have any effect anymore).

Isn't it, tho? The gimbal lock situation is already avoided during animation by CSS's animation rules. Outside of animation, whether someone tracks rotations as quaternions (and thus are safe from gimbal lock) or euler angles (and thus are subject to it) is an implementation choice; either way they can output a CSS transform that represents the rotation state, and then animation between the states will work "correctly" by default.

Since more and more APIs adapting quaternions, such as WebXR and I think there are discussions to get devicemotion/deviceorientation event data also as quat, it would be a natural extension to pass the data directly to CSS. As an overview:

Ooh, this is pretty legit tho. Here's the WebXR interface. Matching platform conventions more easily is a very good reason to add sugar.