2d matrix for 2d transformation and drawing
Posted: Thu Dec 22, 2016 5:14 pm
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.
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
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
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.
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.
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.
for 2d blending, based on my expirience we can setup a simple material as follow for the best 2d drawing.
finally example how the main function looks like
have fun
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
- 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
- matrix->translate(400 - 25, 300 -25)
- and transform the vertices
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;
}
};
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);
}
};
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
}
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.
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);
}
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());
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
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;
}