| 1 | //************************************ bs::framework - Copyright 2018 Marko Pintera **************************************// |
| 2 | //*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********// |
| 3 | #include "BsFreeImgImporter.h" |
| 4 | #include "Resources/BsResource.h" |
| 5 | #include "Debug/BsDebug.h" |
| 6 | #include "FileSystem/BsDataStream.h" |
| 7 | #include "Managers/BsTextureManager.h" |
| 8 | #include "Image/BsTexture.h" |
| 9 | #include "Importer/BsTextureImportOptions.h" |
| 10 | #include "FileSystem/BsFileSystem.h" |
| 11 | #include "BsCoreApplication.h" |
| 12 | #include "CoreThread/BsCoreThread.h" |
| 13 | #include "Math/BsMath.h" |
| 14 | #include "Math/BsVector2.h" |
| 15 | #include "Math/BsVector3.h" |
| 16 | #include "FreeImage.h" |
| 17 | #include "Utility/BsBitwise.h" |
| 18 | #include "Renderer/BsRenderer.h" |
| 19 | |
| 20 | using namespace std::placeholders; |
| 21 | |
| 22 | namespace bs |
| 23 | { |
| 24 | void FreeImageLoadErrorHandler(FREE_IMAGE_FORMAT fif, const char *message) |
| 25 | { |
| 26 | // Callback method as required by FreeImage to report problems |
| 27 | const char* typeName = FreeImage_GetFormatFromFIF(fif); |
| 28 | if (typeName) |
| 29 | { |
| 30 | gDebug().logError("FreeImage error: '" + String(message) + "' when loading format " + typeName); |
| 31 | } |
| 32 | else |
| 33 | { |
| 34 | gDebug().logError("FreeImage error: '" + String(message) + "'" ); |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | FreeImgImporter::FreeImgImporter() |
| 39 | { |
| 40 | FreeImage_Initialise(false); |
| 41 | |
| 42 | // Register codecs |
| 43 | StringStream strExt; |
| 44 | strExt << "Supported formats: " ; |
| 45 | bool first = true; |
| 46 | for (int i = 0; i < FreeImage_GetFIFCount(); ++i) |
| 47 | { |
| 48 | // Skip DDS codec since FreeImage does not have the option |
| 49 | // to keep DXT data compressed, we'll use our own codec |
| 50 | if ((FREE_IMAGE_FORMAT)i == FIF_DDS) |
| 51 | continue; |
| 52 | |
| 53 | String exts = String(FreeImage_GetFIFExtensionList((FREE_IMAGE_FORMAT)i)); |
| 54 | if (!first) |
| 55 | strExt << "," ; |
| 56 | |
| 57 | first = false; |
| 58 | strExt << exts; |
| 59 | |
| 60 | // Pull off individual formats (separated by comma by FI) |
| 61 | Vector<String> extsVector = StringUtil::split(exts, u8"," ); |
| 62 | for (auto v = extsVector.begin(); v != extsVector.end(); ++v) |
| 63 | { |
| 64 | auto findIter = std::find(mExtensions.begin(), mExtensions.end(), *v); |
| 65 | |
| 66 | if(findIter == mExtensions.end()) |
| 67 | { |
| 68 | String ext = *v; |
| 69 | StringUtil::toLowerCase(ext); |
| 70 | |
| 71 | mExtensionToFID.insert(std::make_pair(ext, i)); |
| 72 | mExtensions.push_back(ext); |
| 73 | } |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | // Set error handler |
| 78 | FreeImage_SetOutputMessage(FreeImageLoadErrorHandler); |
| 79 | } |
| 80 | |
| 81 | FreeImgImporter::~FreeImgImporter() |
| 82 | { |
| 83 | FreeImage_DeInitialise(); |
| 84 | } |
| 85 | |
| 86 | bool FreeImgImporter::isExtensionSupported(const String& ext) const |
| 87 | { |
| 88 | String lowerCaseExt = ext; |
| 89 | StringUtil::toLowerCase(lowerCaseExt); |
| 90 | |
| 91 | return find(mExtensions.begin(), mExtensions.end(), lowerCaseExt) != mExtensions.end(); |
| 92 | } |
| 93 | |
| 94 | bool FreeImgImporter::isMagicNumberSupported(const UINT8* magicNumPtr, UINT32 numBytes) const |
| 95 | { |
| 96 | String ext = magicNumToExtension(magicNumPtr, numBytes); |
| 97 | |
| 98 | return isExtensionSupported(ext); |
| 99 | } |
| 100 | |
| 101 | String FreeImgImporter::magicNumToExtension(const UINT8* magic, UINT32 maxBytes) const |
| 102 | { |
| 103 | // Set error handler |
| 104 | FreeImage_SetOutputMessage(FreeImageLoadErrorHandler); |
| 105 | |
| 106 | FIMEMORY* fiMem = |
| 107 | FreeImage_OpenMemory((BYTE*)magic, static_cast<DWORD>(maxBytes)); |
| 108 | |
| 109 | FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeFromMemory(fiMem, (int)maxBytes); |
| 110 | FreeImage_CloseMemory(fiMem); |
| 111 | |
| 112 | if (fif != FIF_UNKNOWN) |
| 113 | { |
| 114 | String ext = String(FreeImage_GetFormatFromFIF(fif)); |
| 115 | StringUtil::toLowerCase(ext); |
| 116 | return ext; |
| 117 | } |
| 118 | else |
| 119 | { |
| 120 | return StringUtil::BLANK; |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | SPtr<ImportOptions> FreeImgImporter::createImportOptions() const |
| 125 | { |
| 126 | return bs_shared_ptr_new<TextureImportOptions>(); |
| 127 | } |
| 128 | |
| 129 | SPtr<Resource> FreeImgImporter::import(const Path& filePath, SPtr<const ImportOptions> importOptions) |
| 130 | { |
| 131 | const TextureImportOptions* textureImportOptions = static_cast<const TextureImportOptions*>(importOptions.get()); |
| 132 | |
| 133 | SPtr<PixelData> imgData = importRawImage(filePath); |
| 134 | if(imgData == nullptr || imgData->getData() == nullptr) |
| 135 | return nullptr; |
| 136 | |
| 137 | Vector<SPtr<PixelData>> faceData; |
| 138 | |
| 139 | TextureType texType; |
| 140 | if(textureImportOptions->cubemap) |
| 141 | { |
| 142 | texType = TEX_TYPE_CUBE_MAP; |
| 143 | |
| 144 | std::array<SPtr<PixelData>, 6> cubemapFaces; |
| 145 | if (generateCubemap(imgData, textureImportOptions->cubemapSourceType, cubemapFaces)) |
| 146 | { |
| 147 | faceData.insert(faceData.begin(), cubemapFaces.begin(), cubemapFaces.end()); |
| 148 | } |
| 149 | else // Fall-back to 2D texture |
| 150 | { |
| 151 | texType = TEX_TYPE_2D; |
| 152 | faceData.push_back(imgData); |
| 153 | } |
| 154 | } |
| 155 | else |
| 156 | { |
| 157 | texType = TEX_TYPE_2D; |
| 158 | faceData.push_back(imgData); |
| 159 | } |
| 160 | |
| 161 | UINT32 numMips = 0; |
| 162 | if (textureImportOptions->generateMips && |
| 163 | Bitwise::isPow2(faceData[0]->getWidth()) && Bitwise::isPow2(faceData[0]->getHeight())) |
| 164 | { |
| 165 | UINT32 maxPossibleMip = PixelUtil::getMaxMipmaps(faceData[0]->getWidth(), faceData[0]->getHeight(), |
| 166 | faceData[0]->getDepth(), faceData[0]->getFormat()); |
| 167 | |
| 168 | if (textureImportOptions->maxMip == 0) |
| 169 | numMips = maxPossibleMip; |
| 170 | else |
| 171 | numMips = std::min(maxPossibleMip, textureImportOptions->maxMip); |
| 172 | } |
| 173 | |
| 174 | int usage = TU_DEFAULT; |
| 175 | if (textureImportOptions->cpuCached) |
| 176 | usage |= TU_CPUCACHED; |
| 177 | |
| 178 | bool sRGB = textureImportOptions->sRGB; |
| 179 | |
| 180 | TEXTURE_DESC texDesc; |
| 181 | texDesc.type = texType; |
| 182 | texDesc.width = faceData[0]->getWidth(); |
| 183 | texDesc.height = faceData[0]->getHeight(); |
| 184 | texDesc.numMips = numMips; |
| 185 | texDesc.format = textureImportOptions->format; |
| 186 | texDesc.usage = usage; |
| 187 | texDesc.hwGamma = sRGB; |
| 188 | |
| 189 | SPtr<Texture> newTexture = Texture::_createPtr(texDesc); |
| 190 | |
| 191 | UINT32 numFaces = (UINT32)faceData.size(); |
| 192 | for (UINT32 i = 0; i < numFaces; i++) |
| 193 | { |
| 194 | Vector<SPtr<PixelData>> mipLevels; |
| 195 | if (numMips > 0) |
| 196 | { |
| 197 | MipMapGenOptions mipOptions; |
| 198 | mipOptions.isSRGB = sRGB; |
| 199 | |
| 200 | mipLevels = PixelUtil::genMipmaps(*faceData[i], mipOptions); |
| 201 | } |
| 202 | else |
| 203 | mipLevels.push_back(faceData[i]); |
| 204 | |
| 205 | for (UINT32 mip = 0; mip < (UINT32)mipLevels.size(); ++mip) |
| 206 | { |
| 207 | SPtr<PixelData> dst = newTexture->getProperties().allocBuffer(0, mip); |
| 208 | |
| 209 | PixelUtil::bulkPixelConversion(*mipLevels[mip], *dst); |
| 210 | newTexture->writeData(dst, i, mip); |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | const String fileName = filePath.getFilename(false); |
| 215 | newTexture->setName(fileName); |
| 216 | |
| 217 | return newTexture; |
| 218 | } |
| 219 | |
| 220 | SPtr<PixelData> FreeImgImporter::importRawImage(const Path& filePath) |
| 221 | { |
| 222 | UPtr<MemoryDataStream> memStream; |
| 223 | FREE_IMAGE_FORMAT imageFormat; |
| 224 | { |
| 225 | Lock lock = FileScheduler::getLock(filePath); |
| 226 | |
| 227 | SPtr<DataStream> fileData = FileSystem::openFile(filePath, true); |
| 228 | if (fileData->size() > std::numeric_limits<UINT32>::max()) |
| 229 | { |
| 230 | BS_EXCEPT(InternalErrorException, "File size larger than supported!" ); |
| 231 | } |
| 232 | |
| 233 | UINT32 magicLen = std::min((UINT32)fileData->size(), 32u); |
| 234 | UINT8 magicBuf[32]; |
| 235 | fileData->read(magicBuf, magicLen); |
| 236 | fileData->seek(0); |
| 237 | |
| 238 | String fileExtension = magicNumToExtension(magicBuf, magicLen); |
| 239 | auto findFormat = mExtensionToFID.find(fileExtension); |
| 240 | if (findFormat == mExtensionToFID.end()) |
| 241 | { |
| 242 | BS_EXCEPT(InvalidParametersException, "Type of the file provided is not supported by this importer. File type: " + fileExtension); |
| 243 | } |
| 244 | |
| 245 | imageFormat = (FREE_IMAGE_FORMAT)findFormat->second; |
| 246 | |
| 247 | // Set error handler |
| 248 | FreeImage_SetOutputMessage(FreeImageLoadErrorHandler); |
| 249 | |
| 250 | // Buffer stream into memory (TODO: override IO functions instead?) |
| 251 | memStream = bs_unique_ptr_new<MemoryDataStream>(fileData); |
| 252 | fileData->close(); |
| 253 | } |
| 254 | |
| 255 | if(!memStream) |
| 256 | return nullptr; |
| 257 | |
| 258 | FIMEMORY* fiMem = FreeImage_OpenMemory(memStream->getPtr(), static_cast<DWORD>(memStream->size())); |
| 259 | |
| 260 | FIBITMAP* fiBitmap = FreeImage_LoadFromMemory( |
| 261 | (FREE_IMAGE_FORMAT)imageFormat, fiMem); |
| 262 | if (!fiBitmap) |
| 263 | { |
| 264 | BS_EXCEPT(InternalErrorException, "Error decoding image" ); |
| 265 | } |
| 266 | |
| 267 | UINT32 width = FreeImage_GetWidth(fiBitmap); |
| 268 | UINT32 height = FreeImage_GetHeight(fiBitmap); |
| 269 | PixelFormat format = PF_UNKNOWN; |
| 270 | |
| 271 | // Must derive format first, this may perform conversions |
| 272 | |
| 273 | FREE_IMAGE_TYPE imageType = FreeImage_GetImageType(fiBitmap); |
| 274 | FREE_IMAGE_COLOR_TYPE colourType = FreeImage_GetColorType(fiBitmap); |
| 275 | unsigned bpp = FreeImage_GetBPP(fiBitmap); |
| 276 | unsigned srcElemSize = 0; |
| 277 | |
| 278 | switch(imageType) |
| 279 | { |
| 280 | case FIT_UNKNOWN: |
| 281 | case FIT_COMPLEX: |
| 282 | case FIT_UINT32: |
| 283 | case FIT_INT32: |
| 284 | case FIT_DOUBLE: |
| 285 | default: |
| 286 | BS_EXCEPT(InternalErrorException, "Unknown or unsupported image format" ); |
| 287 | |
| 288 | break; |
| 289 | case FIT_BITMAP: |
| 290 | // Standard image type |
| 291 | // Perform any colour conversions for greyscale |
| 292 | if (colourType == FIC_MINISWHITE || colourType == FIC_MINISBLACK) |
| 293 | { |
| 294 | FIBITMAP* newBitmap = FreeImage_ConvertToGreyscale(fiBitmap); |
| 295 | // free old bitmap and replace |
| 296 | FreeImage_Unload(fiBitmap); |
| 297 | fiBitmap = newBitmap; |
| 298 | // get new formats |
| 299 | bpp = FreeImage_GetBPP(fiBitmap); |
| 300 | colourType = FreeImage_GetColorType(fiBitmap); |
| 301 | } |
| 302 | // Perform any colour conversions for RGB |
| 303 | else if (bpp < 8 || colourType == FIC_PALETTE || colourType == FIC_CMYK) |
| 304 | { |
| 305 | FIBITMAP* newBitmap = FreeImage_ConvertTo24Bits(fiBitmap); |
| 306 | // free old bitmap and replace |
| 307 | FreeImage_Unload(fiBitmap); |
| 308 | fiBitmap = newBitmap; |
| 309 | // get new formats |
| 310 | bpp = FreeImage_GetBPP(fiBitmap); |
| 311 | colourType = FreeImage_GetColorType(fiBitmap); |
| 312 | } |
| 313 | |
| 314 | // by this stage, 8-bit is greyscale, 16/24/32 bit are RGB[A] |
| 315 | switch(bpp) |
| 316 | { |
| 317 | case 8: |
| 318 | format = PF_R8; |
| 319 | srcElemSize = 1; |
| 320 | break; |
| 321 | case 16: |
| 322 | // Determine 555 or 565 from green mask |
| 323 | // cannot be 16-bit greyscale since that's FIT_UINT16 |
| 324 | if(FreeImage_GetGreenMask(fiBitmap) == FI16_565_GREEN_MASK) |
| 325 | { |
| 326 | assert(false && "Format not supported by the engine. TODO." ); |
| 327 | return nullptr; |
| 328 | } |
| 329 | else |
| 330 | { |
| 331 | assert(false && "Format not supported by the engine. TODO." ); |
| 332 | return nullptr; |
| 333 | // FreeImage doesn't support 4444 format so must be 1555 |
| 334 | } |
| 335 | srcElemSize = 2; |
| 336 | break; |
| 337 | case 24: |
| 338 | // FreeImage differs per platform |
| 339 | // PF_BYTE_BGR[A] for little endian (== PF_ARGB native) |
| 340 | // PF_BYTE_RGB[A] for big endian (== PF_RGBA native) |
| 341 | #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB |
| 342 | format = PF_RGB8; |
| 343 | #else |
| 344 | format = PF_BGR8; |
| 345 | #endif |
| 346 | srcElemSize = 3; |
| 347 | break; |
| 348 | case 32: |
| 349 | #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB |
| 350 | format = PF_RGBA8; |
| 351 | #else |
| 352 | format = PF_BGRA8; |
| 353 | #endif |
| 354 | srcElemSize = 4; |
| 355 | break; |
| 356 | |
| 357 | |
| 358 | }; |
| 359 | break; |
| 360 | case FIT_UINT16: |
| 361 | case FIT_INT16: |
| 362 | // 16-bit greyscale |
| 363 | assert(false && "No INT pixel formats supported currently. TODO." ); |
| 364 | return nullptr; |
| 365 | break; |
| 366 | case FIT_FLOAT: |
| 367 | // Single-component floating point data |
| 368 | format = PF_R32F; |
| 369 | srcElemSize = 4; |
| 370 | break; |
| 371 | case FIT_RGB16: |
| 372 | format = PF_RGBA16; |
| 373 | srcElemSize = 2 * 3; |
| 374 | break; |
| 375 | case FIT_RGBA16: |
| 376 | format = PF_RGBA16; |
| 377 | srcElemSize = 2 * 4; |
| 378 | break; |
| 379 | case FIT_RGBF: |
| 380 | format = PF_RGB32F; |
| 381 | srcElemSize = 4 * 3; |
| 382 | break; |
| 383 | case FIT_RGBAF: |
| 384 | format = PF_RGBA32F; |
| 385 | srcElemSize = 4 * 4; |
| 386 | break; |
| 387 | }; |
| 388 | |
| 389 | unsigned char* srcData = FreeImage_GetBits(fiBitmap); |
| 390 | unsigned srcPitch = FreeImage_GetPitch(fiBitmap); |
| 391 | |
| 392 | // Final data - invert image and trim pitch at the same time |
| 393 | UINT32 dstElemSize = PixelUtil::getNumElemBytes(format); |
| 394 | UINT32 dstPitch = width * PixelUtil::getNumElemBytes(format); |
| 395 | |
| 396 | // Bind output buffer |
| 397 | SPtr<PixelData> texData = bs_shared_ptr_new<PixelData>(width, height, 1, format); |
| 398 | texData->allocateInternalBuffer(); |
| 399 | UINT8* output = texData->getData(); |
| 400 | |
| 401 | UINT8* pSrc; |
| 402 | UINT8* pDst = output; |
| 403 | |
| 404 | // Copy row by row, which is faster |
| 405 | if (srcElemSize == dstElemSize) |
| 406 | { |
| 407 | for (UINT32 y = 0; y < height; ++y) |
| 408 | { |
| 409 | pSrc = srcData + (height - y - 1) * srcPitch; |
| 410 | memcpy(pDst, pSrc, dstPitch); |
| 411 | pDst += dstPitch; |
| 412 | } |
| 413 | } |
| 414 | else |
| 415 | { |
| 416 | for (UINT32 y = 0; y < height; ++y) |
| 417 | { |
| 418 | pSrc = srcData + (height - y - 1) * srcPitch; |
| 419 | |
| 420 | for(UINT32 x = 0; x < width; ++x) |
| 421 | memcpy(pDst + x * dstElemSize, pSrc + x * srcElemSize, srcElemSize); |
| 422 | |
| 423 | pDst += dstPitch; |
| 424 | } |
| 425 | } |
| 426 | |
| 427 | FreeImage_Unload(fiBitmap); |
| 428 | FreeImage_CloseMemory(fiMem); |
| 429 | |
| 430 | return texData; |
| 431 | } |
| 432 | |
| 433 | /** |
| 434 | * Reads the source texture as a horizontal or vertical list of 6 cubemap faces. |
| 435 | * |
| 436 | * @param[in] source Source texture to read. |
| 437 | * @param[out] output Output array that will contain individual cubemap faces. |
| 438 | * @param[in] faceSize Size of a single face, in pixels. Both width & height must match. |
| 439 | * @param[in] vertical True if the faces are laid out vertically, false if horizontally. |
| 440 | */ |
| 441 | void readCubemapList(const SPtr<PixelData>& source, std::array<SPtr<PixelData>, 6>& output, UINT32 faceSize, bool vertical) |
| 442 | { |
| 443 | Vector2I faceStart; |
| 444 | for(UINT32 i = 0; i < 6; i++) |
| 445 | { |
| 446 | output[i] = PixelData::create(faceSize, faceSize, 1, source->getFormat()); |
| 447 | |
| 448 | PixelVolume volume(faceStart.x, faceStart.y, faceStart.x + faceSize, faceStart.y + faceSize); |
| 449 | PixelUtil::copy(*source, *output[i], faceStart.x, faceStart.y); |
| 450 | |
| 451 | if (vertical) |
| 452 | faceStart.y += faceSize; |
| 453 | else |
| 454 | faceStart.x += faceSize; |
| 455 | } |
| 456 | } |
| 457 | |
| 458 | /** |
| 459 | * Reads the source texture as a horizontal or vertical "cross" of 6 cubemap faces. |
| 460 | * |
| 461 | * Vertical layout: |
| 462 | * +Y |
| 463 | * -X +Z +X |
| 464 | * -Y |
| 465 | * -Z |
| 466 | * |
| 467 | * Horizontal layout: |
| 468 | * +Y |
| 469 | * -X +Z +X -Z |
| 470 | * -Y |
| 471 | * |
| 472 | * @param[in] source Source texture to read. |
| 473 | * @param[out] output Output array that will contain individual cubemap faces. |
| 474 | * @param[in] faceSize Size of a single face, in pixels. Both width & height must match. |
| 475 | * @param[in] vertical True if the faces are laid out vertically, false if horizontally. |
| 476 | */ |
| 477 | void readCubemapCross(const SPtr<PixelData>& source, std::array<SPtr<PixelData>, 6>& output, UINT32 faceSize, |
| 478 | bool vertical) |
| 479 | { |
| 480 | const static UINT32 vertFaceIndices[] = { 5, 3, 1, 7, 4, 10 }; |
| 481 | const static UINT32 horzFaceIndices[] = { 6, 4, 1, 9, 5, 7 }; |
| 482 | |
| 483 | const UINT32* faceIndices = vertical ? vertFaceIndices : horzFaceIndices; |
| 484 | UINT32 numFacesInRow = vertical ? 3 : 4; |
| 485 | |
| 486 | for (UINT32 i = 0; i < 6; i++) |
| 487 | { |
| 488 | output[i] = PixelData::create(faceSize, faceSize, 1, source->getFormat()); |
| 489 | |
| 490 | UINT32 faceX = (faceIndices[i] % numFacesInRow) * faceSize; |
| 491 | UINT32 faceY = (faceIndices[i] / numFacesInRow) * faceSize; |
| 492 | |
| 493 | PixelVolume volume(faceX, faceY, faceX + faceSize, faceY + faceSize); |
| 494 | PixelUtil::copy(*source, *output[i], faceX, faceY); |
| 495 | } |
| 496 | |
| 497 | // Flip -Z as it's upside down |
| 498 | if (vertical) |
| 499 | PixelUtil::mirror(*output[5], MirrorModeBits::X | MirrorModeBits::Y); |
| 500 | } |
| 501 | |
| 502 | /** Method that maps a direction to a point on a plane in range [0, 1] using spherical mapping. */ |
| 503 | Vector2 mapCubemapDirToSpherical(const Vector3& dir) |
| 504 | { |
| 505 | // Using the OpenGL spherical mapping formula |
| 506 | Vector3 nrmDir = Vector3::normalize(dir); |
| 507 | nrmDir.z += 1.0f; |
| 508 | |
| 509 | float m = 2 * nrmDir.length(); |
| 510 | |
| 511 | float u = nrmDir.x / m + 0.5f; |
| 512 | float v = nrmDir.y / m + 0.5f; |
| 513 | |
| 514 | return Vector2(u, v); |
| 515 | } |
| 516 | |
| 517 | /** |
| 518 | * Method that maps a direction to a point on a plane in range [0, 1] using cylindrical mapping. This mapping is also |
| 519 | * know as longitude-latitude mapping, Blinn/Newell mapping or equirectangular cylindrical mapping. |
| 520 | */ |
| 521 | Vector2 mapCubemapDirToCylindrical(const Vector3& dir) |
| 522 | { |
| 523 | Vector3 nrmDir = Vector3::normalize(dir); |
| 524 | |
| 525 | float u = (atan2(nrmDir.x, nrmDir.z) + Math::PI) / Math::TWO_PI; |
| 526 | float v = acos(nrmDir.y) / Math::PI; |
| 527 | |
| 528 | return Vector2(u, v); |
| 529 | } |
| 530 | |
| 531 | /** Resizes the provided cubemap faces and outputs a new set of resized faces. */ |
| 532 | void downsampleCubemap(const std::array<SPtr<PixelData>, 6>& input, std::array<SPtr<PixelData>, 6>& output, UINT32 size) |
| 533 | { |
| 534 | for(UINT32 i = 0; i < 6; i++) |
| 535 | { |
| 536 | output[i] = PixelData::create(size, size, 1, input[i]->getFormat()); |
| 537 | PixelUtil::scale(*input[i], *output[i]); |
| 538 | } |
| 539 | } |
| 540 | |
| 541 | /** |
| 542 | * Reads the source texture and remaps its data into six faces of a cubemap. |
| 543 | * |
| 544 | * @param[in] source Source texture to remap. |
| 545 | * @param[out] output Remapped faces of the cubemap. |
| 546 | * @param[in] faceSize Width/height of each individual face, in pixels. |
| 547 | * @param[in] remap Function to use for remapping the cubemap direction to UV. |
| 548 | */ |
| 549 | void readCubemapUVRemap(const SPtr<PixelData>& source, std::array<SPtr<PixelData>, 6>& output, UINT32 faceSize, |
| 550 | const std::function<Vector2(Vector3)>& remap) |
| 551 | { |
| 552 | struct RemapInfo |
| 553 | { |
| 554 | int idx[3]; |
| 555 | Vector3 sign; |
| 556 | }; |
| 557 | |
| 558 | // Mapping from default (X, Y, 1.0f) plane to relevant cube face. Also flipping Y so it corresponds to how pixel |
| 559 | // coordinates are mapped. |
| 560 | static const RemapInfo remapLookup[] = |
| 561 | { |
| 562 | { {2, 1, 0}, { -1.0f, -1.0f, 1.0f }}, // X+ |
| 563 | { {2, 1, 0}, { 1.0f, -1.0f, -1.0f }}, // X- |
| 564 | { {0, 2, 1}, { 1.0f, 1.0f, 1.0f }}, // Y+ |
| 565 | { {0, 2, 1}, { 1.0f, -1.0f, -1.0f }}, // Y- |
| 566 | { {0, 1, 2}, { 1.0f, -1.0f, 1.0f }}, // Z+ |
| 567 | { {0, 1, 2}, { -1.0f, -1.0f, -1.0f }} // Z- |
| 568 | }; |
| 569 | |
| 570 | float invSize = 1.0f / faceSize; |
| 571 | for (UINT32 faceIdx = 0; faceIdx < 6; faceIdx++) |
| 572 | { |
| 573 | output[faceIdx] = PixelData::create(faceSize, faceSize, 1, source->getFormat()); |
| 574 | |
| 575 | const RemapInfo& remapInfo = remapLookup[faceIdx]; |
| 576 | for (UINT32 y = 0; y < faceSize; y++) |
| 577 | { |
| 578 | for (UINT32 x = 0; x < faceSize; x++) |
| 579 | { |
| 580 | // Map from pixel coordinates to [-1, 1] range. |
| 581 | Vector2 sourceCoord = (Vector2((float)x, (float)y) * invSize) * 2.0f - Vector2(1.0f, 1.0f); |
| 582 | Vector3 direction = Vector3(sourceCoord.x, sourceCoord.y, 1.0f); |
| 583 | |
| 584 | direction *= remapInfo.sign; |
| 585 | |
| 586 | // Rotate towards current face |
| 587 | Vector3 rotatedDir; |
| 588 | rotatedDir[remapInfo.idx[0]] = direction.x; |
| 589 | rotatedDir[remapInfo.idx[1]] = direction.y; |
| 590 | rotatedDir[remapInfo.idx[2]] = direction.z; |
| 591 | |
| 592 | // Find location in the source to sample from |
| 593 | Vector2 sourceUV = remap(rotatedDir); |
| 594 | Color color = source->sampleColorAt(sourceUV); |
| 595 | |
| 596 | // Write the sampled color |
| 597 | output[faceIdx]->setColorAt(color, x, y); |
| 598 | } |
| 599 | } |
| 600 | } |
| 601 | } |
| 602 | |
| 603 | bool FreeImgImporter::generateCubemap(const SPtr<PixelData>& source, CubemapSourceType sourceType, |
| 604 | std::array<SPtr<PixelData>, 6>& output) |
| 605 | { |
| 606 | // Note: Expose this as a parameter if needed: |
| 607 | UINT32 cubemapSupersampling = 1; // Set to amount of samples |
| 608 | |
| 609 | Vector2I faceSize; |
| 610 | bool cross = false; |
| 611 | bool vertical = false; |
| 612 | |
| 613 | switch(sourceType) |
| 614 | { |
| 615 | case CubemapSourceType::Faces: |
| 616 | { |
| 617 | float aspect = source->getWidth() / (float)source->getHeight(); |
| 618 | |
| 619 | if(Math::approxEquals(aspect, 6.0f)) // Horizontal list |
| 620 | { |
| 621 | faceSize.x = source->getWidth() / 6; |
| 622 | faceSize.y = source->getHeight(); |
| 623 | } |
| 624 | else if(Math::approxEquals(aspect, 1.0f / 6.0f)) // Vertical list |
| 625 | { |
| 626 | faceSize.x = source->getWidth(); |
| 627 | faceSize.y = source->getHeight() / 6; |
| 628 | vertical = true; |
| 629 | } |
| 630 | else if(Math::approxEquals(aspect, 4.0f / 3.0f)) // Horizontal cross |
| 631 | { |
| 632 | faceSize.x = source->getWidth() / 4; |
| 633 | faceSize.y = source->getHeight() / 3; |
| 634 | cross = true; |
| 635 | } |
| 636 | else if(Math::approxEquals(aspect, 3.0f / 4.0f)) // Vertical cross |
| 637 | { |
| 638 | faceSize.x = source->getWidth() / 3; |
| 639 | faceSize.y = source->getHeight() / 4; |
| 640 | cross = true; |
| 641 | vertical = true; |
| 642 | } |
| 643 | else |
| 644 | { |
| 645 | LOGWRN("Unable to generate a cubemap: unrecognized face configuration." ); |
| 646 | return false; |
| 647 | } |
| 648 | } |
| 649 | break; |
| 650 | case CubemapSourceType::Cylindrical: |
| 651 | case CubemapSourceType::Spherical: |
| 652 | // Half of the source size will be used for each cube face, which should yield good enough quality |
| 653 | faceSize.x = std::max(source->getWidth(), source->getHeight()) / 2; |
| 654 | faceSize.x = Bitwise::closestPow2(faceSize.x); |
| 655 | |
| 656 | // Don't allow sizes larger than 4096 |
| 657 | faceSize.x = std::min(faceSize.x, 4096); |
| 658 | |
| 659 | faceSize.y = faceSize.x; |
| 660 | |
| 661 | break; |
| 662 | default: // Assuming single-image |
| 663 | faceSize.x = source->getWidth(); |
| 664 | faceSize.y = source->getHeight(); |
| 665 | break; |
| 666 | } |
| 667 | |
| 668 | if (faceSize.x != faceSize.y) |
| 669 | { |
| 670 | LOGWRN("Unable to generate a cubemap: width & height must match." ); |
| 671 | return false; |
| 672 | } |
| 673 | |
| 674 | if (!Bitwise::isPow2(faceSize.x)) |
| 675 | { |
| 676 | LOGWRN("Unable to generate a cubemap: width & height must be powers of 2." ); |
| 677 | return false; |
| 678 | } |
| 679 | |
| 680 | switch (sourceType) |
| 681 | { |
| 682 | case CubemapSourceType::Faces: |
| 683 | { |
| 684 | if (cross) |
| 685 | readCubemapCross(source, output, faceSize.x, vertical); |
| 686 | else |
| 687 | readCubemapList(source, output, faceSize.x, vertical); |
| 688 | } |
| 689 | break; |
| 690 | case CubemapSourceType::Cylindrical: |
| 691 | { |
| 692 | UINT32 superSampledFaceSize = faceSize.x * cubemapSupersampling; |
| 693 | |
| 694 | std::array<SPtr<PixelData>, 6> superSampledOutput; |
| 695 | readCubemapUVRemap(source, superSampledOutput, superSampledFaceSize, &mapCubemapDirToCylindrical); |
| 696 | |
| 697 | if (faceSize.x != (INT32)superSampledFaceSize) |
| 698 | downsampleCubemap(superSampledOutput, output, faceSize.x); |
| 699 | else |
| 700 | output = superSampledOutput; |
| 701 | } |
| 702 | break; |
| 703 | case CubemapSourceType::Spherical: |
| 704 | { |
| 705 | UINT32 superSampledFaceSize = faceSize.x * cubemapSupersampling; |
| 706 | |
| 707 | std::array<SPtr<PixelData>, 6> superSampledOutput; |
| 708 | readCubemapUVRemap(source, superSampledOutput, superSampledFaceSize, &mapCubemapDirToSpherical); |
| 709 | |
| 710 | if (faceSize.x != (INT32)superSampledFaceSize) |
| 711 | downsampleCubemap(superSampledOutput, output, faceSize.x); |
| 712 | else |
| 713 | output = superSampledOutput; |
| 714 | } |
| 715 | break; |
| 716 | default: // Single-image |
| 717 | for (UINT32 i = 0; i < 6; i++) |
| 718 | output[i] = source; |
| 719 | |
| 720 | break; |
| 721 | } |
| 722 | |
| 723 | return true; |
| 724 | } |
| 725 | } |
| 726 | |