Tools for preprocessing data files from Quake to make them suitable for use on PS1 hardware
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

180 lines
6.1 KiB

#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include "bsp.h"
#include "rectpack/finders_interface.h"
int main(int argc, char** argv)
{
FILE* f;
dheader_t header;
char path[_MAX_PATH];
fopen_s(&f, argv[1], "rb");
if (f == NULL)
return 1;
fread(&header, sizeof(dheader_t), 1, f);
printf("Header model version: %d\n", header.version);
// Test reading the entity string data
fseek(f, header.entities.offset, SEEK_SET);
char* entities = (char*)malloc((header.entities.size + 1) * sizeof(char));
if (entities == NULL)
{
fclose(f);
return 1;
}
memset(entities, 0, (header.entities.size + 1) * sizeof(char));
fread(entities, sizeof(char), header.entities.size, f);
printf("Entities list:\n%s\n", entities);
free(entities);
// Test exporting texture data
fseek(f, header.miptex.offset, SEEK_SET);
mipheader_t mipheader;
fread(&mipheader.numtex, sizeof(long), 1, f);
mipheader.offset = (long*)malloc(mipheader.numtex * sizeof(long));
if (mipheader.offset == NULL)
{
fclose(f);
return 1;
}
fread(mipheader.offset, sizeof(long), mipheader.numtex, f);
using spaces_type = rectpack2D::empty_spaces<false>;
using rect_type = rectpack2D::output_rect_t<spaces_type>;
auto report_successful = [](rect_type&) {
return rectpack2D::callback_result::CONTINUE_PACKING;
};
auto report_unsuccessful = [](rect_type&) {
return rectpack2D::callback_result::ABORT_PACKING;
};
const auto max_side = 512; // Max height of PS1 VRAM. 8-bit textures take up half the horizontal space so this is 256x512 in practice, or a quarter of the PS1's VRAM allocation.
const auto discard_step = -4;
std::vector<rect_type> rectangles;
miptex_t miptex;
// Try some texture packing and see if we fit inside the PS1's VRAM
for (int texNum = 0; texNum < mipheader.numtex; ++texNum)
{
unsigned long miptexOffset = header.miptex.offset + mipheader.offset[texNum];
fseek(f, miptexOffset, SEEK_SET);
fread(&miptex, sizeof(miptex_t), 1, f);
//printf("Texture %d (%dx%d): %.16s\n", texNum, miptex.width, miptex.height, miptex.name);
// Shrink the larger textures, but keep smaller ones at their original size
int ps1mip = miptex.width > 64 || miptex.height > 64 ? 1 : 0;
if (strcmp(miptex.name, "clip") && strcmp(miptex.name, "trigger"))
rectangles.emplace_back(rectpack2D::rect_xywh(0, 0, miptex.width >> ps1mip, miptex.height >> ps1mip));
else
rectangles.emplace_back(rectpack2D::rect_xywh(0, 0, 0, 0));
}
// Automatic atlas packing. Nice but it tries to make a square atlas which is not what we want. (This is solved by hacking the header itself)
const auto result_size = rectpack2D::find_best_packing<spaces_type>(
rectangles,
rectpack2D::make_finder_input(
max_side,
discard_step,
report_successful,
report_unsuccessful,
rectpack2D::flipping_option::DISABLED
)
);
printf("%d textures. Packed texture atlas size: %d x %d\n", mipheader.numtex, result_size.w, result_size.h);
unsigned char* atlas = (unsigned char*)malloc(result_size.w * result_size.h * sizeof(unsigned char));
memset(atlas, 0, result_size.w * result_size.h * sizeof(unsigned char));
// Try to construct the texture atlas, see what we get
for (int texNum = 0; texNum < mipheader.numtex; ++texNum)
{
unsigned long miptexOffset = header.miptex.offset + mipheader.offset[texNum];
fseek(f, miptexOffset, SEEK_SET);
fread(&miptex, sizeof(miptex_t), 1, f);
char* outName = miptex.name;
if (*outName == '*' || *outName == '+')
outName++;
for (int mipLevel = 0; mipLevel < 4; ++mipLevel)
{
unsigned long mipOffset = *(&miptex.offset1 + mipLevel);
fseek(f, miptexOffset + mipOffset, SEEK_SET);
size_t numBytes = (miptex.width * miptex.height) >> mipLevel;
unsigned char* texBytes = (unsigned char*)malloc(sizeof(unsigned char) * numBytes);
fread(texBytes, sizeof(unsigned char), numBytes, f);
FILE* fout;
sprintf_s(path, _MAX_PATH, "textures/%s-mip%d-%dx%d.raw", outName, mipLevel, miptex.width >> mipLevel, miptex.height >> mipLevel);
fopen_s(&fout, path, "wb");
fwrite(texBytes, sizeof(unsigned char), numBytes, fout);
fclose(fout);
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));
}
}
free(texBytes);
}
}
FILE* fatlas;
sprintf_s(path, _MAX_PATH, "atlas-%dx%d.raw", result_size.w, result_size.h);
fopen_s(&fatlas, path, "wb");
fwrite(atlas, sizeof(unsigned char), result_size.w * result_size.h, fatlas);
fclose(fatlas);
free(atlas);
free(mipheader.offset);
// Inspect vertex data
int numVertices = header.vertices.size / sizeof(vertex_t);
int numFaces = header.faces.size / sizeof(face_t);
fseek(f, header.vertices.offset, SEEK_SET);
vertex_t* vertices = (vertex_t*)malloc(header.vertices.size);
fread(vertices, sizeof(vertex_t), numVertices, f);
vec3_t min = { FLT_MAX, FLT_MAX, FLT_MAX }, max = { -FLT_MAX, -FLT_MAX, -FLT_MAX };
for (int vertIdx = 0; vertIdx < numVertices; ++vertIdx)
{
vertex_t* vert = &vertices[vertIdx];
if (vert->X > max.x) max.x = vert->X;
if (vert->Y > max.y) max.y = vert->Y;
if (vert->Z > max.z) max.z = vert->Z;
if (vert->X < min.x) min.x = vert->X;
if (vert->Y < min.y) min.y = vert->Y;
if (vert->Z < min.z) min.z = vert->Z;
}
printf("%d vertices, %d faces, min = (%f, %f, %f), max = (%f, %f, %f)\n", numVertices, numFaces, min.x, min.y, min.z, max.x, max.y, max.z);
const int fixedScale = 1 << 14;
int fixedMin[3] = { (int)(min.x * fixedScale), (int)(min.y * fixedScale), (int)(min.z * fixedScale) };
int fixedMax[3] = { (int)(max.x * fixedScale), (int)(max.y * fixedScale), (int)(max.z * fixedScale) };
printf("Fixed point min = (%d, %d, %d), max = (%d, %d, %d)\n", fixedMin[0], fixedMin[1], fixedMin[2], fixedMax[0], fixedMax[1], fixedMax[2]);
fclose(f);
return 0;
}