#include #include #include #include #include bool TextureLoader::loadFromFile(std::string filename, GTAData* gameData) { std::ifstream dfile(filename); if ( ! dfile.is_open()) { std::cerr << "Error opening file " << filename << std::endl; return false; } dfile.seekg(0, std::ios_base::end); size_t length = dfile.tellg(); dfile.seekg(0); char *data = new char[length]; dfile.read(data, length); return loadFromMemory(data, gameData); } GLuint gErrorTextureData[] = { 0xFF0000FF, 0xFF00FF00, 0xFFFF0000, 0xFFFF0000 }; GLuint gDebugTextureData[] = {0xFF0000FF, 0xFF00FF00}; GLuint getErrorTexture() { static GLuint errTexName = 0; if(errTexName == 0) { glGenTextures(1, &errTexName); glBindTexture(GL_TEXTURE_2D, errTexName); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, gErrorTextureData ); } return errTexName; } const size_t paletteSize = 1024; void processPalette(uint32_t* fullColor, RW::BSTextureNative& texNative, RW::BinaryStreamSection& rootSection) { uint8_t* dataBase = reinterpret_cast(rootSection.raw() + sizeof(RW::BSSectionHeader) + sizeof(RW::BSTextureNative) - 4); uint8_t* coldata = (dataBase + paletteSize + sizeof(uint32_t)); uint32_t raster_size = *reinterpret_cast(dataBase + paletteSize); uint32_t* palette = reinterpret_cast(dataBase); for(size_t j = 0; j < raster_size; ++j) { fullColor[j] = palette[coldata[j]]; } } GLuint createTexture(RW::BSTextureNative& texNative, RW::BinaryStreamSection& rootSection, bool* transparent) { // TODO: Exception handling. if(texNative.platform != 8) { std::cerr << "Unsupported texture platform " << std::dec << texNative.platform << std::endl; return getErrorTexture(); } bool isPal8 = (texNative.rasterformat & RW::BSTextureNative::FORMAT_EXT_PAL8) == RW::BSTextureNative::FORMAT_EXT_PAL8; bool isFulc = texNative.rasterformat == RW::BSTextureNative::FORMAT_1555 || texNative.rasterformat == RW::BSTextureNative::FORMAT_8888 || texNative.rasterformat == RW::BSTextureNative::FORMAT_888; // Export this value *transparent = !((texNative.rasterformat&RW::BSTextureNative::FORMAT_888) == RW::BSTextureNative::FORMAT_888); if(! (isPal8 || isFulc)) { std::cerr << "Unsuported raster format " << std::dec << texNative.rasterformat << std::endl; return getErrorTexture(); } GLuint textureName = 0; #if ENABLE_ABHORENT_DEBUGGING if(true) { glGenTextures(1, &textureName); glBindTexture(GL_TEXTURE_2D, textureName); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)(gDebugTextureData) + (*transparent ? 0 : 4) ); } else #endif if(isPal8) { uint32_t fullColor[texNative.width * texNative.height]; processPalette(fullColor, texNative, rootSection); glGenTextures(1, &textureName); glBindTexture(GL_TEXTURE_2D, textureName); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, texNative.width, texNative.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, fullColor ); } else if(isFulc) { auto coldata = rootSection.raw() + sizeof(RW::BSTextureNative); coldata += sizeof(uint32_t); GLenum type, format; switch(texNative.rasterformat) { case RW::BSTextureNative::FORMAT_1555: format = GL_RGBA; type = GL_UNSIGNED_SHORT_1_5_5_5_REV; break; case RW::BSTextureNative::FORMAT_8888: format = GL_RGBA; type = GL_UNSIGNED_INT_8_8_8_8_REV; break; case RW::BSTextureNative::FORMAT_888: format = GL_BGRA; type = GL_UNSIGNED_BYTE; break; } glGenTextures(1, &textureName); glBindTexture(GL_TEXTURE_2D, textureName); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, texNative.width, texNative.height, 0, format, type, coldata ); } GLenum texFilter = GL_LINEAR; switch(texNative.filterflags & 0xFF) { default: case RW::BSTextureNative::FILTER_LINEAR: texFilter = GL_LINEAR; break; case RW::BSTextureNative::FILTER_NEAREST: texFilter = GL_NEAREST; break; } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texFilter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texFilter); GLenum texwrap = GL_REPEAT; switch(texNative.wrapU) { default: case RW::BSTextureNative::WRAP_WRAP: texwrap = GL_REPEAT; break; case RW::BSTextureNative::WRAP_CLAMP: texwrap = GL_CLAMP_TO_EDGE; break; case RW::BSTextureNative::WRAP_MIRROR: texwrap = GL_MIRRORED_REPEAT; break; } glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, texwrap ); switch(texNative.wrapV) { default: case RW::BSTextureNative::WRAP_WRAP: texwrap = GL_REPEAT; break; case RW::BSTextureNative::WRAP_CLAMP: texwrap = GL_CLAMP_TO_EDGE; break; case RW::BSTextureNative::WRAP_MIRROR: texwrap = GL_MIRRORED_REPEAT; break; } glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, texwrap ); glGenerateMipmap(GL_TEXTURE_2D); return textureName; } bool TextureLoader::loadFromMemory(char *data, GTAData *gameData) { RW::BinaryStreamSection root(data); /*auto texDict =*/ root.readStructure(); size_t rootI = 0; while (root.hasMoreData(rootI)) { auto rootSection = root.getNextChildSection(rootI); if (rootSection.header.id != RW::SID_TextureNative) continue; RW::BSTextureNative texNative = rootSection.readStructure(); std::string name = std::string(texNative.diffuseName); bool transparent = false; GLuint id = createTexture(texNative, rootSection, &transparent); gameData->textures.insert({name, {id, transparent}}); } return true; }