Firts of all want to say that I've already searched for other people's answers here as same as Google and didn't found anything about it there. Also tried some others arcball implementations but none of them worked as good as the one using at this moment or use older OGL versions.

So I'm following this github page code by subokita to implement an arcball camera myself and got to the point where it works and noticed that whenever you release the mouse and click again the view comes back to the start position. I know that it happens because of the model rotation matrix obtained from prevPos and currPos comes back to the start whenever you click again since prevPos changes but can't see a clear solution to preserve the previous state without messing up the view.

I tried moving the model to the camera class and having two variables for it that gets exchanged depending on the mouse action as seen in the code below so that it calculate the state over a previous model value when dragging and use the result of it when no dragging being made. But even if it works it produces weird frames at the beggining of the cursor redrag(video) even though it success on preserving the previous state and I have no idea why.


// Arcball.h > glm::mat4 model, smodel = glm::mat4(1.0);
glm::mat4 Arcball::createModelRotationMatrix(glm::mat4& view_matrix) {
    switch (mouseEvent)
    case 0:
        return model = smodel;
    case 2:
        glm::vec3 axis = glm::inverse(glm::mat3(view_matrix)) * camAxis;
        return smodel = glm::rotate(glm::degrees(angle) * rollSpeed, axis) * model;


glm::mat4 rotated_model = arcball.createModelRotationMatrix(view);
glm::mat4 mvp = projection * view * rotated_model;

How can I modify this arcball to preserve the rotation when a redrag occurs?

Thanks for your time.


What troubles me is you're not using quaternions for an Arcball camera.

My answer comes from some of my DirectX code so if you see some strange behavior, I probably messed it up for OpenGL ;)

  1. it is far more simple to use quaternions, even if that sounds barbarian to you, it is easier to wrap ones mind around quaternions than matrices (of course in the end you will convert that quaternion into a matrix to use with opengl)

    The quaternion basically define an axe and an angle around that axe, there is nothing special to manage unlike when you're using Euler angles (ok there is one problematic case only, which you will see in the code).

  2. Quaternions interpolate nicely if you want to switch from one viewpoint to another

  3. camera updates /computation are faster using quaternion (only 4 values instead of 16) but i guess most people will say it is inconsequent (but i don't like wasting cpu)

Some basic implementation: (note that I access one of my settings here that allow or not for totally free camera rotation, test with and without, to your taste)

Quaternion QuaternionFromVector(const Vec3& Vec1, const Vec3& Vec2)
    float norm_u_norm_v = (float)sqrt(Vec3Dot(Vec1, Vec1) * Vec3Dot(Vec2, Vec2));
    float real_part = norm_u_norm_v + Vec3Dot(Vec1, Vec2);
    Vec3 w;

    if (real_part < 0.000001f * norm_u_norm_v)
        // If u and v are exactly opposite, rotate 180 degrees
        // around an arbitrary orthogonal axis. Axis normalisation
        // can happen later, when we normalise the quaternion.
        real_part = 0.0f;
        w = abs(Vec1.x) > abs(Vec1.z) ? Vec3(-Vec1.y, Vec1.x, 0.0f)
            : Vec3(0.0f, -Vec1.z, Vec1.y);
    } else
        // build quaternion the standard way. 
        w = Vec3Cross(Vec1, Vec2);

    Quaternion Quat = QuaternionInit(w, real_part);
    return Quat;

    class Camera3D

        int ViewWidth;
        int ViewHeight;
        float Distance = -500.0f; // set default distance here
        float ZNear = 1.0f; // closest view distance to focal point
        float ZFar = 30000.0f; // farthest view distance to focal point

        Vec3 VecPosition = { 0.0f, 0.0f, 0.0f };
        Vec3 VecFocalPoint = { 0.0f, 0.0f, 0.0f };
        Vec3 VecUp = { 0.0f, 0.0f, 1.0f };

        float TranslateH = 0.0f;
        float TranslateV = 0.0f;
        Vec3 CurrentTranslation = { 0.0f, 0.0f, 0.0f };
        Vec3 TrackingOffset = { 0.0f, 0.0f, 0.0f }; // camera's default tracking offset (typically, the center of the object)

        float AngleH = 0.0f;
        float AngleV = 0.0f;
        Quaternion OrientationQuaternion = { 0.0f, 0.0f, 0.0f, 1.0f };


        Camera3D(int ExplicitViewWidth, int ExplicitViewHeight)
            // view width and height of the scene window, in pixels

            ViewWidth = ExplicitViewWidth;
            ViewHeight = ExplicitViewHeight;

            // reset the camera
            VecPosition = { 0.0f, Distance, 0.0f };

        void Resize(int ExplicitViewWidth, int ExplicitViewHeight)
            // call this when the scene window resizes

            ViewWidth = max(64, ExplicitViewWidth);
            ViewHeight = max(64, ExplicitViewHeight);

        void Zoom(float ZoomFactor)
            // zoom in/out of the scene. zoom factor as a magnitude factor. E.g "2" halves the distance to focal point.

            Distance = min(max(Distance * (1.0f / ZoomFactor), -20000.0f), 0.0f);

        void TranslateCam(float HorizontalShift, float VerticalShift)
            // translates the camera horizontally or vertically

            TranslateH += HorizontalShift;
            TranslateV += VerticalShift;

        void RotateCam(float HorizontalShift, float VerticalShift)
            // rotates the camera horizontally or vertically

            AngleH += HorizontalShift;
            AngleV += VerticalShift;

        Matrix GetViewMatrix()
            // computes and returns matrix of the camera

            Vec3 VecDistance = Vec3(0.0f, Distance, 0.0f);
            Vec3 PrePos = Vec3TransformCoord(VecDistance, FLT_MatrixRotationQuaternion(OrientationQuaternion));

            // recompute the up and right vector
            Vec3 VecDir = Vec3Normalize(PrePos - VecFocalPoint);
            Vec3 VecRight = Vec3Normalize(Vec3Cross(VecUp, VecDir));

            VecUp = Vec3(0.000001f, 0.000001f, 1.0f); // cheat
            Vec3 RealVecUp = Vec3Normalize(Vec3Cross(VecDir, VecRight));

            if (!Settings::Inst().getUserSettings()->getBoolValue("NoCameraRoll"))
                VecUp = RealVecUp;

            // has a camera rotation been requested ?
            if ((AngleH != 0.0f) || (AngleV != 0.0f))
                // compute both angles and modify the orientation
                OrientationQuaternion *= QuaternionFromVector(VecUp + VecDir * -AngleV, VecUp);
                OrientationQuaternion *= QuaternionFromVector(VecDir + VecRight * -AngleH, VecDir);
                QuaternionNormalize(OrientationQuaternion);// renormalize

                AngleH = 0.0f; // rotation order was processed, clear the variables
                AngleV = 0.0f;

            // has a camera translation been requested ?
            if (TranslateH != 0.0f || TranslateV != 0.0f)
                // translate depending on the current orientation, i.e. adjust the CurrentTranslation vector depending on VecUp and VecRight
                CurrentTranslation += VecRight * TranslateH;
                CurrentTranslation += RealVecUp * TranslateV;

                TranslateH = 0.0f; // translation order was processed, clear the variables
                TranslateV = 0.0f;

            // transform the position with the new orientation
            VecPosition = Vec3TransformCoord(VecDistance, MatrixRotationQuaternion(OrientationQuaternion));

            // and return the view matrix
            return MatrixLookAtRH(VecPosition + CurrentTranslation + TrackingOffset, VecFocalPoint + CurrentTranslation + TrackingOffset, VecUp);

        Matrix GetProjectionMatrix()
            // returns the projection matrix of the camera

            return MatrixPerspectiveFovRH(Constants::Pi / 4.0f, (float)ViewWidth / (float)ViewHeight, ZNear, ZFar);

Edit: added missing function, cleaned the code a bit.

  • \$\begingroup\$ @Chaotikmind What is HorizontalShift & VerticalShift? \$\endgroup\$ – Onelio Jul 31 '19 at 11:45
  • \$\begingroup\$ It is the vert and horiz rotation; the wording is probably weird i agree, Those two variables were meant to be changed by mouse, hene you need to apply a correct ratio to the mouse movement before sending it here (to your taste) \$\endgroup\$ – Chaotikmind Jul 31 '19 at 22:21
  • \$\begingroup\$ I just realized that i maybe should have provided the QuaternionFromVector function ? or is it clear enough? \$\endgroup\$ – Chaotikmind Jul 31 '19 at 22:41
  • \$\begingroup\$ Provide it, if not for me for any future person searching the same thing. \$\endgroup\$ – Onelio Aug 1 '19 at 8:41
  • \$\begingroup\$ Ok i'm gonna revise the code anyway to make it clearer and more complete \$\endgroup\$ – Chaotikmind Aug 2 '19 at 9:29

Your Answer

