I have been stuck on this issue for a long time and would really like to get my head around it, I hope someone can help…

I have a sphere and would like it to spin left and right when dragged left and right with the mouse, and up and down when dragged up and down with the mouse.

I have it all working except:

In the initial position, I can use ofRotateX(); to give the impression of the sphere rolling up/down.
BUT - If I spin the sphere left by 90 degrees (i.e ofRotateY(90) Now I can no longer use ofRotateX to spin the sphere up/down since the axis has shifted.

There must be a clever bit of maths that I can’t find. I understand vectors and understand the problem, just can’t seem to solve it. easyCam works just like I want, except I find it very limiting and so I’m trying to achieve a similar effect myself.

Matt

This is a great use for quaternions! Check it out:

Every time the mouse is dragged we want to apply an incremental rotation to the existing rotation

so in the h file let’s declare a rotation, call it curRot, and a place to store the last mouse position

//current state of the rotation
ofQuaterion curRot;

//a place to store the mouse position so we can measure incremental change
ofVec2f lastMouse;

now when the mouse is dragged we want to add the change in X and the change in Y independently to curRot, which looks like this:

//--------------------------------------------------------------
void testApp::mouseDragged(int x, int y, int button){
ofVec2f mouse(x,y);
ofQuaternion yRot(x-lastMouse.x, ofVec3f(0,1,0));
ofQuaternion xRot(y-lastMouse.y, ofVec3f(-1,0,0));
curRot *= yRot*xRot;
lastMouse = mouse;
}

now we can pull the rotation of the quaternion apply in the draw function like this:

//--------------------------------------------------------------
void testApp::draw(){
ofVec3f axis;
float angle;
curRot.getRotate(angle, axis);
glPushMatrix();
ofTranslate(ofGetWidth()/2, ofGetHeight()/2, 40);
ofRotate(angle, axis.x, axis.y, axis.z);
ofBox(0, 0, 0, 200); //i'm drawing a box b/c it's easier to see it rotate!
ofPopMatrix();
}

Also it’s a good idea to store the mouse position on mouseDown to prevent big jumps when you first click

//--------------------------------------------------------------
void testApp::mousePressed(int x, int y, int button){
lastMouse = ofVec2f(x,y);
}

hope this helps

3 Likes

Jim, you are a hero!

Not fully got my head around it yet, but your code works great.

Thanks a lot.

This is the tricky part i think:

ofQuaternion yRot(x-lastMouse.x, ofVec3f(0,1,0));
ofQuaternion xRot(y-lastMouse.y, ofVec3f(-1,0,0));
curRot *= yRot*xRot;

And requires a bit of understanding on quaternion multiplication, which is hazy for me too… but the best way I’ve come to describes it to myself is that each quaternion describes a “move” and when you apply that move to another quaternion (by multiplying it), the effect is independent of the current quat’s rotation.

so here we make two “moves” one a rotation around x, one a rotation around y, and we apply those to the current rotation. It doesn’t matter what that current rotation looks like at the moment, since quaternions will always have the same affect on the global rotation. This is why they are so much better than storing things as euler angles/axis when you’re dealing with accumulating rotations.

dunno if that helps clear it up at…

1 Like

wow, this thread is now my new reference to quaternions for arcball style rotations. awesome guide james.

hi, this looks really cool. i stumbled on this post when i ran into problems with something called gimbal lock. this looks like a good solution, although i’m not following the mathematics. eg. how would i align/orient an object to it’s direction in 3d space using quaternions? eg. i thought this would work…

// using a 3d target vector instead of mouse: target + prevTarget
ofQuaternion yRot(target.x - prevTarget.x, ofVec3f(0,1,0));
ofQuaternion xRot(target.y - prevTarget.y, ofVec3f(-1,0,0));
ofQuaternion zRot(target.z - prevTarget.z, ofVec3f(0,0,1));
curRot *= yRot*xRot*zRot;

Thanks.

Hi,

I am trying to convert Latitude and Longitude to the ofQuaternion coords.
But I am not getting success.

I am using the spinningSquare example and quaternationLatLongExample.

The coords are not fitting their positions…
Could you help me?

What I have until now:

testApp.h

#pragma once

#include "ofMain.h"
#include "ofxPuffersphere.h"

typedef struct {
string name;
float latitude;
float longitude;
ofxPuffersphereObject* image;
} City;

class testApp : public ofBaseApp{

public:

void setup();
void update();
void draw();

void keyPressed  (int key);
void keyReleased(int key);
void mouseMoved(int x, int y );
void mouseDragged(int x, int y, int button);
void mousePressed(int x, int y, int button);
void mouseReleased(int x, int y, int button);
void windowResized(int w, int h);

bool drawSphere;
ofxPuffersphere* puffersphere;
ofxPuffersphereObject* fullscaleBG;

vector<City> cities;
};

testApp.cpp

#include "testApp.h"

//--------------------------------------------------------------
void testApp::setup(){

ofEnableAlphaBlending();
ofEnableSmoothing();
ofSetFrameRate(60);
ofSetVerticalSync(true);

puffersphere = new ofxPuffersphere();
puffersphere->setup(1050);

//create assets like this if you're using normal images
fullscaleBG = puffersphere->createObject("spherical.png");

drawSphere = false;

gui.toggleDraw();

City newyork = { "new york", 40+47/60., -73 + 58/60. };
City tokyo = { "tokyo", 35 + 40./60, 139 + 45/60. };
City london = { "london", 51 + 32/60., -5./60. };
City shanghai = { "shanghai", 31 + 10/60., 121 + 28/60. };
City buenosaires = { "buenos aires", -34 + 35/60., -58 + 22/60. };
City melbourne = { "melbourne" , -37 + 47/60., 144 + 58/60. };
City detroit = { "detroit", 42 + 19/60., -83 + 2 / 60. };

cities.push_back( newyork );
cities.push_back( tokyo );
cities.push_back( london );
cities.push_back( shanghai );
cities.push_back( buenosaires );
cities.push_back( melbourne );
cities.push_back( detroit );

for(unsigned int i = 0; i < cities.size(); i++){
cout << cities[i].latitude << " " << cities[i].longitude << endl;
cities[i].image = puffersphere->createObject("marker.png");
}
}

//--------------------------------------------------------------
void testApp::update(){
for(unsigned int i = 0; i < cities.size(); i++){
ofQuaternion latRot, longRot;
latRot.makeRotate(cities[i].latitude, 1, 0, 0);
longRot.makeRotate(cities[i].longitude, 0, 1, 0);
cities[i].image->rotation = latRot * longRot;
}

puffersphere->render();
}

//--------------------------------------------------------------
void testApp::draw(){
ofBackground(255);

if(drawSphere){
puffersphere->drawSphere(ofVec3f(), 300 );
}
else{
puffersphere->draw();
}

ofPushStyle();
gui.draw();
ofEnableAlphaBlending();
ofPopStyle();
}

//--------------------------------------------------------------
void testApp::keyPressed(int key){
if(key == 'p'){
drawSphere = !drawSphere;
}
}

//--------------------------------------------------------------
void testApp::keyReleased(int key){

}

//--------------------------------------------------------------
void testApp::mouseMoved(int x, int y ){

}

//--------------------------------------------------------------
void testApp::mouseDragged(int x, int y, int button){

}

//--------------------------------------------------------------
void testApp::mousePressed(int x, int y, int button){

}

//--------------------------------------------------------------
void testApp::mouseReleased(int x, int y, int button){

}

//--------------------------------------------------------------
void testApp::windowResized(int w, int h){

}