Yellow-Dog-Man / Resonite-Issues

Issue repository for Resonite.
https://resonite.com
137 stars 2 forks source link

Custom axis order on from euler and euler angles flux nodes. #1115

Open amplified1 opened 9 months ago

amplified1 commented 9 months ago

Is your feature request related to a problem? Please describe.

Gimbal lock is a near unavoidable issue, but it can be circumvented in many ways, one way I have seen used by a couple programs is allowing the user to change the order euler rotations are applied to move the locking to a different axis.

Describe the solution you'd like

Add an enum input to the euler angles and from euler flux nodes that allows the selection of an axis order for the node to use when converting between quaternions and euler.

Describe alternatives you've considered

There are many different ways of rotating things with flux, but they all have their quirks, more options would be helpful.

Additional Context

An example of this kind of feature implemented is Blender, where you can change the axis order of rotation constraints and rotation inputs through a dropdown.

lxw404 commented 9 months ago

Gimbal lock is not a problem with quaternion-based rotation, and quaternions are the internal implementation of many 3D software for rotation. If you are attempting to avoid gimbal lock, probably the best bet is to not shift the problem.

Could you give a specific example where you are running into an issue?

amplified1 commented 9 months ago

I think you have misunderstood, have you ever tried to rotate something on the X-axis by 90 degrees using euler inputs? The game tries to automatically adapt but it can't be done normally, which can sometimes suck when you are directly driving rotations using euler with flux. The idea is giving more options for euler rotations in flux, and I reiterate, this is a pretty standard feature, it's present in blender, godot, etc. Quaternions don't have gimbal lock but people don't usually edit quaternion values directly because they are unintuitive, they use the rotation nodes to use another method that maps onto quaternions, one of those options is euler, and the current implementation of euler is incomplete when compared to other 3d software

ProbablePrime commented 9 months ago

Thanks for the elaboration @amplified1 , could you link to the documentation for GoDot or Blender regarding this added capability for that operation?

amplified1 commented 9 months ago

Blender Documentation on rotation modes. An example of a blender transform constraint that allows the changing of this setting in its operation Godot Node3D documentation, contains eulerOrder property The specific documentation on the eulerOrder input in godot

These were just the quickest ones I could find, I don't know if there is anything more technical that would be helpful for implementation. Screenshot of blender UI: Screenshot_232 Screenshot of Godot UI: Screenshot_231

While these specific examples are more focused on their use in these program's respective scene inspectors these properties in these programs extend to the functionality of their scripting APIs, Blender's Python driver support, and Godot's GDscript/C#/C++ scripting, respectively. Having an option for this in the inspector would also be good, but it would be really helpful to have it represented in flux specifically.

lxw404 commented 9 months ago

Godot's documentation explicitly warns never to use Euler angles when programming and that the displayed rotation in the editor and their orders are purely for simple rotations in the editor only. Similarly in Blender, when you perform a rotation using the rotate command, it is converting that user provided Euler rotation in its current orientation into a quaternion and then applying a post rotation multiply on its current one. The only practical use in either of these programs for the changed order is for ease of the end user to specify which order to apply in a setting where they can't apply it to a current rotation directly.

I agree the rotation order should probably be included in the inspector, but not relied on in any capacity when programming.

Let's rotate a box along the X axis 90 degrees in 3 different ways

Method 1: Axis Angle

By far the easiest and most user-friendly way to do this is to just simply specify one axis and a rotation to apply on that axis using the Axis Angle node, analogous to the rotate method in Godot.

In this case we choose the X axis: $<1,0,0>$, and the rotation $90$ degrees and it produces our corresponding rotation.

Resonite_cMpu4z4YQV

If you plug this into the box's rotation, it will be oriented how you expect.

Method 2: Basis Transform

In flux, just as in Godot, you can construct a basis orientation and turn this into a rotation using the DecomposedRotation node. A basis is just a fancy way of saying "which axes point where". It is represented by a 3x3 matrix, each row corresponding to the direction that the X,Y,Z axes will be oriented in. Their magnitude corresponds to the scale, but that isn't relevant for rotations.

Here is the starting orientation of the box with the axes shown:

2024-01-11 08 08 09_r

The starting basis is just each axis is oriented along its original axis so:

This corresponds to the following 3x3 matrix:

$$ \begin{bmatrix} 1 & 0 & 0 \ 0 & 1 & 0 \ 0 & 0 & 1 \end{bmatrix} $$

Imagine what the axes will look like after rotating the box 90 degrees on the X axis, noting which axis is Right, Up, and which is Forward.

2024-01-11 08 08 29_r

The new basis will have the following properties as observed in the image above:

This results in a basis matrix of:

$$ \begin{bmatrix} 1 & 0 & 0 \ 0 & 0 & -1 \ 0 & 1 & 0 \end{bmatrix} $$

You can plug this matrix into the DecomposedRotation node to get a corresponding rotation for this orientation.

Resonite_xhxEkkN0cN

Method 3: Create a floatQ input

A floatQ even though it takes inputs as Euler angles, is really a quaternion. You never have to modify the quaternion values directly. If you want to rotate 90 degrees on the X axis, you input $<90,0,0>$.

Resonite_pNB1WTQ8gC

In all of these solutions you get a nasty looking quaternion, but I would advise absolutely ignoring the values it shows in its quaternion representation. What matters most is that you are able to construct these in multiple user-oriented ways.

That's fine for an exact rotation, but what if you want the rotation to be applied to something that is already oriented in a weird way

Don't you have to learn everything about quaternion math to know how to use them? I would argue not. There are only 2 operations practically you should know: Multiplication, and Inverse.

Let's say your cube is currently rotated some random weird initial amount $A$:

2024-01-11 09 15 54_r

and you want to rotate it $90$ degrees on its local X axis.

First construct the 90 degree X axis rotation using any of the 3 methods above, let's call the result $B$. Now just multiply $A B$.

2024-01-11 10 01 43_r

If you want to smoothly go from its current orientation to the final rotation, you can use the Slerp node:

https://github.com/Yellow-Dog-Man/Resonite-Issues/assets/52231149/6246cb87-79a9-4793-ade0-b15b1ff398eb

If you want to Undo a rotation, you take the rotation's inverse. Order of quaternion multiplication matters, so when you want to undo the rotation $B$ above, you would multiply the inverse $B^{-1}$ after $B$ was applied. The full operation $A B B^{-1}$ results in just $A$ since the rotation and its inverse cancel out. The most important thing to remember is that they only cancel out though when touching. A more complex example would be if you applied more rotations:

$$ A B C D $$

Let's say you want to go back to B, but you don't know what it was. You can perform the operation:

$$ A^{-1} A B C D D^{-1} C^{-1} $$

which cancels out to $B$. Step by step:

$$ \begin{gather} (A^{-1} A) B C D D^{-1} C^{-1} \ B C (D D^{-1}) C^{-1} \ B (C C^{-1}) \ B \end{gather} $$

Implementing axis order

You can already apply rotations in any arbitrary order you like by using the Axis Angle method described above, and multiplying them in whichever order you want.

For example, if you want to perform ZYX order:

Resonite_hCrERNWCaV_rz

If you wanted a one node solution for this I would understand that as a request, but it would not allow arbitrary axes.

Concluding remarks

All of this is to say, I highly recommend against use Euler rotations while programming except in very specific circumstances, as do other game engines.

The inspector probably would benefit from axis order, or preferably even just a way to apply a rotation to a current rotation.

amplified1 commented 9 months ago

I use axis angles a lot when dealing with rotations in flux, but I do still think this should be a one node option, and also eventually included in the inspector (though from what I understand a full inspector rework is needed before the team adds too much to it), if not just for the sake of parity with other 3D programs.

For the sake of the inspector specifically I think this would be a nice option because rotating things 90 degrees on the X-axis is actually a very common operation, as it is the most straightforward way to make planar objects like the default plane, circle mesh, etc, face upwards towards the Y axis, since in the unity engine planar objects are traditionally oriented on a vertical plane facing the Z axis, while it's often more useful for these objects to be facing the Y axis in a 3D environment. The euler inputs in the inspector can be confusing as the lossy process of mapping between a quaternion and euler can be very inconsistent feeling from the end user perspective.

Anyways, this is just something I want to see considered, maybe in the inspector rework when that happens.

5H4D0W-X commented 7 months ago

It should be possible to build a custom piece of UI for the inspector that allows you to enter a difference instead of modifying the existing rotation. So when a cube has the rotation (0, 129.53, 13.42) you can press a button to show a field with (0, 0, 0) and a "Confirm" button. When you enter (0, 90, 0) into that field and press confirm, the cube is rotated by 90 degrees on Y and the field is reset to default. And since you can "stack" multiple rotations temporally instead of having to mess with the entire Euler field at the same time, there is no confusing back-and-forth mapping. This could also feature a transform space selector, with a selection of "Local", "Parent", "World" and "Custom" (the latter showing a slot input field)