Keep path in place with arbitrary ofNode's position and rotation

Hi all,

I’m working on a project that involves visualizing and manipulating polygons on a cartesian plane, basically to learn about isometric transformations. For rotation, I need to rotate a polygon based on an arbitrary axis (or pivot) that can be set with the mouse, and rotated with keys.

I’ve tried several approaches, ranging from simple matrix transformations and then using ofNode, because of its parenting abilities. However I can’t get it to work correctly: when I change the pivot, then rotate, works fine the first time. But when I change the pivot once again, the position jumps to an undesired position.

The desired interaction/behavior is:

  1. Select a pivot position
  2. Lock polygon to pivot (parent node)
  3. Rotate
  4. unlock polygon from pivot (polygon must remain in place)
  5. Select a new pivot position
  6. Lock polygon to new pivot’s position (here’s where things get off)
  7. Rotate
  8. Repeat as many times as desired

I’m new to ofNode and understand matrices a bit, so any suggestion of how to approach this is appreciated. I tried with ofNode only because of parenting, but this is a 2D scene, so maybe there’s a simpler solution with glm::vec2 ?

Thanks!

Here’s my h file

    ofNode origin, pivot, cursor, center;
    ofMatrix4x4 m;

    glm::vec3 originalPos;
    
    ofPolyline poly;
    
    float scale;
    float angle;
    
    bool attach;

And my .cpp

//--------------------------------------------------------------
void ofApp::setup(){
    //NODES
    origin.setPosition(200,0,0);
    pivot.setPosition(0, 0, 0);
    center.setPosition(0, 0, 0);
    origin.setParent(center);

    originalPos = origin.getPosition();
    angle = 0;
    attach = true;
    
    //POLYGON
    scale = 100;
    poly.addVertex(glm::vec3(0,0,0));
    poly.addVertex(glm::vec3(1,0,0) * scale);
    poly.addVertex(glm::vec3(0,2,0) * scale);
    poly.close();
    
    //MATRICES
    m.setTranslation(ofGetWidth()/2, ofGetHeight()/2, 0);
}
//--------------------------------------------------------------
void ofApp::update(){
    cursor.setPosition(ofGetMouseX() - m.getTranslation().x, ofGetMouseY() - m.getTranslation().y, 0);
    
    if(attach){
        origin.setPosition(center.getPosition() - pivot.getPosition() + originalPos);
    }else{
        pivot.setPosition(cursor.getPosition());
        origin.setParent(pivot);
    }
    
    pivot.setOrientation(glm::vec3(0,0,angle));
}
//--------------------------------------------------------------
void ofApp::draw(){
    
    // Draw Nodes
    ofMultMatrix(m);
    
    // center - 0,0 at middle of screen, i.e. the cartesian plane origin
    ofSetColor(0, 100);
    ofDrawBitmapString("CENTER :" + ofToString(center.getGlobalPosition()), center.getGlobalPosition() + glm::vec3(10,-10,0));
    center.draw();

    // token - tied to the mouse, determines pivot position when bool 'attach' is true
    ofSetColor(0, 0, 255);
    ofDrawBitmapString("CURSOR :" + ofToString(cursor.getGlobalPosition()), cursor.getGlobalPosition() + glm::vec3(10,-10,0));
    cursor.draw();
    
    // the shape should rotate around this node
    ofSetColor(0, 255, 255);
    ofDrawBitmapString("PIVOT :" + ofToString(pivot.getGlobalPosition()), pivot.getGlobalPosition() + glm::vec3(10,-10,0));
    pivot.draw();
    
    // this is where the shapes start drawing
    ofSetColor(255, 0, 0);
    ofDrawBitmapString("ORIGIN :" + ofToString(origin.getGlobalPosition()), origin.getGlobalPosition().x + 10, origin.getGlobalPosition().y - 10);
    origin.draw();


    // Draw Shapes
    origin.transformGL();
    poly.draw();
    origin.restoreTransformGL();
        
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    switch (key) {
        case OF_KEY_UP:
            angle += 10;
            break;
        case OF_KEY_DOWN:
            angle -= 10;
            break;
            
        default:
            break;
    }
}
//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button){
    attach = false;
}
//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){
    attach = true;
}

Hi @Francisco Zamorano
It seems that ofNode is a bit of an overkill for what you want to do, or at least how you are using it. You need only one instance of ofNode for each shape.

or you can use the following code.


#pragma once

#include "ofMain.h"

class ofApp : public ofBaseApp{

	public:
		void setup();
		void update();
		void draw();

		void mouseDragged(int x, int y, int button);
		void mousePressed(int x, int y, int button);
		void mouseReleased(int x, int y, int button);

	
	ofPolyline poly, rotPoly;
	glm::vec2 pivot;
	bool bRotating = false;
	float onClicAngle = 0;
	
	void updatePoly(int x, int y, int button);

};
#include "ofApp.h"


ofPolyline getRotatedPoly(const ofPolyline& poly, float angle, const glm::vec2& rotationOrigin)
{
	auto newPoly = poly;
	for(auto & point : newPoly.getVertices()){
		point = glm::rotate(point - rotationOrigin, angle, glm::vec3(0,0,1)) + rotationOrigin;
	}
	return newPoly;
}


//--------------------------------------------------------------
void ofApp::setup(){
	
	ofRectangle rect ( 0,0, ofGetWidth(), ofGetHeight());
	rect.scaleFromCenter(0.6);
	poly = ofPolyline::fromRectangle(rect);
	pivot = poly.getCentroid2D();
	
}

//--------------------------------------------------------------
void ofApp::update(){}
//--------------------------------------------------------------
void ofApp::draw(){

	
	ofSetColor(0);
	poly.draw();
	
	if(bRotating){
		ofSetColor(255);
		rotPoly.draw();
	}
	ofSetColor(0);
	ofDrawCircle(pivot, 7);
	ofSetColor(255);
	ofDrawCircle(pivot, 5);
	
	ofDrawBitmapStringHighlight("clic and drag with:\n    left mouse button: rotate polygon\n    right mouse button: set rotation pivot", 30, 30);
	
}
//--------------------------------------------------------------
void ofApp::updatePoly(int x, int y, int button){
	if(button == OF_MOUSE_BUTTON_LEFT){
		rotPoly = getRotatedPoly(poly, atan2(y - pivot.y, x - pivot.x) - onClicAngle, pivot);
	}else{
		pivot = {x, y};
	}
}
//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button){
	if(bRotating){
		updatePoly(x, y, button);
	}
}

//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button){
	if(poly.inside(x, y)){
		onClicAngle = atan2(y - pivot.y, x - pivot.x);
		updatePoly(x, y, button);
		bRotating = true;
	}
}

//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){
	if(bRotating){
		poly = rotPoly;
	}
	bRotating = false;
	
}

Let me know if this works for you

Wow @roymacdonald , thanks so much! This actually works like a charm and it is exactly what I was looking for. I was also feeling that ofNode was too much for what I needed and the solution was simpler with vec2. Your code will help me a lot with similar things in this project! :smiley:

1 Like