|
|
|
@ -5,9 +5,18 @@ |
|
|
|
#include "texture.h"
|
|
|
|
|
|
|
|
#include "rectpack/finders_interface.h"
|
|
|
|
#include "tim.h"
|
|
|
|
|
|
|
|
#define PALETTE_SIZE 256
|
|
|
|
|
|
|
|
static char path[_MAX_PATH]; |
|
|
|
|
|
|
|
/**
|
|
|
|
tp specifies the color depth for the texture page in the range of 0 to 2 (0:4-bit, 1:8-bit, 2:16-bit). |
|
|
|
abr specifies the blend operator for both non-textured and textured semi-transparent primitives which can be ignored for now and lastly, |
|
|
|
x,y specifies the X,Y coordinates of the VRAM in 16-bit pixel units. |
|
|
|
Keep in mind that the coordinates will be rounded down to the next lowest texture page. |
|
|
|
*/ |
|
|
|
#define getTPage(tp, abr, x, y) ( \
|
|
|
|
(((x) / 64) & 15) | \ |
|
|
|
((((y) / 256) & 1) << 4) | \ |
|
|
|
@ -16,6 +25,40 @@ static char path[_MAX_PATH]; |
|
|
|
((((y) / 512) & 1) << 11) \ |
|
|
|
) |
|
|
|
|
|
|
|
static bool generate_clut(const char* paletteFile, tim::PARAM* outTim) |
|
|
|
{ |
|
|
|
unsigned char palette[PALETTE_SIZE * 3]; |
|
|
|
|
|
|
|
FILE* fp; |
|
|
|
fopen_s(&fp, paletteFile, "rb"); |
|
|
|
if (fp == NULL) |
|
|
|
return false; |
|
|
|
|
|
|
|
fread(palette, sizeof(unsigned char) * 3, PALETTE_SIZE, fp); |
|
|
|
fclose(fp); |
|
|
|
|
|
|
|
tim::PIX_RGB5* clut = (tim::PIX_RGB5*)malloc(PALETTE_SIZE * sizeof(tim::PIX_RGB5)); |
|
|
|
if (clut == NULL) |
|
|
|
return false; |
|
|
|
|
|
|
|
for (int c = 0; c < PALETTE_SIZE; ++c) |
|
|
|
{ |
|
|
|
clut[c].r = palette[3 * c + 0] >> 3; |
|
|
|
clut[c].g = palette[3 * c + 1] >> 3; |
|
|
|
clut[c].b = palette[3 * c + 2] >> 3; |
|
|
|
clut[c].i = 0; |
|
|
|
|
|
|
|
// Completely black pixels are regarded as transparent by the PS1, so prevent that from happening by making those palette entries *nearly* black
|
|
|
|
if (clut[c].r == 0 && clut[c].g == 0 && clut[c].b == 0) |
|
|
|
clut[c].r = clut[c].g = clut[c].b = 1; |
|
|
|
} |
|
|
|
|
|
|
|
outTim->clutData = clut; |
|
|
|
outTim->clutWidth = PALETTE_SIZE; |
|
|
|
outTim->clutHeight = 1; |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
bool process_textures(const world_t* world, std::vector<ps1bsp_texture_t>& outTextures) |
|
|
|
{ |
|
|
|
using spaces_type = rectpack2D::empty_spaces<false>; |
|
|
|
@ -47,7 +90,7 @@ bool process_textures(const world_t* world, std::vector<ps1bsp_texture_t>& outTe |
|
|
|
int ps1mip = miptex->width > 64 || miptex->height > 64 ? 1 : 0; |
|
|
|
|
|
|
|
// Make an exception for the difficulty selection teleporters
|
|
|
|
if (!strncmp(miptex->name, "skill", 5) || !strcmp(miptex->name, "quake")) |
|
|
|
if (!strncmp(miptex->name, "skill", 5))// || !strcmp(miptex->name, "quake"))
|
|
|
|
ps1mip = 0; |
|
|
|
|
|
|
|
if (strcmp(miptex->name, "clip") && strcmp(miptex->name, "trigger")) |
|
|
|
@ -75,6 +118,14 @@ bool process_textures(const world_t* world, std::vector<ps1bsp_texture_t>& outTe |
|
|
|
|
|
|
|
memset(atlas, 0, result_size.w * result_size.h * sizeof(unsigned char)); |
|
|
|
|
|
|
|
tim::PARAM outTim = { 0 }; |
|
|
|
outTim.format = 1; // 8-bit per pixel, all Quake textures use this
|
|
|
|
outTim.imgXoffs = 512; |
|
|
|
outTim.imgYoffs = 256; |
|
|
|
outTim.clutXoffs = 512; |
|
|
|
outTim.clutYoffs = 0; |
|
|
|
generate_clut("palette.lmp", &outTim); |
|
|
|
|
|
|
|
// Try to construct the texture atlas, see what we get
|
|
|
|
for (int texNum = 0; texNum < world->mipheader.numtex; ++texNum) |
|
|
|
{ |
|
|
|
@ -93,21 +144,9 @@ bool process_textures(const world_t* world, std::vector<ps1bsp_texture_t>& outTe |
|
|
|
{ |
|
|
|
unsigned char* texBytes = world->textures[texNum * 4 + mipLevel]; |
|
|
|
|
|
|
|
// Dump each individual texture
|
|
|
|
//FILE* fraw;
|
|
|
|
//sprintf_s(path, _MAX_PATH, "textures/%s-%s-mip%d-%dx%d.raw", world->name, outName, mipLevel, miptex->width >> mipLevel, miptex->height >> mipLevel);
|
|
|
|
//fopen_s(&fraw, path, "wb");
|
|
|
|
//if (fraw != NULL)
|
|
|
|
//{
|
|
|
|
// size_t numBytes = (miptex->width * miptex->height) >> mipLevel;
|
|
|
|
// fwrite(texBytes, sizeof(unsigned char), numBytes, fraw);
|
|
|
|
// fclose(fraw);
|
|
|
|
//}
|
|
|
|
|
|
|
|
const auto& rectangle = rectangles[texNum]; |
|
|
|
if (miptex->width >> mipLevel == rectangle.w) // This is the mip level we've previously decided we want for our PS1 atlas
|
|
|
|
{ |
|
|
|
//printf("Writing texture %s mip %d to position: (%d, %d) w = %d, h = %d\n", miptex->name, mipLevel, rectangle.x, rectangle.y, rectangle.w, rectangle.h);
|
|
|
|
for (int y = 0; y < rectangle.h; ++y) |
|
|
|
{ |
|
|
|
memcpy_s(atlas + ((rectangle.y + y) * result_size.w + rectangle.x), rectangle.w * sizeof(unsigned char), texBytes + (y * rectangle.w), rectangle.w * sizeof(unsigned char)); |
|
|
|
@ -117,23 +156,11 @@ bool process_textures(const world_t* world, std::vector<ps1bsp_texture_t>& outTe |
|
|
|
ps1tex.w = (u_char)rectangle.w; |
|
|
|
ps1tex.h = (u_char)rectangle.h; |
|
|
|
|
|
|
|
// prect is derived from the texture's position inside the atlas (rectangle.x/y) and the planned position of the atlas in VRAM (512, 256)
|
|
|
|
// mode is always 1 (8-bit palletized)
|
|
|
|
//texture->uoffs = (texture->prect.x % 64) << (2 - (texture->mode & 0x3));
|
|
|
|
//texture->voffs = (texture->prect.y & 0xFF);
|
|
|
|
|
|
|
|
u_short x = (rectangle.x / 2) + 512; // Divide by 2 to get the coordinate in 16-bit pixel units
|
|
|
|
u_short y = rectangle.y + 256; |
|
|
|
|
|
|
|
/*
|
|
|
|
tp specifies the color depth for the texture page in the range of 0 to 2 (0:4-bit, 1:8-bit, 2:16-bit). |
|
|
|
abr specifies the blend operator for both non-textured and textured semi-transparent primitives which can be ignored for now and lastly, |
|
|
|
x,y specifies the X,Y coordinates of the VRAM in 16-bit pixel units. |
|
|
|
Keep in mind that the coordinates will be rounded down to the next lowest texture page. */ |
|
|
|
|
|
|
|
const int mode = 1; // 8-bit per pixel, all Quake textures use this
|
|
|
|
ps1tex.tpage = getTPage(mode, 0, x, y); |
|
|
|
ps1tex.uoffs = (u_char)((x % 64) << (2 - mode)); |
|
|
|
u_short x = (rectangle.x / 2) + outTim.imgXoffs; // Divide by 2 to get the coordinate in 16-bit pixel units
|
|
|
|
u_short y = rectangle.y + outTim.imgYoffs; |
|
|
|
|
|
|
|
ps1tex.tpage = getTPage(outTim.format, 0, x, y); |
|
|
|
ps1tex.uoffs = (u_char)((x % 64) << (2 - outTim.format)); |
|
|
|
ps1tex.voffs = (u_char)(y & 0xFF); |
|
|
|
// TODO: animated textures
|
|
|
|
|
|
|
|
@ -142,15 +169,14 @@ bool process_textures(const world_t* world, std::vector<ps1bsp_texture_t>& outTe |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
FILE* fatlas; |
|
|
|
sprintf_s(path, _MAX_PATH, "%s-atlas-%dx%d.raw", world->name, result_size.w, result_size.h); |
|
|
|
fopen_s(&fatlas, path, "wb"); |
|
|
|
if (fatlas != NULL) |
|
|
|
{ |
|
|
|
fwrite(atlas, sizeof(unsigned char), result_size.w * result_size.h, fatlas); |
|
|
|
fclose(fatlas); |
|
|
|
} |
|
|
|
outTim.imgData = atlas; |
|
|
|
outTim.imgWidth = result_size.w; |
|
|
|
outTim.imgHeight = result_size.h; |
|
|
|
|
|
|
|
sprintf_s(path, _MAX_PATH, "atlas-%s.tim", world->name); |
|
|
|
tim::ExportFile(path, &outTim); |
|
|
|
|
|
|
|
free(atlas); |
|
|
|
free(outTim.imgData); |
|
|
|
free(outTim.clutData); |
|
|
|
return true; |
|
|
|
} |