// Pack textures into a larger one and return it. outPositions returns the new positions of the textures in the order they appeared in the array irr::video::ITexture* CMeshCombiner::packTextures(irr::video::IVideoDriver *driver, irr::core::array<irr::video::ITexture*> textures, irr::core::array<irr::core::position2di> &outPositions) { irr::video::ITexture* newTexture; irr::core::array<irr::core::rect<irr::u32>> textureRects; for (int x = 0; x < textures.size(); x++) { textureRects.push_back(irr::core::rect<irr::u32>(0,0,textures[x]->getSize().Width+mTexturePadding,textures[x]->getSize().Height+mTexturePadding)); } irr::core::dimension2du textureSize = findOptimalPackingArea(textureRects); irr::core::array<irr::core::array<CRectPacker::SPacked>> packed; CRectPacker packer; packer.pack(textureRects,packed,textureSize.Height); irr::video::IImage* packedImage = driver->createImage(textures[0]->getColorFormat(),textureSize); irr::video::IImage** textureImages = new irr::video::IImage*[textures.size()]; for (int x = 0; x < textures.size(); x++) { textureImages[x] = driver->createImage(textures[x],irr::core::vector2di(0,0),textures[x]->getSize()); outPositions.push_back(irr::core::position2di(0,0)); } for (int x = 0; x < packed[0].size(); x++) { irr::video::IImage* im = textureImages[packed[0][x].id]; int xPos = packed[0][x].pos.UpperLeftCorner.X+mTexturePadding/2; int yPos = packed[0][x].pos.UpperLeftCorner.Y+mTexturePadding/2; im->copyTo(packedImage,irr::core::vector2di(xPos,yPos)); if (mTexturePaddingTechnique == ETPT_EXPAND) { for (int y = 0; y < mTexturePadding/2; y++) { im->copyTo(packedImage,irr::core::vector2di(xPos-y,yPos),irr::core::rect<irr::s32>(0,0,1,textures[packed[0][x].id]->getSize().Height)); im->copyTo(packedImage,irr::core::vector2di(xPos+y+textures[packed[0][x].id]->getSize().Width,yPos),irr::core::rect<irr::s32>(0,0,1,textures[packed[0][x].id]->getSize().Height)); im->copyTo(packedImage,irr::core::vector2di(xPos,yPos-y),irr::core::rect<irr::s32>(0,0,textures[packed[0][x].id]->getSize().Width,1)); im->copyTo(packedImage,irr::core::vector2di(xPos,yPos+y+textures[packed[0][x].id]->getSize().Height),irr::core::rect<irr::s32>(0,0,textures[packed[0][x].id]->getSize().Width,1)); } } else if (mTexturePaddingTechnique == ETPT_TILE) { im->copyTo(packedImage,irr::core::vector2di(xPos,yPos-mTexturePadding/2),irr::core::rect<irr::s32>(0,textures[packed[0][x].id]->getSize().Height-mTexturePadding/2,textures[packed[0][x].id]->getSize().Width,textures[packed[0][x].id]->getSize().Height)); im->copyTo(packedImage,irr::core::vector2di(xPos,yPos+textures[packed[0][x].id]->getSize().Height),irr::core::rect<irr::s32>(0,0,textures[packed[0][x].id]->getSize().Width,mTexturePadding/2)); im->copyTo(packedImage,irr::core::vector2di(xPos-mTexturePadding/2,yPos),irr::core::rect<irr::s32>(textures[packed[0][x].id]->getSize().Width-mTexturePadding/2,0,textures[packed[0][x].id]->getSize().Width,textures[packed[0][x].id]->getSize().Height)); im->copyTo(packedImage,irr::core::vector2di(xPos+textures[packed[0][x].id]->getSize().Width,yPos),irr::core::rect<irr::s32>(0,0,mTexturePadding/2,textures[packed[0][x].id]->getSize().Height)); im->copyTo(packedImage,irr::core::vector2di(xPos-mTexturePadding/2,yPos-mTexturePadding/2),irr::core::rect<irr::s32>(textures[packed[0][x].id]->getSize().Width-mTexturePadding/2,textures[packed[0][x].id]->getSize().Height-mTexturePadding/2,textures[packed[0][x].id]->getSize().Width,textures[packed[0][x].id]->getSize().Height)); im->copyTo(packedImage,irr::core::vector2di(xPos+textures[packed[0][x].id]->getSize().Width,yPos-mTexturePadding/2),irr::core::rect<irr::s32>(0,textures[packed[0][x].id]->getSize().Height-mTexturePadding/2,mTexturePadding/2,textures[packed[0][x].id]->getSize().Height)); im->copyTo(packedImage,irr::core::vector2di(xPos-mTexturePadding/2,yPos+textures[packed[0][x].id]->getSize().Height),irr::core::rect<irr::s32>(textures[packed[0][x].id]->getSize().Width-mTexturePadding/2,0,textures[packed[0][x].id]->getSize().Width,mTexturePadding/2)); im->copyTo(packedImage,irr::core::vector2di(xPos+textures[packed[0][x].id]->getSize().Width,yPos+textures[packed[0][x].id]->getSize().Height),irr::core::rect<irr::s32>(0,0,mTexturePadding/2,mTexturePadding/2)); } outPositions[packed[0][x].id].set(xPos,yPos); } irr::core::stringc textureName = "PackedTexture"; newTexture = driver->addTexture(textureName, packedImage); newTexture->regenerateMipMapLevels(); textureName += globalPackedTextureCount; globalPackedTextureCount++; return newTexture; }
// Pack textures into a larger one and return it. outPositions returns the new positions of the textures in the order they appeared in the array irr::video::ITexture* CMeshCombiner::packTextures(irr::video::IVideoDriver *driver, irr::core::array<irr::video::ITexture*> textures, irr::core::array<irr::core::position2di> &outPositions) { irr::video::ITexture* newTexture; int area = 0; for (int x = 0; x < textures.size(); x++) { area += textures[x]->getSize().getArea(); } float squareDim = sqrt((float)(area*1.5)); int nearestPow2 = ceil(log((float)squareDim)/log(2.0f)); int safePadding = pow(2.0f,nearestPow2)/32+4; if (safePadding == 0) safePadding = 1; irr::core::array<irr::core::rect<irr::u32>> textureRects; for (int x = 0; x < textures.size(); x++) { textureRects.push_back(irr::core::rect<irr::u32>(0,0,textures[x]->getSize().Width+safePadding,textures[x]->getSize().Height+safePadding)); } irr::core::dimension2du textureSize = findOptimalPackingArea(textureRects); // This is used to tell if an actual texture occupies a certain pixel bool *occupied = new bool[textureSize.getArea()]; for (int i = 0; i < textureSize.getArea(); i++) occupied[i] = false; irr::core::array<irr::core::array<CRectPacker::SPacked>> packed; CRectPacker packer; packer.pack(textureRects,packed,textureSize.Height); irr::video::IImage* packedImage = driver->createImage(textures[0]->getColorFormat(),textureSize); irr::video::IImage** textureImages = new irr::video::IImage*[textures.size()]; for (int x = 0; x < textures.size(); x++) { textureImages[x] = driver->createImage(textures[x],irr::core::vector2di(0,0),textures[x]->getSize()); outPositions.push_back(irr::core::position2di(0,0)); } for (int x = 0; x < packed[0].size(); x++) { irr::video::IImage* im = textureImages[packed[0][x].id]; int xPos = packed[0][x].pos.UpperLeftCorner.X+safePadding/2; int yPos = packed[0][x].pos.UpperLeftCorner.Y+safePadding/2; int width = packed[0][x].pos.getWidth(); int height = packed[0][x].pos.getWidth(); // Anywhere the texture exists, mark the pixel as occupied for (int u = xPos; u < xPos+width; u++) { for (int v = yPos; v < yPos+height; v++) { occupied[u+v*textureSize.Width] = true; } } im->copyTo(packedImage,irr::core::vector2di(xPos,yPos)); if (mTexturePaddingTechnique == ETPT_EXPAND) { for (int y = 0; y < safePadding/2; y++) { im->copyTo(packedImage,irr::core::vector2di(xPos-y,yPos),irr::core::rect<irr::s32>(0,0,1,textures[packed[0][x].id]->getSize().Height)); im->copyTo(packedImage,irr::core::vector2di(xPos+y+textures[packed[0][x].id]->getSize().Width,yPos),irr::core::rect<irr::s32>(0,0,1,textures[packed[0][x].id]->getSize().Height)); im->copyTo(packedImage,irr::core::vector2di(xPos,yPos-y),irr::core::rect<irr::s32>(0,0,textures[packed[0][x].id]->getSize().Width,1)); im->copyTo(packedImage,irr::core::vector2di(xPos,yPos+y+textures[packed[0][x].id]->getSize().Height),irr::core::rect<irr::s32>(0,0,textures[packed[0][x].id]->getSize().Width,1)); } } else if (mTexturePaddingTechnique == ETPT_TILE) { im->copyTo(packedImage,irr::core::vector2di(xPos,yPos-safePadding/2),irr::core::rect<irr::s32>(0,textures[packed[0][x].id]->getSize().Height-safePadding/2,textures[packed[0][x].id]->getSize().Width,textures[packed[0][x].id]->getSize().Height)); im->copyTo(packedImage,irr::core::vector2di(xPos,yPos+textures[packed[0][x].id]->getSize().Height),irr::core::rect<irr::s32>(0,0,textures[packed[0][x].id]->getSize().Width,safePadding/2)); im->copyTo(packedImage,irr::core::vector2di(xPos-safePadding/2,yPos),irr::core::rect<irr::s32>(textures[packed[0][x].id]->getSize().Width-safePadding/2,0,textures[packed[0][x].id]->getSize().Width,textures[packed[0][x].id]->getSize().Height)); im->copyTo(packedImage,irr::core::vector2di(xPos+textures[packed[0][x].id]->getSize().Width,yPos),irr::core::rect<irr::s32>(0,0,safePadding/2,textures[packed[0][x].id]->getSize().Height)); im->copyTo(packedImage,irr::core::vector2di(xPos-safePadding/2,yPos-safePadding/2),irr::core::rect<irr::s32>(textures[packed[0][x].id]->getSize().Width-safePadding/2,textures[packed[0][x].id]->getSize().Height-safePadding/2,textures[packed[0][x].id]->getSize().Width,textures[packed[0][x].id]->getSize().Height)); im->copyTo(packedImage,irr::core::vector2di(xPos+textures[packed[0][x].id]->getSize().Width,yPos-safePadding/2),irr::core::rect<irr::s32>(0,textures[packed[0][x].id]->getSize().Height-safePadding/2,safePadding/2,textures[packed[0][x].id]->getSize().Height)); im->copyTo(packedImage,irr::core::vector2di(xPos-safePadding/2,yPos+textures[packed[0][x].id]->getSize().Height),irr::core::rect<irr::s32>(textures[packed[0][x].id]->getSize().Width-safePadding/2,0,textures[packed[0][x].id]->getSize().Width,safePadding/2)); im->copyTo(packedImage,irr::core::vector2di(xPos+textures[packed[0][x].id]->getSize().Width,yPos+textures[packed[0][x].id]->getSize().Height),irr::core::rect<irr::s32>(0,0,safePadding/2,safePadding/2)); } outPositions[packed[0][x].id].set(xPos,yPos); } // Create mipmap data int mipmapSize = 0; int lowestDim = (packedImage->getDimension().Width < packedImage->getDimension().Height) ? packedImage->getDimension().Width : packedImage->getDimension().Height; int highestDim = (packedImage->getDimension().Width > packedImage->getDimension().Height) ? packedImage->getDimension().Width : packedImage->getDimension().Height; int mipmapCount = 0; for (mipmapCount = 0; lowestDim > 1; lowestDim/=2, highestDim /=2, mipmapCount++) { if (mipmapCount >= 1) mipmapSize += lowestDim*highestDim; } irr::u8* mipmapData = new irr::u8[mipmapSize*4]; irr::u8* imageData = (irr::u8*)packedImage->lock(); // To prevent bleeding artifacts, do NOT blend any unoccupied pixels with occupied pixels. If an unoccupied pixel would be blended with an occupied one, use only occupied pixels instead int currentIndex = 0; for (int i = 0; i < mipmapCount; i++) { int blendSize = pow(2.0,i+1); int currentLevelWidth = packedImage->getDimension().Width/blendSize; int currentLevelHeight = packedImage->getDimension().Height/blendSize; // Create mipmap of current level for (int x = 0; x < currentLevelWidth; x++) { for (int y = 0; y < currentLevelHeight; y++) { // Check to see if it has any occupied pixels that will be blended int occupiedPixelR, occupiedPixelG, occupiedPixelB, occupiedPixelA; occupiedPixelR = occupiedPixelG = occupiedPixelB = occupiedPixelA = 0; int occupiedPixelCount = 0; int unoccupiedPixelR, unoccupiedPixelG, unoccupiedPixelB, unoccupiedPixelA; unoccupiedPixelR = unoccupiedPixelG = unoccupiedPixelB = unoccupiedPixelA = 0; int unoccupiedPixelCount = 0; /*for (int u = x*blendSize; u < std::min(x*blendSize+blendSize,(int)packedImage->getDimension().Width); u++) { for (int v = y*blendSize; v < std::min(y*blendSize+blendSize,(int)packedImage->getDimension().Height); v++) { int realX = u*4; int realY = v*4; if (!occupied[u+v*packedImage->getDimension().Width]) { unoccupiedPixelB += imageData[realX+realY*packedImage->getDimension().Width]; unoccupiedPixelG += imageData[realX+realY*packedImage->getDimension().Width+1]; unoccupiedPixelR += imageData[realX+realY*packedImage->getDimension().Width+2]; unoccupiedPixelA += imageData[realX+realY*packedImage->getDimension().Width+3]; unoccupiedPixelCount++; } else { occupiedPixelB += imageData[realX+realY*packedImage->getDimension().Width]; occupiedPixelG += imageData[realX+realY*packedImage->getDimension().Width+1]; occupiedPixelR += imageData[realX+realY*packedImage->getDimension().Width+2]; occupiedPixelA += imageData[realX+realY*packedImage->getDimension().Width+3]; occupiedPixelCount++; } } }*/ if (occupiedPixelCount == 0 && unoccupiedPixelCount != 0) { unoccupiedPixelB /= unoccupiedPixelCount; unoccupiedPixelG /= unoccupiedPixelCount; unoccupiedPixelR /= unoccupiedPixelCount; unoccupiedPixelA /= unoccupiedPixelCount; mipmapData[currentIndex+x*4+y*4*currentLevelWidth] = unoccupiedPixelB; mipmapData[currentIndex+x*4+y*4*currentLevelWidth+1] = unoccupiedPixelG; mipmapData[currentIndex+x*4+y*4*currentLevelWidth+2] = unoccupiedPixelR; mipmapData[currentIndex+x*4+y*4*currentLevelWidth+3] = unoccupiedPixelA; } else if (occupiedPixelCount != 0) { occupiedPixelB /= occupiedPixelCount; occupiedPixelG /= occupiedPixelCount; occupiedPixelR /= occupiedPixelCount; occupiedPixelA /= occupiedPixelCount; mipmapData[currentIndex+x*4+y*4*currentLevelWidth] = occupiedPixelB; mipmapData[currentIndex+x*4+y*4*currentLevelWidth+1] = occupiedPixelG; mipmapData[currentIndex+x*4+y*4*currentLevelWidth+2] = occupiedPixelR; mipmapData[currentIndex+x*4+y*4*currentLevelWidth+3] = occupiedPixelA; } } } currentIndex += (currentLevelWidth*currentLevelHeight)*4; } packedImage->unlock(); irr::core::stringc textureName = "PackedTexture"; newTexture = driver->addTexture(textureName, packedImage, mipmapData); textureName += globalPackedTextureCount; globalPackedTextureCount++; delete [] occupied; for (int x = 0; x < textures.size(); x++) { textureImages[x]->drop(); } delete [] textureImages; packedImage->drop(); return newTexture; }