ofMatrix4x4, (interpolate / lerp / tween) with respect to registration point

im trying to (interpolate / lerp / tween) between two ofMatrix4x4.
where my approach falls short is when the interpolation needs to take into account the “registration point” as they call it in flash. the “registration point” being the centre around which the matrix transformation needs to take place… for example if the registration point is the centre of the box, when spun, the box will rotate around its centre. here is my working code so far,

static float mod1 = 1.0;

void ofApp::draw(){
    float p = modf(ofGetElapsedTimef() * 0.1, &mod1);
    
    ofRectangle box(0, 0, 200, 200);
    ofVec3f pos0(100, 100);
    ofVec3f pos1(300, 100);
    ofVec3f registration(100, 100);
    
    ofMatrix4x4 mat0;
    mat0.preMultTranslate(pos0);
    mat0.preMultTranslate(registration);
    mat0.preMultRotate(ofQuaternion(0, ofVec3f(0, 0, 1)));
    mat0.preMultTranslate(-registration);
    
    ofMatrix4x4 mat1;
    mat1.preMultTranslate(pos1);
    mat1.preMultTranslate(registration);
    mat1.preMultRotate(ofQuaternion(90, ofVec3f(0, 0, 1)));
    mat1.preMultTranslate(-registration);
    
    ofVec3f p0(mat0.getTranslation());
    ofVec3f p1(mat1.getTranslation());
    ofVec3f s0(mat0.getScale());
    ofVec3f s1(mat1.getScale());
    float r0, r1;
    ofVec3f zAxis(0, 0, 1);
    mat0.getRotate().getRotate(r0, zAxis);
    mat1.getRotate().getRotate(r1, zAxis);
    
    ofVec3f pos = (p1 - p0) * p + p0;
    ofVec3f scl = (s1 - s0) * p + s0;
    float rot = (r1 - r0) * p + r0;
    
    ofMatrix4x4 mat;
    mat.preMultTranslate(pos);
    mat.preMultRotate(ofQuaternion(rot, ofVec3f(0, 0, 1)));
    mat.preMultScale(scl);
    
    ofPushMatrix();
    
    ofMultMatrix(mat);
    
    ofSetColor(255);
    ofRect(box);
    ofSetColor(0);
    ofCircle(registration, 4);
    ofSetColor(255);
    
    ofPopMatrix();
    
    ofSetColor(0);
    ofCircle(pos0 + registration, 4);
    ofCircle(pos1 + registration, 4);
    ofLine(pos0 + registration,
           pos1 + registration);
    ofSetColor(255);
}

this is what this code looks like in action,

but what should be happening is the centre of the box should be rotating and moving along the line.

just to explain the code a bit,

ofRectangle box(0, 0, 200, 200);
ofVec3f pos0(100, 100);
ofVec3f pos1(300, 100);
ofVec3f registration(100, 100);

pos0 and pos1 are the start and end positions of the box.
registration is the centre position of the box around which it rotates.

ofMatrix4x4 mat0;
mat0.preMultTranslate(pos0);
mat0.preMultTranslate(registration);
mat0.preMultRotate(ofQuaternion(0, ofVec3f(0, 0, 1)));
mat0.preMultTranslate(-registration);

ofMatrix4x4 mat1;
mat1.preMultTranslate(pos1);
mat1.preMultTranslate(registration);
mat1.preMultRotate(ofQuaternion(90, ofVec3f(0, 0, 1)));
mat1.preMultTranslate(-registration);

mat0 and mat1 are the two matrices of where the box is at the start and finish.

ofVec3f p0(mat0.getTranslation());
ofVec3f p1(mat1.getTranslation());
ofVec3f s0(mat0.getScale());
ofVec3f s1(mat1.getScale());
float r0, r1;
ofVec3f zAxis(0, 0, 1);
mat0.getRotate().getRotate(r0, zAxis);
mat1.getRotate().getRotate(r1, zAxis);

ofVec3f pos = (p1 - p0) * p + p0;
ofVec3f scl = (s1 - s0) * p + s0;
float rot = (r1 - r0) * p + r0;

ofMatrix4x4 mat;
mat.preMultTranslate(pos);
mat.preMultRotate(ofQuaternion(rot, ofVec3f(0, 0, 1)));
mat.preMultScale(scl);

this is the interpolation code between the two matrices.
as you can probably see, it does not take the registration point into account… and thats what im trying to figure out.

the rest is just drawing everything to the screen.

it would be amazing if someone could help me with this problem in some way.

Hey,

This might be cheating in terms of your broader goal but this works if you translate to the registration only with the tweened matrix. Obviously this doesn’t help if you need the original matrices to include that translation for some reason.

i.e.

static float mod1 = 1.0;

void ofApp::draw(){
float p = modf(ofGetElapsedTimef() * 0.1, &mod1);

ofRectangle box(0, 0, 200, 200);
ofVec3f pos0(100, 100);
ofVec3f pos1(300, 100);
ofVec3f registration(100, 100);

ofMatrix4x4 mat0;
mat0.preMultTranslate(pos0);
// mat0.preMultTranslate(registration); // pulled this out
mat0.preMultRotate(ofQuaternion(0, ofVec3f(0, 0, 1)));
// mat0.preMultTranslate(-registration); // pulled this out
      
ofMatrix4x4 mat1;
mat1.preMultTranslate(pos1);

// mat1.preMultTranslate(registration); // pulled this out
mat1.preMultRotate(ofQuaternion(90, ofVec3f(0, 0, 1)));
// mat1.preMultTranslate(-registration); // pulled this out

ofVec3f p0(mat0.getTranslation());
ofVec3f p1(mat1.getTranslation());
ofVec3f s0(mat0.getScale());
ofVec3f s1(mat1.getScale());
float r0, r1;
ofVec3f zAxis(0, 0, 1);
mat0.getRotate().getRotate(r0, zAxis);
mat1.getRotate().getRotate(r1, zAxis);

ofVec3f pos = (p1 - p0) * p + p0;
ofVec3f scl = (s1 - s0) * p + s0;
float rot = (r1 - r0) * p + r0;

ofMatrix4x4 mat;
mat.preMultTranslate(pos);
mat.preMultTranslate(registration); // added this
mat.preMultRotate(ofQuaternion(rot, ofVec3f(0, 0, 1)));
mat.preMultTranslate(-registration); // added this
mat.preMultScale(scl);

ofPushMatrix();

ofMultMatrix(mat);

ofSetColor(255);
ofRect(box);
ofSetColor(0);
ofCircle(registration, 4);
ofSetColor(255);

ofPopMatrix();

ofSetColor(0);
ofCircle(pos0 + registration, 4);
ofCircle(pos1 + registration, 4);
ofLine(pos0 + registration,pos1 + registration);
ofSetColor(255);
}

Hope that helps…

1 Like

Hey Julapy,
I just runned the code you posted and it behaves as you expected. The box center moves along the line.
On the other hand, have you considered using ofNode? I’m quite sure that it will make handling this so much easier.
Just make a new class that inherits from ofNode and make a function called customDraw() that shold overload the one defined in ofNode.

Best!

thanks for that dave,
its a very good clue to solving this.

thanks roy, i never really used ofNode too much.
will dig around inside and see if it can do this.

no prob -

a quick question. Is it safe to assume your box is always a square?
I think I have solved it in the tween but it would need extra work if you needed to handle any rectangle.

void ofApp::draw(){
    float p = modf(ofGetElapsedTimef() * 0.1, &mod1);

ofRectangle box(0, 0, 200, 200);
ofVec3f pos0(100, 100);
ofVec3f pos1(300, 100);
ofVec3f registration(100, 100);

ofMatrix4x4 mat0;
mat0.preMultTranslate(pos0);
mat0.preMultTranslate(registration);
mat0.preMultRotate(ofQuaternion(0, ofVec3f(0, 0, 1)));
mat0.preMultTranslate(-registration);


ofMatrix4x4 mat1;
mat1.preMultTranslate(pos1);
mat1.preMultTranslate(registration);
mat1.preMultRotate(ofQuaternion(90, ofVec3f(0, 0, 1)));
mat1.preMultTranslate(-registration);


ofVec3f p0(mat0.getTranslation());
ofVec3f p1(mat1.getTranslation());
ofVec3f s0(mat0.getScale());
ofVec3f s1(mat1.getScale());
float r0, r1;
ofVec3f zAxis(0, 0, 1);
mat0.getRotate().getRotate(r0, zAxis);
mat1.getRotate().getRotate(r1, zAxis);


ofVec3f pos = (p1 - p0) * p + p0;
ofVec3f scl = (s1 - s0) * p + s0;
float rot = ((r1 - r0) * p + r0) * 2; // not sure why but need to double the rotation
float regAngle = atan((box.height*0.5)/(box.width*0.5)); // angle from p0 to registration
float rotR = ofDegToRad(rot) + regAngle; // angle from p0 to rotated registration in radians
float r = registration.length(); // radius of circle
float ox = r * cos(regAngle); // x of registration
float oy = r * sin(regAngle); // y of registration
float rx = r * cos(rotR); // x of rotated registration
float ry = r * sin(rotR); // y of rotated registration

float dx = rx - ox; // diff
float dy = ry - oy; // diff
pos -= ofVec3f(dx, dy, 0.0);

ofMatrix4x4 mat;
mat.preMultTranslate(pos);
mat.preMultRotate(ofQuaternion(rot, ofVec3f(0, 0, 1)));
mat.preMultScale(scl);

ofPushMatrix();

ofMultMatrix(mat);

ofSetColor(255);
ofRect(box);
ofSetColor(0);
ofCircle(registration, 4);
ofSetColor(255);

ofPopMatrix();

ofSetColor(0);
ofCircle(pos0 + registration, 4);
ofCircle(pos1 + registration, 4);
ofLine(pos0 + registration,
       pos1 + registration);
ofSetColor(255);
}

hey dave,
yeah the box is a rectangle, not necessarily a square.
ive figured out a solution with that first clue you posted but not sure its the most efficient method, your workings seem to be more direct. so i’d still be interested in the rectangle solution if you have it…

here is my working solution, still need to test it extensively but so far it seems to be holding up.

static float mod1 = 1.0;
static ofVec3f zAxis(0, 0, 1);

void ofApp::draw(){
    float p = modf(ofGetElapsedTimef() * 0.2, &mod1);
    
    ofRectangle box(0, 0, 200, 200);
    ofVec3f pos0(100, 100);
    ofVec3f pos1(300, 100);
    ofVec3f registration(100, 100);
    
    // two matrices, one for each key frame of the tween animation.
    
    ofMatrix4x4 mat0;
    mat0.preMultTranslate(pos0);
    mat0.preMultTranslate(registration);
    mat0.preMultRotate(ofQuaternion(0, ofVec3f(0, 0, 1)));
    mat0.preMultTranslate(-registration);
    
    ofMatrix4x4 mat1;
    mat1.preMultTranslate(pos1);
    mat1.preMultTranslate(registration);
    mat1.preMultRotate(ofQuaternion(90, ofVec3f(0, 0, 1)));
    mat1.preMultScale(ofVec3f(2));
    mat1.preMultTranslate(-registration);
    
    // decompose matrices to position, scale, rotation.
    
    ofVec3f p0, p1;
    ofVec3f s0, s1;
    float r0, r1;

    p0.set(mat0.getTranslation());
    p1.set(mat1.getTranslation());
    s0.set(mat0.getScale());
    s1.set(mat1.getScale());
    mat0.getRotate().getRotate(r0, zAxis);
    mat1.getRotate().getRotate(r1, zAxis);
    
    // reverse the effect of the transformation point on the matrix,
    // and then rotate and scale it again,
    // so now the matrix is what it would be when ignoring the transformation ponit.
    
    mat0.preMultTranslate(registration);
    mat0.preMultRotate(ofQuaternion(-r0, ofVec3f(0, 0, 1)));
    mat0.preMultScale(1.0/s0);
    mat0.preMultTranslate(-registration);
    
    mat0.preMultRotate(ofQuaternion(r0, ofVec3f(0, 0, 1)));
    mat0.preMultScale(s0);
    
    // reverse the effect of the transformation point on the matrix,
    // and then rotate and scale it again,
    // so now the matrix is what it would be when ignoring the transformation ponit.
    
    mat1.preMultTranslate(registration);
    mat1.preMultRotate(ofQuaternion(-r1, ofVec3f(0, 0, 1)));
    mat1.preMultScale(1.0/s1);
    mat1.preMultTranslate(-registration);
    
    mat1.preMultRotate(ofQuaternion(r1, ofVec3f(0, 0, 1)));
    mat1.preMultScale(s1);
    
    // decompose matrices again to interpolate between position, scale and rotation.
    
    p0.set(mat0.getTranslation());
    p1.set(mat1.getTranslation());
    s0.set(mat0.getScale());
    s1.set(mat1.getScale());
    mat0.getRotate().getRotate(r0, zAxis);
    mat1.getRotate().getRotate(r1, zAxis);
    
    ofVec3f pos = (p1 - p0) * p + p0;
    ofVec3f scl = (s1 - s0) * p + s0;
    float rot = (r1 - r0) * p + r0;
    
    // make position, rotation and scale transformations while taking the transformation point into account.
    
    ofMatrix4x4 mat;
    mat.preMultTranslate(pos);
    mat.preMultTranslate(registration);
    mat.preMultRotate(ofQuaternion(rot, ofVec3f(0, 0, 1)));
    mat.preMultScale(scl);
    mat.preMultTranslate(-registration);
    
    // draw.
    
    ofPushMatrix();
    
    ofMultMatrix(mat);
    
    ofSetColor(255);
    ofRect(box);
    ofSetColor(0);
    ofCircle(registration, 4);
    ofSetColor(255);
    
    ofPopMatrix();
    
    // debug draw.
    
    ofSetColor(0);
    ofCircle(pos0 + registration, 4);
    ofCircle(pos1 + registration, 4);
    ofLine(pos0 + registration,
           pos1 + registration);
    ofSetColor(255);
}

the trick is to first reverse the effect of the transformation point in the matrices.
it seems a bit hacky and id be interested if anyone know a better way of doing this part.

mat0.preMultTranslate(registration);
mat0.preMultRotate(ofQuaternion(-r0, ofVec3f(0, 0, 1)));
mat0.preMultScale(1.0/s0);
mat0.preMultTranslate(-registration);

mat0.preMultRotate(ofQuaternion(r0, ofVec3f(0, 0, 1)));
mat0.preMultScale(s0);

and then adding the transformation point back in when doing the lerp.

ofMatrix4x4 mat;
mat.preMultTranslate(pos);
mat.preMultTranslate(registration);
mat.preMultRotate(ofQuaternion(rot, ofVec3f(0, 0, 1)));
mat.preMultScale(scl);
mat.preMultTranslate(-registration);

its probably not the most efficient method, so if anyone has any ideas for a more concise approach, im all ears!

Here is a version that I think works for any rectangle (not heavily tested)
I found with mat0 and mat1 that the related p0 and p1 values were not what I wanted with other rectangles unless I decomposed them before doing the rotation - I hope that is cool.

static float mod1 = 1.0;

void ofApp::draw(){
    float p = modf(ofGetElapsedTimef() * 0.1, &mod1);

ofRectangle box(0, 0, 200, 300);
ofVec3f pos0(100, 100);
ofVec3f pos1(400, 100);
ofVec3f registration(100, 150);

float r0, r1;
ofVec3f zAxis(0, 0, 1);

ofMatrix4x4 mat0;
mat0.preMultTranslate(pos0);
mat0.preMultTranslate(registration);
// scale
mat0.preMultTranslate(-registration);

ofVec3f p0(mat0.getTranslation()); // decompose postion before rotation
ofVec3f s0(mat0.getScale());  // decompose scale before rotation

mat0.preMultTranslate(registration);
mat0.preMultRotate(ofQuaternion(0, ofVec3f(0, 0, 1)));
mat0.preMultTranslate(-registration);

mat0.getRotate().getRotate(r0, zAxis); // decompose rotation

ofMatrix4x4 mat1;
mat1.preMultTranslate(pos1);
mat1.preMultTranslate(registration);
mat1.preMultScale(ofVec3f(2));
mat1.preMultTranslate(-registration);

ofVec3f p1(mat1.getTranslation()); // decompose postion before rotation
ofVec3f s1(mat1.getScale());    // decompose scale before rotation

mat1.preMultTranslate(registration);
mat1.preMultRotate(ofQuaternion(90, ofVec3f(0, 0, 1)));
mat1.preMultTranslate(-registration);

mat1.getRotate().getRotate(r1, zAxis); // decompose rotation


ofVec3f pos = (p1 - p0) * p + p0;
ofVec3f scl = (s1 - s0) * p + s0;
float rot = ((r1 - r0) * p + r0);
float regAngle = atan((box.height*0.5)/(box.width*0.5)); // angle from p0 to registration
float rotR = ofDegToRad(rot) + regAngle; // angle from p0 to rotated registration in radians
float r = (registration*scl).length(); // scaled radius or rotation (top left to registration)
float ox = r * cos(regAngle); //  x of unrotated registration
float oy = r * sin(regAngle); // y of unrotated registration
float rx = r * cos(rotR); // x of rotated registration
float ry = r * sin(rotR); // y of rotated registration

float dx = rx - ox; // diff x
float dy = ry - oy; // diff y
pos -= ofVec3f(dx, dy, 0.0); // subtract the difference

ofMatrix4x4 mat;
mat.preMultTranslate(pos);
mat.preMultRotate(ofQuaternion(rot, ofVec3f(0, 0, 1)));
mat.preMultScale(scl);

ofPushMatrix();

ofMultMatrix(mat);

ofSetColor(255);
ofRect(box);

ofSetColor(0);
ofCircle(registration, 4);
ofSetColor(255);

ofPopMatrix();

ofSetColor(0);
ofCircle(pos0 + registration, 4);
ofCircle(pos1 + registration, 4);

ofLine(pos0 + registration,
       pos1 + registration);


ofSetColor(255);
}

nice, i’ll try out this approach as well.