private void changeMapX(float radians) {
    Geocentric lookVector = mUser.getLookDir();
    Geocentric crossVector = MathUtils.crossProduct(lookVector, mUser.getLookNormal());
    Geocentric deltaLookVector =
        new Geocentric(crossVector.x * radians, crossVector.y * radians, crossVector.z * radians);

    Geocentric newLookDir = MathUtils.add(lookVector, deltaLookVector);
    newLookDir = MathUtils.normalize(newLookDir);

    mUser.setLookDir(newLookDir);
  }
  private void rotate(float radians) {
    Geocentric lookVector = mUser.getLookDir();
    Geocentric upVector = mUser.getLookNormal();

    Matrix3x3 rotationMatrix = MathUtils.createRotationMatrix(radians, lookVector);
    Geocentric newUpVector = MathUtils.multiplyGeocentricAndMatrix3x3(rotationMatrix, upVector);

    newUpVector = MathUtils.normalize(newUpVector);

    mUser.setLookNormal(newUpVector);
  }
  private void changeMapY(float radians) {
    Geocentric lookVector = mUser.getLookDir();
    Geocentric upVector = mUser.getLookNormal();

    Geocentric deltaLookVector =
        new Geocentric(upVector.x * -radians, upVector.y * -radians, upVector.z * -radians);
    Geocentric newLookDir = MathUtils.add(lookVector, deltaLookVector);
    newLookDir = MathUtils.normalize(newLookDir);

    Geocentric deltaUpVector =
        new Geocentric(lookVector.x * radians, lookVector.y * radians, lookVector.z * radians);
    Geocentric newUpDir = MathUtils.add(upVector, deltaUpVector);
    newUpDir = MathUtils.normalize(newUpDir);

    mUser.setLookDir(newLookDir);
    mUser.setLookNormal(newUpDir);
  }
  private boolean processTouch(int action, MotionEvent e, boolean sensorMode) {
    switch (action) {
      case MotionEvent.ACTION_DOWN:
        if (mCurrentTouchState == TouchState.INIT) {
          mInitX = (int) e.getX();
          mInitY = (int) e.getY();
          if (sensorMode) {
            return true;
          }
          mCurrentTouchState = TouchState.DRAG_ONE;
          mPreviousX1 = e.getX();
          mPreviousY1 = e.getY();
          return true;
        }
      case MotionEvent.ACTION_POINTER_DOWN:
        if (sensorMode) {
          return true;
        }
        if (mCurrentTouchState == TouchState.DRAG_ONE) {
          mCurrentTouchState = TouchState.DRAG_TWO;
          mPointerIndex = e.getActionIndex();
          mPreviousX1 = e.getX();
          mPreviousY1 = e.getY();
          mPreviousX2 = e.getX(mPointerIndex);
          mPreviousY2 = e.getY(mPointerIndex);
          return true;
        }
      case MotionEvent.ACTION_MOVE:
        if (sensorMode) {
          return true;
        }
        if (mCurrentTouchState == TouchState.DRAG_ONE) {
          float currentX = e.getX();
          float currentY = e.getY();
          float deltaX = currentX - mPreviousX1;
          float deltaY = currentY - mPreviousY1;

          float radsPerPixel = mStarMapperRenderer.mFovYRad / mStarMapperRenderer.mScreenHeight;
          changeMapX(-deltaX * radsPerPixel);
          changeMapY(-deltaY * radsPerPixel);

          mStarMapperRenderer.mLookX = mUser.getLookX();
          mStarMapperRenderer.mLookY = mUser.getLookY();
          mStarMapperRenderer.mLookZ = mUser.getLookZ();
          mStarMapperRenderer.mUpX = mUser.getNormalX();
          mStarMapperRenderer.mUpY = mUser.getNormalY();
          mStarMapperRenderer.mUpZ = mUser.getNormalZ();

          mPreviousX1 = currentX;
          mPreviousY1 = currentY;
          return true;
        } else if (mCurrentTouchState == TouchState.DRAG_TWO) {
          float currentX1 = e.getX();
          float currentY1 = e.getY();
          float currentX2 = e.getX(mPointerIndex);
          float currentY2 = e.getY(mPointerIndex);
          float deltaX1 = currentX1 - mPreviousX1;
          float deltaY1 = currentY1 - mPreviousY1;
          float deltaX2 = currentX2 - mPreviousX2;
          float deltaY2 = currentY2 - mPreviousY2;

          float radsPerPixel = mStarMapperRenderer.mFovYRad / mStarMapperRenderer.mScreenHeight;
          changeMapX(-((deltaX1 + deltaX2) / 2) * radsPerPixel);
          changeMapY(-((deltaY1 + deltaY2) / 2) * radsPerPixel);

          float vPreviousX = mPreviousX1 - mPreviousX2;
          float vPreviousY = mPreviousY1 - mPreviousY2;
          float vCurrentX = currentX1 - currentX2;
          float vCurrentY = currentY1 - currentY2;

          /* Need a 'stretch' function here for field of view once zoom is implemented */
          float vectorRatio =
              (float)
                  Math.sqrt(
                      (vCurrentX * vCurrentX + vCurrentY * vCurrentY)
                          / (vPreviousX * vPreviousX + vPreviousY * vPreviousY));
          mStarMapperRenderer.mFovYRad =
              mZoomer.zoomBy(mStarMapperRenderer.mFovYRad, 1.0f / vectorRatio);
          mStarMapperRenderer.mUpdatePerspective = true;

          double anglePrevious = Math.atan2(vPreviousX, vPreviousY);
          double angleCurrent = Math.atan2(vCurrentX, vCurrentY);
          float angleDelta = (float) (anglePrevious - angleCurrent);

          rotate(angleDelta);

          mStarMapperRenderer.mLookX = mUser.getLookX();
          mStarMapperRenderer.mLookY = mUser.getLookY();
          mStarMapperRenderer.mLookZ = mUser.getLookZ();
          mStarMapperRenderer.mUpX = mUser.getNormalX();
          mStarMapperRenderer.mUpY = mUser.getNormalY();
          mStarMapperRenderer.mUpZ = mUser.getNormalZ();

          mPreviousX1 = currentX1;
          mPreviousY1 = currentY1;
          mPreviousX2 = currentX2;
          mPreviousY2 = currentY2;
          return true;
        }
      case MotionEvent.ACTION_UP:
        int last_X = (int) e.getX();
        int last_Y = (int) e.getY();
        int dx = Math.abs(last_X - mInitX);
        int dy = Math.abs(last_Y - mInitY);
        if (dx <= 20 && dy <= 20) {
          for (Label label : mStarMapperRenderer.mLabelManager.LabelSet) {
            int xll = label.screenPos_xll;
            int yll = label.screenPos_yll;
            int xur = label.screenPos_xur;
            int yur = label.screenPos_yur;
            LabelTypeEnum type = label.getType();
            if (label.isOnScreen()
                && xll <= mInitX
                && xur >= mInitX
                && yll <= mInitY
                && yur >= mInitY
                && type != LabelTypeEnum.GRID) {
              String name = label.getText().replaceAll(" ", "_");
              String URL = "http://en.wikipedia.org/wiki/" + name;
              if (type == LabelTypeEnum.CONSTELLATION
                  || type == LabelTypeEnum.STAR
                  || type == LabelTypeEnum.PLANET) {
                URL = URL + "_(" + label.getType().name().toLowerCase() + ")";
              }
              Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(URL));
              startActivity(browserIntent);
              // Toast.makeText(this, label.getText(), Toast.LENGTH_SHORT).show();
            }
          }
        }
        if (sensorMode) {
          return true;
        }
        if (mCurrentTouchState != TouchState.INIT) {
          mCurrentTouchState = TouchState.INIT;
          return true;
        }
      case MotionEvent.ACTION_POINTER_UP:
        if (sensorMode) {
          return true;
        }
        if (mCurrentTouchState == TouchState.DRAG_TWO) {
          mCurrentTouchState = TouchState.INIT;
        }
    }
    return false;
  }
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

    PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
    settingsFragment = new SettingsFragment();

    sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
    sharedPreferences.registerOnSharedPreferenceChangeListener(this);

    // Debug
    //		setContentView(R.layout.title);

    mGLSurfaceView = new MyGLSurfaceView(this);
    // Create an OpenGL ES 2.0 context
    mGLSurfaceView.setEGLContextClientVersion(2);
    mGLSurfaceView.setPreserveEGLContextOnPause(true);
    //		mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    setContentView(mGLSurfaceView);

    // The app user object
    mUser = new User();

    mStarMapperRenderer = new StarMapperRenderer(this, mUser);

    // Set the StarMapperRenderer for drawing on the GLSurfaceView
    mGLSurfaceView.setRenderer(mStarMapperRenderer);

    // Retrieving location
    mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    Criteria locCriteria = new Criteria();
    // gps
    locCriteria.setAccuracy(Criteria.ACCURACY_FINE);
    locCriteria.setBearingRequired(false);
    locCriteria.setAltitudeRequired(false);
    locCriteria.setCostAllowed(true);
    locCriteria.setSpeedRequired(false);
    locCriteria.setPowerRequirement(Criteria.POWER_LOW);

    String locProvider = mLocationManager.getBestProvider(locCriteria, true);
    Location loc = mLocationManager.getLastKnownLocation(locProvider);

    mUser.setGeoLocation((float) loc.getLatitude(), (float) loc.getLongitude());
    mUser.setGeomagneticField();
    mUser.setZenith();

    // Initializing touch variables
    mPreviousX1 = 0;
    mPreviousY1 = 0;
    mPreviousX2 = 0;
    mPreviousY2 = 0;
    mPointerIndex = 0;
    mCurrentTouchState = TouchState.INIT;
    mZoomer = new Zoom();

    // Initializing auto sensors
    mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
    mAccelerometerSensor = null;
    mMagneticFieldSensor = null;
    mAccelerometerModel = null;
    mMagneticFieldModel = null;
    // Check for necessary sensors
    if ((mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) == null)
        || (mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) == null)) {
      // phone doesn't have appropriate sensors, only use manual mode
      mUseAutoSensorMode = false;
    } else {
      // phone has sensors, initialize them
      mAccelerometerSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
      mMagneticFieldSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
      mUseAutoSensorMode = true;
      mAccelerometerModel = new AccelerometerModel(mUser, mStarMapperRenderer, mGLSurfaceView);
      mMagneticFieldModel = new MagneticFieldModel(mUser, mStarMapperRenderer, mGLSurfaceView);
    }

    if (mAccelerometerSensor != null) {
      mSensorManager.registerListener(
          mAccelerometerModel, mAccelerometerSensor, SensorManager.SENSOR_DELAY_FASTEST);
    }
    if (mMagneticFieldSensor != null) {
      mSensorManager.registerListener(
          mMagneticFieldModel, mMagneticFieldSensor, SensorManager.SENSOR_DELAY_FASTEST);
    }
    // ****** DEBUG ******
    // mUseAutoSensorMode = false;

    // flinger
    flinger =
        new GestureDetector(
            this,
            new Flinger(
                new FlingListener() {
                  public void fling(float distanceX, float distanceY) {
                    float radsPerPixel =
                        mStarMapperRenderer.mFovYRad / mStarMapperRenderer.mScreenHeight;
                    changeMapX(-distanceX * radsPerPixel);
                    changeMapY(-distanceY * radsPerPixel);

                    mStarMapperRenderer.mLookX = mUser.getLookX();
                    mStarMapperRenderer.mLookY = mUser.getLookY();
                    mStarMapperRenderer.mLookZ = mUser.getLookZ();
                    mStarMapperRenderer.mUpX = mUser.getNormalX();
                    mStarMapperRenderer.mUpY = mUser.getNormalY();
                    mStarMapperRenderer.mUpZ = mUser.getNormalZ();
                  }
                }));
  }
  @Override
  public void onSensorChanged(SensorEvent event) {
    // smoothing code
    for (int i = 0; i < 3; ++i) {
      last[i] = current[i];
      float diff = event.values[i] - last[i];
      float correction = diff * alpha;
      for (int j = 1; j < exponent; ++j) {
        correction *= Math.abs(diff);
      }
      if (correction > Math.abs(diff) || correction < -Math.abs(diff)) {
        correction = diff;
      }
      current[i] = last[i] + correction;
    }

    mNewAccelerationReading.x = -current[0];
    mNewAccelerationReading.y = -current[1];
    mNewAccelerationReading.z = -current[2];

    mUser.setAcceleration(mNewAccelerationReading);

    Matrix3x3 phoneSpaceMatrix = mUser.getLocalNorthAndUpMatrix_PhoneSpace();
    Matrix3x3 celestialSpaceMatrix = mUser.getLocalNorthAndUpMatrix_CelestialSpace();
    Matrix3x3 viewTransform = MathUtils.multiplyMatrices(celestialSpaceMatrix, phoneSpaceMatrix);

    Geocentric lookVector = MathUtils.multiplyGeocentricAndMatrix3x3(viewTransform, Z_DOWN_VECTOR);
    Geocentric upVector = MathUtils.multiplyGeocentricAndMatrix3x3(viewTransform, INIT_UP_VECTOR);

    //		Log.d("AccelerometerModel", "INSIDE ACCELEROMETER SENSORCHANGED");

    //		Log.d("AccelerometerModel", "ps_xx: " + String.valueOf(phoneSpaceMatrix.xx) + " ps_xy: " +
    // String.valueOf(phoneSpaceMatrix.xy) + " ps_xz: " + String.valueOf(phoneSpaceMatrix.xz));
    //		Log.d("AccelerometerModel", "ps_yx: " + String.valueOf(phoneSpaceMatrix.yx) + " ps_yy: " +
    // String.valueOf(phoneSpaceMatrix.yy) + " ps_yz: " + String.valueOf(phoneSpaceMatrix.yz));
    //		Log.d("AccelerometerModel", "ps_zx: " + String.valueOf(phoneSpaceMatrix.zx) + " ps_zy: " +
    // String.valueOf(phoneSpaceMatrix.zy) + " ps_zz: " + String.valueOf(phoneSpaceMatrix.zz));

    //		Log.d("AccelerometerModel", "vt_xx: " + String.valueOf(viewTransform.xx) + " vt_xy: " +
    // String.valueOf(viewTransform.xy) + " vt_xz: " + String.valueOf(viewTransform.xz));
    //		Log.d("AccelerometerModel", "vt_yx: " + String.valueOf(viewTransform.yx) + " vt_yy: " +
    // String.valueOf(viewTransform.yy) + " vt_yz: " + String.valueOf(viewTransform.yz));
    //		Log.d("AccelerometerModel", "vt_zx: " + String.valueOf(viewTransform.zx) + " vt_zy: " +
    // String.valueOf(viewTransform.zy) + " vt_zz: " + String.valueOf(viewTransform.zz));

    //		Log.d("AccelerometerModel", "event.values[0]: " + String.valueOf(event.values[0]) + "
    // event.values[1]: " + String.valueOf(event.values[1] + " event.values[2]: " +
    // String.valueOf(event.values[2])));

    mUser.lookDir = lookVector;
    mUser.lookNormal = upVector;

    mRenderer.mLookX = mUser.getLookX();
    mRenderer.mLookY = mUser.getLookY();
    mRenderer.mLookZ = mUser.getLookZ();
    mRenderer.mUpX = mUser.getNormalX();
    mRenderer.mUpY = mUser.getNormalY();
    mRenderer.mUpZ = mUser.getNormalZ();

    //		Log.d("AccelerometerModel", "lastX: " + String.valueOf(last[0]) + " lastY: " +
    // String.valueOf(last[1]) + " lastZ: " + String.valueOf(last[2]));
    //		Log.d("AccelerometerModel", "currentX: " + String.valueOf(current[0]) + " currentY: " +
    // String.valueOf(current[1]) + " currentZ: " + String.valueOf(current[2]));
    //		Log.d("AccelerometerModel", "mLookX: " + String.valueOf(mUser.getLookX()) + " mLookY: " +
    // String.valueOf(mUser.getLookY()) + " mLookZ: " + String.valueOf(mUser.getLookZ()));
    //		Log.d("AccelerometerModel", "mUpX: " + String.valueOf(mUser.getNormalX()) + " mUpY: " +
    // String.valueOf(mUser.getNormalY()) + " mUpZ: " + String.valueOf(mUser.getNormalZ()));

    // mGLSurfaceView.requestRender();
  }