Browse Source

First pass at converting texture info to PS1 VRAM data. Not quite right yet but it's starting to work.

master
Nico de Poel 3 years ago
parent
commit
d84a14973b
  1. 2
      PS1BSP.vcxproj
  2. 6
      PS1BSP.vcxproj.filters
  3. 14
      bsp.h
  4. 210
      main.cpp
  5. 24
      ps1bsp.h
  6. 150
      texture.cpp
  7. 3
      texture.h

2
PS1BSP.vcxproj

@ -143,6 +143,7 @@
<ItemGroup> <ItemGroup>
<ClCompile Include="lighting.cpp" /> <ClCompile Include="lighting.cpp" />
<ClCompile Include="main.cpp" /> <ClCompile Include="main.cpp" />
<ClCompile Include="texture.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="bsp.h" /> <ClInclude Include="bsp.h" />
@ -156,6 +157,7 @@
<ClInclude Include="rectpack\finders_interface.h" /> <ClInclude Include="rectpack\finders_interface.h" />
<ClInclude Include="rectpack\insert_and_split.h" /> <ClInclude Include="rectpack\insert_and_split.h" />
<ClInclude Include="rectpack\rect_structs.h" /> <ClInclude Include="rectpack\rect_structs.h" />
<ClInclude Include="texture.h" />
</ItemGroup> </ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">

6
PS1BSP.vcxproj.filters

@ -24,6 +24,9 @@
<ClCompile Include="lighting.cpp"> <ClCompile Include="lighting.cpp">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="texture.cpp">
<Filter>Header Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="bsp.h"> <ClInclude Include="bsp.h">
@ -59,5 +62,8 @@
<ClInclude Include="common.h"> <ClInclude Include="common.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="texture.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
</Project> </Project>

14
bsp.h

@ -127,6 +127,17 @@ typedef struct
// must be in [0,numvertices[ // must be in [0,numvertices[
} edge_t; } edge_t;
typedef struct
{
vec3_t vectorS; // S vector, horizontal in texture space)
scalar_t distS; // horizontal offset in texture space
vec3_t vectorT; // T vector, vertical in texture space
scalar_t distT; // vertical offset in texture space
unsigned long texture_id; // Index of Mip Texture
// must be in [0,numtex[
unsigned long animated; // 0 for ordinary textures, 1 for water
} texinfo_t;
typedef struct typedef struct
{ {
unsigned short plane_id; // The plane in which the face lies unsigned short plane_id; // The plane in which the face lies
@ -193,6 +204,9 @@ typedef struct World
int edgeListLength; int edgeListLength;
int* edgeList; int* edgeList;
int numTexInfos;
texinfo_t* texInfos;
int numFaces; int numFaces;
face_t* faces; face_t* faces;

210
main.cpp

@ -1,167 +1,12 @@
#include "common.h" #include "common.h"
#include "bsp.h" #include "bsp.h"
#include "rectpack/finders_interface.h"
#include "ps1types.h" #include "ps1types.h"
#include "ps1bsp.h" #include "ps1bsp.h"
#include "lighting.h" #include "lighting.h"
#include "texture.h"
static char path[_MAX_PATH]; static char path[_MAX_PATH];
int process_entities(const world_t *world)
{
printf("Entities list:\n%s\n", world->entities);
return 1;
}
int process_textures(const world_t* world)
{
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 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 < world->mipheader.numtex; ++texNum)
{
miptex_t* miptex = &world->miptexes[texNum];
if (miptex->name[0] == '\0') // Weird edge case on N64START.bsp, corrupt data perhaps?
continue;
char* outName = miptex->name;
if (*outName == '*' || *outName == '+')
outName++;
for (int mipLevel = 0; mipLevel < 4; ++mipLevel)
{
unsigned char* texBytes = world->textures[texNum * 4 + mipLevel];
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));
}
}
}
}
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 1;
}
int process_vertices(const world_t* world)
{
vec3_t min = { FLT_MAX, FLT_MAX, FLT_MAX }, max = { -FLT_MAX, -FLT_MAX, -FLT_MAX };
for (int vertIdx = 0; vertIdx < world->numVertices; ++vertIdx)
{
vertex_t* vert = &world->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", world->numVertices, world->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;
}
typedef struct
{
vertex_t* vertex;
unsigned short index;
} vertexref_t;
// Determines a floating origin for the given leaf
static void leaf_zone(const dleaf_t* leaf, short zone[3])
{
const unsigned short mask = 0xFE00; // Zero out the 9 least significant bits
short midX = (leaf->bound.min[0] + leaf->bound.max[0]) / 2;
short midY = (leaf->bound.min[1] + leaf->bound.max[1]) / 2;
short midZ = (leaf->bound.min[2] + leaf->bound.max[2]) / 2;
zone[0] = midX & mask;
zone[1] = midY & mask;
zone[2] = midZ & mask;
}
template<class TData> size_t writeMapData(const std::vector<TData>& data, ps1bsp_dentry_t& dentry, FILE* f) template<class TData> size_t writeMapData(const std::vector<TData>& data, ps1bsp_dentry_t& dentry, FILE* f)
{ {
dentry.offset = (unsigned int)ftell(f); dentry.offset = (unsigned int)ftell(f);
@ -225,7 +70,7 @@ static float computeFaceArea(const world_t* world, const face_t* face)
return extents.x * extents.y; return extents.x * extents.y;
} }
int process_faces(const world_t* world)
int process_faces(const world_t* world, const std::vector<ps1bsp_texture_t>& textures)
{ {
// Write some data to a file // Write some data to a file
FILE* fbsp; FILE* fbsp;
@ -271,11 +116,15 @@ int process_faces(const world_t* world)
for (int faceIdx = 0; faceIdx < world->numFaces; ++faceIdx) for (int faceIdx = 0; faceIdx < world->numFaces; ++faceIdx)
{ {
face_t* face = &world->faces[faceIdx]; face_t* face = &world->faces[faceIdx];
const texinfo_t* texinfo = &world->texInfos[face->texinfo_id];
const miptex_t* miptex = &world->miptexes[texinfo->texture_id];
const ps1bsp_texture_t& ps1tex = textures[texinfo->texture_id];
ps1bsp_face_t outFace = { 0 }; ps1bsp_face_t outFace = { 0 };
outFace.planeId = face->plane_id; outFace.planeId = face->plane_id;
outFace.side = face->side; outFace.side = face->side;
outFace.firstFaceVertex = (unsigned short)outFaceVertices.size(); outFace.firstFaceVertex = (unsigned short)outFaceVertices.size();
outFace.textureId = (unsigned char)face->texinfo_id;
// Traverse the list of face edges to collect all of the face's vertices // Traverse the list of face edges to collect all of the face's vertices
Vec3 vertexSum; Vec3 vertexSum;
@ -288,14 +137,24 @@ int process_faces(const world_t* world)
world->edges[edgeIdx].vertex0 : world->edges[edgeIdx].vertex0 :
world->edges[-edgeIdx].vertex1; world->edges[-edgeIdx].vertex1;
const vertex_t* vertex = &world->vertices[vertIndex];
Vec3 vertexPoint = vertex->toVec();
ps1bsp_facevertex_t faceVertex; ps1bsp_facevertex_t faceVertex;
faceVertex.index = vertIndex; faceVertex.index = vertIndex;
faceVertex.light = 0; faceVertex.light = 0;
outFaceVertices.push_back(faceVertex); outFaceVertices.push_back(faceVertex);
// Calculate texture UVs
float s = (vertexPoint.dotProduct(texinfo->vectorS) + texinfo->distS) / miptex->width;
float t = (vertexPoint.dotProduct(texinfo->vectorT) + texinfo->distT) / miptex->height;
while (s > 1) s -= 1; while (s < 0) s += 1; // TODO: this is a nasty fudge to deal with the lack of texture tiling on PS1 hardware
while (t > 1) t -= 1; while (t < 0) t += 1; // We'll need to break up the faces and manually tile I guess...
// Rescale the UVs to the dimensions of the mipmap we've selected for our texture atlas
faceVertex.u = (unsigned char)(s * ps1tex.w);
faceVertex.v = (unsigned char)(t * ps1tex.h);
// Calculate bounding box of this face // Calculate bounding box of this face
const vertex_t* vertex = &world->vertices[vertIndex];
Vec3 vertexPoint = vertex->toVec();
if (edgeListIdx == 0) if (edgeListIdx == 0)
bounds.init(vertexPoint); bounds.init(vertexPoint);
else else
@ -317,13 +176,15 @@ int process_faces(const world_t* world)
//if (face->ledge_num >= 10) //if (face->ledge_num >= 10)
// export_lightmap(world, face, bounds, faceIdx); // export_lightmap(world, face, bounds, faceIdx);
outFace.numFaceVertices = (unsigned short)(outFaceVertices.size() - outFace.firstFaceVertex);
outFace.numFaceVertices = (unsigned char)(outFaceVertices.size() - outFace.firstFaceVertex);
outFace.center = convertWorldPosition(vertexSum / outFace.numFaceVertices); outFace.center = convertWorldPosition(vertexSum / outFace.numFaceVertices);
float area = computeFaceArea(world, face); float area = computeFaceArea(world, face);
outFace.center.pad = (short)(sqrt(area)); outFace.center.pad = (short)(sqrt(area));
outFaces.push_back(outFace); outFaces.push_back(outFace);
} }
SurfaceList surfaces = group_surfaces(world, vertexFaces);
// Iterate over all faces again; now that we know the bounds of each face, we can calculate lighting for all of them // Iterate over all faces again; now that we know the bounds of each face, we can calculate lighting for all of them
for (int faceIdx = 0; faceIdx < world->numFaces; ++faceIdx) for (int faceIdx = 0; faceIdx < world->numFaces; ++faceIdx)
{ {
@ -391,6 +252,7 @@ int process_faces(const world_t* world)
std::vector<unsigned char> outVisData(world->visList, world->visList + world->visListLength); std::vector<unsigned char> outVisData(world->visList, world->visList + world->visListLength);
// Write collected data to file and update header info // Write collected data to file and update header info
writeMapData(textures, outHeader.textures, fbsp);
writeMapData(outVertices, outHeader.vertices, fbsp); writeMapData(outVertices, outHeader.vertices, fbsp);
writeMapData(outFaces, outHeader.faces, fbsp); writeMapData(outFaces, outHeader.faces, fbsp);
writeMapData(outFaceVertices, outHeader.faceVertices, fbsp); writeMapData(outFaceVertices, outHeader.faceVertices, fbsp);
@ -414,26 +276,15 @@ int process_faces(const world_t* world)
int process_bsp(const world_t *world) int process_bsp(const world_t *world)
{ {
// Test reading the entity string data
if (!process_entities(world))
{
return 0;
}
// Test exporting texture data // Test exporting texture data
if (!process_textures(world))
{
return 0;
}
// Inspect vertex data
if (!process_vertices(world))
std::vector<ps1bsp_texture_t> textures;
if (!process_textures(world, textures))
{ {
return 0; return 0;
} }
// Inspect faces/edges data // Inspect faces/edges data
if (!process_faces(world))
if (!process_faces(world, textures))
{ {
return 0; return 0;
} }
@ -545,6 +396,15 @@ int load_bsp(const char* bspname, world_t* world)
fseek(f, header->ledges.offset, SEEK_SET); fseek(f, header->ledges.offset, SEEK_SET);
fread(world->edgeList, sizeof(int), world->edgeListLength, f); fread(world->edgeList, sizeof(int), world->edgeListLength, f);
// Load texture info
world->numTexInfos = header->texinfo.size / sizeof(texinfo_t);
world->texInfos = (texinfo_t*)malloc(header->texinfo.size);
if (world->texInfos == NULL)
return 0;
fseek(f, header->texinfo.offset, SEEK_SET);
fread(world->texInfos, sizeof(texinfo_t), world->numTexInfos, f);
// Load faces // Load faces
world->numFaces = header->faces.size / sizeof(face_t); world->numFaces = header->faces.size / sizeof(face_t);
world->faces = (face_t*)malloc(header->faces.size); world->faces = (face_t*)malloc(header->faces.size);
@ -604,10 +464,12 @@ int load_bsp(const char* bspname, world_t* world)
void free_bsp(world_t* world) void free_bsp(world_t* world)
{ {
free(world->lightmap);
free(world->leaves); free(world->leaves);
free(world->nodes); free(world->nodes);
free(world->visList); free(world->visList);
free(world->texInfos);
free(world->faces); free(world->faces);
free(world->faceList); free(world->faceList);
free(world->edges); free(world->edges);

24
ps1bsp.h

@ -28,6 +28,7 @@ typedef struct
{ {
u_short version; u_short version;
ps1bsp_dentry_t textures;
ps1bsp_dentry_t vertices; ps1bsp_dentry_t vertices;
ps1bsp_dentry_t faces; ps1bsp_dentry_t faces;
ps1bsp_dentry_t faceVertices; ps1bsp_dentry_t faceVertices;
@ -41,17 +42,15 @@ typedef struct
typedef struct typedef struct
{ {
unsigned char w, h; // These may be necessary for scaling UVs, especially since we use a mix of mip0 and mip1 textures unsigned char w, h; // These may be necessary for scaling UVs, especially since we use a mix of mip0 and mip1 textures
int tpage; // Texture page in PS1 VRAM (precalculated when generating the texture atlas)
short uoffs, voffs; // Texture coordinate offset within the texture page
unsigned short tpage; // Texture page in PS1 VRAM (precalculated when generating the texture atlas)
unsigned char uoffs, voffs; // Texture coordinate offset within the texture page
unsigned short nextframe; // If non-zero, the texture is animated and this points to the next texture in the sequence unsigned short nextframe; // If non-zero, the texture is animated and this points to the next texture in the sequence
} ps1bsp_texture_t; } ps1bsp_texture_t;
// This matches the SVECTOR data type; we can use the extra padding to store some more data. // This matches the SVECTOR data type; we can use the extra padding to store some more data.
typedef struct typedef struct
{ {
short x;
short y;
short z;
short x, y, z;
short pad; short pad;
} ps1bsp_vertex_t; } ps1bsp_vertex_t;
@ -60,8 +59,13 @@ typedef struct
unsigned short index; unsigned short index;
unsigned short light; unsigned short light;
// TODO: add texture uv's
// TODO: add sampled texture color * light, for untextured gouraud shaded drawing at range
unsigned char u, v; // TODO: make into unsigned short, clamp/mask/modulo to u_char at run-time. So we can build tiling polygons later.
// Sampled texture color * light, for untextured gouraud shaded drawing at range
unsigned char a : 1;
unsigned char r : 5;
unsigned char g : 5;
unsigned char b : 5;
} ps1bsp_facevertex_t; } ps1bsp_facevertex_t;
typedef struct typedef struct
@ -70,7 +74,9 @@ typedef struct
unsigned short side; unsigned short side;
unsigned short firstFaceVertex; unsigned short firstFaceVertex;
unsigned short numFaceVertices;
unsigned char numFaceVertices;
unsigned char textureId;
SVECTOR center; SVECTOR center;
@ -120,7 +126,7 @@ typedef struct
typedef struct typedef struct
{ {
unsigned short length; unsigned short length;
char message[];
char message[1];
} ps1bsp_message_t; } ps1bsp_message_t;
#ifdef __cplusplus #ifdef __cplusplus

150
texture.cpp

@ -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;
}

3
texture.h

@ -0,0 +1,3 @@
#pragma once
bool process_textures(const world_t* world, std::vector<ps1bsp_texture_t>& outTextures);
Loading…
Cancel
Save