Rotate of node around a specific point

I see there are three different rotation methods for ofNode, but I am having trouble with them. I would like to do the following: move a point out from the ofNode by ‘x’ amount, and then rotate the ofNode around that point.

If I use rotate, I will rotate around the node.

If I use rotateAround, the entire node and children will move around the new point but the orientation of the node will not change.

orbit doesn’t appear to do anything.

I tried using rotateAround and lookAt, but… well, using lookAt made the whole thing squirrellie as it jumped around the screen and would not stop.

I would like to assume I should be able to control the rotation in the same method as ofNode.rotate, but around the rotation point, rp. Which of the methods is the best to use, and how?

Thank you.

there’s 2 ways to do it, one is to use orbit which should work, if you specify the radius, latitude and longitude correctly, someone reported a problem recently but i think it was only in combination with lookat.

the other way is to use a parent node and then rotate that node:

ofNode translation;
ofNode rotation;
translation.setParent(rotation);
translation.setPosition({x,0,0});
rotation.rotate(90);

will move the first node x apart from the origin and then rotate it 90 degrees around 0,0

How will I be able to use that parent node’s transformation matrix to calculate a point’s actual location doohickey?

global position thingie.

this doesn’t seem to work. I mean, I can line things up, but it is really unintuitive in it’s behaviour.

    <<draw first kinect point cloud stuff here>>>
kinect2cam.move(k2Truck,k2Dolly,k2Boom);
kinect2cam.transformGL(); // draw the original kinect2 node, line of sight and rotation point 
    ofLine(ofPoint(0,0,0), ofPoint(0,0,5000));
    ofDrawSphere(ofPoint(0,0,k2RotatePoint), 10);
    ofDrawBox(0,0,0,100,20,20);
kinect2cam.restoreTransformGL();

ofNode translation = kinect2cam;  //rotate around rotation point along line of sight
    translation.setParent(rotateThis);
    rotateThis.setPosition(ofPoint(0,k2RotatePoint,0));
    rotateThis.rotate(k2Rotate, ofPoint(0,1,0));
    
translation.transformGL();
   <<draw fancy schmancy kinect 2 point cloud related stuff here>>
translation.restoreTransformGL();

The kinect point cloud for kinect number 2 seems to be moving when I move k2RotatePoint along the line of sight. Should I be replacing this before drawing the point cloud, since all I want is the ability to rotate at that point?

I mean, it ‘kind of’ works, but it’s more magical than logical (i.e.: other people won’t grasp what is intended to happen, but I can fake it because I built it)

the way ofNode works is pretty standard, really similar to how other tools like any 3d software or for example three.js works with 3d transformations.

The idea is that any object in 3d space has a node that represents it’s 3d position, rotation and scale but in this kind of transformations order matters so in order to have something that is predictable ofNode applies those transformations in an specific order:

  1. translation
  2. rotation
  3. scale

that means that it doesn’t matter if you do:

node.setPosition(...)
node.setOrientation(...)

or

node.setOrientation(x)
node.setPosition(y)

there result will be the same, the object drawn using that node’s transformation will be at position x rotated around it’s local origin by y. then if there’s any scale it’ll scale the object geometry but not it’s local transformations.

the rest of the methods just use those 3 so anything else that you do will just be as setPosition/setRotation/setScale, for example lookAt is the same as setRotation or orbit the same as setPosition + setRotation

The fact that transformations are applied in the same order makes it simpler to reason about 1 node but has a limitation in that you can’t for example rotate something around something else using only one node so if you need to do more complex transformations you just use 2 nodes, with 2 nodes you should be able to do any possible 3d translation/rotation/scale combination.

You can think about 1 node parented to another as a node being hold by a solid rod attached to the parent. If you translate the child node that rod extends from the parent to wherever is the child. If you rotate the parent the child rotates around it like moved by that bar. if you move the parent the child moves with it and if you scale the parent the child also scales but also the separation between them as if this imaginary bar was also scaled.

In other terms when you parent a node to another the parent becomes the origin of coordinates of the child, when you transform the child it’s always relative to the parent not to the world 0,0 anymore and if you translate / rotate / scale the parent you are effectively traslating / rotating / scaling the coordinate space of the child.

to put another example imagine a car (1 node) moving forwards, that car is on a ship (it’s parent node) which is also moving forward, the absolute movement of the car is it’s own translation + the translation of the ship, the ship is on the earth (a parent node to the ship) which is rotating around itself so the ship rotates around the earth center while moving forwards and the car rotates around the earth center while moving forwards with the combined movement of the ship + it’s local movement, the earth rotates around the sun which you could model as a parent node to the earth which rotates making the earth rotate around it at a certain distance.

mathematically, when a node is applied through transformGL it just gets the translation, orientation and scale and creates a 4x4 matrix with it, if it has a parent it asks for it’s parent’s global transformation as a 4x4 matrix and multiplies it’s local matrix by it’s parent’s global matrix. the resulting matrix is the global transformation for the node which is then uploaded to opengl as the modelMatrix which represents the transformation for the geometry in the world

1 Like

Thank you very much for listening to my ramblings. I think one of the problems I am having is that, since the rotate ofNode is tied to a point whose distance resolved from the original ofNode, changing that distance moves the original ofNode, since it is tied to the new parent. Resetting the parent will not alter the original ofNode rotation, but will rotate the object being moved.

I have some practice code for trouble shooting my fumbling. This code assumes ofxGui is an included addon:

#pragma once

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

class ofApp : public ofBaseApp{
	public:
		void setup();
		void update();
		void draw();
		
		void keyPressed(int key);
    
    ofNode source, rotateThis;
    
    vector<ofMesh> items;
    
    //gui basics
    ofxPanel gui;
    
    //movemet basics
    ofParameterGroup k2Motion;
    ofParameter<bool> k2FreezeMotion;
    ofParameter<float>  k2Dolly;
    ofParameter<float>  k2Truck;
    ofParameter<float>  k2Boom;
    ofParameter<float>  k2Rotate;
    ofParameter<float>  k2Tilt;
    ofParameter<int> k2RotatePoint;
    
    ofMesh buildHalfSpherePoints(ofPoint thisOrigin, bool sides);
    void drawNode();
    
    ofEasyCam thisCam;
};

and the other file

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    gui.setup(); // most of the time you don't need a name
    
    k2Motion.setName("k2Motion");
    ///for kinect2 dolly movement
    k2Motion.add(k2Dolly.set("kinect2y (up/down)", 0.0, -5.0, 5.0));
    k2Motion.add(k2Truck.set("kinect2x (side-to-side)", 0.0,-5.0,5.0));
    k2Motion.add(k2Boom.set("kinect2z (for/back)", 0.0,-5.0,5.0));
    k2Motion.add(k2Rotate.set("kinect2 rotate", 0.0,-1.0,1.0));
    k2Motion.add(k2Tilt.set("kinect2 tilt", 0.0,-1.0,1.0));
    k2Motion.add(k2FreezeMotion.set("freeze Motion", false));
    k2Motion.add(k2RotatePoint.set("kinect2 rotate point", 0,0,5000));
    
    gui.add(k2Motion); //used to reference my old code
    
    items.push_back(buildHalfSpherePoints(ofPoint(0,1000,0),true)); //first point 1000 points in from origin
    items.push_back(buildHalfSpherePoints(ofPoint(0,1000,0),false)); //second is offset from origin
    
    source.setPosition(200,0,200); ///start it where the second sphere is built
}

//--------------------------------------------------------------
void ofApp::update(){
    
    if(k2FreezeMotion){
        k2Dolly = 0.0;
        k2Truck = 0.0;
        k2Boom = 0.0;
        k2Rotate = 0.0;
        k2Tilt = 0.0;
        k2FreezeMotion = false;
    }
}

//--------------------------------------------------------------
void ofApp::draw(){
    
    thisCam.begin();
    
    ofSetColor(0,0,255);
    drawNode();
    items[0].drawVertices();
    
    ofSetColor(255,0,0);
    ofPushMatrix();
        ofNode thisNode;
        source.move(k2Truck,k2Dolly,k2Boom);
    
            ///draw node at original position
        source.transformGL();
            drawNode();
            ofDrawSphere(ofPoint(0,k2RotatePoint,0), 10);
        source.restoreTransformGL();
    
            //link the source node to the rotation point
        source.setParent(rotateThis);
            rotateThis.setPosition(ofPoint(0,k2RotatePoint,0));
            rotateThis.rotate(k2Rotate, ofPoint(0,0,1));
    
            ofPushMatrix();
                    //move back to original sphere position
                //ofTranslate(0,-k2RotatePoint);
                    //draw rotated half sphere
                source.transformGL();

                    items[1].drawVertices();
                source.restoreTransformGL();
            ofPopMatrix();
                ///free the source node from the parent.
        source.clearParent();
    ofPopMatrix();
    
    thisCam.end();
    
    ofSetColor(255);
    gui.draw();
}

void ofApp::drawNode(){
    ofDrawLine(ofPoint(-20,0,0),ofPoint(20,0,0));
    ofDrawLine(ofPoint(0,0,-20),ofPoint(0,0,20));
    ofDrawLine(ofPoint(0,0,0),ofPoint(0,1000,0));
}
                    
//--------------------------------------------------------------
ofMesh ofApp::buildHalfSpherePoints(ofPoint thisOrigin, bool sides){
    int number = 1000; /// 100 points long the sphere
    int radius = 200; /// the radius of the sphere we are building
    ofMesh thisHalfSphere;
    for (int i=0; i<number; i++){
        ofPoint p;
        p.x = ofRandom(-1,1);
        p.y = ofRandom(-1,1);
        if (sides){
            p.z = ofRandom(-1,0);
        } else {
            p.z = ofRandom(0,1);
        }
        
        p /= sqrt(p.x*p.x+p.y*p.y+p.z*p.z);
        
        p *= radius;
        
        thisHalfSphere.addVertex(p+thisOrigin);
    }
    
    return thisHalfSphere;
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    if (key==' '){
        k2FreezeMotion = true;
    }
}

The spheres are offset from the node in the same way a typical kinect point cloud would be (where none of the points are closer than 250 or so from the kinect). I effectively want to rotate a kinect by manipulating it’s point cloud, since rotating the kinect itself will alter any movements made to line everything up in the first place… Does that make sense?

I cannot create a new parent on every frame because there are odd errors thrown in the ofNode code. In fact, closing this test program will sometimes result in an error as well… a “while” error or something.

I think I figured it out. Basically, the fulcrum (‘rotate distance’ along line from source) is not set to the rotatePoint ofNode every time. When it is, the ‘real world’ location of the point at ‘rotate distance’ along the line from the source ofNode is calculated by multiplying it’s location along the line by the source ofNode transformation matrix. The rotatePoint ofNode is then given this new location in real world coordinates, and the source is moved ‘back’ the same distance as the original rotate distance. I commented what is happening in the updated code.

the point cloud is then drawn within the source transformGL matrix … thingie. (sorry. brain hurts.)

updated code is here:

#pragma once

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

class ofApp : public ofBaseApp{
	public:
		void setup();
		void update();
		void draw();
		
		void keyPressed(int key);
    
    ofNode source, rotateThis;
    
    vector<ofMesh> items;
    
    //gui basics
    ofxPanel gui;
    
    //movemet basics
    ofParameterGroup k2Motion;
    ofParameter<bool> k2FreezeMotion;
    ofParameter<float>  k2Dolly;
    ofParameter<float>  k2Truck;
    ofParameter<float>  k2Boom;
    ofParameter<float>  k2Rotate;
    ofParameter<float>  k2Tilt;
    ofParameter<int> k2RotatePoint;
    ofParameter<bool> setk2Fulcrum;
    
    ofMesh buildHalfSpherePoints(ofPoint thisOrigin, bool sides);
    void drawNode();
    
    ofEasyCam thisCam;
};

and the other file:

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    gui.setup(); // most of the time you don't need a name
    
    k2Motion.setName("k2Motion");
    ///for kinect2 dolly movement
    k2Motion.add(k2Dolly.set("kinect2y (up/down)", 0.0, -5.0, 5.0));
    k2Motion.add(k2Truck.set("kinect2x (side-to-side)", 0.0,-5.0,5.0));
    k2Motion.add(k2Boom.set("kinect2z (for/back)", 0.0,-5.0,5.0));
    k2Motion.add(k2Rotate.set("kinect2 rotate", 0.0,-1.0,1.0));
    k2Motion.add(k2Tilt.set("kinect2 tilt", 0.0,-1.0,1.0));
    k2Motion.add(k2FreezeMotion.set("freeze Motion", false));
    k2Motion.add(k2RotatePoint.set("kinect2 rotate point", 0,0,5000));
    
    k2Motion.add(setk2Fulcrum.set("set fulcrum location", false));
    
    gui.add(k2Motion); //used to reference my old code
    
    items.push_back(buildHalfSpherePoints(ofPoint(0,1000,0),true)); //first point 1000 points in from origin
    items.push_back(buildHalfSpherePoints(ofPoint(0,1000,0),false)); //second is offset from origin
    
    rotateThis.setPosition(200,0,200);      ///start it where the second sphere is built
    source.setPosition(0,0,0);  ///source and fulcrum are the same at first
    source.setParent(rotateThis);
}

//--------------------------------------------------------------
void ofApp::update(){
    
    if(k2FreezeMotion){
        k2Dolly = 0.0;
        k2Truck = 0.0;
        k2Boom = 0.0;
        k2Rotate = 0.0;
        k2Tilt = 0.0;
        k2FreezeMotion = false;
    }
}

//--------------------------------------------------------------
void ofApp::draw(){
    
    thisCam.begin();
        //draw non-moving sphere
    ofSetColor(0,0,255);
    drawNode();
    items[0].drawVertices();
    
    ofSetColor(255,0,0);
    ofPushMatrix();
        rotateThis.move(k2Truck,k2Dolly,k2Boom);
    
        if (setk2Fulcrum){
                //find the fulcrum point for rotating
            ofPoint testThis = ofPoint(0,k2RotatePoint,0);
                //get the fulcrum's location in real world coordinates (not local)
            ofPoint thisHere = testThis*source.getGlobalTransformMatrix();
                //set the parent node to the fulcrum
            rotateThis.setPosition(thisHere);
                //flash this location breifly on screen
            ofSphere(thisHere,100);
                //move the main movement node so it is no longer at origin
            source.setPosition(ofPoint(0,-k2RotatePoint,0));
            setk2Fulcrum = false;
        }
            //rotate around the fulcrum
        rotateThis.rotate(k2Rotate, ofPoint(0,0,1));

        ofPushMatrix();
            source.transformGL();
                    //draw origin for source node
                drawNode();
                ofDrawSphere(ofPoint(0,k2RotatePoint,0), 10);
                    //draw rotated half sphere
                items[1].drawVertices();
            source.restoreTransformGL();
        ofPopMatrix();
    ofPopMatrix();
    
    thisCam.end();
    
    ofSetColor(255);
    gui.draw();
}

void ofApp::drawNode(){
    ofDrawLine(ofPoint(-20,0,0),ofPoint(20,0,0));
    ofDrawLine(ofPoint(0,0,-20),ofPoint(0,0,20));
    ofDrawLine(ofPoint(0,0,0),ofPoint(0,1000,0));
}
                    
//--------------------------------------------------------------
ofMesh ofApp::buildHalfSpherePoints(ofPoint thisOrigin, bool sides){
    int number = 1000; /// 100 points long the sphere
    int radius = 200; /// the radius of the sphere we are building
    ofMesh thisHalfSphere;
    for (int i=0; i<number; i++){
        ofPoint p;
        p.x = ofRandom(-1,1);
        p.y = ofRandom(-1,1);
        if (sides){
            p.z = ofRandom(-1,0);
        } else {
            p.z = ofRandom(0,1);
        }
        
        p /= sqrt(p.x*p.x+p.y*p.y+p.z*p.z);
        
        p *= radius;
        
        thisHalfSphere.addVertex(p+thisOrigin);
    }
    
    return thisHalfSphere;
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    if (key==' '){
        k2FreezeMotion = true;
    }
}

I’m still getting a weird error if I pause the program while it is running, and have to stop it manually.
It is a ‘bad access’ code at the ‘while’ in this part of __tree (line 126):

// Returns:  pointer to the left-most node under __x.
// Precondition:  __x != nullptr.
template <class _NodePtr>
inline _LIBCPP_INLINE_VISIBILITY
_NodePtr
__tree_min(_NodePtr __x) _NOEXCEPT
{
    while (__x->__left_ != nullptr)
        __x = __x->__left_;
    return __x;
}

this error is rather frustrating as it leaves the program hanging, and that causes delays within the IDE.

Thanks again for your help…

a couple of things:

  • when using ofNode you don’t need to use push/pop matrix at all, the node itself will upload the corresponding matrix and revert to the previous one when you call restoreTransformGL
  • seems like you are using ofNode as an immedaite mode kind of api by creating them on the fly every draw call and even reusing them inside the call for more than one object, the idea though is to have a node per object inside the object itself so you can parent objects between them, move an object around…

take a look at how ofCamera or of3dPrimitive use ofNode internally by inheriting from it or you can also just have a node as an instance variable and expose whatever methods you need like:

class PointCloud{
ofNode node;

public:
void setPosition(vec3){
    node.setPosition(vec3);
}
}

the crash you see is surely derived from how you are using the nodes, since you are parenting nodes with other nodes created in draw that will disappear as soon as the draw call finishes. if instead you put the nodes in clases like explained above or like ofCamera or of3dPrimitive use them the destructor of the class will take care of also destroying the node properly

Thank you very much for pointing me in the right direction (er… no pun intended?)

I will be exploring this a bit more, as well as refactoring this example exploration to fit a typical Kinect scenario a bit better. It was difficult to shoehorn it into my existing delicately coded um… “spaghetti”

The idea of an ofNode tied to a kinect point cloud through the use of a class is intriguing.

wowsers, that certainly did the trick. No more error codes, and prettier code, too. Please pardon my nomenclature…

pointcloud.h

#pragma once
#include "ofMain.h"
class thePointcloud{
    public :
        thePointcloud();
        thePointcloud( ofNode _theFulcrum, ofMesh _thePoints, ofNode _theKinect)
        {
            theFulcrum = _theFulcrum; //the parent node caluculated as xx-distance from 'kinect'
            thePoints = _thePoints;   //the points in the point cloud, x-distance from the 'kinect'
            theKinect = _theKinect;     //the kinect node, xx-distance from the 'kinect'
        }
    
    void initialize(ofVec3f startHere, bool sphereSide);
    void setFulcrumLocation();
    void drawNode();
    ofMesh buildHalfSpherePoints(ofVec3f rightHere, bool thisWay);
    
    // variables
    ofNode theFulcrum;
    ofMesh thePoints;
    ofNode theKinect;
    
    int fulDistance;  //the distance from the fulcrum to the 'kinect'
        
};

point cloud.cpp

#include "pointCloud.h"
thePointcloud::thePointcloud(){       
}
void thePointcloud::setFulcrumLocation (){ //rotatePoint = distance from 'kinect' along line
    theKinect.transformGL();
    ofPoint testThis = ofPoint(0,fulDistance,0);
    theKinect.restoreTransformGL();
    //get the fulcrum's location in real world coordinates (not local)
    ofPoint thisHere = testThis*theKinect.getGlobalTransformMatrix();
    //set the parent node to the fulcrum
    theFulcrum.setPosition(thisHere);
    //move the main movement node so it is no longer at origin
    theKinect.setPosition(ofPoint(0,-fulDistance,0));
}

void thePointcloud::drawNode(){
    theKinect.transformGL();
    ofDrawLine(ofPoint(-20,0,0),ofPoint(20,0,0));
    ofDrawLine(ofPoint(0,0,-20),ofPoint(0,0,20));
    ofDrawLine(ofPoint(0,0,0),ofPoint(0,1000,0));
    
    thePoints.drawVertices();
    ofDrawSphere(ofPoint(0,fulDistance,0), 10);
    theKinect.restoreTransformGL();
}

void thePointcloud::initialize( ofVec3f startHere, bool sphereSide){
    theFulcrum.setPosition(startHere); //start it in a location different than bKinect
    fulDistance = 0;                    //fulcrum is kinect at start;
    theKinect.setParent(theFulcrum);
    thePoints = buildHalfSpherePoints(ofPoint(0,400,0),sphereSide); //400 in front of kinect
    
}

ofMesh thePointcloud::buildHalfSpherePoints(ofPoint thisOrigin, bool sides){
    int number = 1000; /// 1000 points on the sphere side
    int radius = 200; /// the radius of the sphere we are building
    ofMesh thisHalfSphere;
    for (int i=0; i<number; i++){
        ofPoint p;
        p.x = ofRandom(-1,1);
        p.z = ofRandom(-1,1);
        if (sides){
            p.y = ofRandom(-1,0);
        } else {
            p.y = ofRandom(0,1);
        }
        
        p /= sqrt(p.x*p.x+p.y*p.y+p.z*p.z);
        
        p *= radius;
        
        thisHalfSphere.addVertex(p+thisOrigin);
    }
    return thisHalfSphere;
}

app.h

#pragma once
#include "ofMain.h"
#include "ofxGui.h"
#include "pointCLoud.h"

class ofApp : public ofBaseApp{
    
	public:
		void setup();
		void update();
		void draw();
		
		void keyPressed(int key);
    
    //gui basics
    ofxPanel gui;
    
    //movemet basics
    ofParameterGroup k2Motion;
    ofParameter<bool> k2FreezeMotion;
    ofParameter<float>  k2Dolly;
    ofParameter<float>  k2Truck;
    ofParameter<float>  k2Boom;
    ofParameter<float>  k2Rotate;
    ofParameter<float>  k2Tilt;
    ofParameter<int> k2RotatePoint;
    ofParameter<bool> setk2Fulcrum;
    
    ofEasyCam thisCam;
    thePointcloud aKinect, bKinect;
};

app.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    gui.setup(); // most of the time you don't need a name
    
    k2Motion.setName("k2Motion");
    ///for kinect2 dolly movement
    k2Motion.add(k2Dolly.set("kinect2y (up/down)", 0.0, -5.0, 5.0));
    k2Motion.add(k2Truck.set("kinect2x (side-to-side)", 0.0,-5.0,5.0));
    k2Motion.add(k2Boom.set("kinect2z (for/back)", 0.0,-5.0,5.0));
    k2Motion.add(k2Rotate.set("kinect2 rotate", 0.0,-1.0,1.0));
    k2Motion.add(k2Tilt.set("kinect2 tilt", 0.0,-1.0,1.0));
    k2Motion.add(k2FreezeMotion.set("freeze (spacebar)", false));
    k2Motion.add(k2RotatePoint.set("kinect2 rotate point", 0,0,500));
    
    gui.add(k2Motion); //used to reference my old code
    
    aKinect.initialize(ofPoint(200,0,200),false);
    bKinect.initialize(ofPoint(0,0,0),false);
}

//--------------------------------------------------------------
void ofApp::update(){
    
    if(k2FreezeMotion){
        k2Dolly = 0.0;
        k2Truck = 0.0;
        k2Boom = 0.0;
        k2Rotate = 0.0;
        k2Tilt = 0.0;
        k2FreezeMotion = false;
    }
}

//--------------------------------------------------------------
void ofApp::draw(){
    
    thisCam.begin();
            //draw non-moving sphere
        ofSetColor(0,0,255);
        bKinect.drawNode();
        
        ofSetColor(255,0,0);
        aKinect.theFulcrum.move(k2Boom, k2Dolly, k2Truck);
        aKinect.fulDistance = k2RotatePoint;
        aKinect.setFulcrumLocation();
        aKinect.theFulcrum.rotate(k2Rotate,ofPoint(0,0,1));
        
        aKinect.drawNode();
        
    thisCam.end();
    
    ofSetColor(255);
    gui.draw();
}


//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    if (key==' '){
        k2FreezeMotion = true;
    }
}

Thank you very much @arturo for your help. This will help a lot in my project as I wanted a general movement for pointclouds in a kinect without the complications of exact measurements. The errors are more… interesting… I hope this code will help others figure out this stuff, too.

Some mishaps with open frameworks today, and some code for manipulating kinect point clouds. Happy accidents, mind you...