Giter Site home page Giter Site logo

Weird Y axis rotation bug about minko HOT 36 CLOSED

aerys avatar aerys commented on August 19, 2024
Weird Y axis rotation bug

from minko.

Comments (36)

JMLX42 avatar JMLX42 commented on August 19, 2024

Thank you for your feedback! :)

Actually this is not a "bug". At least I'm pretty sure it's not.

Rotation properties in 3D are ambiguous for many reasons. This is one of them: gimbal lock (it's not the classic symptom of what we call "gimbal lock" but both problems have the same root):

http://en.wikipedia.org/wiki/Gimbal_lock

Here is the explanation: 3D matrix decomposition is ambiguous. Extracting the translation is not an issue since it is stored in the 4th row/column. But there are several ways to extract the rotation and the scale. The main issue is that both the rotation and the scale are stored in the upper left 3x3 matrix. And there is now way to make the difference between a Math.PI rotation and a -1 scale (to summarize).

When you read one of the Matrix4x4.rotation* properties, it will try to decompose the matrix. But it can fail at preserving the consistency of the rotation values: some will be clamped to [-PI .. PI] when others will be negated for example. So setting rotation values work: it's the getter that returns ambiguous values.

For example, this works as expected:

node.rotationY = 2.;

But if you want to increment/decrement the rotation and keep the consistency, you should use the Matrix4x4.appendRotation() and Matrix4x4.preprendRotation() methods instead. Those methods use axis angles and are not ambiguous:

private function onKeyDown(e:KeyboardEvent):void 
{
    switch (e.keyCode) 
    {
        case Keyboard.LEFT:
            _node.transform.appendRotation(.1, Vector4.Y_AXIS);
            break;
        case Keyboard.RIGHT:
            _node.transform.appendRotation(-.1, Vector4.Y_AXIS);
            break;
        default:
    }
}

You could also do something like this:

_rotation += .1;
node.rotationY = _rotation;

But it will burn a lot (lot) more CPU than append/prependRotation for the very same result...

A workaround would be to store the roation aside from the Matrix3D object. But it would be pretty hard to maintain this property and quite cumbersome. And I guess it won't help with actual gimbal lock so you'll still have to learn more about rotation in a 3D space anyway :)

To help people, I will write a sample and a tutorial today about this very problem.

from minko.

Alexx999 avatar Alexx999 commented on August 19, 2024

in fact, in our project we where doing it like

_mesh.rotationY = Number(_settings.getProperties(Settings3D.PROPERTIES_ROTATION_Y) / (180 /  Math.PI))

and it caused mesh flipping upside down.

prependRotation is not that good because it's cumulative, but setRotation worked for me (however, it seems to be much slower than modifying object's "rotation" properties directly)

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024
prependRotation is not that good because it's cumulative

But the bug should only exist when you want the rotation to be cumulative...

_mesh.rotationY = Number(_settings.getProperties(Settings3D.PROPERTIES_ROTATION_Y) / (180 /  Math.PI))

Are you sure it's not because your local Y axis is not the up axis?
What do you get in the console if you do this:

_mesh.localToWorld.transformVector(Vector4.Y_AXIS);

?

from minko.

makc avatar makc commented on August 19, 2024

But the bug should only exists when you want the rotation to be cumulative...

Not entirely true, the bug could be seen when ever the user can compare two rotations, for example if he uses angle slider.

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024
he uses angle slider

What kind of "angle slider"? If the slider simply sets the rotation it works fine.

If you want to compare rotations, you should use quaternions because euler angles are ambiguous.
Maybe we should add a Matrix4x4.getRotationQuaternion() method...

from minko.

makc avatar makc commented on August 19, 2024

what would be the difference between doing .rotationY += 0.1 and moving the slider by 0.1 and then .rotationY = slider.value? do you mean rotationY getter makes the jump?

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024
do you mean rotationY getter makes the jump?

Exactly. As I explained earlier, the decomposition is ambiguous. The composition is not.
Therefore, rotationY = slider.value should work without any problem just like the last sample I provided.

At least it will rotate on the Y axis. Now that Y axis might not be the UP axis and that's what I suspect :)

It is really a math VS programming problem... And this is one of the reasons I wanted to avoid the rotation* properties in the scene node itself in the first place: I'd rather have people take some time to learn about 3D transformations, matrices and the associated pitfalls.

In Minko Studio, the rotation gizmo uses append/prependRotation() and it really works fine.

If someone adds Matrix4x4.getRotationQuaternion() I'll be more than happy to merge it of course :)

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024

Here you go, Matrix4x4.getRotationQuaternion() 371c748
This should make it easier to compare rotations using Vector4.equals()

from minko.

Alexx999 avatar Alexx999 commented on August 19, 2024

actually, we do use slider (we are developing editor), and our meshes and lights are flipped upside down in that cases (angle between 90 and 270), but with setRotation it does not flip (works as intended, but SLOW)

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024
we are developing editor

Why don't you use Minko Studio? I can give you access to the private beta.

and our meshes and lights are flipped upside down in that cases, but with setRotation it does not flip

The rotation setter uses setRotation. So they have the very same behavior...
Are you sure they behave differently?
Can you provide some sample code?

Thank you for your feedback :)

works as intended, but SLOW

Setting the rotation directly implies decomposing/recomposing the matrix.
This is why you should use append/prependRotation() instead.

from minko.

Alexx999 avatar Alexx999 commented on August 19, 2024

We do following:

                var rotationX:Number = Number(_settings.getProperties(Settings3D.PROPERTIES_ROTATION_X))  / (180 /  Math.PI) + Math.PI / 2;
                var rotationY:Number = Number(_settings.getProperties(Settings3D.PROPERTIES_ROTATION_Y)) / (180 /  Math.PI);
                var rotationZ:Number = Number(_settings.getProperties(Settings3D.PROPERTIES_ROTATION_Z)) / (180 /  Math.PI) + Math.PI;

                _mesh.transform.setRotation(rotationX, rotationY, rotationZ);

This code is actually working correct, but when we set all 3 properties on mesh independently (in same order as they are calculated) it flips

About Minko studio - it would be great to have a look at it (and we are really interested in ShaderLab - is it included in studio or it comes as separate product?)

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024
About Minko studio - it would be great to have a look at it (and we are really interested in ShaderLab - is it included in studio or it comes as separate product?)

Just contact us: http://aerys.in/contact

ShaderLab open beta is here : http://hub.aerys.in/index.php/Minko:Tools
But it's an old release.
A new version will be directly integrated inside Minko Studio before the end of the year.

This code is actually working correct, but when we set all 3 properties on mesh independently (in same order as they are calculated) it flips

Yes. Because the rotation* setter uses setRotation(), which has to decompose the matrix to know the value of the other 2 rotations (ie. if you set only Y, we have to get the value of the X and Z rotations).

If you call setRotation() with the 3 rotations, the euler angle values extracted from the matrix are replaced with the 3 values you provide. Thus the issue does not show.

Also, make sure you always lock() and unlock() matrices if you perform multiple operations. Read the Locking/unlocking the transform section here:

http://hub.aerys.in/index.php/Minko:Moving,_Rotating_And_Scaling_Objects

from minko.

makc avatar makc commented on August 19, 2024

Yes. Because the rotation* setter uses setRotation(), which has to decompose the matrix to know the value of the other 2 rotations

lol so it does not work with the slider, after all.

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024

Indeed :) My mistake...
Another way to do it is to use append/prependRotation() with push/pop:

// init the matrix with initial translation/scale/rotation
// push it to "save" its state
matrix.push();
// on enterframe
// restore the matrix at its init. state
matrix.pop();
// save it again to make sure we don't lose the init state
matrix.push();
matrix.appendRotation(_rotationX, Vector4.Y_AXIS);

Matrix4x4.push() will save the matrix.
Matrix4x4.pop() will restore it.
It's like somekind of "states" stack directly in the Matrix4x4 object.

It's pretty cool for a lot of use cases :)

from minko.

makc avatar makc commented on August 19, 2024

Indeed, very nice feature 👍

from minko.

Alexx999 avatar Alexx999 commented on August 19, 2024

by the way, following code that was suggested by you as working is broken too (it actually reproduces that "flip" bug I got when trying to rotate my mesh by sliders)

        private var _rot:Number = 0;
        private function onKeyDown(e:KeyboardEvent):void 
        {
            switch (e.keyCode) 
            {
                case Keyboard.LEFT:
                    _rot += Math.PI / 8;
                break;
                case Keyboard.RIGHT:
                    _rot -= Math.PI / 8;
                break;
                default:
            }
            _node.rotationX = 0;
            _node.rotationY = _rot;
            _node.rotationZ = 0;
        }

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024

And that's why I said "Indeed :) My mistake..." :p

from minko.

Alexx999 avatar Alexx999 commented on August 19, 2024

well, then forcing users to work with transform seems to be way better than providing ambiguous methods - personally I'm OK with matrices and only reason why I used that rotation* because it's there and lays on surface (and in most engines which recalculate matrix completely on-demand it works just OK) ;)

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024
in most engines which recalculate matrix completely on-demand it works just OK

But that's simply not possible.

As soon as you multiply to matrices for example, you'll have to decompose the result to get the rotation.
I've never heard of any other solution than working with quaternions or axis angles...

If you have an implementation example I'll be happy to have a look :)

from minko.

Alexx999 avatar Alexx999 commented on August 19, 2024

to give one quick example - it's Away3D, matrix there is duplicated by set of fields (x,y,z, rotX, rotY, rotZ, scaleX, scaleY, scaleZ), is marked as dirty when field changes and then recalculated when matrix getter is accessed

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024

And they have the exact same bug, don't they?
I found this: http://away3d.com/forum/viewthread/3299/

When those properties are "dirty", you have to decompose the matrix to refresh them.
And decomposition will return those ambiguous values.

from minko.

Alexx999 avatar Alexx999 commented on August 19, 2024
/**
         * Defines the euler angle of rotation of the 3d object around the x-axis, relative to the local coordinates of the parent <code>ObjectContainer3D</code>.
         */
        public function get rotationX() : Number
        {
            return _rotationX * MathConsts.RADIANS_TO_DEGREES;
        }

        public function set rotationX(val:Number) : void
        {
            if (rotationX == val)
                return;

            _rotationX = val * MathConsts.DEGREES_TO_RADIANS;

            invalidateRotation();
        }

from minko.

makc avatar makc commented on August 19, 2024

@promethe42 well you could make a matrix class with all these angles tracked internally (along with scales and translations). while this would limit the range of matrices you could use for transformation, it's not necessary tight limit - software like Maya are doing exactly that and noone complains.

from minko.

Alexx999 avatar Alexx999 commented on August 19, 2024

and in the end they're doing

        protected function updateTransform() : void
        {
            _pos.x = _x;
            _pos.y = _y;
            _pos.z = _z;

            _rot.x = _rotationX;
            _rot.y = _rotationY;
            _rot.z = _rotationZ;

            _sca.x = _scaleX;
            _sca.y = _scaleY;
            _sca.z = _scaleZ;

            _transform.recompose(_transformComponents);

            if (!_pivotZero) {
                _transform.prependTranslation(-_pivotPoint.x, -_pivotPoint.y, -_pivotPoint.z);
                _transform.appendTranslation(_pivotPoint.x, _pivotPoint.y, _pivotPoint.z);
            }

            _transformDirty = false;
            _positionDirty = false;
            _rotationDirty = false;
            _scaleDirty = false;
        }

where "transformComponents" is following:

            _transformComponents = new Vector.<Vector3D>(3, true);
            _transformComponents[0] = _pos;
            _transformComponents[1] = _rot;
            _transformComponents[2] = _sca;

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024

If you have the actual link to the source file it would be very helpful :)
Away3D 4 doesn't have its own Matrix type so it's sure they have the same issue.

well you could make a matrix class with all these angles tracked internally

Ok. But what do you do when those private properties have to be refreshed?
The only way is to decompose the matrix and you get ambiguous values.

and in the end they're doing

The thing is their matrices are polled.
Ours are pushed (this is mainly for performances reasons and it works great.) so the behavior has to be a little bit different.

I'm investigating a bug in minko-lighting and I'll give it a try...

from minko.

makc avatar makc commented on August 19, 2024

what do you do when those private properties have to be refreshed?

they just dont :) you can't transform the node in Maya in any other way than that formula allows.

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024
they just dont :) you can't transform the node in Maya in any other way than that formula allows.

That means goodbye for 99% of what you actually need to do when you do "real" 3D programming stuff.
A 3D editor and a 3D engine are used in very different ways. For example, you could not have lookAt() or orientTo() methods. You would not be able to multiply two matrices, which happen 100x more than incrementing rotation* properties...

Look at minko studio: the rotation gizmo works perfectly well and yes, the tool "limits" what you can do with the matrix since you can't affect it directly. But in the code, you absolutely need to...

from minko.

Alexx999 avatar Alexx999 commented on August 19, 2024

https://github.com/away3d/away3d-core-fp11/blob/master/src/away3d/core/base/Object3D.as

and by the way, I'll vote for just removing that rotation* stuff at all as broken - as you've said earlier it's actually impossible to decompose matrix without any ambiguity involved

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024

If it can't be fixed we will. I wasn't too happy with this in the first place.
You know it's always the same thing: people complain it's not here despite they don't know how much they actually don't need it/should not use it :p
We usually don't do this kind of things... we've been to compromising on this one :)

from minko.

makc avatar makc commented on August 19, 2024

A 3D editor and a 3D engine are used in very different ways.

You could still make such a matrix class (extending the current one) and move all rotation* there, just in case of that 1% (and as you guessed this thread is exactly one of them)

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024

I get it. But it doesn't sound very elegant :)
I'd rather have people learn things...

from minko.

Alexx999 avatar Alexx999 commented on August 19, 2024

It's ugly compromise. As I've said earlier I'm happy with matrices, I like math and I even can write my own lightning system with bare AGAL (already done to replace that crappy stuff in Away3D). But well, I work in team and not everyone in team know anything about math - most people just don't care (as long as they don't see obvious bugs, and even if they see they just say something like "that minko of yours is broken piece of shit!").
I'm currently fixing bugs left after such team member :(

from minko.

Alexx999 avatar Alexx999 commented on August 19, 2024

the point is that forcing everybody to do it the right way is not that bad at all :)

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024
the point is that forcing everybody to do it the right way is not that bad at all :)

100% pro!
And I think that's what we don't with the rest of the framework.

from minko.

JMLX42 avatar JMLX42 commented on August 19, 2024

fixed in f861b24
please confirm that fix works for you

from minko.

Alexx999 avatar Alexx999 commented on August 19, 2024

Yes, no more flipping now.

from minko.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.