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.
222 lines
6.8 KiB
222 lines
6.8 KiB
#include <memory.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include "bsp.h"
|
|
#include "rectpack/finders_interface.h"
|
|
|
|
static char path[_MAX_PATH];
|
|
|
|
int process_entities(dheader_t* header, FILE* f)
|
|
{
|
|
fseek(f, header->entities.offset, SEEK_SET);
|
|
|
|
char* entities = (char*)malloc((header->entities.size + 1) * sizeof(char));
|
|
if (entities == NULL)
|
|
return 0;
|
|
|
|
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);
|
|
return 1;
|
|
}
|
|
|
|
int process_textures(const char* bspname, dheader_t* header, FILE* f)
|
|
{
|
|
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)
|
|
return 0;
|
|
|
|
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 512x256 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));
|
|
if (atlas == NULL)
|
|
return 0;
|
|
|
|
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-%s-mip%d-%dx%d.raw", bspname, outName, mipLevel, miptex.width >> mipLevel, miptex.height >> mipLevel);
|
|
fopen_s(&fout, path, "wb");
|
|
if (fout != NULL)
|
|
{
|
|
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, "%s-atlas-%dx%d.raw", bspname, 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);
|
|
}
|
|
|
|
free(atlas);
|
|
free(mipheader.offset);
|
|
return 1;
|
|
}
|
|
|
|
int process_vertices(dheader_t* header, FILE* f)
|
|
{
|
|
int numVertices = header->vertices.size / sizeof(vertex_t);
|
|
int numFaces = header->faces.size / sizeof(face_t);
|
|
|
|
vertex_t* vertices = (vertex_t*)malloc(header->vertices.size);
|
|
if (vertices == NULL)
|
|
return 0;
|
|
|
|
fseek(f, header->vertices.offset, SEEK_SET);
|
|
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]);
|
|
return 1;
|
|
}
|
|
|
|
int process_bsp(const char* bspname)
|
|
{
|
|
FILE* f;
|
|
dheader_t header;
|
|
|
|
sprintf_s(path, _MAX_PATH, "%s.bsp", bspname);
|
|
fopen_s(&f, path, "rb");
|
|
if (f == NULL)
|
|
return 0;
|
|
|
|
fread(&header, sizeof(dheader_t), 1, f);
|
|
printf("Header model version: %d\n", header.version);
|
|
|
|
// Test reading the entity string data
|
|
if (!process_entities(&header, f))
|
|
{
|
|
fclose(f);
|
|
return 0;
|
|
}
|
|
|
|
// Test exporting texture data
|
|
if (!process_textures(bspname, &header, f))
|
|
{
|
|
fclose(f);
|
|
return 0;
|
|
}
|
|
|
|
// Inspect vertex data
|
|
if (!process_vertices(&header, f))
|
|
{
|
|
fclose(f);
|
|
return 0;
|
|
}
|
|
|
|
fclose(f);
|
|
return 1;
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
return !process_bsp(argv[1]);
|
|
}
|