Best way to convert between euler angles and quaternions, avoiding gimbal lock?

I’m having trouble with finding a good way to convert between quaternion rotations and euler angles without gimbal lock issues.

I’m trying to expose GUI controls for an ofNode - the GUI shows euler angles for intuitive control, but the node calculates the rotations internally using quaternions.

This works perfectly with X and Z euler rotations of any degree, but my Y axis rotation is limited -90 to 90 degrees before it starts spazzing out:

I’m assuming this is a gimbal lock issue?
Any suggestions on how to avoid this, and get full rotation controls on all axes?

My euler/quaternion conversion functions are:

glm::vec3 euler = node.getOrientationEulerDeg();    // uses glm::eulerAngles( quaternion ) under the hood
glm::quat orientation( glm::vec3( ofDegToRad( euler.x ), ofDegToRad( euler.y ), ofDegToRad( euler.z ) ) );

Here’s the full code for above example:

#pragma once

#include "ofMain.h"
#include "ofxGui.h"

class ofApp : public ofBaseApp
{

public:
	void setup()
	{
		ofBackground( 0 );
		gui.setup( "controls" );
		gui.add( eulerAngles );
		eulerAngles.addListener(this, &ofApp::setNodeOrientation);
	}

	void update()
	{
		glm::vec3 euler = node.getOrientationEulerDeg();
		eulerAngles.setWithoutEventNotifications( euler );
	}

	void draw()
	{
		cam.begin();
		node.transformGL();
		ofNoFill();
		ofDrawBox( 300 );
		ofDrawAxis( 100 );
		ofFill();
		node.restoreTransformGL();
		cam.end();

		gui.draw();
	}
	void setNodeOrientation( glm::vec3& euler )
	{
		glm::quat orientation( glm::vec3( ofDegToRad( euler.x ), ofDegToRad( euler.y ), ofDegToRad( euler.z ) ) );
		node.setOrientation(orientation);
	}

	ofxPanel gui;
	ofParameter<glm::vec3> eulerAngles{ "euler", glm::vec3( 0 ), glm::vec3( -180 ), glm::vec3( 180 ) };
	ofNode node;
	ofEasyCam cam;
};

1 Like

Hi, it seems to be a problem related to how the conversion happens rather than gimbal lock


The following seems to work for your example code

#pragma once

#include "ofMain.h"
#include "ofxGui.h"

class ofApp : public ofBaseApp
{

public:
	void setup()
	{
		ofBackground( 0 );
		gui.setup( "controls" );
		gui.add( eulerAngles );
		eulerAngles.addListener(this, &ofApp::setNodeOrientation);
	}

	void update()
	{
//		glm::vec3 euler = node.getOrientationEulerDeg();
//		eulerAngles.setWithoutEventNotifications( euler );
	}

	void draw()
	{
		cam.begin();
		node.transformGL();
		ofNoFill();
		ofDrawBox( 300 );
		ofDrawAxis( 100 );
		ofFill();
		node.restoreTransformGL();
		cam.end();

		gui.draw();
	}
	void setNodeOrientation( glm::vec3& euler )
	{
//
		glm::quat QuatAroundX = glm::angleAxis(ofDegToRad(euler.x), glm::vec3(1.0,0.0,0.0) );
		glm::quat QuatAroundY = glm::angleAxis(ofDegToRad(euler.y), glm::vec3(0.0,1.0,0.0) );
		glm::quat QuatAroundZ = glm::angleAxis(ofDegToRad(euler.z), glm::vec3(0.0,0.0,1.0) );
		glm::quat orientation = QuatAroundX * QuatAroundY * QuatAroundZ;
		
//		glm::quat orientation( glm::vec3( ofDegToRad( euler.x ), ofDegToRad( euler.y ), ofDegToRad( euler.z ) ) );
		node.setOrientation(orientation);
	}

	ofxPanel gui;
	ofParameter<glm::vec3> eulerAngles{ "euler", glm::vec3( 0 ), glm::vec3( -180 ), glm::vec3( 180 ) };
	ofNode node;
	ofEasyCam cam;
};
3 Likes