diff --git a/src/Animations.c b/src/Animations.c deleted file mode 100644 index 19c64cd10..000000000 --- a/src/Animations.c +++ /dev/null @@ -1,376 +0,0 @@ -#include "Animations.h" -#include "ExtMath.h" -#include "TerrainAtlas.h" -#include "Platform.h" -#include "Event.h" -#include "Funcs.h" -#include "Graphics.h" -#include "Chat.h" -#include "World.h" -#include "Options.h" -#include "ErrorHandler.h" -#include "Errors.h" -#include "Stream.h" -#include "Bitmap.h" -#define LIQUID_ANIM_MAX 64 - -/*########################################################################################################################* -*-----------------------------------------------------Lava animation------------------------------------------------------* -*#########################################################################################################################*/ -static float L_soupHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; -static float L_potHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; -static float L_flameHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; -static RNGState L_rnd; -static bool L_rndInitalised; - -static void LavaAnimation_Tick(BitmapCol* ptr, int size) { - int mask = size - 1, shift = Math_Log2(size); - float soupHeat, potHeat, col; - int x, y, i = 0; - - if (!L_rndInitalised) { - Random_InitFromCurrentTime(&L_rnd); - L_rndInitalised = true; - } - - for (y = 0; y < size; y++) { - for (x = 0; x < size; x++) { - /* Calculate the colour at this coordinate in the heatmap */ - - /* Lookup table for (int)(1.2 * sin([ANGLE] * 22.5 * MATH_DEG2RAD)); */ - /* [ANGLE] is integer x/y, so repeats every 16 intervals */ - static int8_t sin_adj_table[16] = { 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, -1, -1, -1, 0, 0 }; - int xx = x + sin_adj_table[y & 0xF], yy = y + sin_adj_table[x & 0xF]; - - soupHeat = - L_soupHeat[((yy - 1) & mask) << shift | ((xx - 1) & mask)] + - L_soupHeat[((yy - 1) & mask) << shift | (xx & mask)] + - L_soupHeat[((yy - 1) & mask) << shift | ((xx + 1) & mask)] + - - L_soupHeat[(yy & mask) << shift | ((xx - 1) & mask)] + - L_soupHeat[(yy & mask) << shift | (xx & mask)] + - L_soupHeat[(yy & mask) << shift | ((xx + 1) & mask)] + - - L_soupHeat[((yy + 1) & mask) << shift | ((xx - 1) & mask)] + - L_soupHeat[((yy + 1) & mask) << shift | (xx & mask)] + - L_soupHeat[((yy + 1) & mask) << shift | ((xx + 1) & mask)]; - - potHeat = - L_potHeat[i] + /* x , y */ - L_potHeat[y << shift | ((x + 1) & mask)] + /* x + 1, y */ - L_potHeat[((y + 1) & mask) << shift | x] + /* x , y + 1 */ - L_potHeat[((y + 1) & mask) << shift | ((x + 1) & mask)];/* x + 1, y + 1 */ - - L_soupHeat[i] = soupHeat * 0.1f + potHeat * 0.2f; - - L_potHeat[i] += L_flameHeat[i]; - if (L_potHeat[i] < 0.0f) L_potHeat[i] = 0.0f; - - L_flameHeat[i] -= 0.06f * 0.01f; - if (Random_Float(&L_rnd) <= 0.005f) L_flameHeat[i] = 1.5f * 0.01f; - - /* Output the pixel */ - col = 2.0f * L_soupHeat[i]; - Math_Clamp(col, 0.0f, 1.0f); - - ptr->R = (uint8_t)(col * 100.0f + 155.0f); - ptr->G = (uint8_t)(col * col * 255.0f); - ptr->B = (uint8_t)(col * col * col * col * 128.0f); - ptr->A = 255; - - ptr++; i++; - } - } -} - - -/*########################################################################################################################* -*----------------------------------------------------Water animation------------------------------------------------------* -*#########################################################################################################################*/ -static float W_soupHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; -static float W_potHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; -static float W_flameHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; -static RNGState W_rnd; -static bool W_rndInitalised; - -static void WaterAnimation_Tick(BitmapCol* ptr, int size) { - int mask = size - 1, shift = Math_Log2(size); - float soupHeat, col; - int x, y, i = 0; - - if (!W_rndInitalised) { - Random_InitFromCurrentTime(&W_rnd); - W_rndInitalised = true; - } - - for (y = 0; y < size; y++) { - for (x = 0; x < size; x++) { - /* Calculate the colour at this coordinate in the heatmap */ - soupHeat = - W_soupHeat[y << shift | ((x - 1) & mask)] + - W_soupHeat[y << shift | x ] + - W_soupHeat[y << shift | ((x + 1) & mask)]; - - W_soupHeat[i] = soupHeat / 3.3f + W_potHeat[i] * 0.8f; - - W_potHeat[i] += W_flameHeat[i]; - if (W_potHeat[i] < 0.0f) W_potHeat[i] = 0.0f; - - W_flameHeat[i] -= 0.1f * 0.05f; - if (Random_Float(&W_rnd) <= 0.05f) W_flameHeat[i] = 0.5f * 0.05f; - - /* Output the pixel */ - col = W_soupHeat[i]; - Math_Clamp(col, 0.0f, 1.0f); - col = col * col; - - ptr->R = (uint8_t)(32.0f + col * 32.0f); - ptr->G = (uint8_t)(50.0f + col * 64.0f); - ptr->A = (uint8_t)(146.0f + col * 50.0f); - ptr->B = 255; - - ptr++; i++; - } - } -} - - -/*########################################################################################################################* -*-------------------------------------------------------Animations--------------------------------------------------------* -*#########################################################################################################################*/ -struct AnimationData { - TextureLoc TexLoc; /* Tile (not pixel) coordinates in terrain.png */ - uint16_t FrameX, FrameY; /* Top left pixel coordinates of start frame in animatons.png */ - uint16_t FrameSize; /* Size of each frame in pixel coordinates */ - uint16_t State; /* Current animation frame index */ - uint16_t StatesCount; /* Total number of animation frames */ - int16_t Tick, TickDelay; -}; - -static Bitmap anims_bmp; -static struct AnimationData anims_list[ATLAS1D_MAX_ATLASES]; -static int anims_count; -static bool anims_validated, anims_useLavaAnim, anims_useWaterAnim; -#define ANIM_MIN_ARGS 7 - -static void Animations_ReadDescription(struct Stream* stream, const String* path) { - String line; char lineBuffer[STRING_SIZE * 2]; - String parts[ANIM_MIN_ARGS]; - int count; - struct AnimationData data = { 0 }; - uint8_t tileX, tileY; - - uint8_t buffer[2048]; - struct Stream buffered; - ReturnCode res; - - String_InitArray(line, lineBuffer); - /* ReadLine reads single byte at a time */ - Stream_ReadonlyBuffered(&buffered, stream, buffer, sizeof(buffer)); - - for (;;) { - res = Stream_ReadLine(&buffered, &line); - if (res == ERR_END_OF_STREAM) break; - if (res) { Chat_LogError2(res, "reading from", path); break; } - - if (!line.length || line.buffer[0] == '#') continue; - count = String_UNSAFE_Split(&line, ' ', parts, ANIM_MIN_ARGS); - if (count < ANIM_MIN_ARGS) { - Chat_Add1("&cNot enough arguments for anim: %s", &line); continue; - } - - if (!Convert_TryParseUInt8(&parts[0], &tileX) || tileX >= ATLAS2D_TILES_PER_ROW) { - Chat_Add1("&cInvalid anim tile X coord: %s", &line); continue; - } - if (!Convert_TryParseUInt8(&parts[1], &tileY) || tileY >= ATLAS2D_MAX_ROWS_COUNT) { - Chat_Add1("&cInvalid anim tile Y coord: %s", &line); continue; - } - if (!Convert_TryParseUInt16(&parts[2], &data.FrameX)) { - Chat_Add1("&cInvalid anim frame X coord: %s", &line); continue; - } - if (!Convert_TryParseUInt16(&parts[3], &data.FrameY)) { - Chat_Add1("&cInvalid anim frame Y coord: %s", &line); continue; - } - if (!Convert_TryParseUInt16(&parts[4], &data.FrameSize)) { - Chat_Add1("&cInvalid anim frame size: %s", &line); continue; - } - if (!Convert_TryParseUInt16(&parts[5], &data.StatesCount)) { - Chat_Add1("&cInvalid anim states count: %s", &line); continue; - } - if (!Convert_TryParseInt16(&parts[6], &data.TickDelay)) { - Chat_Add1("&cInvalid anim tick delay: %s", &line); continue; - } - - if (anims_count == Array_Elems(anims_list)) { - Chat_AddRaw("&cCannot show over 512 animations"); return; - } - - data.TexLoc = tileX + (tileY * ATLAS2D_TILES_PER_ROW); - anims_list[anims_count++] = data; - } -} - -#define ANIMS_FAST_SIZE 64 -static void Animations_Draw(struct AnimationData* data, TextureLoc texLoc, int size) { - int dstX = Atlas1D_Index(texLoc), srcX; - int dstY = Atlas1D_RowId(texLoc) * Atlas_TileSize; - GfxResourceID tex; - - uint8_t buffer[Bitmap_DataSize(ANIMS_FAST_SIZE, ANIMS_FAST_SIZE)]; - uint8_t* ptr = buffer; - Bitmap frame; - - /* cannot allocate memory on the stack for very big animation.png frames */ - if (size > ANIMS_FAST_SIZE) { - ptr = Mem_Alloc(size * size, 4, "anim frame"); - } - Bitmap_Create(&frame, size, size, ptr); - - if (!data) { - if (texLoc == 30) { - LavaAnimation_Tick((BitmapCol*)frame.Scan0, size); - } else if (texLoc == 14) { - WaterAnimation_Tick((BitmapCol*)frame.Scan0, size); - } - } else { - srcX = data->FrameX + data->State * size; - Bitmap_CopyBlock(srcX, data->FrameY, 0, 0, &anims_bmp, &frame, size); - } - - tex = Atlas1D_TexIds[dstX]; - if (tex) { Gfx_UpdateTexturePart(tex, 0, dstY, &frame, Gfx_Mipmaps); } - if (size > ANIMS_FAST_SIZE) Mem_Free(ptr); -} - -static void Animations_Apply(struct AnimationData* data) { - TextureLoc loc; - data->Tick--; - if (data->Tick >= 0) return; - - data->State++; - data->State %= data->StatesCount; - data->Tick = data->TickDelay; - - loc = data->TexLoc; - if (loc == 30 && anims_useLavaAnim) return; - if (loc == 14 && anims_useWaterAnim) return; - Animations_Draw(data, loc, data->FrameSize); -} - -static bool Animations_IsDefaultZip(void) { - String texPack; char texPackBuffer[STRING_SIZE]; - if (World_TextureUrl.length) return false; - - String_InitArray(texPack, texPackBuffer); - Options_Get(OPT_DEFAULT_TEX_PACK, &texPack, "default.zip"); - return String_CaselessEqualsConst(&texPack, "default.zip"); -} - -static void Animations_Clear(void) { - Mem_Free(anims_bmp.Scan0); - anims_count = 0; - anims_bmp.Scan0 = NULL; - anims_validated = false; -} - -static void Animations_Validate(void) { - struct AnimationData data; - int maxX, maxY, tileX, tileY; - int i, j; - - anims_validated = true; - for (i = 0; i < anims_count; i++) { - data = anims_list[i]; - - maxX = data.FrameX + data.FrameSize * data.StatesCount; - maxY = data.FrameY + data.FrameSize; - tileX = Atlas2D_TileX(data.TexLoc); - tileY = Atlas2D_TileY(data.TexLoc); - - if (data.FrameSize > Atlas_TileSize || tileY >= Atlas_RowsCount) { - Chat_Add2("&cAnimation frames for tile (%i, %i) are bigger than the size of a tile in terrain.png", &tileX, &tileY); - } else if (maxX > anims_bmp.Width || maxY > anims_bmp.Height) { - Chat_Add2("&cSome of the animation frames for tile (%i, %i) are at coordinates outside animations.png", &tileX, &tileY); - } else { - continue; - } - - /* Remove this animation from the list */ - for (j = i; j < anims_count - 1; j++) { - anims_list[j] = anims_list[j + 1]; - } - i--; anims_count--; - } -} - - -static void Animations_Tick(struct ScheduledTask* task) { - int i, size; - - if (anims_useLavaAnim) { - size = min(Atlas_TileSize, 64); - Animations_Draw(NULL, 30, size); - } - if (anims_useWaterAnim) { - size = min(Atlas_TileSize, 64); - Animations_Draw(NULL, 14, size); - } - - if (!anims_count) return; - if (!anims_bmp.Scan0) { - Chat_AddRaw("&cCurrent texture pack specifies it uses animations,"); - Chat_AddRaw("&cbut is missing animations.png"); - anims_count = 0; return; - } - - /* deferred, because when reading animations.txt, might not have read animations.png yet */ - if (!anims_validated) Animations_Validate(); - for (i = 0; i < anims_count; i++) { - Animations_Apply(&anims_list[i]); - } -} - -static void Animations_PackChanged(void* obj) { - Animations_Clear(); - anims_useLavaAnim = Animations_IsDefaultZip(); - anims_useWaterAnim = anims_useLavaAnim; -} - -static void Animations_FileChanged(void* obj, struct Stream* stream, const String* name) { - ReturnCode res; - if (String_CaselessEqualsConst(name, "animations.png")) { - res = Png_Decode(&anims_bmp, stream); - if (!res) return; - - Chat_LogError2(res, "decoding", name); - Mem_Free(anims_bmp.Scan0); - anims_bmp.Scan0 = NULL; - } else if (String_CaselessEqualsConst(name, "animations.txt")) { - Animations_ReadDescription(stream, name); - } else if (String_CaselessEqualsConst(name, "uselavaanim")) { - anims_useLavaAnim = true; - } else if (String_CaselessEqualsConst(name, "usewateranim")) { - anims_useWaterAnim = true; - } -} - - -/*########################################################################################################################* -*--------------------------------------------------Animations component---------------------------------------------------* -*#########################################################################################################################*/ -static void Animations_Init(void) { - ScheduledTask_Add(GAME_DEF_TICKS, Animations_Tick); - Event_RegisterVoid(&TextureEvents_PackChanged, NULL, Animations_PackChanged); - Event_RegisterEntry(&TextureEvents_FileChanged, NULL, Animations_FileChanged); -} - -static void Animations_Free(void) { - Animations_Clear(); - Event_UnregisterVoid(&TextureEvents_PackChanged, NULL, Animations_PackChanged); - Event_UnregisterEntry(&TextureEvents_FileChanged, NULL, Animations_FileChanged); -} - -struct IGameComponent Animations_Component = { - Animations_Init, /* Init */ - Animations_Free /* Free */ -}; diff --git a/src/Animations.h b/src/Animations.h deleted file mode 100644 index 3ddf6694d..000000000 --- a/src/Animations.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef CC_ANIMS_H -#define CC_ANIMS_H -#include "Core.h" -/* Texture animations, and water and lava liquid animations. - Copyright 2014 - 2017 ClassicalSharp | Licensed under BSD-3 - Based off the incredible work from https://dl.dropboxusercontent.com/u/12694594/lava.txt - mirrored at https://github.com/UnknownShadow200/ClassicalSharp/wiki/Minecraft-Classic-lava-animation-algorithm - Water animation originally written by cybertoon, big thanks! -*/ - -struct IGameComponent; -struct ScheduledTask; -extern struct IGameComponent Animations_Component; -#endif diff --git a/src/Block.c b/src/Block.c index 508045fad..e09a7c4f8 100644 --- a/src/Block.c +++ b/src/Block.c @@ -1,13 +1,12 @@ #include "Block.h" #include "Funcs.h" #include "ExtMath.h" -#include "TerrainAtlas.h" +#include "TexturePack.h" #include "Game.h" #include "Entity.h" #include "Inventory.h" #include "Event.h" #include "Platform.h" -#include "Bitmap.h" #include "GameStructs.h" bool Block_IsLiquid[BLOCK_COUNT]; diff --git a/src/Builder.c b/src/Builder.c index 6b0e50eb8..5f5886b1d 100644 --- a/src/Builder.c +++ b/src/Builder.c @@ -12,7 +12,7 @@ #include "BlockID.h" #include "Block.h" #include "PackedCol.h" -#include "TerrainAtlas.h" +#include "TexturePack.h" #include "VertexStructs.h" int Builder_SidesLevel, Builder_EdgeLevel; diff --git a/src/ClassiCube.vcxproj b/src/ClassiCube.vcxproj index 707102fcb..bad7110e5 100644 --- a/src/ClassiCube.vcxproj +++ b/src/ClassiCube.vcxproj @@ -218,7 +218,6 @@ - @@ -231,7 +230,6 @@ - @@ -280,7 +278,6 @@ - @@ -296,7 +293,6 @@ - diff --git a/src/ClassiCube.vcxproj.filters b/src/ClassiCube.vcxproj.filters index 0917ef396..6d89e34ea 100644 --- a/src/ClassiCube.vcxproj.filters +++ b/src/ClassiCube.vcxproj.filters @@ -195,9 +195,6 @@ Header Files\MeshBuilder - - Header Files\TexturePack - Header Files\Math @@ -216,9 +213,6 @@ Header Files\Entities - - Header Files\TexturePack - Header Files\2D @@ -353,9 +347,6 @@ Source Files - - Source Files\TexturePack - Source Files\Entities @@ -386,9 +377,6 @@ Source Files\Entities - - Source Files\TexturePack - Source Files\2D diff --git a/src/Deflate.c b/src/Deflate.c index e9cde793d..e3a2bbeb5 100644 --- a/src/Deflate.c +++ b/src/Deflate.c @@ -1056,3 +1056,156 @@ void ZLib_MakeStream(struct Stream* stream, struct ZLibState* state, struct Stre stream->Write = ZLib_StreamWriteFirst; stream->Close = ZLib_StreamClose; } + + +/*########################################################################################################################* +*--------------------------------------------------------ZipEntry---------------------------------------------------------* +*#########################################################################################################################*/ +#define ZIP_MAXNAMELEN 512 +static ReturnCode Zip_ReadLocalFileHeader(struct ZipState* state, struct ZipEntry* entry) { + struct Stream* stream = state->Input; + uint8_t header[26]; + uint32_t compressedSize, uncompressedSize; + int method, pathLen, extraLen; + + String path; char pathBuffer[ZIP_MAXNAMELEN]; + struct Stream portion, compStream; + struct InflateState inflate; + ReturnCode res; + if ((res = Stream_Read(stream, header, sizeof(header)))) return res; + + method = Stream_GetU16_LE(&header[4]); + compressedSize = Stream_GetU32_LE(&header[14]); + uncompressedSize = Stream_GetU32_LE(&header[18]); + + /* Some .zip files don't set these in local file header */ + if (!compressedSize) compressedSize = entry->CompressedSize; + if (!uncompressedSize) uncompressedSize = entry->UncompressedSize; + + pathLen = Stream_GetU16_LE(&header[22]); + extraLen = Stream_GetU16_LE(&header[24]); + if (pathLen > ZIP_MAXNAMELEN) return ZIP_ERR_FILENAME_LEN; + + path = String_Init(pathBuffer, pathLen, pathLen); + if ((res = Stream_Read(stream, pathBuffer, pathLen))) return res; + + if (!state->SelectEntry(&path)) return 0; + /* local file may have extra data before actual data (e.g. ZIP64) */ + if ((res = stream->Skip(stream, extraLen))) return res; + + if (method == 0) { + Stream_ReadonlyPortion(&portion, stream, uncompressedSize); + state->ProcessEntry(&path, &portion, entry); + } else if (method == 8) { + Stream_ReadonlyPortion(&portion, stream, compressedSize); + Inflate_MakeStream(&compStream, &inflate, &portion); + state->ProcessEntry(&path, &compStream, entry); + } else { + Platform_Log1("Unsupported.zip entry compression method: %i", &method); + } + return 0; +} + +static ReturnCode Zip_ReadCentralDirectory(struct ZipState* state, struct ZipEntry* entry) { + struct Stream* stream = state->Input; + uint8_t header[42]; + int pathLen, extraLen, commentLen; + ReturnCode res; + if ((res = Stream_Read(stream, header, sizeof(header)))) return res; + + entry->Crc32 = Stream_GetU32_LE(&header[12]); + entry->CompressedSize = Stream_GetU32_LE(&header[16]); + entry->UncompressedSize = Stream_GetU32_LE(&header[20]); + + pathLen = Stream_GetU16_LE(&header[24]); + extraLen = Stream_GetU16_LE(&header[26]); + commentLen = Stream_GetU16_LE(&header[28]); + + entry->LocalHeaderOffset = Stream_GetU32_LE(&header[38]); + /* skip data following central directory entry header */ + return stream->Skip(stream, pathLen + extraLen + commentLen); +} + +static ReturnCode Zip_ReadEndOfCentralDirectory(struct ZipState* state, uint32_t* centralDirectoryOffset) { + struct Stream* stream = state->Input; + uint8_t header[18]; + + ReturnCode res; + if ((res = Stream_Read(stream, header, sizeof(header)))) return res; + + state->EntriesCount = Stream_GetU16_LE(&header[6]); + *centralDirectoryOffset = Stream_GetU32_LE(&header[12]); + return 0; +} + +enum ZipSig { + ZIP_SIG_ENDOFCENTRALDIR = 0x06054b50, + ZIP_SIG_CENTRALDIR = 0x02014b50, + ZIP_SIG_LOCALFILEHEADER = 0x04034b50 +}; + +static void Zip_DefaultProcessor(const String* path, struct Stream* data, struct ZipEntry* entry) { } +static bool Zip_DefaultSelector(const String* path) { return true; } +void Zip_Init(struct ZipState* state, struct Stream* input) { + state->Input = input; + state->EntriesCount = 0; + state->ProcessEntry = Zip_DefaultProcessor; + state->SelectEntry = Zip_DefaultSelector; +} + +ReturnCode Zip_Extract(struct ZipState* state) { + struct Stream* stream = state->Input; + uint32_t stream_len, centralDirOffset; + uint32_t sig = 0; + int i, count; + + ReturnCode res; + state->EntriesCount = 0; + if ((res = stream->Length(stream, &stream_len))) return res; + + /* At -22 for nearly all zips, but try a bit further back in case of comment */ + count = min(257, stream_len); + for (i = 22; i < count; i++) { + res = stream->Seek(stream, stream_len - i); + if (res) return ZIP_ERR_SEEK_END_OF_CENTRAL_DIR; + + if ((res = Stream_ReadU32_LE(stream, &sig))) return res; + if (sig == ZIP_SIG_ENDOFCENTRALDIR) break; + } + + if (sig != ZIP_SIG_ENDOFCENTRALDIR) return ZIP_ERR_NO_END_OF_CENTRAL_DIR; + res = Zip_ReadEndOfCentralDirectory(state, ¢ralDirOffset); + if (res) return res; + + res = stream->Seek(stream, centralDirOffset); + if (res) return ZIP_ERR_SEEK_CENTRAL_DIR; + if (state->EntriesCount > ZIP_MAX_ENTRIES) return ZIP_ERR_TOO_MANY_ENTRIES; + + /* Read all the central directory entries */ + for (count = 0; count < state->EntriesCount; count++) { + if ((res = Stream_ReadU32_LE(stream, &sig))) return res; + + if (sig == ZIP_SIG_CENTRALDIR) { + res = Zip_ReadCentralDirectory(state, &state->Entries[count]); + if (res) return res; + } else if (sig == ZIP_SIG_ENDOFCENTRALDIR) { + break; + } else { + return ZIP_ERR_INVALID_CENTRAL_DIR; + } + } + + /* Now read the local file header entries */ + for (i = 0; i < count; i++) { + struct ZipEntry* entry = &state->Entries[i]; + res = stream->Seek(stream, entry->LocalHeaderOffset); + if (res) return ZIP_ERR_SEEK_LOCAL_DIR; + + if ((res = Stream_ReadU32_LE(stream, &sig))) return res; + if (sig != ZIP_SIG_LOCALFILEHEADER) return ZIP_ERR_INVALID_LOCAL_DIR; + + res = Zip_ReadLocalFileHeader(state, entry); + if (res) return res; + } + return 0; +} diff --git a/src/Deflate.h b/src/Deflate.h index 737998769..ec4c742a1 100644 --- a/src/Deflate.h +++ b/src/Deflate.h @@ -1,6 +1,6 @@ #ifndef CC_DEFLATE_H #define CC_DEFLATE_H -#include "Core.h" +#include "String.h" /* Decodes data compressed using DEFLATE in a streaming manner. Partially based off information from https://handmade.network/forums/wip/t/2363-implementing_a_basic_png_reader_the_handmade_way @@ -65,11 +65,14 @@ struct InflateState { uint8_t Window[INFLATE_WINDOW_SIZE]; /* Holds circular buffer of recent output data, used for LZ77 */ }; -void Inflate_Init(struct InflateState* state, struct Stream* source); +/* Initialises DEFLATE decompressor state to defaults. */ +CC_EXPORT void Inflate_Init(struct InflateState* state, struct Stream* source); +/* Attempts to decompress all currently pending data. */ +/* NOTE: This is a low level call - usually you should use Inflate_MakeStream. */ void Inflate_Process(struct InflateState* state); /* Deompresses input data read from another stream using DEFLATE. Read only stream. */ /* NOTE: This only uncompresses pure DEFLATE compressed data. */ -/* If the data starts with a GZIP or ZLIB header, use GZipHeader_Read or ZLibHeader_Read to skip it. */ +/* If data starts with a GZIP or ZLIB header, use GZipHeader_Read or ZLibHeader_Read to first skip it. */ CC_EXPORT void Inflate_MakeStream(struct Stream* stream, struct InflateState* state, struct Stream* underlying); @@ -100,8 +103,35 @@ struct GZipState { struct DeflateState Base; uint32_t Crc32, Size; }; /* Compresses input data using GZIP, then writes compressed output to another stream. Write only stream. */ /* GZIP compression is GZIP header, followed by DEFLATE compressed data, followed by GZIP footer. */ CC_EXPORT void GZip_MakeStream(struct Stream* stream, struct GZipState* state, struct Stream* underlying); + struct ZLibState { struct DeflateState Base; uint32_t Adler32; }; /* Compresses input data using ZLIB, then writes compressed output to another stream. Write only stream. */ /* ZLIB compression is ZLIB header, followed by DEFLATE compressed data, followed by ZLIB footer. */ CC_EXPORT void ZLib_MakeStream(struct Stream* stream, struct ZLibState* state, struct Stream* underlying); + +/* Minimal data needed to describe an entry in a .zip archive. */ +struct ZipEntry { uint32_t CompressedSize, UncompressedSize, LocalHeaderOffset, Crc32; }; +#define ZIP_MAX_ENTRIES 2048 + +/* Stores state for reading and processing entries in a .zip archive. */ +struct ZipState { + /* Source of the .zip archive data. Must be seekable. */ + struct Stream* Input; + /* Callback function to process the data in a .zip archive entry. */ + /* NOTE: data stream may not be seekable. (entry data might be compressed) */ + void (*ProcessEntry)(const String* path, struct Stream* data, struct ZipEntry* entry); + /* Predicate used to select which entries in a .zip archive get proessed. */ + /* NOTE: returning false entirely skips the entry. (avoids pointless seek to entry) */ + bool (*SelectEntry)(const String* path); + /* Number of entries in the .zip archive. */ + int EntriesCount; + /* Data for each entry in the .zip archive. */ + struct ZipEntry Entries[ZIP_MAX_ENTRIES]; +}; + +/* Initialises .zip archive reader state to defaults. */ +CC_EXPORT void Zip_Init(struct ZipState* state, struct Stream* input); +/* Reads and processes the entries in a .zip archive. */ +/* NOTE: Must have been initialised with Zip_Init first. */ +CC_EXPORT ReturnCode Zip_Extract(struct ZipState* state); #endif diff --git a/src/Drawer.c b/src/Drawer.c index 40fb5d55c..ae031a971 100644 --- a/src/Drawer.c +++ b/src/Drawer.c @@ -1,5 +1,5 @@ #include "Drawer.h" -#include "TerrainAtlas.h" +#include "TexturePack.h" #include "Constants.h" bool Drawer_Tinted; diff --git a/src/EnvRenderer.c b/src/EnvRenderer.c index b933c6a73..d80df144c 100644 --- a/src/EnvRenderer.c +++ b/src/EnvRenderer.c @@ -13,7 +13,7 @@ #include "Stream.h" #include "Block.h" #include "Event.h" -#include "TerrainAtlas.h" +#include "TexturePack.h" #include "Platform.h" #include "Camera.h" #include "Particle.h" diff --git a/src/Game.c b/src/Game.c index 924a8a454..5d464b889 100644 --- a/src/Game.c +++ b/src/Game.c @@ -19,7 +19,6 @@ #include "Model.h" #include "Particle.h" #include "AsyncDownloader.h" -#include "Animations.h" #include "Inventory.h" #include "InputHandler.h" #include "ServerConnection.h" @@ -33,7 +32,6 @@ #include "Menus.h" #include "Audio.h" #include "Stream.h" -#include "TerrainAtlas.h" int Game_Width, Game_Height; double Game_Accumulator; diff --git a/src/IsometricDrawer.c b/src/IsometricDrawer.c index 0a503bf73..9f88d1afb 100644 --- a/src/IsometricDrawer.c +++ b/src/IsometricDrawer.c @@ -4,7 +4,7 @@ #include "PackedCol.h" #include "ExtMath.h" #include "Block.h" -#include "TerrainAtlas.h" +#include "TexturePack.h" #include "Block.h" static float iso_scale; diff --git a/src/MapRenderer.c b/src/MapRenderer.c index 87265a4a5..00016a113 100644 --- a/src/MapRenderer.c +++ b/src/MapRenderer.c @@ -10,7 +10,7 @@ #include "Game.h" #include "Graphics.h" #include "Platform.h" -#include "TerrainAtlas.h" +#include "TexturePack.h" #include "Utils.h" #include "World.h" diff --git a/src/Menus.c b/src/Menus.c index 0f5e68740..dbd353482 100644 --- a/src/Menus.c +++ b/src/Menus.c @@ -7,7 +7,6 @@ #include "Drawer2D.h" #include "Graphics.h" #include "Funcs.h" -#include "TerrainAtlas.h" #include "Model.h" #include "MapGenerator.h" #include "ServerConnection.h" diff --git a/src/Model.c b/src/Model.c index 75dd84f95..804956d28 100644 --- a/src/Model.c +++ b/src/Model.c @@ -7,7 +7,7 @@ #include "Camera.h" #include "Event.h" #include "ExtMath.h" -#include "TerrainAtlas.h" +#include "TexturePack.h" #include "Drawer.h" #include "Block.h" #include "Stream.h" diff --git a/src/PacketHandlers.c b/src/PacketHandlers.c index d2e65b408..ab763145f 100644 --- a/src/PacketHandlers.c +++ b/src/PacketHandlers.c @@ -23,7 +23,6 @@ #include "TexturePack.h" #include "Gui.h" #include "Errors.h" -#include "TerrainAtlas.h" /* Classic state */ static uint8_t classic_tabList[ENTITIES_MAX_COUNT >> 3]; diff --git a/src/Particle.c b/src/Particle.c index cda6442df..ddd1fe91b 100644 --- a/src/Particle.c +++ b/src/Particle.c @@ -4,7 +4,7 @@ #include "ExtMath.h" #include "Lighting.h" #include "Entity.h" -#include "TerrainAtlas.h" +#include "TexturePack.h" #include "Graphics.h" #include "Funcs.h" #include "Game.h" diff --git a/src/Screens.c b/src/Screens.c index 8463d791b..9243e3ecb 100644 --- a/src/Screens.c +++ b/src/Screens.c @@ -7,7 +7,7 @@ #include "Drawer2D.h" #include "Graphics.h" #include "Funcs.h" -#include "TerrainAtlas.h" +#include "TexturePack.h" #include "Model.h" #include "MapGenerator.h" #include "ServerConnection.h" diff --git a/src/TerrainAtlas.c b/src/TerrainAtlas.c deleted file mode 100644 index 3f591de84..000000000 --- a/src/TerrainAtlas.c +++ /dev/null @@ -1,113 +0,0 @@ -#include "TerrainAtlas.h" -#include "Block.h" -#include "ExtMath.h" -#include "Funcs.h" -#include "Graphics.h" -#include "Platform.h" - -Bitmap Atlas_Bitmap; -int Atlas_TileSize, Atlas_RowsCount; -int Atlas1D_Count, Atlas1D_TilesPerAtlas; -int Atlas1D_Mask, Atlas1D_Shift; -float Atlas1D_InvTileSize; -GfxResourceID Atlas1D_TexIds[ATLAS1D_MAX_ATLASES]; - -TextureRec Atlas1D_TexRec(TextureLoc texLoc, int uCount, int* index) { - TextureRec rec; - int y = Atlas1D_RowId(texLoc); - *index = Atlas1D_Index(texLoc); - - /* Adjust coords to be slightly inside - fixes issues with AMD/ATI cards */ - rec.U1 = 0.0f; - rec.V1 = y * Atlas1D_InvTileSize; - rec.U2 = (uCount - 1) + UV2_Scale; - rec.V2 = rec.V1 + UV2_Scale * Atlas1D_InvTileSize; - return rec; -} - -static void Atlas_Convert2DTo1D(void) { - int tileSize = Atlas_TileSize; - int tilesPerAtlas = Atlas1D_TilesPerAtlas; - int atlasesCount = Atlas1D_Count; - Bitmap atlas1D; - int atlasX, atlasY; - int tile = 0, i, y; - - Platform_Log2("Loaded new atlas: %i bmps, %i per bmp", &atlasesCount, &tilesPerAtlas); - Bitmap_Allocate(&atlas1D, tileSize, tilesPerAtlas * tileSize); - - for (i = 0; i < atlasesCount; i++) { - for (y = 0; y < tilesPerAtlas; y++, tile++) { - atlasX = Atlas2D_TileX(tile) * tileSize; - atlasY = Atlas2D_TileY(tile) * tileSize; - - Bitmap_CopyBlock(atlasX, atlasY, 0, y * tileSize, - &Atlas_Bitmap, &atlas1D, tileSize); - } - Atlas1D_TexIds[i] = Gfx_CreateTexture(&atlas1D, true, Gfx_Mipmaps); - } - Mem_Free(atlas1D.Scan0); -} - -static void Atlas_Update1D(void) { - int maxAtlasHeight, maxTilesPerAtlas, maxTiles; - - maxAtlasHeight = min(4096, Gfx_MaxTexHeight); - maxTilesPerAtlas = maxAtlasHeight / Atlas_TileSize; - maxTiles = Atlas_RowsCount * ATLAS2D_TILES_PER_ROW; - - Atlas1D_TilesPerAtlas = min(maxTilesPerAtlas, maxTiles); - Atlas1D_Count = Math_CeilDiv(maxTiles, Atlas1D_TilesPerAtlas); - - Atlas1D_InvTileSize = 1.0f / Atlas1D_TilesPerAtlas; - Atlas1D_Mask = Atlas1D_TilesPerAtlas - 1; - Atlas1D_Shift = Math_Log2(Atlas1D_TilesPerAtlas); -} - -void Atlas_Update(Bitmap* bmp) { - Atlas_Bitmap = *bmp; - Atlas_TileSize = bmp->Width / ATLAS2D_TILES_PER_ROW; - Atlas_RowsCount = bmp->Height / Atlas_TileSize; - Atlas_RowsCount = min(Atlas_RowsCount, ATLAS2D_MAX_ROWS_COUNT); - - Block_RecalculateAllSpriteBB(); - Atlas_Update1D(); - Atlas_Convert2DTo1D(); -} - -static GfxResourceID Atlas_LoadTile_Raw(TextureLoc texLoc, Bitmap* element) { - int size = Atlas_TileSize; - int x = Atlas2D_TileX(texLoc), y = Atlas2D_TileY(texLoc); - if (y >= Atlas_RowsCount) return GFX_NULL; - - Bitmap_CopyBlock(x * size, y * size, 0, 0, &Atlas_Bitmap, element, size); - return Gfx_CreateTexture(element, false, Gfx_Mipmaps); -} - -GfxResourceID Atlas_LoadTile(TextureLoc texLoc) { - int tileSize = Atlas_TileSize; - Bitmap tile; - GfxResourceID texId; - uint8_t scan0[Bitmap_DataSize(64, 64)]; - - /* Try to allocate bitmap on stack if possible */ - if (tileSize > 64) { - Bitmap_Allocate(&tile, tileSize, tileSize); - texId = Atlas_LoadTile_Raw(texLoc, &tile); - Mem_Free(tile.Scan0); - return texId; - } else { - Bitmap_Create(&tile, tileSize, tileSize, scan0); - return Atlas_LoadTile_Raw(texLoc, &tile); - } -} - -void Atlas_Free(void) { - int i; - Mem_Free(Atlas_Bitmap.Scan0); - Atlas_Bitmap.Scan0 = NULL; - - for (i = 0; i < Atlas1D_Count; i++) { - Gfx_DeleteTexture(&Atlas1D_TexIds[i]); - } -} diff --git a/src/TerrainAtlas.h b/src/TerrainAtlas.h deleted file mode 100644 index 23bb5cf55..000000000 --- a/src/TerrainAtlas.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef CC_TERRAINATLAS_H -#define CC_TERRAINATLAS_H -#include "Bitmap.h" -/* Represents the 2D texture atlas of terrain.png, and converted into an array of 1D textures. - Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 -*/ - -/* Number of tiles in each row */ -#define ATLAS2D_TILES_PER_ROW 16 -#define ATLAS2D_MASK 15 -#define ATLAS2D_SHIFT 4 -/* Maximum supported number of rows in the atlas. */ -#ifdef EXTENDED_TEXTURES -#define ATLAS2D_MAX_ROWS_COUNT 32 -#else -#define ATLAS2D_MAX_ROWS_COUNT 16 -#endif -/* Maximum possible number of 1D terrain atlases. (worst case, each 1D atlas only has 1 tile) */ -#define ATLAS1D_MAX_ATLASES (ATLAS2D_TILES_PER_ROW * ATLAS2D_MAX_ROWS_COUNT) - -/* Bitmap that contains the textures of all tiles. */ -/* Tiles are indexed left to right, top to bottom. */ -extern Bitmap Atlas_Bitmap; -/* Size of each tile in pixels. (default 16x16) */ -extern int Atlas_TileSize; -/* Number of rows in the atlas. (default 16, can be 32) */ -extern int Atlas_RowsCount; -/* Number of 1D atlases the atlas was split into. */ -extern int Atlas1D_Count; -/* Number of tiles in each 1D atlas. */ -extern int Atlas1D_TilesPerAtlas; -/* Converts a tile id into 1D atlas index, and index within that atlas. */ -extern int Atlas1D_Mask, Atlas1D_Shift; -/* Texture V coord that equals the size of one tile. (i.e. 1/Atlas1D_TilesPerAtlas) */ -/* NOTE: The texture U coord that equals the size of one tile is 1. */ -extern float Atlas1D_InvTileSize; -/* Textures for each 1D atlas. Only Atlas1D_Count of these are valid. */ -extern GfxResourceID Atlas1D_TexIds[ATLAS1D_MAX_ATLASES]; - -#define Atlas2D_TileX(texLoc) ((texLoc) & ATLAS2D_MASK) /* texLoc % ATLAS2D_TILES_PER_ROW */ -#define Atlas2D_TileY(texLoc) ((texLoc) >> ATLAS2D_SHIFT) /* texLoc / ATLAS2D_TILES_PER_ROW */ -/* Returns the index of the given tile id within a 1D atlas */ -#define Atlas1D_RowId(texLoc) ((texLoc) & Atlas1D_Mask) /* texLoc % Atlas1D_TilesPerAtlas */ -/* Returns the index of the 1D atlas within the array of 1D atlases that contains the given tile id */ -#define Atlas1D_Index(texLoc) ((texLoc) >> Atlas1D_Shift) /* texLoc / Atlas1D_TilesPerAtlas */ - -/* Loads the given atlas and converts it into an array of 1D atlases. */ -/* NOTE: Use Game_ChangeTerrainAtlas to change atlas, because that raises TextureEvents_AtlasChanged */ -void Atlas_Update(Bitmap* bmp); -/* Loads the given tile into a new separate texture. */ -GfxResourceID Atlas_LoadTile(TextureLoc texLoc); -/* Frees the atlas and 1D atlas textures. */ -void Atlas_Free(void); -/* Returns the UV rectangle of the given tile id in the 1D atlases. */ -/* That is, returns U1/U2/V1/V2 coords that make up the tile in a 1D atlas. */ -/* index is set to the index of the 1D atlas that the tile is in. */ -TextureRec Atlas1D_TexRec(TextureLoc texLoc, int uCount, int* index); -#endif diff --git a/src/TexturePack.c b/src/TexturePack.c index c41fa76ad..23389ce2e 100644 --- a/src/TexturePack.c +++ b/src/TexturePack.c @@ -1,189 +1,499 @@ #include "TexturePack.h" #include "Constants.h" #include "Platform.h" -#include "ErrorHandler.h" #include "Stream.h" -#include "Bitmap.h" #include "World.h" #include "Graphics.h" #include "Event.h" #include "Game.h" #include "AsyncDownloader.h" -#include "ErrorHandler.h" #include "Platform.h" #include "Deflate.h" #include "Stream.h" #include "Funcs.h" #include "Errors.h" #include "Chat.h" +#include "ExtMath.h" +#include "Block.h" /* NOTE: Just for block_recalculateallspritebb */ +#include "Chat.h" +#include "Options.h" + +#define LIQUID_ANIM_MAX 64 +/* Based off the incredible work from https://dl.dropboxusercontent.com/u/12694594/lava.txt + mirrored at https://github.com/UnknownShadow200/ClassicalSharp/wiki/Minecraft-Classic-lava-animation-algorithm + Water animation originally written by cybertoon, big thanks! +*/ /*########################################################################################################################* -*--------------------------------------------------------ZipEntry---------------------------------------------------------* +*-----------------------------------------------------Lava animation------------------------------------------------------* *#########################################################################################################################*/ -#define ZIP_MAXNAMELEN 512 -static ReturnCode Zip_ReadLocalFileHeader(struct ZipState* state, struct ZipEntry* entry) { - struct Stream* stream = state->Input; - uint8_t contents[26]; - uint32_t compressedSize, uncompressedSize; - int method, pathLen, extraLen; +static float L_soupHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; +static float L_potHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; +static float L_flameHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; +static RNGState L_rnd; +static bool L_rndInitalised; - String path; char pathBuffer[ZIP_MAXNAMELEN]; - struct Stream portion, compStream; - struct InflateState inflate; - ReturnCode res; - if ((res = Stream_Read(stream, contents, sizeof(contents)))) return res; +static void LavaAnimation_Tick(BitmapCol* ptr, int size) { + int mask = size - 1, shift = Math_Log2(size); + float soupHeat, potHeat, col; + int x, y, i = 0; - /* contents[0] (2) version needed */ - /* contents[2] (2) flags */ - method = Stream_GetU16_LE(&contents[4]); - /* contents[6] (4) last modified */ - /* contents[10] (4) CRC32 */ - - compressedSize = Stream_GetU32_LE(&contents[14]); - if (!compressedSize) compressedSize = entry->CompressedSize; - uncompressedSize = Stream_GetU32_LE(&contents[18]); - if (!uncompressedSize) uncompressedSize = entry->UncompressedSize; - - pathLen = Stream_GetU16_LE(&contents[22]); - extraLen = Stream_GetU16_LE(&contents[24]); - if (pathLen > ZIP_MAXNAMELEN) return ZIP_ERR_FILENAME_LEN; - - path = String_Init(pathBuffer, pathLen, pathLen); - if ((res = Stream_Read(stream, pathBuffer, pathLen))) return res; - - if (!state->SelectEntry(&path)) return 0; - if ((res = stream->Skip(stream, extraLen))) return res; - - if (method == 0) { - Stream_ReadonlyPortion(&portion, stream, uncompressedSize); - state->ProcessEntry(&path, &portion, entry); - } else if (method == 8) { - Stream_ReadonlyPortion(&portion, stream, compressedSize); - Inflate_MakeStream(&compStream, &inflate, &portion); - state->ProcessEntry(&path, &compStream, entry); - } else { - Platform_Log1("Unsupported.zip entry compression method: %i", &method); + if (!L_rndInitalised) { + Random_InitFromCurrentTime(&L_rnd); + L_rndInitalised = true; } - return 0; -} + + for (y = 0; y < size; y++) { + for (x = 0; x < size; x++) { + /* Calculate the colour at this coordinate in the heatmap */ -static ReturnCode Zip_ReadCentralDirectory(struct ZipState* state, struct ZipEntry* entry) { - struct Stream* stream = state->Input; - uint8_t contents[42]; - int pathLen, extraLen, commentLen; - uint32_t extraDataLen; + /* Lookup table for (int)(1.2 * sin([ANGLE] * 22.5 * MATH_DEG2RAD)); */ + /* [ANGLE] is integer x/y, so repeats every 16 intervals */ + static int8_t sin_adj_table[16] = { 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, -1, -1, -1, 0, 0 }; + int xx = x + sin_adj_table[y & 0xF], yy = y + sin_adj_table[x & 0xF]; - ReturnCode res; - if ((res = Stream_Read(stream, contents, sizeof(contents)))) return res; + soupHeat = + L_soupHeat[((yy - 1) & mask) << shift | ((xx - 1) & mask)] + + L_soupHeat[((yy - 1) & mask) << shift | (xx & mask)] + + L_soupHeat[((yy - 1) & mask) << shift | ((xx + 1) & mask)] + - /* contents[0] (2) OS */ - /* contents[2] (2) version needed*/ - /* contents[4] (2) flags */ - /* contents[6] (2) compresssion method*/ - /* contents[8] (4) last modified */ - entry->Crc32 = Stream_GetU32_LE(&contents[12]); - entry->CompressedSize = Stream_GetU32_LE(&contents[16]); - entry->UncompressedSize = Stream_GetU32_LE(&contents[20]); + L_soupHeat[(yy & mask) << shift | ((xx - 1) & mask)] + + L_soupHeat[(yy & mask) << shift | (xx & mask)] + + L_soupHeat[(yy & mask) << shift | ((xx + 1) & mask)] + - pathLen = Stream_GetU16_LE(&contents[24]); - extraLen = Stream_GetU16_LE(&contents[26]); - commentLen = Stream_GetU16_LE(&contents[28]); - /* contents[30] (2) disk number */ - /* contents[32] (2) internal attributes */ - /* contents[34] (4) external attributes */ - entry->LocalHeaderOffset = Stream_GetU32_LE(&contents[38]); + L_soupHeat[((yy + 1) & mask) << shift | ((xx - 1) & mask)] + + L_soupHeat[((yy + 1) & mask) << shift | (xx & mask)] + + L_soupHeat[((yy + 1) & mask) << shift | ((xx + 1) & mask)]; - extraDataLen = pathLen + extraLen + commentLen; - return stream->Skip(stream, extraDataLen); -} + potHeat = + L_potHeat[i] + /* x , y */ + L_potHeat[y << shift | ((x + 1) & mask)] + /* x + 1, y */ + L_potHeat[((y + 1) & mask) << shift | x] + /* x , y + 1 */ + L_potHeat[((y + 1) & mask) << shift | ((x + 1) & mask)];/* x + 1, y + 1 */ -static ReturnCode Zip_ReadEndOfCentralDirectory(struct ZipState* state, uint32_t* centralDirectoryOffset) { - struct Stream* stream = state->Input; - uint8_t contents[18]; + L_soupHeat[i] = soupHeat * 0.1f + potHeat * 0.2f; - ReturnCode res; - if ((res = Stream_Read(stream, contents, sizeof(contents)))) return res; + L_potHeat[i] += L_flameHeat[i]; + if (L_potHeat[i] < 0.0f) L_potHeat[i] = 0.0f; - /* contents[0] (2) disk number */ - /* contents[2] (2) disk number start */ - /* contents[4] (2) disk entries */ - state->EntriesCount = Stream_GetU16_LE(&contents[6]); - /* contents[8] (4) central directory size */ - *centralDirectoryOffset = Stream_GetU32_LE(&contents[12]); - /* contents[16] (2) comment length */ - return 0; -} + L_flameHeat[i] -= 0.06f * 0.01f; + if (Random_Float(&L_rnd) <= 0.005f) L_flameHeat[i] = 1.5f * 0.01f; -enum ZipSig { - ZIP_SIG_ENDOFCENTRALDIR = 0x06054b50, - ZIP_SIG_CENTRALDIR = 0x02014b50, - ZIP_SIG_LOCALFILEHEADER = 0x04034b50 -}; + /* Output the pixel */ + col = 2.0f * L_soupHeat[i]; + Math_Clamp(col, 0.0f, 1.0f); -static void Zip_DefaultProcessor(const String* path, struct Stream* data, struct ZipEntry* entry) { } -static bool Zip_DefaultSelector(const String* path) { return true; } -void Zip_Init(struct ZipState* state, struct Stream* input) { - state->Input = input; - state->EntriesCount = 0; - state->ProcessEntry = Zip_DefaultProcessor; - state->SelectEntry = Zip_DefaultSelector; -} + ptr->R = (uint8_t)(col * 100.0f + 155.0f); + ptr->G = (uint8_t)(col * col * 255.0f); + ptr->B = (uint8_t)(col * col * col * col * 128.0f); + ptr->A = 255; -ReturnCode Zip_Extract(struct ZipState* state) { - struct Stream* stream = state->Input; - uint32_t stream_len, centralDirOffset; - uint32_t sig = 0; - int i, count; - - ReturnCode res; - state->EntriesCount = 0; - if ((res = stream->Length(stream, &stream_len))) return res; - - /* At -22 for nearly all zips, but try a bit further back in case of comment */ - count = min(257, stream_len); - for (i = 22; i < count; i++) { - res = stream->Seek(stream, stream_len - i); - if (res) return ZIP_ERR_SEEK_END_OF_CENTRAL_DIR; - - if ((res = Stream_ReadU32_LE(stream, &sig))) return res; - if (sig == ZIP_SIG_ENDOFCENTRALDIR) break; - } - - if (sig != ZIP_SIG_ENDOFCENTRALDIR) return ZIP_ERR_NO_END_OF_CENTRAL_DIR; - res = Zip_ReadEndOfCentralDirectory(state, ¢ralDirOffset); - if (res) return res; - - res = stream->Seek(stream, centralDirOffset); - if (res) return ZIP_ERR_SEEK_CENTRAL_DIR; - if (state->EntriesCount > ZIP_MAX_ENTRIES) return ZIP_ERR_TOO_MANY_ENTRIES; - - /* Read all the central directory entries */ - for (count = 0; count < state->EntriesCount; count++) { - if ((res = Stream_ReadU32_LE(stream, &sig))) return res; - - if (sig == ZIP_SIG_CENTRALDIR) { - res = Zip_ReadCentralDirectory(state, &state->Entries[count]); - if (res) return res; - } else if (sig == ZIP_SIG_ENDOFCENTRALDIR) { - break; - } else { - return ZIP_ERR_INVALID_CENTRAL_DIR; + ptr++; i++; } } +} - /* Now read the local file header entries */ - for (i = 0; i < count; i++) { - struct ZipEntry* entry = &state->Entries[i]; - res = stream->Seek(stream, entry->LocalHeaderOffset); - if (res) return ZIP_ERR_SEEK_LOCAL_DIR; - if ((res = Stream_ReadU32_LE(stream, &sig))) return res; - if (sig != ZIP_SIG_LOCALFILEHEADER) return ZIP_ERR_INVALID_LOCAL_DIR; +/*########################################################################################################################* +*----------------------------------------------------Water animation------------------------------------------------------* +*#########################################################################################################################*/ +static float W_soupHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; +static float W_potHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; +static float W_flameHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; +static RNGState W_rnd; +static bool W_rndInitalised; - res = Zip_ReadLocalFileHeader(state, entry); - if (res) return res; +static void WaterAnimation_Tick(BitmapCol* ptr, int size) { + int mask = size - 1, shift = Math_Log2(size); + float soupHeat, col; + int x, y, i = 0; + + if (!W_rndInitalised) { + Random_InitFromCurrentTime(&W_rnd); + W_rndInitalised = true; + } + + for (y = 0; y < size; y++) { + for (x = 0; x < size; x++) { + /* Calculate the colour at this coordinate in the heatmap */ + soupHeat = + W_soupHeat[y << shift | ((x - 1) & mask)] + + W_soupHeat[y << shift | x ] + + W_soupHeat[y << shift | ((x + 1) & mask)]; + + W_soupHeat[i] = soupHeat / 3.3f + W_potHeat[i] * 0.8f; + + W_potHeat[i] += W_flameHeat[i]; + if (W_potHeat[i] < 0.0f) W_potHeat[i] = 0.0f; + + W_flameHeat[i] -= 0.1f * 0.05f; + if (Random_Float(&W_rnd) <= 0.05f) W_flameHeat[i] = 0.5f * 0.05f; + + /* Output the pixel */ + col = W_soupHeat[i]; + Math_Clamp(col, 0.0f, 1.0f); + col = col * col; + + ptr->R = (uint8_t)(32.0f + col * 32.0f); + ptr->G = (uint8_t)(50.0f + col * 64.0f); + ptr->A = (uint8_t)(146.0f + col * 50.0f); + ptr->B = 255; + + ptr++; i++; + } + } +} + + +/*########################################################################################################################* +*-------------------------------------------------------Animations--------------------------------------------------------* +*#########################################################################################################################*/ +struct AnimationData { + TextureLoc TexLoc; /* Tile (not pixel) coordinates in terrain.png */ + uint16_t FrameX, FrameY; /* Top left pixel coordinates of start frame in animatons.png */ + uint16_t FrameSize; /* Size of each frame in pixel coordinates */ + uint16_t State; /* Current animation frame index */ + uint16_t StatesCount; /* Total number of animation frames */ + int16_t Tick, TickDelay; +}; + +static Bitmap anims_bmp; +static struct AnimationData anims_list[ATLAS1D_MAX_ATLASES]; +static int anims_count; +static bool anims_validated, anims_useLavaAnim, anims_useWaterAnim; +#define ANIM_MIN_ARGS 7 + +static void Animations_ReadDescription(struct Stream* stream, const String* path) { + String line; char lineBuffer[STRING_SIZE * 2]; + String parts[ANIM_MIN_ARGS]; + int count; + struct AnimationData data = { 0 }; + uint8_t tileX, tileY; + + uint8_t buffer[2048]; + struct Stream buffered; + ReturnCode res; + + String_InitArray(line, lineBuffer); + /* ReadLine reads single byte at a time */ + Stream_ReadonlyBuffered(&buffered, stream, buffer, sizeof(buffer)); + + for (;;) { + res = Stream_ReadLine(&buffered, &line); + if (res == ERR_END_OF_STREAM) break; + if (res) { Chat_LogError2(res, "reading from", path); break; } + + if (!line.length || line.buffer[0] == '#') continue; + count = String_UNSAFE_Split(&line, ' ', parts, ANIM_MIN_ARGS); + if (count < ANIM_MIN_ARGS) { + Chat_Add1("&cNot enough arguments for anim: %s", &line); continue; + } + + if (!Convert_TryParseUInt8(&parts[0], &tileX) || tileX >= ATLAS2D_TILES_PER_ROW) { + Chat_Add1("&cInvalid anim tile X coord: %s", &line); continue; + } + if (!Convert_TryParseUInt8(&parts[1], &tileY) || tileY >= ATLAS2D_MAX_ROWS_COUNT) { + Chat_Add1("&cInvalid anim tile Y coord: %s", &line); continue; + } + if (!Convert_TryParseUInt16(&parts[2], &data.FrameX)) { + Chat_Add1("&cInvalid anim frame X coord: %s", &line); continue; + } + if (!Convert_TryParseUInt16(&parts[3], &data.FrameY)) { + Chat_Add1("&cInvalid anim frame Y coord: %s", &line); continue; + } + if (!Convert_TryParseUInt16(&parts[4], &data.FrameSize)) { + Chat_Add1("&cInvalid anim frame size: %s", &line); continue; + } + if (!Convert_TryParseUInt16(&parts[5], &data.StatesCount)) { + Chat_Add1("&cInvalid anim states count: %s", &line); continue; + } + if (!Convert_TryParseInt16(&parts[6], &data.TickDelay)) { + Chat_Add1("&cInvalid anim tick delay: %s", &line); continue; + } + + if (anims_count == Array_Elems(anims_list)) { + Chat_AddRaw("&cCannot show over 512 animations"); return; + } + + data.TexLoc = tileX + (tileY * ATLAS2D_TILES_PER_ROW); + anims_list[anims_count++] = data; + } +} + +#define ANIMS_FAST_SIZE 64 +static void Animations_Draw(struct AnimationData* data, TextureLoc texLoc, int size) { + int dstX = Atlas1D_Index(texLoc), srcX; + int dstY = Atlas1D_RowId(texLoc) * Atlas_TileSize; + GfxResourceID tex; + + uint8_t buffer[Bitmap_DataSize(ANIMS_FAST_SIZE, ANIMS_FAST_SIZE)]; + uint8_t* ptr = buffer; + Bitmap frame; + + /* cannot allocate memory on the stack for very big animation.png frames */ + if (size > ANIMS_FAST_SIZE) { + ptr = Mem_Alloc(size * size, 4, "anim frame"); + } + Bitmap_Create(&frame, size, size, ptr); + + if (!data) { + if (texLoc == 30) { + LavaAnimation_Tick((BitmapCol*)frame.Scan0, size); + } else if (texLoc == 14) { + WaterAnimation_Tick((BitmapCol*)frame.Scan0, size); + } + } else { + srcX = data->FrameX + data->State * size; + Bitmap_CopyBlock(srcX, data->FrameY, 0, 0, &anims_bmp, &frame, size); + } + + tex = Atlas1D_TexIds[dstX]; + if (tex) { Gfx_UpdateTexturePart(tex, 0, dstY, &frame, Gfx_Mipmaps); } + if (size > ANIMS_FAST_SIZE) Mem_Free(ptr); +} + +static void Animations_Apply(struct AnimationData* data) { + TextureLoc loc; + data->Tick--; + if (data->Tick >= 0) return; + + data->State++; + data->State %= data->StatesCount; + data->Tick = data->TickDelay; + + loc = data->TexLoc; + if (loc == 30 && anims_useLavaAnim) return; + if (loc == 14 && anims_useWaterAnim) return; + Animations_Draw(data, loc, data->FrameSize); +} + +static bool Animations_IsDefaultZip(void) { + String texPack; char texPackBuffer[STRING_SIZE]; + if (World_TextureUrl.length) return false; + + String_InitArray(texPack, texPackBuffer); + Options_Get(OPT_DEFAULT_TEX_PACK, &texPack, "default.zip"); + return String_CaselessEqualsConst(&texPack, "default.zip"); +} + +static void Animations_Clear(void) { + Mem_Free(anims_bmp.Scan0); + anims_count = 0; + anims_bmp.Scan0 = NULL; + anims_validated = false; +} + +static void Animations_Validate(void) { + struct AnimationData data; + int maxX, maxY, tileX, tileY; + int i, j; + + anims_validated = true; + for (i = 0; i < anims_count; i++) { + data = anims_list[i]; + + maxX = data.FrameX + data.FrameSize * data.StatesCount; + maxY = data.FrameY + data.FrameSize; + tileX = Atlas2D_TileX(data.TexLoc); + tileY = Atlas2D_TileY(data.TexLoc); + + if (data.FrameSize > Atlas_TileSize || tileY >= Atlas_RowsCount) { + Chat_Add2("&cAnimation frames for tile (%i, %i) are bigger than the size of a tile in terrain.png", &tileX, &tileY); + } else if (maxX > anims_bmp.Width || maxY > anims_bmp.Height) { + Chat_Add2("&cSome of the animation frames for tile (%i, %i) are at coordinates outside animations.png", &tileX, &tileY); + } else { + continue; + } + + /* Remove this animation from the list */ + for (j = i; j < anims_count - 1; j++) { + anims_list[j] = anims_list[j + 1]; + } + i--; anims_count--; + } +} + + +static void Animations_Tick(struct ScheduledTask* task) { + int i, size; + + if (anims_useLavaAnim) { + size = min(Atlas_TileSize, 64); + Animations_Draw(NULL, 30, size); + } + if (anims_useWaterAnim) { + size = min(Atlas_TileSize, 64); + Animations_Draw(NULL, 14, size); + } + + if (!anims_count) return; + if (!anims_bmp.Scan0) { + Chat_AddRaw("&cCurrent texture pack specifies it uses animations,"); + Chat_AddRaw("&cbut is missing animations.png"); + anims_count = 0; return; + } + + /* deferred, because when reading animations.txt, might not have read animations.png yet */ + if (!anims_validated) Animations_Validate(); + for (i = 0; i < anims_count; i++) { + Animations_Apply(&anims_list[i]); + } +} + +static void Animations_PackChanged(void* obj) { + Animations_Clear(); + anims_useLavaAnim = Animations_IsDefaultZip(); + anims_useWaterAnim = anims_useLavaAnim; +} + +static void Animations_FileChanged(void* obj, struct Stream* stream, const String* name) { + ReturnCode res; + if (String_CaselessEqualsConst(name, "animations.png")) { + res = Png_Decode(&anims_bmp, stream); + if (!res) return; + + Chat_LogError2(res, "decoding", name); + Mem_Free(anims_bmp.Scan0); + anims_bmp.Scan0 = NULL; + } else if (String_CaselessEqualsConst(name, "animations.txt")) { + Animations_ReadDescription(stream, name); + } else if (String_CaselessEqualsConst(name, "uselavaanim")) { + anims_useLavaAnim = true; + } else if (String_CaselessEqualsConst(name, "usewateranim")) { + anims_useWaterAnim = true; + } +} + + +/*########################################################################################################################* +*--------------------------------------------------Animations component---------------------------------------------------* +*#########################################################################################################################*/ +static void Animations_Init(void) { + ScheduledTask_Add(GAME_DEF_TICKS, Animations_Tick); + Event_RegisterVoid(&TextureEvents_PackChanged, NULL, Animations_PackChanged); + Event_RegisterEntry(&TextureEvents_FileChanged, NULL, Animations_FileChanged); +} + +static void Animations_Free(void) { + Animations_Clear(); + Event_UnregisterVoid(&TextureEvents_PackChanged, NULL, Animations_PackChanged); + Event_UnregisterEntry(&TextureEvents_FileChanged, NULL, Animations_FileChanged); +} + +struct IGameComponent Animations_Component = { + Animations_Init, /* Init */ + Animations_Free /* Free */ +}; + + +/*########################################################################################################################* +*------------------------------------------------------TerrainAtlas-------------------------------------------------------* +*#########################################################################################################################*/ +Bitmap Atlas_Bitmap; +int Atlas_TileSize, Atlas_RowsCount; +int Atlas1D_Count, Atlas1D_TilesPerAtlas; +int Atlas1D_Mask, Atlas1D_Shift; +float Atlas1D_InvTileSize; +GfxResourceID Atlas1D_TexIds[ATLAS1D_MAX_ATLASES]; + +TextureRec Atlas1D_TexRec(TextureLoc texLoc, int uCount, int* index) { + TextureRec rec; + int y = Atlas1D_RowId(texLoc); + *index = Atlas1D_Index(texLoc); + + /* Adjust coords to be slightly inside - fixes issues with AMD/ATI cards */ + rec.U1 = 0.0f; + rec.V1 = y * Atlas1D_InvTileSize; + rec.U2 = (uCount - 1) + UV2_Scale; + rec.V2 = rec.V1 + UV2_Scale * Atlas1D_InvTileSize; + return rec; +} + +static void Atlas_Convert2DTo1D(void) { + int tileSize = Atlas_TileSize; + int tilesPerAtlas = Atlas1D_TilesPerAtlas; + int atlasesCount = Atlas1D_Count; + Bitmap atlas1D; + int atlasX, atlasY; + int tile = 0, i, y; + + Platform_Log2("Loaded new atlas: %i bmps, %i per bmp", &atlasesCount, &tilesPerAtlas); + Bitmap_Allocate(&atlas1D, tileSize, tilesPerAtlas * tileSize); + + for (i = 0; i < atlasesCount; i++) { + for (y = 0; y < tilesPerAtlas; y++, tile++) { + atlasX = Atlas2D_TileX(tile) * tileSize; + atlasY = Atlas2D_TileY(tile) * tileSize; + + Bitmap_CopyBlock(atlasX, atlasY, 0, y * tileSize, + &Atlas_Bitmap, &atlas1D, tileSize); + } + Atlas1D_TexIds[i] = Gfx_CreateTexture(&atlas1D, true, Gfx_Mipmaps); + } + Mem_Free(atlas1D.Scan0); +} + +static void Atlas_Update1D(void) { + int maxAtlasHeight, maxTilesPerAtlas, maxTiles; + + maxAtlasHeight = min(4096, Gfx_MaxTexHeight); + maxTilesPerAtlas = maxAtlasHeight / Atlas_TileSize; + maxTiles = Atlas_RowsCount * ATLAS2D_TILES_PER_ROW; + + Atlas1D_TilesPerAtlas = min(maxTilesPerAtlas, maxTiles); + Atlas1D_Count = Math_CeilDiv(maxTiles, Atlas1D_TilesPerAtlas); + + Atlas1D_InvTileSize = 1.0f / Atlas1D_TilesPerAtlas; + Atlas1D_Mask = Atlas1D_TilesPerAtlas - 1; + Atlas1D_Shift = Math_Log2(Atlas1D_TilesPerAtlas); +} + +void Atlas_Update(Bitmap* bmp) { + Atlas_Bitmap = *bmp; + Atlas_TileSize = bmp->Width / ATLAS2D_TILES_PER_ROW; + Atlas_RowsCount = bmp->Height / Atlas_TileSize; + Atlas_RowsCount = min(Atlas_RowsCount, ATLAS2D_MAX_ROWS_COUNT); + + Block_RecalculateAllSpriteBB(); + Atlas_Update1D(); + Atlas_Convert2DTo1D(); +} + +static GfxResourceID Atlas_LoadTile_Raw(TextureLoc texLoc, Bitmap* element) { + int size = Atlas_TileSize; + int x = Atlas2D_TileX(texLoc), y = Atlas2D_TileY(texLoc); + if (y >= Atlas_RowsCount) return GFX_NULL; + + Bitmap_CopyBlock(x * size, y * size, 0, 0, &Atlas_Bitmap, element, size); + return Gfx_CreateTexture(element, false, Gfx_Mipmaps); +} + +GfxResourceID Atlas_LoadTile(TextureLoc texLoc) { + int tileSize = Atlas_TileSize; + Bitmap tile; + GfxResourceID texId; + uint8_t scan0[Bitmap_DataSize(64, 64)]; + + /* Try to allocate bitmap on stack if possible */ + if (tileSize > 64) { + Bitmap_Allocate(&tile, tileSize, tileSize); + texId = Atlas_LoadTile_Raw(texLoc, &tile); + Mem_Free(tile.Scan0); + return texId; + } else { + Bitmap_Create(&tile, tileSize, tileSize, scan0); + return Atlas_LoadTile_Raw(texLoc, &tile); + } +} + +void Atlas_Free(void) { + int i; + Mem_Free(Atlas_Bitmap.Scan0); + Atlas_Bitmap.Scan0 = NULL; + + for (i = 0; i < Atlas1D_Count; i++) { + Gfx_DeleteTexture(&Atlas1D_TexIds[i]); } - return 0; } diff --git a/src/TexturePack.h b/src/TexturePack.h index 4f4e3ec0d..475fea231 100644 --- a/src/TexturePack.h +++ b/src/TexturePack.h @@ -1,38 +1,69 @@ #ifndef CC_TEXPACKS_H #define CC_TEXPACKS_H -#include "Utils.h" -/* Extracts entries from a .zip archive stream (mostly resources for .zip texture pack) - Caches terrain atlases and texture packs to avoid making redundant downloads. - Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 -*/ +#include "String.h" +#include "Bitmap.h" +/* Contains everything relating to texture packs. + - Extracting the textures from a .zip archive + - Caching terrain atlases and texture packs to avoid redundant downloads + - Terrain atlas (including breaking it down into multiple 1D atlases) + - Texture animations (including default water/lava ones) + Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 */ + struct Stream; struct AsyncRequest; +struct IGameComponent; +extern struct IGameComponent Animations_Component; -/* Minimal data needed to describe an entry in a .zip archive. */ -struct ZipEntry { uint32_t CompressedSize, UncompressedSize, LocalHeaderOffset, Crc32; }; +/* Number of tiles in each row */ +#define ATLAS2D_TILES_PER_ROW 16 +#define ATLAS2D_MASK 15 +#define ATLAS2D_SHIFT 4 +/* Maximum supported number of rows in the atlas. */ +#ifdef EXTENDED_TEXTURES +#define ATLAS2D_MAX_ROWS_COUNT 32 +#else +#define ATLAS2D_MAX_ROWS_COUNT 16 +#endif +/* Maximum possible number of 1D terrain atlases. (worst case, each 1D atlas only has 1 tile) */ +#define ATLAS1D_MAX_ATLASES (ATLAS2D_TILES_PER_ROW * ATLAS2D_MAX_ROWS_COUNT) -#define ZIP_MAX_ENTRIES 2048 -/* Stores state for reading and processing entries in a .zip archive. */ -struct ZipState { - /* Source of the .zip archive data. Must be seekable. */ - struct Stream* Input; - /* Callback function to process the data in a .zip archive entry. */ - /* Note that data stream may not be seekable. (entry data might be compressed) */ - void (*ProcessEntry)(const String* path, struct Stream* data, struct ZipEntry* entry); - /* Predicate used to select which entries in a .zip archive get proessed.*/ - /* Return false to skip the entry. (this avoids seeking to the entry's data) */ - bool (*SelectEntry)(const String* path); - /* Number of entries in the .zip archive. */ - int EntriesCount; - /* Data for each entry in the .zip archive. */ - struct ZipEntry Entries[ZIP_MAX_ENTRIES]; -}; +/* Bitmap that contains the textures of all tiles. */ +/* Tiles are indexed left to right, top to bottom. */ +extern Bitmap Atlas_Bitmap; +/* Size of each tile in pixels. (default 16x16) */ +extern int Atlas_TileSize; +/* Number of rows in the atlas. (default 16, can be 32) */ +extern int Atlas_RowsCount; +/* Number of 1D atlases the atlas was split into. */ +extern int Atlas1D_Count; +/* Number of tiles in each 1D atlas. */ +extern int Atlas1D_TilesPerAtlas; +/* Converts a tile id into 1D atlas index, and index within that atlas. */ +extern int Atlas1D_Mask, Atlas1D_Shift; +/* Texture V coord that equals the size of one tile. (i.e. 1/Atlas1D_TilesPerAtlas) */ +/* NOTE: The texture U coord that equals the size of one tile is 1. */ +extern float Atlas1D_InvTileSize; +/* Textures for each 1D atlas. Only Atlas1D_Count of these are valid. */ +extern GfxResourceID Atlas1D_TexIds[ATLAS1D_MAX_ATLASES]; -/* Initialises .zip archive reader state. */ -void Zip_Init(struct ZipState* state, struct Stream* input); -/* Reads and processes the entries in a .zip archive. */ -/* Must have been initialised with Zip_Init first. */ -ReturnCode Zip_Extract(struct ZipState* state); +#define Atlas2D_TileX(texLoc) ((texLoc) & ATLAS2D_MASK) /* texLoc % ATLAS2D_TILES_PER_ROW */ +#define Atlas2D_TileY(texLoc) ((texLoc) >> ATLAS2D_SHIFT) /* texLoc / ATLAS2D_TILES_PER_ROW */ +/* Returns the index of the given tile id within a 1D atlas */ +#define Atlas1D_RowId(texLoc) ((texLoc) & Atlas1D_Mask) /* texLoc % Atlas1D_TilesPerAtlas */ +/* Returns the index of the 1D atlas within the array of 1D atlases that contains the given tile id */ +#define Atlas1D_Index(texLoc) ((texLoc) >> Atlas1D_Shift) /* texLoc / Atlas1D_TilesPerAtlas */ + +/* Loads the given atlas and converts it into an array of 1D atlases. */ +/* NOTE: Use Game_ChangeTerrainAtlas to change atlas, because that raises TextureEvents_AtlasChanged */ +void Atlas_Update(Bitmap* bmp); +/* Loads the given tile into a new separate texture. */ +GfxResourceID Atlas_LoadTile(TextureLoc texLoc); +/* Frees the atlas and 1D atlas textures. */ +void Atlas_Free(void); +/* Returns the UV rectangle of the given tile id in the 1D atlases. */ +/* That is, returns U1/U2/V1/V2 coords that make up the tile in a 1D atlas. */ +/* index is set to the index of the 1D atlas that the tile is in. */ +TextureRec Atlas1D_TexRec(TextureLoc texLoc, int uCount, int* index); /* Initialises cache state. (e.g. loading accepted/denied lists) */ void TextureCache_Init(void);