• Editor
  • Inherit Rotation still affecting negative scale in parent.

Related Discussions
...

I don't remember when is the last time someone bring this up. I just tested it on latest editor version. This problem still persist in latest version.

When "inherit rotation" is off and "inherit scale" is on, setting the parent with -1 scale on x axis (to flip it ) would NOT get the whole hierarchy tree flipped correctly. The child which is suppose to flip with parent don't actually flip.

My guess is you guys probably implemented the flipping with rotation. However, from user perspective, it doesn't make much sense when disabling "inherit rotation" is breaking the scale inheritance.

While sometimes it can be fixed by flipping the whole thing in game, sometimes we just want to flip a local hierarchy instead of the whole tree and that local hierarchy happens to have "inherit rotation" is off making the it difficult to flip. You know, its very common to have "inherit rotation" off when we want something to respect gravity. e.g. earring or ponytail of a character and we want to flip the head only.

Thus I am asking. Can the logic be further improved to make sure inherit rotation respect negative scale on parent node?
e.g. keep a table and remember the source of the extra rotation is from the system (by negative scale) instead of from the user and don't ignore that rotation even "Inherit Rotation" is unchecked.

TL;DR: Unfortunately disabling rotation inheritance must also disable reflection because math. 🙁

This is a very tricky area. Most apps don't allow disabling parts of the transform inheritance because it's difficult to make it behave sanely in all situations. We put a lot of effort into it and came up with these combinations:

normal
onlyTranslation
noRotationOrReflection
noScale
noScaleOrReflection

When you uncheck only Rotation, you'll notice that Reflection becomes unchecked, even though it's disabled. The behavior you are getting from the above list is noRotationOrReflection.

The reason why is in the math. It's been years so I'm a bit rusty on the details, but an affine transformation matrix is used to apply the bone transforms in a way that ascendant bone transforms are combined and applied to all children. The matrix has 4 values that describe rotation, scale, and shear and 2 more values for translation:

Image removed due to the lack of support for HTTPS. | Show Anyway

Once you convert the individual values for rotation, scale, and shear into a matrix, it is not always possible to extract those same values. That is because sometimes there are more than one set of rotation, scale, and shear values that result in the same matrix. This is super damned annoying!

For example, there is no difference between 180 rotation, 1,1 scale and 0 rotation, -1,-1 scale. You may think it's only a problem with specific values, but the transform behavior needs to be consistent across the range of all possible value combinations, otherwise you'd get sudden jumps when you arrive at certain values.

Another horrible example is scale 0,0. You're definitely not getting your rotation and shear values back out of the matrix after multiplying it with zero! This one is less important, since just about everything falls apart with zero scale.

Anyway, at each level in the bone hierarchy we have a matrix that is the combined transforms of all parent bones. Now we say we want to not inherit rotation from that matrix, but we've established that rotation, scale, and shear are intertwined in the matrix and can't be extracted back into the original set of values. If we just do it anyway, we get inconsistent, jumpy behavior that isn't acceptable. This is where most apps give up, but not Spine!

We came up with ways to support disabling inheritance as much as possible while keeping the behavior consistent. It has some drawbacks, such as that disabling rotation inheritance must also disable reflection, but it can still be useful.

Anyway, at each level in the bone hierarchy we have a matrix that is the combined transforms of all parent bones. Now we say we want to not inherit rotation from that matrix, but we've established that rotation, scale, and shear are intertwined in the matrix and can't be extracted back into the original set of values. If we just do it anyway, we get inconsistent, jumpy behavior that isn't acceptable. This is where most apps give up, but not Spine!

Can it be solved by having each bone layer in hierarchy (optionally) store an additional matrix just for the flipping correction when that node has negative scale? So that the nodes down below with rotation inheritance off can check if any parent nodes has negative scale and recalculate the transformation of that branch with the correction information?

Anyway, assuming this issue cannot be fixed. Is there workaround for the situation I described above. Flipping with scale-x on a node and some of the child nodes happen to have rotation inheritance off.

Maybe. I know we tried something similar, to use information from the bone hierarchy for better results. We tried very hard to make it happen, but we couldn't come up with a better solution that was still stable in all scenarios. If someone can do it, we would support it! The code is in the Bone class. The code is quite simple, but there are many edge cases. Also beware IK constraint needs to know about the transform mode to do the Right Things.

I don't think there is a workaround if you uncheck inherit Rotation, but you can achieve a similar effect via a transform constraint. It's hard to explain... try this project:
http://n4te.com/x/5271-hackyboy.spine
The hair3 bone won't rotate when the head bone rotates because of the *** hair3 constraint transform constraint. To rotate hair3, you'd need to rotate *** rotation for hair3 comes from this. If you scale the head bone, hair3 won't reflect, but if you scale *** scale this instead of head then it will.

Using a transform constraint and extra bones allows you a sort of partial inheritance bypass. I know this is clunky and it would be great to have a nicer way of doing it.

If you are flipping the entire skeleton, you can do that using Skeleton scaleX or Skeleton scaleY. Those work differently in that they affect all bones, even those with some parts of the normal transform inheritance disabled. They are currently only available at runtime.

Maybe. I know we tried something similar, to use information from the bone hierarchy for better results. We tried very hard to make it happen, but we couldn't come up with a better solution that was still stable in all scenarios. If someone can do it, we would support it! The code is in the Bone class. The code is quite simple, but there are many edge cases. Also beware IK constraint needs to know about the transform mode to do the Right Things.

I see, I can imagine how things would mess up when there can be constraints and stuff applying together. All the pieces involved need to choose the right matrix for calculation according to their situation. In theory, it should work. Too bad I just don't have enough free time. Otherwise, I would like to take a look at the code and do some experiment. In my opinion, if you already already try that path and didn't add it to release because it could not handle some edge cases, you may want to reconsider (unless the progress is breaking other stuff badly). In the end, both

  1. "unable to flip with rotation inheritance off " and
  2. "able to flip but would cause some other issues when constraints are involved"
    are both edge cases from user perspective after all. It just that facing #2 is better than facing #1 because it is bringing the user closer to their goal.

Thanks for the transform constraint idea. It could work in some cases but it does indeed increase the complexity of the project and may not be better than manually fixing the rotation in the dope sheet.
Hope this issue can be improved further in the future. Don't give up!

It's true that both options can cause trouble for the user, but we wouldn't want to use the option that produces unstable results in some cases. Unexpected results can be catastrophic and would have no workaround. A few more thoughts:

If we consider explicit flipX and flipY properties, we'd have the problem that the flipping needs to occur using each parent bone's axes. That's what the matrix does, it encodes the combined scale (and other transform properties) of all the parents, including their reflection, and the reflection is flipping over each parent bone's axes. An effort to track reflection at each level would also need to know what axes to reflect over. We'd basically be storing all this as local transform values and then apply them up the bone hierarchy without using a matrix. As soon as you use a matrix, even if trying to store only reflection, rotation gets mixed in to record the angle of the axes you are reflecting over. Not using a matrix makes transform vertices much more expensive.

The Skeleton scaleX and Skeleton scaleY properties work because they are always applied using the world axes. That's done by flipping the root bone and then specially applying the skeleton scale when parts of the transform are not inherited. We should expose the skeleton scale in the editor, I believe there is an issue for that.

First of all, my math is bad. I don't know if the concept below is practical.

What in my mind is something like this:

The node that have x scale flipped could calculate matrix 1 and 2.

Matrix 1. the normal matrix we are using right now.
Matrix 2. the matrix that ignores rotation but accumulate all the flipping from all the negative scale nodes above. (of course with translation and scaling)

It pass both M 1 and 2 to the children and let the children decide which one to use based on whether rotation check box is unchecked. There is no traverse upward and everything is downward. Performance should not be a problem because its just an extra set of matrix calc per such situation. No to mention the extra matrix calc can be pruned early because we already know whether there is rotation ignoring node exist down stair during initialization.

Does it work? :think:

When you store the matrix without rotation, the scale applied in that matrix doesn't get applied using the bone's axes. When you get down to G and you use that matrix, the scale inherited from the parents is applied along the world axes (ie the unrotated axes). I implemented it for kicks. Here hair3 doesn't inherit rotation and when head is scaled, hair3 doesn't get scaled in the expected direction:

Image removed due to the lack of support for HTTPS. | Show Anyway

It will only appear to work correctly when the head bone scale aligns with the world axis:

Image removed due to the lack of support for HTTPS. | Show Anyway

You are right. Some information is missing by just using that matrix alone. Is it a dead end using matrix?

Is it that we just need to also pass down the cumulative rotation value for the subsequent node to correct the local scale and translate before adding to that special matrix? I feel like it is getting close to the right path. I don't know... My math is really not good.

Using a matrix is great because it efficiently combines multiple transforms. For each bone in the hierarchy, it encodes the transform using the origin of that bone. The matrix mashes together the transform values and we have to give it both scale and rotation for it to use the bone's origin like we want. Once we do that, the transform properties for which we can't get the same values back out prevent us from compensating for those properties. I think using a matrix makes having no-rotation-inherit-but-keep-reflection a dead end. Not using a matrix loses too much, so I think we're a bit stuck with how it currently works. It would be great to figure out a better way, but so far it has eluded us!

Wait, I think something is wrong from the very beginning.

Matrix 1. the normal matrix we are using right now.
Matrix 2. the matrix that ignores rotation but accumulate all the flipping from all the negative scale nodes above. (of course with translation and scaling)

I didn't pay much attention to it but after I read the original concept again, the meaning doesn't make sense. I think I messed up the meaning of Matrix 2 when I try to write it down without realizing it!

While I call it "the matrix that ignores rotation", it is not ignoring rotation all the time. It should only ignore (reset) the rotation on Node G because rotation is unchecked starting from node G ONLY. The reset or rotation should only happen on every rotation ignoring nodes in a hierarchy.

The matrix m-flip should actually run an additional local space flipping, in addition to what it originally need to do using parant's m-flip matrix, to undone the any negative scale. This is what I mean to "accumulate all the flipping from nodes above". That's what it should happen.

If you put rotation into matrix 2, then G will inherit rotation from parent bones. How does G compensate for that inherited rotation in the matrix? In the matrix the rotation is merged with scale and shear, it can't be reliably extracted to undo only the rotation. That is the crux of the problem.

Ah. I See...
This make me wonder, how does G ignore the rotation normally when the parent node is not flipped? I thought they are the same situation where it taking a parent matrix and override the rotation with local one to continue the propagation. Maybe I am misunderstanding something fundamentally.

Anyway, if you are busy or no longer have interest on this issue, feel free to let me know. I am ok to stop at anytime. I would like this issue to be fixed but at the same time I understand that I could not offer much help on math related issue like this one.

It's done here:
https://github.com/EsotericSoftware/spine-runtimes/blob/4.0/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java#L148-L171
The rotation prx is extracted from the parent's matrix and subtracted from the local rotation. This rotates the bone opposite to the parent's rotation. It works fine, except for the interaction that scale has on the rotation extracted from the matrix.

I don't mind revisiting this too much, the PTSD has grown faint in time. The more I look at it the more confident I am that it can't be improved without getting rid of matrices, which we don't want to do. 🙁

It's ok.

Let's move on and discuss about how to make our life easier under the assumption that flipping is not friendly with inherit rotation turned off.

In the current implementation, one thing that make the issue troublesome to fix from dopesheet is that the once a node is flipped with scale, the user also need to flip the scale of every other sub nodes that has "rotation" unchecked, because that sub-tree is not flipped, in addition to correcting the rotation.

For an instance, I was working on a character yesterday trying to flip a major part of the skeleton but not everything, the first sight I see is that the head, L/R shoulder and foot need to be fixed. After I fixed the head, I also found that the beard is also flipped and need to be fixed.

There are several ways to reduce the steps to correct flipping.

When we break down the steps, its just

  1. correct the flipping by applying negative scale on x or y on sub nodes
  2. correct the rotation manually.

The 1st step can be improved if rotation inheritance can be turned back on midway by either..

  1. making it keyable
  2. add a setting to enable auto turn-on rotation when under flipped hierarchy is detected.

Method 1 require more works than 2 for both implementation effort and end user involvement.
For the end user, compared to manually negating the scale value, it became the operation to toggling the check box. It still require us to visit every other subnode with rotation unchecked. The advantage is that the scale value in dope sheet doesn't get polluted by flipping. (The scale track could already be in used during the flip.) It is also easier to operate than typing a '-' on the scale field not to mention we sometime are working on world space instead of local space but the negative need to be enter in local space value.

While you may think 2 is bad because it is a special case handling, it is actually not that bad. Considering the visual (flipping) is already wrong and needed to fixed anyway. Turning back on the rotation automatically would not make thing worst than what the user already got. It save the effort of fixing the flipping completely and user can jump into correcting the rotation right away. Since this can be implemented as a setting, user who don't like this auto correction behaviour can just leave the off. I would say if you let me choose, this one is much more preferred as a victim of this flipping hell issue.

What do you think?