title: How to avoid a sphere from turning upside down when user rotates it, in OpenGL ES 2.0/ Rajawali?
body: <p>I'm trying to make a 360video player app for Android. So far I've managed to create a sphere and project the video on to it.
  I also have an Arcball camera that follows the sphere in order to handle user touch-events and object rotations.</p>
  
  <p>I've extended the <code>ArcballCamera</code> class from Rajawali to make some changes on it as I don't like some weird movements the video does when rotating the sphere. I've managed to change vertical and horizontal rotating directions.<br>
  The problem is, if the user scrolls up and up, there is a moment when you pass the sphere's pole where the video gets upside down.
  Also, depending on how the sphere rotates (I'm not sure of what is the reason exactly) the video also is sloping. 
  I want to let the user to look up and down, behind and forward, but I always want the northern video pole to point up, 
  I don't know if I'm explaining myself well.</p>
  
  <p>I can only post two links, so here is a screenshot of the app - This happens either when the user continues scrolling up or down passing the sphere's poles:</p>
  
  <p><a href="http://i.imgur.com/y4ks6hY.png" rel="nofollow">upside down capture</a></p>
  
  <p>I am using quaternions for the rotations and, since they're made with a rotation axis and a rotation angle, I first tried to keep the x-axis or z-axis at 0, but that didn't work. 
  I've searched for similar things but I can't find a way out.</p>
  
  <p>Here's the <code>ArcballCamera</code> class code (only things related to rotation):</p>
  
  <pre><code>public class mArcballCamera extends ArcballCamera{
  
      /*...*/
  
      /*with the given x and y coordinates returns a 3D vector with x,y,z sphere coordinates*/
      private void mapToSphere(float x, float y, Vector3 out) {
          float lengthSquared = x * x + y * y;
          if(lengthSquared &gt; 1.0F) {
              out.setAll((double)x, (double)y, 0.0D);
              out.normalize();
          } else {
              out.setAll((double)x, (double)y, Math.sqrt((double)(1.0F - lengthSquared)));
          }
  
      }
  
      /**with the given x and y coordinates returns a 2D vector with x,y screen coordinates*/
      private void mapToScreen(float x, float y, Vector2 out) {
          out.setX((double)((2.0F * x - (float)this.mLastWidth) / (float)this.mLastWidth));
          out.setY((double)(-(2.0F * y - (float)this.mLastHeight) / (float)this.mLastHeight));
      }
  
      /**maps initial x and y touch event coordinates to &lt;mPrevScreenCoord/&gt; and then copies it to
       * mCurrScreenCoord */
      private void startRotation(float x, float y) {
          this.mapToScreen(x, y, this.mPrevScreenCoord);
          this.mCurrScreenCoord.setAll(this.mPrevScreenCoord.getX(), this.mPrevScreenCoord.getY());
          this.mIsRotating = true;
      }
  
  
      /**updates &lt;mCurrScreenCoord/&gt; to new screen mapped x and y and then applies rotation*/
      private void updateRotation(float x, float y) {
          this.mapToScreen(x, y, this.mCurrScreenCoord);
          this.applyRotation();
      }
          /** applies the rotation to the target object*/
      private void applyRotation() {
          if(this.mIsRotating) {
              //maps to sphere coordinates previous and current position
              this.mapToSphere((float) this.mPrevScreenCoord.getX(), (float) this.mPrevScreenCoord.getY(), this.mPrevSphereCoord);
              this.mapToSphere((float) this.mCurrScreenCoord.getX(), (float) this.mCurrScreenCoord.getY(), this.mCurrSphereCoord);
              //rotationAxis is the crossproduct between the two resultant vectors (normalized)
              Vector3 rotationAxis = this.mPrevSphereCoord.clone();
              rotationAxis.cross(this.mCurrSphereCoord);
              rotationAxis.normalize();
              //rotationAngle is the acos of the vectors' dot product
              double rotationAngle = Math.acos(Math.min(1.0D, this.mPrevSphereCoord.dot(this.mCurrSphereCoord)));
              //creates a quaternion using rotantionAngle and rotationAxis (normalized)
  
              this.mCurrentOrientation.fromAngleAxis(rotationAxis, MathUtil.radiansToDegrees(rotationAngle));
              this.mCurrentOrientation.normalize();
              //accumulates start and current orientation in mEmpty object
              Quaternion q = new Quaternion(this.mStartOrientation);
              q.multiply(this.mCurrentOrientation);
              double orientacionX = q.angleBetween(new Quaternion(0f,0f,1f,0f));
              this.mEmpty.setOrientation(q);
          }
  
      }
  
          /** adds the basic listeners to the camera*/
      private void addListeners() {
          //runs this on the ui thread
          ((Activity)this.mContext).runOnUiThread(new Runnable() {
              public void run() {
                  //sets a gesture detector (touch)
                  mArcballCamera.this.mDetector = new GestureDetector(mArcballCamera.this.mContext, mArcballCamera.this.new GestureListener());
                  //sets a scale detector (zoom)
                  mArcballCamera.this.mScaleDetector = new ScaleGestureDetector(mArcballCamera.this.mContext, mArcballCamera.this.new ScaleListener());
                  //sets a touch listener
                  mArcballCamera.this.mGestureListener = new View.OnTouchListener() {
                      public boolean onTouch(View v, MotionEvent event) {
                          //sees if it is a scale event
                          mArcballCamera.this.mScaleDetector.onTouchEvent(event);
                          if(!mArcballCamera.this.mIsScaling) {
                              //if not, delivers the event to the movement detector to start rotation
                              mArcballCamera.this.mDetector.onTouchEvent(event);
                              if(event.getAction() == 1 &amp;&amp; mArcballCamera.this.mIsRotating) {
                                  //ends the rotation if the event ended
                                  mArcballCamera.this.endRotation();
                                  mArcballCamera.this.mIsRotating = false;
                              }
                          }
  
                          return true;
                      }
                  };
                  //sets the touch listener
                  mArcballCamera.this.mView.setOnTouchListener(mArcballCamera.this.mGestureListener);
              }
          });
      }
  
          /*gesture listener*/
      private class GestureListener extends GestureDetector.SimpleOnGestureListener {
          private GestureListener() {
          }
  
          public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY) {
              if(!mArcballCamera.this.mIsRotating) {
                  mArcballCamera.this.originalX=event2.getX();
                  mArcballCamera.this.originalY=event2.getY();
                  mArcballCamera.this.startRotation(mArcballCamera.this.mLastWidth/2, mArcballCamera.this.mLastHeight/2); //0,0 es la esquina superior izquierda. Buscar centro camara en algun lugar
                  return false;
              } else {
                  float x =  Math.abs((mArcballCamera.this.originalX - event2.getX() + mArcballCamera.this.mLastWidth/2)%mArcballCamera.this.mLastWidth);
                  float y = Math.abs((mArcballCamera.this.originalY - event2.getY() + mArcballCamera.this.mLastHeight/2)%mArcballCamera.this.mLastHeight);
                  mArcballCamera.this.mIsRotating = true;
                  mArcballCamera.this.updateRotation(x, y);
                  return false;
              }
          }
      }
  </code></pre>
  
  <p>If you want to see it on your own, here is the <a href="https://github.com/SMalpica/RajawaliSampleProject" rel="nofollow">Github repository</a> of the project. I've tested it on a Samsung Galaxy Tab4 and an Xperia Z mostly.</p>
  
  <p>If you see the code, I also tried to change the rotation by moving the start point to the screen center in the <code>onScroll</code> method of the gesture listener, but that didn't worked either (although changed the movement direction, which I also wanted to do) and left some unwanted side effects.</p>
  
  <p>So, <strong>is there a way to balance the sphere so that the northern pole always points up while the user can move around?</strong> Anything that can help me in the right direction will be welcomed.</p>
  
  <p>I'm working on Android Studio, using mostly Rajawali (a library built up on OpenGL ES 2.0)</p>
  
  <p>If you need some more information tell me and I'll edit the question. Also if the english is bad and didn't explained myself in a good way I'll try to do it better.</p>