Browse Source
First pass at converting texture info to PS1 VRAM data. Not quite right yet but it's starting to work.
master
First pass at converting texture info to PS1 VRAM data. Not quite right yet but it's starting to work.
master
7 changed files with 226 additions and 183 deletions
-
2PS1BSP.vcxproj
-
6PS1BSP.vcxproj.filters
-
14bsp.h
-
210main.cpp
-
24ps1bsp.h
-
150texture.cpp
-
3texture.h
@ -0,0 +1,150 @@ |
|||||
|
#include "common.h"
|
||||
|
#include "bsp.h"
|
||||
|
#include "ps1types.h"
|
||||
|
#include "ps1bsp.h"
|
||||
|
#include "texture.h"
|
||||
|
|
||||
|
#include "rectpack/finders_interface.h"
|
||||
|
|
||||
|
static char path[_MAX_PATH]; |
||||
|
|
||||
|
#define getTPage(tp, abr, x, y) ( \
|
||||
|
(((x) / 64) & 15) | \ |
||||
|
((((y) / 256) & 1) << 4) | \ |
||||
|
(((abr) & 3) << 5) | \ |
||||
|
(((tp) & 3) << 7) | \ |
||||
|
((((y) / 512) & 1) << 11) \ |
||||
|
) |
||||
|
|
||||
|
bool process_textures(const world_t* world, std::vector<ps1bsp_texture_t>& outTextures) |
||||
|
{ |
||||
|
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; |
||||
|
|
||||
|
// Try some texture packing and see if we fit inside the PS1's VRAM
|
||||
|
for (int texNum = 0; texNum < world->mipheader.numtex; ++texNum) |
||||
|
{ |
||||
|
miptex_t* miptex = &world->miptexes[texNum]; |
||||
|
if (miptex->name[0] == '\0') // Weird edge case on N64START.bsp, corrupt data perhaps?
|
||||
|
miptex->width = miptex->height = 0; |
||||
|
|
||||
|
//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", world->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 false; |
||||
|
|
||||
|
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 < world->mipheader.numtex; ++texNum) |
||||
|
{ |
||||
|
miptex_t* miptex = &world->miptexes[texNum]; |
||||
|
if (miptex->name[0] == '\0') // Weird edge case on N64START.bsp, corrupt data perhaps?
|
||||
|
{ |
||||
|
outTextures.push_back(ps1bsp_texture_t{ 0 }); // We have to add something, otherwise the texture IDs get messed up
|
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
char* outName = miptex->name; |
||||
|
if (*outName == '*' || *outName == '+') |
||||
|
outName++; |
||||
|
|
||||
|
for (int mipLevel = 0; mipLevel < 4; ++mipLevel) |
||||
|
{ |
||||
|
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)); |
||||
|
} |
||||
|
|
||||
|
ps1bsp_texture_t ps1tex = { 0 }; |
||||
|
ps1tex.w = (u_char)miptex->width; |
||||
|
ps1tex.h = (u_char)miptex->height; |
||||
|
|
||||
|
u_short x = rectangle.x + 512; |
||||
|
u_short y = rectangle.y + 256; |
||||
|
|
||||
|
// 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);
|
||||
|
|
||||
|
/*
|
||||
|
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. */ |
||||
|
|
||||
|
ps1tex.tpage = getTPage(1, 0, x, y); |
||||
|
ps1tex.uoffs = (u_char)((x % 64) << 1); |
||||
|
ps1tex.voffs = (u_char)(y & 0xFF); |
||||
|
// TODO: animated textures
|
||||
|
outTextures.push_back(ps1tex); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
free(atlas); |
||||
|
return true; |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
#pragma once |
||||
|
|
||||
|
bool process_textures(const world_t* world, std::vector<ps1bsp_texture_t>& outTextures); |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue