This method is basically a rewrite of the drawing2dbatch function, but using rectangles, and clipping them against the current rendertarget and, if provided, a clipping rectangle.
Note that i have only tested it with the screen resolution, and not the clipping against a rectangle, so it is posible it is buggy.
The main reason to make things like this is that the method for filtering 2D drawing routines, while it is useful it is also a bit limiting due to the fact that you have to enable and disable it between drawing calls, and that seems to be slow. This way, not only you draw and scale, it is also filtered and it draws several images at once.
I hope you find it useful!
Code: Select all
void CD3D9Driver::draw2DImageBatch(const video::ITexture* texture,
const core::array<core::rect<s32> >& destinationRects, //positions
const core::array<core::rect<s32> >& sourceRects,
const core::rect<s32>* clipRect,
SColor color,
bool useAlphaChannelOfTexture)
{
if (!texture)
return;
if (!setActiveTexture(0, const_cast<video::ITexture*>(texture)))
return;
const irr::u32 drawCount = core::min_<u32>(destinationRects.size(), sourceRects.size());
core::array<S3DVertex> vtx(drawCount * 4);
core::array<u16> indices(drawCount * 6);
for(u32 i = 0;i < drawCount;i++)
{
core::position2d<s32> targetPos = destinationRects[i].UpperLeftCorner;
core::position2d<s32> sourcePos = sourceRects[i].UpperLeftCorner;
// This needs to be signed as it may go negative.
core::dimension2d<s32> sourceSize(sourceRects[i].getSize());
core::dimension2d<s32> destinationSize(destinationRects[i].getSize());
const core::dimension2d<u32>& renderTargetSize = getCurrentRenderTargetSize();
s32 originalSize,shift;
core::rect<s32> currentClipRect(0,0,renderTargetSize.Width,renderTargetSize.Height);
//The original clip is the screen, or the current renderTarget.
//This way we supress extra, and potentially undesirable modifications to the sizes
//It is actually faster (no extra divisions or multiplications, only additions and substractions)
//It only relies on Irrlitch code, which is portable
//And we make sure the clipping works as expected in EVERY situation.
if(clipRect){
if(clipRect->UpperLeftCorner.X > currentClipRect.UpperLeftCorner.X){ currentClipRect.UpperLeftCorner.X=clipRect->UpperLeftCorner.X;}
if(clipRect->UpperLeftCorner.Y > currentClipRect.UpperLeftCorner.Y){ currentClipRect.UpperLeftCorner.Y=clipRect->UpperLeftCorner.Y;}
if(clipRect->LowerRightCorner.X < currentClipRect.LowerRightCorner.X){ currentClipRect.LowerRightCorner.X=clipRect->LowerRightCorner.X;}
if(clipRect->LowerRightCorner.Y < currentClipRect.LowerRightCorner.Y){ currentClipRect.LowerRightCorner.Y=clipRect->LowerRightCorner.Y;}
}
//We intersect the clipping rectangles here.
if (targetPos.X < currentClipRect.UpperLeftCorner.X)
{
shift=(targetPos.X-currentClipRect.UpperLeftCorner.X)*sourceSize.Width/destinationSize.Width;
destinationSize.Width += targetPos.X - currentClipRect.UpperLeftCorner.X;
if (destinationSize.Width <= 0)
continue;
sourcePos.X -= shift;
sourceSize.Width += shift;
targetPos.X = currentClipRect.UpperLeftCorner.X;
}
if (targetPos.X + (s32)destinationSize.Width > currentClipRect.LowerRightCorner.X)
{
originalSize=destinationSize.Width;
destinationSize.Width -= (targetPos.X + destinationSize.Width) - currentClipRect.LowerRightCorner.X;
if (destinationSize.Width <= 0)
continue;
sourceSize.Width = destinationSize.Width*sourceSize.Width/originalSize;
}
if (targetPos.Y < currentClipRect.UpperLeftCorner.Y)
{
shift=(targetPos.Y-currentClipRect.UpperLeftCorner.Y)*sourceSize.Height/destinationSize.Height;
destinationSize.Height += targetPos.Y - currentClipRect.UpperLeftCorner.Y;
if (destinationSize.Height <= 0)
continue;
sourcePos.Y -= shift;
sourceSize.Height += shift;
targetPos.Y = currentClipRect.UpperLeftCorner.Y;
}
if (targetPos.Y + (s32)destinationSize.Height > currentClipRect.LowerRightCorner.Y)
{
originalSize=destinationSize.Height;
destinationSize.Height -= (targetPos.Y + destinationSize.Height) - currentClipRect.LowerRightCorner.Y;
if (destinationSize.Height <= 0)
continue;
sourceSize.Height = destinationSize.Height*sourceSize.Height/originalSize;
}
// ok, we've clipped everything.
// now draw it.
core::rect<f32> tcoords;
tcoords.UpperLeftCorner.X = (((f32)sourcePos.X)) / texture->getOriginalSize().Width ;
tcoords.UpperLeftCorner.Y = (((f32)sourcePos.Y)) / texture->getOriginalSize().Height;
tcoords.LowerRightCorner.X = tcoords.UpperLeftCorner.X + ((f32)(sourceSize.Width) / texture->getOriginalSize().Width);
tcoords.LowerRightCorner.Y = tcoords.UpperLeftCorner.Y + ((f32)(sourceSize.Height) / texture->getOriginalSize().Height);
const core::rect<s32> poss(targetPos, destinationSize);
setRenderStates2DMode(color.getAlpha()<255, true, useAlphaChannelOfTexture);
vtx.push_back(S3DVertex((f32)poss.UpperLeftCorner.X, (f32)poss.UpperLeftCorner.Y, 0.0f,
0.0f, 0.0f, 0.0f, color,
tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y));
vtx.push_back(S3DVertex((f32)poss.LowerRightCorner.X, (f32)poss.UpperLeftCorner.Y, 0.0f,
0.0f, 0.0f, 0.0f, color,
tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y));
vtx.push_back(S3DVertex((f32)poss.LowerRightCorner.X, (f32)poss.LowerRightCorner.Y, 0.0f,
0.0f, 0.0f, 0.0f, color,
tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y));
vtx.push_back(S3DVertex((f32)poss.UpperLeftCorner.X, (f32)poss.LowerRightCorner.Y, 0.0f,
0.0f, 0.0f, 0.0f, color,
tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y));
const u32 curPos = vtx.size()-4;
indices.push_back(0+curPos);
indices.push_back(1+curPos);
indices.push_back(2+curPos);
indices.push_back(0+curPos);
indices.push_back(2+curPos);
indices.push_back(3+curPos);
}
if (vtx.size())
{
setVertexShader(EVT_STANDARD);
pID3DDevice->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, vtx.size(), indices.size() / 3, indices.pointer(),
D3DFMT_INDEX16,vtx.pointer(), sizeof(S3DVertex));
}
}
EDIT: added driver independent version.
Code: Select all
void draw2DScaledBatch(
video::ITexture* texture,
const core::array<core::recti>& destinationRects,
const core::array<core::recti>& sourceRects,
const core::recti* clipRect,
video::SColor& col,
bool useAlpha)
{
if(!texture)
{
return;
}
const core::dimension2d<u32>& renderTargetSize = drv->getCurrentRenderTargetSize();
video::SMaterial mat;
mat.setTexture(0,texture);
//IF there was another option on the parameters, we could allow more blending modes for the 2D drawings
//such as additive or substractive blending, besides the regular alpha blend. It is left like this to achieve the same //effect of the regular 2D functions of Irrlicht, but can be easily modified. exercise left to the reader :)
mat.MaterialType = useAlpha ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID;
mat.ZWriteEnable = false;
mat.Lighting = false;
mat.FrontfaceCulling = false;
mat.BackfaceCulling = false;
mat.MaterialTypeParam = 0;
const u32 drawCount = core::min_<u32>(destinationRects.size(), sourceRects.size());
core::array<video::S3DVertex> vtx(drawCount * 4);
core::array<u16> indices(drawCount * 6);
for(u32 i = 0;i < drawCount;i++)
{
core::position2d<s32> targetPos = destinationRects[i].UpperLeftCorner;
core::position2d<s32> sourcePos = sourceRects[i].UpperLeftCorner;
// This needs to be signed as it may go negative.
core::dimension2d<s32> sourceSize(sourceRects[i].getSize());
core::dimension2d<s32> destinationSize(destinationRects[i].getSize());
s32 originalSize,shift;
core::rect<s32> currentClipRect(0,0,renderTargetSize.Width,renderTargetSize.Height);
//The original clip is the screen, or the current renderTarget.
//This way we supress extra modifications to the sizes
//And we make sure the clipping works as expected.
if(clipRect)
{
if(clipRect->UpperLeftCorner.X > currentClipRect.UpperLeftCorner.X)
{
currentClipRect.UpperLeftCorner.X=clipRect->UpperLeftCorner.X;
}
if(clipRect->UpperLeftCorner.Y > currentClipRect.UpperLeftCorner.Y)
{
currentClipRect.UpperLeftCorner.Y=clipRect->UpperLeftCorner.Y;
}
if(clipRect->LowerRightCorner.X < currentClipRect.LowerRightCorner.X)
{
currentClipRect.LowerRightCorner.X=clipRect->LowerRightCorner.X;
}
if(clipRect->LowerRightCorner.Y < currentClipRect.LowerRightCorner.Y)
{
currentClipRect.LowerRightCorner.Y=clipRect->LowerRightCorner.Y;
}
}
//Then, we intersect the clipping rects here.
if (targetPos.X < currentClipRect.UpperLeftCorner.X)
{
shift=(targetPos.X-currentClipRect.UpperLeftCorner.X)*sourceSize.Width/destinationSize.Width;
destinationSize.Width += targetPos.X - currentClipRect.UpperLeftCorner.X;
if (destinationSize.Width <= 0)
continue;
sourcePos.X -= shift;
sourceSize.Width += shift;
targetPos.X = currentClipRect.UpperLeftCorner.X;
}
if (targetPos.X + (s32)destinationSize.Width > currentClipRect.LowerRightCorner.X)
{
originalSize=destinationSize.Width;
destinationSize.Width -= (targetPos.X + destinationSize.Width) - currentClipRect.LowerRightCorner.X;
if (destinationSize.Width <= 0)
continue;
sourceSize.Width = destinationSize.Width*sourceSize.Width/originalSize;
}
if (targetPos.Y < currentClipRect.UpperLeftCorner.Y)
{
shift=(targetPos.Y-currentClipRect.UpperLeftCorner.Y)*sourceSize.Height/destinationSize.Height;
destinationSize.Height += targetPos.Y - currentClipRect.UpperLeftCorner.Y;
if (destinationSize.Height <= 0)
continue;
sourcePos.Y -= shift;
sourceSize.Height += shift;
targetPos.Y = currentClipRect.UpperLeftCorner.Y;
}
if (targetPos.Y + (s32)destinationSize.Height > currentClipRect.LowerRightCorner.Y)
{
originalSize=destinationSize.Height;
destinationSize.Height -= (targetPos.Y + destinationSize.Height) - currentClipRect.LowerRightCorner.Y;
if (destinationSize.Height <= 0)
continue;
sourceSize.Height = destinationSize.Height*sourceSize.Height/originalSize;
}
// ok, we've soft' clipped everything.
// now draw it.
core::rect<f32> tcoords;
tcoords.UpperLeftCorner.X = (((f32)sourcePos.X)) / texture->getOriginalSize().Width ;
tcoords.UpperLeftCorner.Y = (((f32)sourcePos.Y)) / texture->getOriginalSize().Height;
tcoords.LowerRightCorner.X = tcoords.UpperLeftCorner.X + ((f32)(sourceSize.Width) / texture->getOriginalSize().Width);
tcoords.LowerRightCorner.Y = tcoords.UpperLeftCorner.Y + ((f32)(sourceSize.Height) / texture->getOriginalSize().Height);
core::rect<s32> poss(targetPos, destinationSize);
vtx.push_back(video::S3DVertex((f32)poss.UpperLeftCorner.X, (f32)poss.UpperLeftCorner.Y, 0.0f,
0.0f, 0.0f, 0.0f, col,
tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y));
vtx.push_back(video::S3DVertex((f32)poss.LowerRightCorner.X, (f32)poss.UpperLeftCorner.Y, 0.0f,
0.0f, 0.0f, 0.0f, col,
tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y));
vtx.push_back(video::S3DVertex((f32)poss.LowerRightCorner.X, (f32)poss.LowerRightCorner.Y, 0.0f,
0.0f, 0.0f, 0.0f, col,
tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y));
vtx.push_back(video::S3DVertex((f32)poss.UpperLeftCorner.X, (f32)poss.LowerRightCorner.Y, 0.0f,
0.0f, 0.0f, 0.0f, col,
tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y));
const u32 curPos = vtx.size()-4;
indices.push_back(0+curPos);
indices.push_back(1+curPos);
indices.push_back(2+curPos);
indices.push_back(0+curPos);
indices.push_back(2+curPos);
indices.push_back(3+curPos);
}
if(vtx.size())
{
drv->setMaterial(mat);
core::matrix4 m;
drv->setTransform(video::ETS_WORLD, m);
m.setTranslation(core::vector3df(0.5,0.5,0));
drv->setTransform(video::ETS_VIEW, m);
m.buildProjectionMatrixOrthoLH(f32(renderTargetSize.Width), f32(-(s32)(renderTargetSize.Height)), -1.0, 1.0);
m.setTranslation(core::vector3df(-1,1,0));
drv->setTransform(video::ETS_PROJECTION, m);
drv->drawIndexedTriangleList(vtx.pointer(),vtx.size(),indices.pointer(),indices.size()/3);
}
}