mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-15 18:45:23 -04:00
Reorganise texture pack/terrain atlas/animations files
This commit is contained in:
parent
b888213adc
commit
3ae356beba
376
src/Animations.c
376
src/Animations.c
@ -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 */
|
||||
};
|
@ -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
|
@ -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];
|
||||
|
@ -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;
|
||||
|
@ -218,7 +218,6 @@
|
||||
<ClInclude Include="Inventory.h" />
|
||||
<ClInclude Include="IsometricDrawer.h" />
|
||||
<ClInclude Include="Lighting.h" />
|
||||
<ClInclude Include="Animations.h" />
|
||||
<ClInclude Include="MapGenerator.h" />
|
||||
<ClInclude Include="MapRenderer.h" />
|
||||
<ClInclude Include="Options.h" />
|
||||
@ -231,7 +230,6 @@
|
||||
<ClInclude Include="ServerConnection.h" />
|
||||
<ClInclude Include="Stream.h" />
|
||||
<ClInclude Include="GameStructs.h" />
|
||||
<ClInclude Include="TerrainAtlas.h" />
|
||||
<ClInclude Include="TexturePack.h" />
|
||||
<ClInclude Include="Utils.h" />
|
||||
<ClInclude Include="PackedCol.h" />
|
||||
@ -280,7 +278,6 @@
|
||||
<ClCompile Include="IsometricDrawer.c" />
|
||||
<ClCompile Include="Input.c" />
|
||||
<ClCompile Include="Lighting.c" />
|
||||
<ClCompile Include="Animations.c" />
|
||||
<ClCompile Include="Entity.c" />
|
||||
<ClCompile Include="MapRenderer.c" />
|
||||
<ClCompile Include="Options.c" />
|
||||
@ -296,7 +293,6 @@
|
||||
<ClCompile Include="ServerConnection.c" />
|
||||
<ClCompile Include="Stream.c" />
|
||||
<ClCompile Include="String.c" />
|
||||
<ClCompile Include="TerrainAtlas.c" />
|
||||
<ClCompile Include="TexturePack.c" />
|
||||
<ClCompile Include="Utils.c" />
|
||||
<ClCompile Include="Vectors.c" />
|
||||
|
@ -195,9 +195,6 @@
|
||||
<ClInclude Include="Builder.h">
|
||||
<Filter>Header Files\MeshBuilder</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TerrainAtlas.h">
|
||||
<Filter>Header Files\TexturePack</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Picking.h">
|
||||
<Filter>Header Files\Math</Filter>
|
||||
</ClInclude>
|
||||
@ -216,9 +213,6 @@
|
||||
<ClInclude Include="Particle.h">
|
||||
<Filter>Header Files\Entities</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Animations.h">
|
||||
<Filter>Header Files\TexturePack</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Widgets.h">
|
||||
<Filter>Header Files\2D</Filter>
|
||||
</ClInclude>
|
||||
@ -353,9 +347,6 @@
|
||||
<ClCompile Include="Program.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TerrainAtlas.c">
|
||||
<Filter>Source Files\TexturePack</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Entity.c">
|
||||
<Filter>Source Files\Entities</Filter>
|
||||
</ClCompile>
|
||||
@ -386,9 +377,6 @@
|
||||
<ClCompile Include="Particle.c">
|
||||
<Filter>Source Files\Entities</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Animations.c">
|
||||
<Filter>Source Files\TexturePack</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Widgets.c">
|
||||
<Filter>Source Files\2D</Filter>
|
||||
</ClCompile>
|
||||
|
153
src/Deflate.c
153
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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -1,5 +1,5 @@
|
||||
#include "Drawer.h"
|
||||
#include "TerrainAtlas.h"
|
||||
#include "TexturePack.h"
|
||||
#include "Constants.h"
|
||||
|
||||
bool Drawer_Tinted;
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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];
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
@ -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
|
@ -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);
|
||||
}
|
||||
return 0;
|
||||
if (!L_rndInitalised) {
|
||||
Random_InitFromCurrentTime(&L_rnd);
|
||||
L_rndInitalised = true;
|
||||
}
|
||||
|
||||
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;
|
||||
for (y = 0; y < size; y++) {
|
||||
for (x = 0; x < size; x++) {
|
||||
/* Calculate the colour at this coordinate in the heatmap */
|
||||
|
||||
ReturnCode res;
|
||||
if ((res = Stream_Read(stream, contents, sizeof(contents)))) return res;
|
||||
/* 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];
|
||||
|
||||
/* 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]);
|
||||
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)] +
|
||||
|
||||
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 & mask) << shift | ((xx - 1) & mask)] +
|
||||
L_soupHeat[(yy & mask) << shift | (xx & mask)] +
|
||||
L_soupHeat[(yy & mask) << shift | ((xx + 1) & mask)] +
|
||||
|
||||
extraDataLen = pathLen + extraLen + commentLen;
|
||||
return stream->Skip(stream, extraDataLen);
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ReturnCode Zip_ReadEndOfCentralDirectory(struct ZipState* state, uint32_t* centralDirectoryOffset) {
|
||||
struct Stream* stream = state->Input;
|
||||
uint8_t contents[18];
|
||||
|
||||
ReturnCode res;
|
||||
if ((res = Stream_Read(stream, contents, sizeof(contents)))) return res;
|
||||
/*########################################################################################################################*
|
||||
*----------------------------------------------------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;
|
||||
|
||||
/* 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;
|
||||
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;
|
||||
}
|
||||
|
||||
enum ZipSig {
|
||||
ZIP_SIG_ENDOFCENTRALDIR = 0x06054b50,
|
||||
ZIP_SIG_CENTRALDIR = 0x02014b50,
|
||||
ZIP_SIG_LOCALFILEHEADER = 0x04034b50
|
||||
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 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;
|
||||
}
|
||||
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
|
||||
|
||||
ReturnCode Zip_Extract(struct ZipState* state) {
|
||||
struct Stream* stream = state->Input;
|
||||
uint32_t stream_len, centralDirOffset;
|
||||
uint32_t sig = 0;
|
||||
int i, count;
|
||||
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;
|
||||
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;
|
||||
String_InitArray(line, lineBuffer);
|
||||
/* ReadLine reads single byte at a time */
|
||||
Stream_ReadonlyBuffered(&buffered, stream, buffer, sizeof(buffer));
|
||||
|
||||
if ((res = Stream_ReadU32_LE(stream, &sig))) return res;
|
||||
if (sig == ZIP_SIG_ENDOFCENTRALDIR) break;
|
||||
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 (sig != ZIP_SIG_ENDOFCENTRALDIR) return ZIP_ERR_NO_END_OF_CENTRAL_DIR;
|
||||
res = Zip_ReadEndOfCentralDirectory(state, ¢ralDirOffset);
|
||||
if (res) return res;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
if (anims_count == Array_Elems(anims_list)) {
|
||||
Chat_AddRaw("&cCannot show over 512 animations"); return;
|
||||
}
|
||||
|
||||
/* Read all the central directory entries */
|
||||
for (count = 0; count < state->EntriesCount; count++) {
|
||||
if ((res = Stream_ReadU32_LE(stream, &sig))) return res;
|
||||
data.TexLoc = tileX + (tileY * ATLAS2D_TILES_PER_ROW);
|
||||
anims_list[anims_count++] = data;
|
||||
}
|
||||
}
|
||||
|
||||
if (sig == ZIP_SIG_CENTRALDIR) {
|
||||
res = Zip_ReadCentralDirectory(state, &state->Entries[count]);
|
||||
if (res) return res;
|
||||
} else if (sig == ZIP_SIG_ENDOFCENTRALDIR) {
|
||||
break;
|
||||
#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 {
|
||||
return ZIP_ERR_INVALID_CENTRAL_DIR;
|
||||
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--;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
static void Animations_Tick(struct ScheduledTask* task) {
|
||||
int i, size;
|
||||
|
||||
res = Zip_ReadLocalFileHeader(state, entry);
|
||||
if (res) return res;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user