Set orientation of a 3D model using quaternions

I’m working on an equivalent of bunnyrotate.

I’m getting the stream of orientation data in quaternions, but I can’t figure out how to rotate a model I loaded with ofxAssimpModelLoader object using quaternions.

Am I supposed to convert the model into some other data representation? ofNode for example seems to have a suitable setOrientation(const glm::quat &q) function, but I’m not sure if it’s the right object for a 3D model.

Or is there some way to rotate the ofxAssimpModelLoader using quaternions directly?

Did it with:

ofRotateRad(2 * glm::acos(w), x, y, z);

Inside the ofApp::draw().

ofxAssimpModelLoader loads a 3d model. A 3d model in OF is an ofMesh, and ofNode and an ofMaterial.
The position and rotation of that model are governed by its ofNode. In the examples folder there is an example with ofNode.
You can rotate your ofNode using angles or using quaternions.
There is a nice tutorial about rotations using GLM http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-17-quaternions/

1 Like

I’m actually having trouble making the rotation correct.

Example:

Illustrated is the global coordinate system at (0, 0, 0) and a model with it’s coordinate system at (200, 0, 0).

In real life the model is rotated around two axes: yaw (Y, green), pitch (Z, blue). In OF, the model is rotated around the Y correctly, but when it comes to Z, it seems that the model is rotated around the global coordinate system instead of the local, effectively adding a rotation around the X as well.

If the rotation around Z is removed, everything looks correct.

Same data is displayed in a web visualizer using quaternions correctly.

I’ve tried both:

ofRotateRad(2 * glm::acos(w), x, y, z);

and

ofMatrix4x4 rotation_matrix = ofMatrix4x4::newRotationMatrix(2 * glm::acos(w), ofVec3f(x, y, z));
ofMultMatrix(rotation_matrix);

and they seem completely the same.

Here’s the ofApp.cpp:

void ofApp::drawXYZ(glm::vec3 &position, ofQuaternion &orientation) {
	ofPushMatrix();
	light.enable();
	cam.begin();

	ofDisableDepthTest();

	ofTranslate(position);

	float angle = 2 * glm::acos(orientation.w());
	ofRotateRad(angle, orientation.x(), orientation.y(), orientation.z());

	glm::vec3 origin(0, 0, 0);
	glm::vec3 x_unit(100, 0, 0);
	glm::vec3 y_unit(0, 100, 0);
	glm::vec3 z_unit(0, 0, 100);
	
	ofSetLineWidth(3);

	ofSetColor(255, 0, 0);
	ofDrawLine(origin, x_unit);

	ofSetColor(0, 255, 0);
	ofDrawLine(origin, y_unit);

	ofSetColor(0, 0, 255);
	ofDrawLine(origin, z_unit);

	cam.end();
	light.disable();
	ofPopMatrix();
}

void ofApp::drawBreadboard(glm::vec3 &position, ofQuaternion &orientation) {
	ofPushMatrix();
	light.enable();
	cam.begin();
	
	ofEnableDepthTest();
	
	ofTranslate(position);

	float angle = 2 * glm::acos(orientation.w());
	ofRotateRad(angle, orientation.x(), orientation.z(), orientation.y());

	breadboard.drawFaces();

	cam.end();
	light.disable();
	ofPopMatrix();
}

void ofApp::draw() {
	glm::vec3 breadboard_position(200, 0, 0);
	drawBreadboard(breadboard_position, breadboard_quat);
	drawXYZ(breadboard_position, breadboard_quat);

	glm::vec3 center_position(0, 0, 0);
	ofQuaternion n(0, 0, 0, -1);
	drawXYZ(center_position, n);
}

https://openframeworks.cc/documentation/3d/ofNode/

openFrameworks\examples\3d\3DModelLoaderExample\src

1 Like

I’ve simplified the code as much as possible. I’m still experiencing a problem where the box seems to be rotating about the wrong (global) axis, instead of the local one.

com-optimize (1)

ofApp.cpp:

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup() {
	ofBackground(255, 255, 255, 0);

	ofSetVerticalSync(true);

	light.setPosition(0, 2000, 0);
	light.setAmbientColor(ofFloatColor(1.0f, 1.0f, 1.0f));

	cam.setDistance(100);

	box.set(20);
}

//--------------------------------------------------------------
void ofApp::draw() {
	if (rotating) {
		pitch++;
	}
	
	ofVec3f euler{ ofDegToRad(pitch), ofDegToRad(yaw), ofDegToRad(roll) };
	ofQuaternion of_quat{ glm::quat{euler} };

	ofVec3f position{ 0, 0, 0 };
	drawCube(position, of_quat);
	drawXYZ(position, of_quat, 3);
	drawXYZ();
}

void ofApp::drawCube(ofVec3f &position, ofQuaternion &orientation) {
	ofPushMatrix();
	light.enable();
	cam.begin();

	ofEnableDepthTest();

	ofTranslate(position);

	float quat_angle;
	ofVec3f quat_vec;
	orientation.getRotate(quat_angle, quat_vec);
	ofRotateDeg(quat_angle, quat_vec.x, quat_vec.z, quat_vec.y);

	ofNoFill();
	ofSetColor(0, 0, 0);
	ofSetLineWidth(3);
	box.drawWireframe();

	cam.end();
	light.disable();
	ofPopMatrix();
}

void ofApp::drawXYZ(ofVec3f &position, ofQuaternion &orientation, uint32_t line_width) {
	ofPushMatrix();
	light.enable();
	cam.begin();

	ofDisableDepthTest();

	ofTranslate(position);

	float quat_angle;
	ofVec3f quat_vec;
	orientation.getRotate(quat_angle, quat_vec);
	ofRotateDeg(quat_angle, quat_vec.x, quat_vec.z, quat_vec.y);

	ofVec3f origin(0, 0, 0);
	ofVec3f x_unit(10, 0, 0);
	ofVec3f y_unit(0, 10, 0);
	ofVec3f z_unit(0, 0, 10);

	ofSetLineWidth(line_width);

	ofSetColor(255, 0, 0);
	ofDrawLine(origin, x_unit);

	ofSetColor(0, 255, 0);
	ofDrawLine(origin, y_unit);

	ofSetColor(0, 0, 255);
	ofDrawLine(origin, z_unit);

	cam.end();
	light.disable();
	ofPopMatrix();
}

void ofApp::keyReleased(int key) {
	rotating = !rotating;
}

ofApp.h:

#pragma once

#include "ofMain.h"

class ofApp : public ofBaseApp{

	public:
		void setup();
		void update();
		void draw();
		void drawCube(ofVec3f &position = ofVec3f{ 0, 0, 0 }, ofQuaternion &orientation = ofQuaternion{ 0, 0, 0, -1 });
		void drawXYZ(ofVec3f &position = ofVec3f{ 0, 0, 0 }, ofQuaternion &orientation = ofQuaternion{ 0, 0, 0, -1 }, uint32_t line_width = 1);
		void keyReleased(int key);
		
		ofLight	light;
		ofEasyCam cam;

		ofBoxPrimitive box;

		float roll = 25;
		float pitch = 0;
		float yaw = 0;

		bool rotating = false;
};

Got it to rotate about the X-axis correctly, still no luck with Y and Z :slight_smile:

com-optimize (2) com-crop (2) com-crop (3)

Modified to use a matrix to rotate:

ofMatrix4x4 rotation_matrix = ofMatrix4x4::newRotationMatrix(orientation);
ofMultMatrix(rotation_matrix);

instead of

float quat_angle;
ofVec3f quat_vec;
orientation.getRotate(quat_angle, quat_vec);
ofRotateDeg(quat_angle, quat_vec.x, quat_vec.z, quat_vec.y);

What am I missing?

Full code:
ofApp.c:

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup() {
	ofBackground(255, 255, 255, 0);

	ofSetVerticalSync(true);

	light.setPosition(0, 2000, 0);
	light.setAmbientColor(ofFloatColor(1.0f, 1.0f, 1.0f));

	cam.setDistance(100);

	box.set(20);
}

//--------------------------------------------------------------
void ofApp::draw() {
	if (rotating) {
		euler.x++;
	}

	ofVec3f euler_rad{ ofDegToRad(euler.x), ofDegToRad(euler.y), ofDegToRad(euler.z) };
	ofQuaternion of_quat{ glm::quat{euler_rad} };

	ofVec3f position{ 0, 0, 0 };
	drawCube(position, of_quat);
	drawXYZ(position, of_quat, 3);
	drawXYZ();
}

void ofApp::drawCube(ofVec3f &position, ofQuaternion &orientation) {
	ofPushMatrix();
	light.enable();
	cam.begin();

	ofEnableDepthTest();

	ofTranslate(position);

	ofMatrix4x4 rotation_matrix = ofMatrix4x4::newRotationMatrix(orientation);
	ofMultMatrix(rotation_matrix);

	ofNoFill();
	ofSetColor(0, 0, 0);
	ofSetLineWidth(3);
	box.drawWireframe();

	cam.end();
	light.disable();
	ofPopMatrix();
}

void ofApp::drawXYZ(ofVec3f &position, ofQuaternion &orientation, uint32_t line_width) {
	ofPushMatrix();
	light.enable();
	cam.begin();

	ofDisableDepthTest();

	ofTranslate(position);

	ofMatrix4x4 rotation_matrix = ofMatrix4x4::newRotationMatrix(orientation);
	ofMultMatrix(rotation_matrix);

	ofVec3f origin(0, 0, 0);
	ofVec3f x_unit(10, 0, 0);
	ofVec3f y_unit(0, 10, 0);
	ofVec3f z_unit(0, 0, 10);

	ofSetLineWidth(line_width);

	ofSetColor(255, 0, 0);
	ofDrawLine(origin, x_unit);

	ofSetColor(0, 255, 0);
	ofDrawLine(origin, y_unit);

	ofSetColor(0, 0, 255);
	ofDrawLine(origin, z_unit);

	cam.end();
	light.disable();
	ofPopMatrix();
}

void ofApp::keyReleased(int key) {
	rotating = !rotating;
}

ofApp.h:

#pragma once

#include "ofMain.h"

class ofApp : public ofBaseApp{

	public:
		void setup();
		void update();
		void draw();
		void drawCube(ofVec3f &position = ofVec3f{ 0, 0, 0 }, ofQuaternion &orientation = ofQuaternion{ 0, 0, 0, -1 });
		void drawXYZ(ofVec3f &position = ofVec3f{ 0, 0, 0 }, ofQuaternion &orientation = ofQuaternion{ 0, 0, 0, -1 }, uint32_t line_width = 1);
		void keyReleased(int key);
		
		ofLight	light;
		ofEasyCam cam;

		ofBoxPrimitive box;
		ofVec3f euler{ 0, 25, 0 };

		bool rotating = false;
};
1 Like

Ok, I figured it out. I had quaternions completely wrong.
Same problem as this person – https://stackoverflow.com/questions/9715776/using-quaternions-for-opengl-rotations

com-crop (4)

Working code:

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup() {
	ofBackground(255, 255, 255, 0);

	ofSetVerticalSync(true);

	light.setPosition(0, 2000, 0);
	light.setAmbientColor(ofFloatColor(1.0f, 1.0f, 1.0f));

	cam.setDistance(50);

	box.set(20);
}

//--------------------------------------------------------------
void ofApp::draw() {
	ofVec3f axis{ 1, 1, 1 };
	axis.normalize();
	float angle_deg = 1;
	ofQuaternion rotation(angle_deg, axis);
	orientation = orientation * rotation;

	ofVec3f position{ 0, 0, 0 };
	drawCube(position, orientation);
	drawXYZ(position, orientation, 3);
	drawXYZ();
}

void ofApp::drawCube(ofVec3f &position, ofQuaternion &orientation) {
	ofPushMatrix();
	light.enable();
	cam.begin();

	ofEnableDepthTest();

	ofTranslate(position);

	ofMatrix4x4 rotation_matrix = ofMatrix4x4::newRotationMatrix(orientation);
	ofMultMatrix(rotation_matrix);

	ofNoFill();
	ofSetColor(0, 0, 0);
	ofSetLineWidth(3);
	box.drawWireframe();

	cam.end();
	light.disable();
	ofPopMatrix();
}

void ofApp::drawXYZ(ofVec3f &position, ofQuaternion &orientation, uint32_t line_width) {
	ofPushMatrix();
	light.enable();
	cam.begin();

	ofDisableDepthTest();

	ofTranslate(position);

	ofMatrix4x4 rotation_matrix = ofMatrix4x4::newRotationMatrix(orientation);
	ofMultMatrix(rotation_matrix);

	ofVec3f origin(0, 0, 0);
	ofVec3f x_unit(10, 0, 0);
	ofVec3f y_unit(0, 10, 0);
	ofVec3f z_unit(0, 0, 10);

	float angle{};
	ofVec3f axis{};
	orientation.getRotate(angle, axis);
	axis.scale(10);

	ofSetLineWidth(line_width);

	ofSetColor(255, 0, 0);
	ofDrawLine(origin, x_unit);

	ofSetColor(0, 255, 0);
	ofDrawLine(origin, y_unit);

	ofSetColor(0, 0, 255);
	ofDrawLine(origin, z_unit);

	if (angle != 0 and angle != 360) {
		ofSetLineWidth(line_width * 1.5);

		ofSetColor(255, 0, 255);
		ofDrawLine(origin, axis);
	}

	cam.end();
	light.disable();
	ofPopMatrix();
}

void ofApp::keyReleased(int key) {
	rotating = !rotating;
}
#pragma once

#include "ofMain.h"

class ofApp : public ofBaseApp{

	public:
		void setup();
		void update();
		void draw();
		void drawCube(ofVec3f &position = ofVec3f{ 0, 0, 0 }, ofQuaternion &orientation = ofQuaternion{ 0, 0, 0, -1 });
		void drawXYZ(ofVec3f &position = ofVec3f{ 0, 0, 0 }, ofQuaternion &orientation = ofQuaternion{ 0, 0, 0, -1 }, uint32_t line_width = 1);
		void keyReleased(int key);
		
		ofLight	light;
		ofEasyCam cam;

		ofBoxPrimitive box;
		ofQuaternion orientation{ 0, 0, 0, -1 };

		bool rotating = true;
};
1 Like

Nice. You can take a look also into ofNode methods too because usually it helps a lot.

Oh and I wanted to mention that my exercise with simplifying the problem was a complete wash for the base case of setting the model’s orientation with quaternions.

My problem was that the X and Y of the sensor were swapped in the model. You’d think that would just swap the rotations 90deg, but what happened instead was that the model had a rotation component about the Z as well, making it seem as if it was rotating about the global CS.

I guess I have more to learn about the quaternions.

1 Like