Quaternion increment on smartphone


#1

I am playing a 360 video: an equirectangular movie is projected on sphere, camera sits in the sphere’s center, the camera’s orientation is driven by the MotionManager::getOrientation, so that the user can rotate the smartphone to investigate the whole scene.

The problem with this approach is that every time the video starts, depending on the smartphone orientation, the user looks at a different viewport. This is what I would like to avoid.

I would like my user, regardless of her phone orientation, to always start watching the movie through the same viewport. I am only interested in y-axis rotation, assuming that the initial phone orientation is vertical.

For this I am saving the initial phone orientation on first MotionManager update, when the app starts:

    quat init = MotionManager::getRotation( getOrientation() );
    auto w = init.w; //rotation angle
    mInitialRotationQuat = quat(-w,0.0,1.,0.); // only y-axis is important

Then in the update() I am multiplying the current orientation quaternion by the initial one to set the camera orientation:

   mCamPersp->setOrientation( MotionManager::getRotation( getOrientation() ) * mInitialRotationQuat);

In the above I am following the QuaternionAccum example, in which it is shown how to increment a quaternion.

However, this produces a reproducible but undesired result –– the camera orientation gets corrected, but it is pointing to a different part of the video, instead of the same part of video, every time I change the phone orientation when starting the video.

What am I doing wrong?

Thanks.


#2

i’m shit at maths so these might not even make sense, but you a few things to try:

  1. you might need to normalize your initial quaternion.
  2. i don’t think quaternions are commutative so you may try flipping the order of multiplication?
  3. is this the right way to extract values from a quaternion? Maybe try the glm functions.
    float a = glm::angle(initialQuat);
    initialQuat = glm::angleAxis(a, vec3(0, 1, 0));
    

shrugs


#3

@lithium.snepo Thanks for your help.

  1. MotionManager seems to return normalized quaternion: glm::normalize does not change its values
  2. Yes the quaternions are not commutative, but I think I am using correct order –– at least the result makes sense direction wise, if I switch the order, the resulting rotation is opposite to what one would expect.
  3. I think the way you propose to build the initial quaternion is equivalent to mine for the purposes of this application.

But I think the main reason for the behaviour I am observing is that MotionManager needs to be warmed up before building the initial quaternion. This is what I observe if I print the following in the update:

   float a = glm::angle(init);
     cout<< "DEGREES "<< glm::degrees(a) << " [" << MotionManager::isDataAvailable() <<"] "<<mCurrentFrame << endl;

DEGREES 11.661 [1] 1
DEGREES 11.668 [1] 2
DEGREES 11.702 [1] 3
DEGREES 11.707 [1] 4
DEGREES 11.735 [1] 5
DEGREES 11.747 [1] 6
DEGREES 11.751 [1] 7
DEGREES 11.753 [1] 8
DEGREES 11.723 [1] 9
DEGREES 11.711 [1] 10
DEGREES 11.713 [1] 11
DEGREES 11.741 [1] 12
DEGREES 250.842 [1] 13
DEGREES 250.85 [1] 14
DEGREES 250.854 [1] 15
DEGREES 250.875 [1] 16
DEGREES 250.876 [1] 17
DEGREES 250.866 [1] 18
DEGREES 250.87 [1] 19
DEGREES 250.884 [1] 20
DEGREES 250.887 [1] 21
DEGREES 250.881 [1] 22
DEGREES 250.863 [1] 23
DEGREES 250.865 [1] 24

while keeping the phone motionless. Notice a jump at frame 13.

So I decided to take the value of the initial quaternion at frame 30, and that seems to have fixed the problem. Not sure how that would change on a different iphone or on android.

–8


#4

Glad you found a workaround.

Just in the interest of pedantry, the quat i was talking about normalising was mInitialRotationQuat, not the one returned by the MotionManager. My (limited) understanding of quaternions is that the sum of their components is 1, meaning that a quat constructed with quat(-w,0.0,1.,0.) is only valid for w = 0.0.


#5

I had the same problem when made the cardboard block.

I put a naive fix similar to the one you did and kinda get the initial direction thing working:

if(mDesiredDirectionApplied && !mMotionReady && MotionManager::isDataAvailable()){
    mMotionCount += 1;
    //skip first few unstable frames to get the correct initial facing direction
    if(mMotionCount > SKIP_UNSTABLE_FRAMES)
        mInitDirection += mCamera.getViewDirection();
    if(mMotionCount >= CALC_MOTION_FRAMES + SKIP_UNSTABLE_FRAMES){
        mInitDirection /= CALC_MOTION_FRAMES;
        float ang = glm::atan(mInitDirection.z, mInitDirection.x);
        ang = (ang > 0 ? ang : (2*M_PI + ang));
        mDefaultAngle = mDesiredDirection + mDefaultAngle - ang;
        mMotionReady = true;
    }
}

And then use the mDefaultAngle for desired view direction offset.

Recently while playing around with the PSVR raw sensor data, I found this class:


which basically is a smoothed acceleration / rotation integrator that supports recenter / calibrating stuff, I’m not 100% percent sure if the data coming out from MotionManager is raw, but this class is proved to be pretty stable for my PSVR.


#6

@seph : Thanks for sharing!

A note: I think MotionManager already has an accelerometer noise filter built in, which can be controlled via MotionManager::setAccelerometerFilter(float)

–8