What is the best way to rotate rectangles/images?

Hello,

I was wondering what the best method of drawing shapes or images that are rotated. Currently, the method I use is the following code:

// Translates the screen so that the point of rotation 
// is at 0, 0 (pixel coordinates)
gl::translate(rotPt.x, rotPt.y);

// Rotates the screen to the specified amount, if the amount is non-zero
if(rotation != 0)				
{
    gl::rotate(-rotation);
}

// Draws the rectangle
gl::drawSolidRect(Rectf(pointA.x - rotPt.x, pointA.y - rotPt.y, pointB.x - rotPt.x, pointB.y - rotPt.y));	

// Rotates the screen back so that the screen is now level, 
// leaving the rectangle rotated to the specified angle, 
// if the amount is non-zero
if(rotation != 0)	
{
    gl::rotate(rotation);
}

// Translates the screen back so that the origin 
// is in the correct place again (undoes the above translation)
gl::translate((-rotPt.x), (-rotPt.y));	

However, I would think that this isn’t the best way to produce a rotated object, as it seems rather clunky, and the calculations for translations and rotations* would become very intensive once there are many objects on the screen. Is there a better way to do this?

*This is assuming that the gl::translate() and gl::rotate() functions translate and rotate the entire screen, not just the object. As far as I know, this is how they function, but I could be wrong, so let me know if I am.

Thanks, Zack

Hi,

there are a few improvements you can make, which I’ll address briefly:

  • Instead of undoing your transformations by applying the reverse translation or rotation, use gl::pushModelMatrix() and gl::popModelMatrix(). See this article for more general information about the matrix stack. Note that Cinder uses slightly different function names, but the idea is the same.
{
    gl::pushModelMatrix();
    gl::translate( ... );
    gl::rotate( ... );
    gl::draw( ... );
    gl::popModelMatrix();
}
  • If you don’t want to remember to call gl::popModelMatrix() for each gl::pushModelMatrix(), you can use the scoped version which will do it for you. In the following example, we will create a variable named scpModel which pushes the current matrix. When it goes out of scope, it automatically pops the matrix for you. Very powerful. There’s many more scoped functions for you to take advantage of. Learn to use them.
{
    // #include "cinder/gl/scoped.h"
    gl::ScopedModelMatrix scpModel;

    gl::translate( ... );
    gl::rotate( ... );
    gl::draw( ... );
}
  • Store the combined transformations in a ci::mat4 variable. That way you don’t have to recalculate them every frame. See e.g. glm::translate(), glm::rotate(), glm::angleAxis() and glm::scale(). You can then apply the transformation by calling gl::setModelMatrix().
  • Pushing and popping a matrix isn’t very expensive, but if you want to draw thousands of objects it’s better to avoid it. That’s another advantage of using a ci::mat4 to store the matrix, as you can call gl::pushModelMatrix() once, then call gl::setModelMatrix() for each object and finally call gl::popModelMatrix() once you’re done.
{
    gl::ScopedModelMatrix  scpModel;
    // mMatrices is of type std::vector<ci::mat4>
    for( auto &modelMatrix : mMatrices ) {
        gl::setModelMatrix( modelMatrix );
        gl::draw( ... );
    }
}
  • The gl::drawSolidRect() function is slow. Consider creating a gl::Batch from a geom::Rect instead and use it over and over.
  • Speaking of which: if you want to draw thousands of rectangles, you better use instanced rendering. This will greatly reduce the number of draw calls (from 1000’s to just 1), hugely increasing render performance. For each instance, you’d store a mat4 in a Vbo, attach it to a VboMesh, store it in a Batch and render it by calling Batch::drawInstanced(). See the Cinder samples for more information. Or have a look at my Depth-Of-Field sample, which uses exactly this technique to render hundreds of teapots.

-Paul

1 Like

Thanks!

Does this still apply to things that change position/angle every frame?

Yes, just update its transformation matrix.