2d matrix for 2d transformation and drawing

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
kornwaretm
Competition winner
Posts: 189
Joined: Tue Oct 16, 2007 3:53 am
Location: Indonesia
Contact:

2d matrix for 2d transformation and drawing

Post by kornwaretm »

actually i write this code for testing the texture->lock() testing. but i think everybody can use it too. so here it is the matrix2D.

the first thing we need for drawing 2d graphic is a good transformation tool. the matrix2D class has the complete 2D operation needed to transform vertices in 2d manner. it has identity, scale, rotate, translate, invert, and concat operation etc.
  • identity reset the matrix to identity
  • scale -> scale the matrix
  • rotate -> rotate the matrix
  • translate -> translate the matrix
  • invert -> invert the matrix, basically do the oposite of the previous transformation
  • concat -> basically matrix multiplication, or in other word, combine 2 or more operation
for example, we want a (50, 50) square placed at the middle of the 800x600 screen
  • matrix->translate(-25,-25); // translate to negative half of the square's edge. because we want it to perfectly centered
  • matrix->translate(400,300); // translate to the midle of the screen
  • transform the 4 vertices
or we can just do our own math
  • matrix->translate(400 - 25, 300 -25)
  • and transform the vertices
here is the code for the matrix2d class, you can put these codes in to a header or just put it on your main.cpp

Code: Select all

 
/** \brief 2D matrix math, handles 2d transformation, based on as3 adobe flash matrix. created by Kornelius Heru Cakra Murti 2016
 */
class matrix2D
{
public:
    /// x scale
    f32 a;
    /// y skewness
    f32 b;
    /// x skewness
    f32 c;
    /// y scale
    f32 d;
    /// translation x
    f32 tx;
    /// translation y
    f32 ty;
 
 
    /** \brief construct a matrix object
      */
    matrix2D()
    {
        a = 1.0;
        b = 0.0;
        c = 0.0;
        d = 1.0;
        tx = 0.0;
        ty = 0.0;
    }
 
    /** \brief destruct a matrix object
      */
    ~matrix2D();
 
    /** \brief reset matrix, change value to identity
      * \return void.
      */
    void identity()
    {
        a = 1.0;
        b = 0.0;
        c = 0.0;
        d = 1.0;
        tx = 0.0;
        ty = 0.0;
    }
 
    /** \brief concatenate matrix with other matrix.
      * \param other matrix to concatenate to.
      * \return void.
      */
    void concat(const matrix2D * other)
    {
        f32 a1 = a;
        f32 b1 = b;
        f32 c1 = c;
        f32 d1 = d;
        f32 tx1 = tx;
        f32 ty1 = ty;
 
        f32 a2  = other->a;
        f32 b2  = other->b;
        f32 c2  = other->c;
        f32 d2  = other->d;
        f32 tx2 = other->tx;
        f32 ty2 = other->ty;
 
        a = a1 * a2 + b1 * c2;
        b = a1 * b2 + b1 * d2;
        c = c1 * a2 + d1 * c2;
        d = c1 * b2 + d1 * d2;
        tx = tx1 * a2 + ty1 * c2 + tx2;
        ty = tx1 * b2 + ty1 * d2 + ty2;
    }
 
    /** \brief invert matrix. inversed matrix does the opposite transformation
      * \return void.
      */
    void invert()
    {
        f32 ta = a;
        f32 tb = b;
        f32 tc = c;
        f32 td = d;
        f32 ttx = tx;
        f32 tty = ty;
 
        f32 tadbc = ta * td - tb * tc;
 
        a =  td / tadbc;
        b = -tb / tadbc;
        c = -tc / tadbc;
        d =  ta / tadbc;
        tx = ( tc * tty - td * ttx ) / tadbc;
        ty = -( ta * tty - tb * ttx ) / tadbc;
    }
 
    /** \brief translate the matrix object.
      * \param x how much translation in x direction.
      * \param y how much translation in y direction.
      * \return void.
      */
    void translate(const f32 x, const f32 y)
    {
        tx += x;
        ty += y;
    }
 
    /** \brief scale the matrix object.
      * \param x how much scaling in x direction.
      * \param y how much scaling in y direction.
      * \return void.
      */
    void scale(const f32 x, const f32 y)
    {
        a *= x;
        b *= y;
        c *= x;
        d *= y;
        tx *= x;
        ty *= y;
    }
 
    /** \brief rotate the matrix object.
      * \param rad how much rotation in radians
      * \return void.
      */
    void rotate(const f32 rad)
    {
        f32 ta = a;
        f32 tb = b;
        f32 tc = c;
        f32 td = d;
        f32 ttx = tx;
        f32 tty = ty;
        f32 tcos = cos(rad);
        f32 tsin = sin(rad);
 
        a = ta * tcos - tb * tsin;
        b = ta * tsin + tb * tcos;
        c = tc * tcos - td * tsin;
        d = tc * tsin + td * tcos;
        tx = ttx * tcos - tty * tsin;
        ty = ttx * tsin + tty * tcos;
    }
 
    /** \brief copy other matrix values.
      * \param other other matrix to copy from.
      * \return void.
      */
    void copyFrom(const matrix2D* other)
    {
        a = other->a;
        b = other->b;
        c = other->c;
        d = other->d;
        tx = other->tx;
        ty = other->ty;
    }
 
    /** \brief transform a x y coordinate.
      * \param x value to modify.
      * \param y value to modify.
      * \return void.
      */
    void transformXY(f32 &x, f32 &y)
    {
        f32 rx = x;
        f32 ry = y;
        x = rx * a + ry * c + tx;
        y = rx * b + ry * d + ty;
    }
};
 
the actual implementation. Here is a simple class called simpleObject to give deeper example of how to use the matrix2d class to draw 2d graphics

Code: Select all

 
class simpleObject
{
    // hurrah all public :D :D
public:
    ITexture* texture; // texture being use
    f32 x;  // x position in screen space
    f32 y;  // y position in screen space
    f32 scaleX;  // object scale
    f32 scaleY;  // object scale
    f32 centerX; // center of the object. act like a pivot point for rotation etc
    f32 centerY; // center of the object. act like a pivot point for rotation etc
    f32 rotation;   // rotation in radians
    f32 width;  // object width
    f32 height; // object height
    matrix2D* transformation; // object transformation matrix
    matrix2D* deviceSpaceTransformation; // transformation to bring x y values to device space
    S3DVertex vertices[4];  // 4 vertices for a quad
    u16 indices[6]; // 6 indices for 2 triangle
    SMaterial mat;
 
 
    // simple constructor
    simpleObject(ITexture* texture, f32 x, f32 y, f32 width, f32 height)
    {
        this->width = width;
        this->height = height;
        this->x = x;
        this->y = y;
        this->texture = texture;
        centerX = 0;
        centerY = 0;
        scaleX = 1;
        scaleY = 1;
        rotation = 0;
        transformation = new matrix2D();
        deviceSpaceTransformation = new matrix2D();
 
        /// initiate vertices, just basic vertex type for now.
        vertices[0] = video::S3DVertex(0.0f,0.0f,0.0f,0,0,1,video::SColor(255,255,255,255),  0,  0);
        vertices[1] = video::S3DVertex(0.0f,0.0f,0.0f,0,0,1,video::SColor(255,255,255,255),1.0,  0);
        vertices[2] = video::S3DVertex(0.0f,0.0f,0.0f,0,0,1,video::SColor(255,255,255,255),1.0,1.0);
        vertices[3] = video::S3DVertex(0.0f,0.0f,0.0f,0,0,1,video::SColor(255,255,255,255),0.0,1.0);
 
        // initiate indices
        indices[0] = 0;
        indices[1] = 1;
        indices[2] = 2;
        indices[3] = 2;
        indices[4] = 3;
        indices[5] = 0;
 
        // setting up material
        mat.setTexture(0, texture);
        mat.setFlag(EMF_BACK_FACE_CULLING, false);  // afraid of negative scale
        mat.setFlag(EMF_LIGHTING, false);   // no lighting
        mat.MaterialType = EMT_SOLID;   // best way to blend transparent texture
        mat.BlendFactor = pack_textureBlendFunc(EBF_ONE, EBF_ONE_MINUS_SRC_ALPHA); // best way to blend transparent texture
        mat.BlendOperation = EBO_ADD; // best way to blend transparent texture
        mat.setFlag(EMF_ZWRITE_ENABLE, false); // no z operation
        mat.setFlag(EMF_ZBUFFER, false);    // no z operation
 
    }
 
    // finish this one your self :D
    ~simpleObject() {}
 
    // update routine
    void update()
    {
        // routine update
        // can do a lot of stuff here. key frame animation? sprite sheet animation? :D
        // for now just go vanilla and calculate transformation
        transformation->identity();
        transformation->translate( - centerX, - centerY);
        transformation->scale(scaleX, scaleY);
        transformation->rotate(rotation);
        transformation->translate(x,y);
    }
 
    // method for drawing this object
    void render(IVideoDriver* driver, matrix2D* deviceSpaceMatrix)
    {
        // concatenate the 2 important matrices;
        deviceSpaceTransformation->identity();
        deviceSpaceTransformation->copyFrom(transformation);
        deviceSpaceTransformation->concat(deviceSpaceMatrix);
 
        // update vertices position
        // by transforming all 4 vertices
        f32 x1 = 0;
        f32 y1 = 0;
        f32 x2 = width;
        f32 y2 = 0;
        f32 x3 = width;
        f32 y3 = height;
        f32 x4 = 0;
        f32 y4 = height;
 
        deviceSpaceTransformation->transformXY(x1,y1);
        deviceSpaceTransformation->transformXY(x2,y2);
        deviceSpaceTransformation->transformXY(x3,y3);
        deviceSpaceTransformation->transformXY(x4,y4);
 
        vertices[0].Pos.X = x1;
        vertices[0].Pos.Y = y1;
        vertices[1].Pos.X = x2;
        vertices[1].Pos.Y = y2;
        vertices[2].Pos.X = x3;
        vertices[2].Pos.Y = y3;
        vertices[3].Pos.X = x4;
        vertices[3].Pos.Y = y4;
 
        // all done.
        // lets draw our object
        driver->setMaterial(mat);
        driver->drawIndexedTriangleList(&vertices[0], 4, &indices[0], 2);
    }
 
    // get pixel color from screen coordinate
    SColor getColorFromScreenXY(f32 screenX, f32 screenY)
    {
        // invert transformation
        transformation->invert();
 
        // transform our screen position
        f32 ptx = screenX;
        f32 pty = screenY;
        transformation->transformXY(ptx, pty);
 
        // no negative
        if((ptx >= 0)&&(pty >= 0))
        {
            // get our pixel
            if(texture)
            {
                core::dimension2d<u32> dimension = texture->getOriginalSize();
                u32 ppx = (u32)round(ptx);
                u32 ppy = (u32)round(pty);
 
                // no out of bound
                if((ppx < dimension.Width) && (ppy < dimension.Height))
                {
                    u8 * texels = (u8 *)texture->lock(ETLM_READ_WRITE,0);
                    s32 pitch = texture->getPitch();
                    ECOLOR_FORMAT format = texture->getColorFormat();
                    s32 bytes = video::IImage::getBitsPerPixelFromFormat(format) / 8;
                    SColor texel = *(u32*)(texels + ((ppy * pitch) + (ppx * bytes)));
                    texture->unlock();
 
                    return texel;
                }
            }
        }
 
        return SColor(255,0,0,0);
    }
 
 
};
 
each object will has their own transformation matrix. the property scale, center, x,y, and rotation are used to build the transform matrix. then the transform matrix used to transform the vertices. for example if you use box2d for physics you can simply do object->setPosition(rigidBody->getPosition) and object->setRotation(rigidBody->getRotation()) etc etc. here is how we build the transformation matrix

Code: Select all

 
// update routine
    void update()
    {
        // routine update
        // can do a lot of stuff here. key frame animation? sprite sheet animation? or sync up with rigid body transformation. :D
        // for now just go vanilla and calculate transformation
        transformation->identity(); // reset the matrix first
        transformation->translate( - centerX, - centerY); // a nice pivot point for rotation and stuff
        transformation->scale(scaleX, scaleY); // apply scale
        transformation->rotate(rotation); // apply rotation
        transformation->translate(x,y); // finally apply translation
    }
 
transformation below are not enough to draw the object on to screen. we need to bring the object in to the device space. the device coordinate are from X axis -1 to 1 and Y axis from -1 to 1. how to do that? simple we need to create a matrix that transform any number into that space. here is an example to create matrix that transform any given point into device space coordinate.

Code: Select all

 
// calculate our device space matrix
        deviceSpaceMatrix->identity();
        deviceSpaceMatrix->scale( 1.0f / 800.0f * 2.0, 1.0 / 600.0f * 2.0); // asuming screen are 800x600. otherwise just change the 800x600 with any resolution.
        deviceSpaceMatrix->translate( -1.0, -1.0);
        deviceSpaceMatrix->scale( 1.0, -1.0);// flip y axis so we dont need to flip our thought.
 
so we have the object transform matrix and the deviceSpace matrix. all we need to do is concatenate the transform matrix with the deviceSPaceMatrix. The code below shows part of the simpleObject class that combine the two matrices, and use the resulting matrix to transform all our vertices.

Code: Select all

 
    // method for drawing this object
    void render(IVideoDriver* driver, matrix2D* deviceSpaceMatrix)
    {
        // concatenate the 2 important matrices;
        deviceSpaceTransformation->identity();
        deviceSpaceTransformation->copyFrom(transformation);
        deviceSpaceTransformation->concat(deviceSpaceMatrix);
 
        // update vertices position
        // by transforming all 4 vertices
        f32 x1 = 0;
        f32 y1 = 0;
        f32 x2 = width;
        f32 y2 = 0;
        f32 x3 = width;
        f32 y3 = height;
        f32 x4 = 0;
        f32 y4 = height;
 
        deviceSpaceTransformation->transformXY(x1,y1);
        deviceSpaceTransformation->transformXY(x2,y2);
        deviceSpaceTransformation->transformXY(x3,y3);
        deviceSpaceTransformation->transformXY(x4,y4);
 
        vertices[0].Pos.X = x1;
        vertices[0].Pos.Y = y1;
        vertices[1].Pos.X = x2;
        vertices[1].Pos.Y = y2;
        vertices[2].Pos.X = x3;
        vertices[2].Pos.Y = y3;
        vertices[3].Pos.X = x4;
        vertices[3].Pos.Y = y4;
 
        // all done.
        // lets draw our object
        driver->setMaterial(mat);
        driver->drawIndexedTriangleList(&vertices[0], 4, &indices[0], 2);
    }
for the whole thing to work we need to set our video driver transformation, so all vertices drawn correctly. the code below show how to switch between 3d and 2d drawing. first set the transformation for 3D scene. we can obtain the transformation from the active camera. then set the transformation to identity so our 2d matrix can work.

Code: Select all

 
        // reset transform for 3d rendering
        ICameraSceneNode* activeCam = smgr->getActiveCamera();
        if(activeCam)
        {
            driver->setTransform(video::ETS_VIEW, activeCam->getViewMatrix());
            driver->setTransform(video::ETS_PROJECTION, activeCam->getProjectionMatrix());
        }
 
        smgr->drawAll();
        guienv->drawAll();
 
        // reset transform form 2d rendering
        device->getVideoDriver()->setTransform(ETS_VIEW, matrix4());
        device->getVideoDriver()->setTransform(ETS_PROJECTION, matrix4());
        device->getVideoDriver()->setTransform(ETS_WORLD, matrix4());
 
for 2d blending, based on my expirience we can setup a simple material as follow for the best 2d drawing.

Code: Select all

 
        // setting up material
        mat.setTexture(0, texture);
        mat.setFlag(EMF_BACK_FACE_CULLING, false);  // afraid of negative scale
        mat.setFlag(EMF_LIGHTING, false);   // no lighting
        mat.MaterialType = EMT_SOLID;   // best way to blend transparent texture
        mat.BlendFactor = pack_textureBlendFunc(EBF_ONE, EBF_ONE_MINUS_SRC_ALPHA); // best way to blend transparent texture
        mat.BlendOperation = EBO_ADD; // best way to blend transparent texture
        mat.setFlag(EMF_ZWRITE_ENABLE, false); // no z operation
        mat.setFlag(EMF_ZBUFFER, false);    // no z operation
 
finally example how the main function looks like

Code: Select all

 
int main(int argc, char** argv)
{
 
    IrrlichtDevice *device =
        createDevice(EDT_OPENGL, dimension2d<u32>(800, 600), 32,
                     false, false, false, 0);
 
    device->setWindowCaption(L"Hello World! - Irrlicht Engine Demo");
 
    IVideoDriver* driver = device->getVideoDriver();
    ISceneManager* smgr = device->getSceneManager();
    IGUIEnvironment* guienv = device->getGUIEnvironment();
 
    guienv->addStaticText(L"Hello World! This is the Irrlicht Software renderer!",
                          rect<int>(10,10,200,22), true);
 
    // create the matrix
    matrix2D* deviceSpaceMatrix = new matrix2D();
 
    // create our objects
    driver->setTextureCreationFlag(ETCF_CREATE_MIP_MAPS, false); // 2d no need for mip maps
    irr::core::array<simpleObject*> objects;
    simpleObject* object1 = new simpleObject(driver->getTexture("./bambu.png"), 50,50,100,100);
    simpleObject* object2 = new simpleObject(driver->getTexture("./kerokot.png"), 0,0,200,400);
    object2->centerX = 100;
    object2->centerY = 50;
    object2->x = 400;
    object2->y = 300;
 
    objects.push_back(object1);
    objects.push_back(object2);
    f32 rotation = 0;
 
    SColor textureColorFromMouse(255,255,255,255);
 
    //search_me
    // this is the texture lock before beginScene() & endScene()
    textureColorFromMouse = object1->getColorFromScreenXY( 5, 5);
 
    while(device->run())
    {
        driver->beginScene(true, true, SColor(0,200,200,200));
 
        // reset transform for 3d rendering
        ICameraSceneNode* activeCam = smgr->getActiveCamera();
        if(activeCam)
        {
            driver->setTransform(video::ETS_VIEW, activeCam->getViewMatrix());
            driver->setTransform(video::ETS_PROJECTION, activeCam->getProjectionMatrix());
        }
 
        smgr->drawAll();
        guienv->drawAll();
 
        // reset transform form 2d rendering
        device->getVideoDriver()->setTransform(ETS_VIEW, matrix4());
        device->getVideoDriver()->setTransform(ETS_PROJECTION, matrix4());
        device->getVideoDriver()->setTransform(ETS_WORLD, matrix4());
 
        // calculate our device space matrix
        deviceSpaceMatrix->identity();
        deviceSpaceMatrix->scale( 1.0f / 800.0f * 2.0, 1.0 / 600.0f * 2.0);
        deviceSpaceMatrix->translate( -1.0, -1.0);
        deviceSpaceMatrix->scale( 1.0, -1.0);
 
        rotation += 0.01f;
        object2->rotation = rotation;
 
        // update and render all object
        for(u8 i= 0 ; i < objects.size() ; i++)
        {
            objects[i]->update();
            objects[i]->render(driver, deviceSpaceMatrix);
        }
 
        // test get objects pixel
        position2d<s32> p = device->getCursorControl()->getPosition();
 
        //search_me
        // this is the textureLock inside beginScene() endScene()
        textureColorFromMouse = object2->getColorFromScreenXY( p.X, p.Y);
 
        driver->draw2DRectangle(SColor(255,0,0,0), rect<s32>(0,500,100, 600));
        driver->draw2DRectangle(textureColorFromMouse, rect<s32>(5,505,95, 595));
 
 
        driver->endScene();
        device->sleep(30);
    }
 
    device->drop();
 
    return 0;
}
 
have fun
chronologicaldot
Competition winner
Posts: 684
Joined: Mon Sep 10, 2012 8:51 am

Re: 2d matrix for 2d transformation and drawing

Post by chronologicaldot »

Thanks! I might use this... :)
I thought there may have been a sprite engine/wrapper for Irrlicht created some time ago, but I can't find it.
funkey
Posts: 4
Joined: Wed Jul 18, 2018 12:57 pm

Re: 2d matrix for 2d transformation and drawing

Post by funkey »

Quick semi tested mouse click

Code: Select all

 
s32 test_point(f32 x, f32 y)
{
    // invert transformation
    transformation->invert();
 
    // transform our screen position
    f32 ptx = x;
    f32 pty = y;
    transformation->transformXY(ptx, pty);
 
    // no negative
    if((ptx >= 0)&&(pty >= 0))
    {
        core::dimension2d<u32> dimension = texture->getOriginalSize();
        u32 ppx = (u32)round(ptx);
        u32 ppy = (u32)round(pty);
        
        //if((ppx < scaleX) && (ppy < scaleY))
        if((ppx < width) && (ppy < height))
        {
            return 1;
        }
    }
 
    return 0;
}
 
Stella365
Posts: 1
Joined: Thu Nov 15, 2018 9:02 pm

Re: 2d matrix for 2d transformation and drawing

Post by Stella365 »

Thanks a lot, kornwaretm!
Post Reply