Created
August 19, 2017 00:31
-
-
Save iamgreaser/2a67f7473d9c48a70946018b73fa1e40 to your computer and use it in GitHub Desktop.
THPS2 model viewer - quick release 1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
THPS2 level viewer | |
quick release 1 | |
by GreaseMonkey, 2017 - Public Domain | |
takes two args | |
first arg is the main model (e.g. skhan.psx, skhan_o.psx) | |
second arg is the texture lib (e.g. skhan_l.psx) | |
needs SDL2 and OpenGL | |
*/ | |
#include <assert.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <stdbool.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <errno.h> | |
#include <math.h> | |
#include <GL/gl.h> | |
#include <GL/glu.h> | |
#include <GL/glext.h> | |
#include <SDL.h> | |
#define PSX_OBJECT_MAX 1600 | |
#define PSX_MODEL_MAX 1600 | |
#define PSX_MODEL_VERTEX_MAX 256 | |
#define PSX_MODEL_PLANE_MAX 256 | |
#define PSX_MODEL_FACE_MAX 256 | |
#define PSX_TEXTURE_ATLAS_SIZE 256 | |
#define PSX_TEXTURE_ATLAS_STEP 32 | |
#define PSX_TEXTURE_SIZE (PSX_TEXTURE_ATLAS_SIZE * PSX_TEXTURE_ATLAS_STEP) | |
#define PSX_TEXTURE_MAX (PSX_TEXTURE_ATLAS_STEP * PSX_TEXTURE_ATLAS_STEP) | |
typedef struct trg | |
{ | |
int dummy; | |
} trg_t; | |
typedef struct psx_header | |
{ | |
char magic[4]; | |
uint32_t ptr_meta, object_count; | |
} __attribute__((__packed__)) psx_header_t; | |
typedef struct psx_object | |
{ | |
uint32_t flags1; | |
int32_t px, py, pz; | |
uint32_t unk1; | |
uint16_t unk2; | |
uint16_t model_idx; | |
int16_t tx, ty; | |
uint32_t unk3; | |
uint32_t ptr_paldata; | |
} __attribute__((__packed__)) psx_object_t; | |
typedef struct psx_model_header | |
{ | |
uint16_t unk1; | |
uint16_t vertex_count; | |
uint16_t plane_count; | |
uint16_t face_count; | |
uint32_t unk2; | |
int16_t xmax, xmin; | |
int16_t ymax, ymin; | |
int16_t zmax, zmin; | |
int32_t unk4; | |
} __attribute__((__packed__)) psx_model_header_t; | |
typedef struct psx_model_vertex | |
{ | |
int16_t x, y, z, pad; | |
} __attribute__((__packed__)) psx_model_vertex_t; | |
typedef struct psx_model_plane | |
{ | |
int16_t x, y, z, pad; | |
} __attribute__((__packed__)) psx_model_plane_t; | |
typedef struct psx_model_face | |
{ | |
uint16_t typ, len; | |
uint8_t vidx_list[4]; | |
uint8_t gpu_cmd[4]; // also has another meaning in some cases - light level for gouraud? | |
uint16_t plane_idx, surf_flags; | |
uint32_t texture_idx; | |
uint8_t texpts[4][2]; | |
uint32_t pads[4]; // sometimes these are padded. | |
} __attribute__((__packed__)) psx_model_face_t; | |
typedef struct psx_model | |
{ | |
psx_model_header_t header; | |
psx_model_vertex_t vertices[PSX_MODEL_VERTEX_MAX]; | |
psx_model_plane_t planes[PSX_MODEL_PLANE_MAX]; | |
psx_model_face_t faces[PSX_MODEL_FACE_MAX]; | |
} psx_model_t; | |
typedef struct psx_palette | |
{ | |
uint8_t magic[4]; | |
uint32_t len; | |
uint8_t colors[256][4]; | |
} __attribute__((__packed__)) psx_palette_t; | |
typedef struct psx_texpal4 | |
{ | |
uint32_t hash; | |
uint16_t colors[16]; | |
} __attribute__((__packed__)) psx_texpal4_t; | |
typedef struct psx_texpal8 | |
{ | |
uint32_t hash; | |
uint16_t colors[256]; | |
} __attribute__((__packed__)) psx_texpal8_t; | |
typedef struct psx_texheader | |
{ | |
uint32_t unk1, palsize, namehash, texidx; | |
uint16_t width, height; | |
} __attribute__((__packed__)) psx_texheader_t; | |
typedef struct psx | |
{ | |
psx_header_t header; | |
psx_object_t objects[PSX_OBJECT_MAX]; | |
uint32_t model_count; | |
uint32_t model_ptrs[PSX_MODEL_MAX]; | |
psx_model_t models[PSX_MODEL_MAX]; | |
psx_palette_t palette; | |
uint32_t model_names[PSX_OBJECT_MAX]; | |
uint32_t texhash_count; | |
uint32_t texhash_list[PSX_TEXTURE_MAX]; | |
uint32_t texpal4_count; | |
psx_texpal4_t texpal4_list[PSX_TEXTURE_MAX]; | |
uint32_t texpal8_count; | |
psx_texpal8_t texpal8_list[PSX_TEXTURE_MAX]; | |
uint32_t tex_count; | |
uint32_t tex_ptrs[PSX_TEXTURE_MAX]; | |
psx_texheader_t tex_header[PSX_TEXTURE_MAX]; | |
uint8_t *tex_rawdata[PSX_TEXTURE_MAX]; | |
uint32_t *tex_convdata[PSX_TEXTURE_MAX]; | |
GLuint tex_atlas_ref; | |
GLuint displist; | |
GLuint displist_transp; | |
GLuint displist_box; | |
} psx_t; | |
SDL_Window *window = NULL; | |
SDL_GLContext glctx; | |
float cam_x = 0.0f; | |
float cam_y = 0.0f; | |
float cam_z = 0.0f; | |
float cam_ay = 0.0f; | |
float cam_ax = M_PI/4.0f; | |
uint32_t ps1_to_32bpp(int c) | |
{ | |
int r = (c)&0x1F; | |
int g = (c>>5)&0x1F; | |
int b = (c>>10)&0x1F; | |
int a = (c>>15)&0x1; | |
if((c&0x7FFF) == 0 || (r==31 && g==0 && b==31)) { | |
// Fully transparent | |
return 0x00000000; | |
} else { | |
r = (r<<3)|(r>>2); | |
g = (g<<3)|(g>>2); | |
b = (b<<3)|(b>>2); | |
a = (a == 0 ? 0xFF : 0x00); | |
return (a<<24)|(b<<16)|(g<<8)|r; // RGBA LE | |
} | |
} | |
int find_tex(psx_t *psx, psx_t *texture_psx, int texture_idx) | |
{ | |
// Map texture index to relevant hash | |
uint32_t texture_hash = psx->texhash_list[texture_idx]; | |
for(int ihash = 0; ihash < texture_psx->tex_count; ihash++) { | |
if(texture_psx->texhash_list[ihash] == texture_hash) { | |
texture_idx = ihash; | |
for(int itex = 0; itex < texture_psx->tex_count; itex++) { | |
if(texture_psx->tex_header[itex].texidx == texture_idx) { | |
return itex; | |
} | |
} | |
break; | |
} | |
} | |
return PSX_TEXTURE_MAX-1; | |
} | |
psx_t *load_psx_texlib(const char *fname) | |
{ | |
// FIXME: needs to be merged into load_psx | |
FILE *fp = fopen(fname, "rb"); | |
assert(fp != NULL); | |
psx_t *psx = malloc(sizeof(psx_t)); | |
assert(psx != NULL); | |
memset(psx, 0, sizeof(psx)); | |
// Header | |
fread(&psx->header, sizeof(psx->header), 1, fp); | |
assert(!memcmp(psx->header.magic, "\x04\x00\x02\x00", 4)); | |
printf("Meta ptr: %08X\n", psx->header.ptr_meta); | |
printf("Objects: %d\n", psx->header.object_count); | |
assert(psx->header.object_count <= PSX_OBJECT_MAX); | |
assert(psx->header.object_count == 0); | |
// Go to meta pointer | |
fseek(fp, psx->header.ptr_meta, SEEK_SET); | |
uint8_t texblkmagic[4]; | |
fread(texblkmagic, sizeof(texblkmagic), 1, fp); | |
assert(!memcmp(texblkmagic, "\xFF\xFF\xFF\xFF", 4)); | |
// Get texture info | |
fread(&psx->texhash_count, sizeof(psx->texhash_count), 1, fp); | |
printf("Tex hash count: %d\n", psx->texhash_count); | |
assert(psx->texhash_count <= PSX_TEXTURE_MAX); | |
fread(&psx->texhash_list, sizeof(psx->texhash_list[0]), psx->texhash_count, fp); | |
fread(&psx->texpal4_count, sizeof(psx->texpal4_count), 1, fp); | |
printf("Tex pal4 count: %d\n", psx->texpal4_count); | |
assert(psx->texpal4_count <= PSX_TEXTURE_MAX); | |
fread(&psx->texpal4_list, sizeof(psx->texpal4_list[0]), psx->texpal4_count, fp); | |
fread(&psx->texpal8_count, sizeof(psx->texpal8_count), 1, fp); | |
printf("Tex pal8 count: %d\n", psx->texpal8_count); | |
assert(psx->texpal8_count <= PSX_TEXTURE_MAX); | |
fread(&psx->texpal8_list, sizeof(psx->texpal8_list[0]), psx->texpal8_count, fp); | |
fread(&psx->tex_count, sizeof(psx->tex_count), 1, fp); | |
printf("Tex count: %d\n", psx->tex_count); | |
assert(psx->tex_count <= PSX_TEXTURE_MAX); | |
fread(&psx->tex_ptrs, sizeof(psx->tex_ptrs[0]), psx->tex_count, fp); | |
for(int itex = 0; itex < psx->tex_count; itex++) { | |
fseek(fp, psx->tex_ptrs[itex], SEEK_SET); | |
psx_texheader_t *T = &psx->tex_header[itex]; | |
fread(T, sizeof(*T), 1, fp); | |
printf("- %04d: %08X %4d %08X %4d - %4d x %4d\n", | |
itex, | |
T->unk1, T->palsize, T->namehash, T->texidx, | |
T->width, T->height); | |
uint32_t padwidth; | |
uint32_t reallen; | |
uint32_t *DC = psx->tex_convdata[itex] = malloc(T->width*T->height*sizeof(uint32_t)); | |
if(T->palsize == 16) { | |
padwidth = (T->width+0x3)&~0x3; | |
padwidth >>= 1; | |
reallen = (padwidth*T->height); | |
uint8_t *DR = psx->tex_rawdata[itex] = malloc(reallen); | |
fread(DR, reallen, 1, fp); | |
psx_texpal4_t *P = NULL; | |
for(int ipal = 0; ipal < psx->texpal4_count; ipal++) { | |
if(psx->texpal4_list[ipal].hash == T->namehash) { | |
P = &psx->texpal4_list[ipal]; | |
break; | |
} | |
} | |
assert(P != NULL); | |
for(int y = 0; y < T->height; y++) { | |
for(int x = 0; x < T->width; x++) { | |
int v = (DR[y*padwidth+(x>>1)]>>((x&0x1)*4))&0xF; | |
int c = P->colors[v]; | |
DC[y*T->width+x] = ps1_to_32bpp(c); | |
} | |
} | |
} else if(T->palsize == 256) { | |
padwidth = (T->width+0x1)&~0x1; | |
reallen = (padwidth*T->height); | |
uint8_t *DR = psx->tex_rawdata[itex] = malloc(reallen); | |
fread(DR, reallen, 1, fp); | |
psx_texpal8_t *P = NULL; | |
for(int ipal = 0; ipal < psx->texpal8_count; ipal++) { | |
if(psx->texpal8_list[ipal].hash == T->namehash) { | |
P = &psx->texpal8_list[ipal]; | |
break; | |
} | |
} | |
assert(P != NULL); | |
for(int y = 0; y < T->height; y++) { | |
for(int x = 0; x < T->width; x++) { | |
int v = (DR[y*padwidth+x])&0xFF; | |
int c = P->colors[v]; | |
DC[y*T->width+x] = ps1_to_32bpp(c); | |
} | |
} | |
} else { | |
assert(!"DAMMIT"); | |
} | |
} | |
fclose(fp); | |
return psx; | |
} | |
psx_t *load_psx(const char *fname) | |
{ | |
FILE *fp = fopen(fname, "rb"); | |
assert(fp != NULL); | |
psx_t *psx = malloc(sizeof(psx_t)); | |
assert(psx != NULL); | |
memset(psx, 0, sizeof(psx)); | |
// Header | |
fread(&psx->header, sizeof(psx->header), 1, fp); | |
assert(!memcmp(psx->header.magic, "\x04\x00\x02\x00", 4)); | |
printf("Meta ptr: %08X\n", psx->header.ptr_meta); | |
printf("Objects: %d\n", psx->header.object_count); | |
assert(psx->header.object_count <= PSX_OBJECT_MAX); | |
// Object entries | |
for(int iobj = 0; iobj < psx->header.object_count; iobj++) { | |
psx_object_t *O = &psx->objects[iobj]; | |
fread(O, sizeof(*O), 1, fp); | |
if(false) { | |
printf("%5d %08X %11d %11d %11d %08X %04X %5d %6d %6d %08X %08X\n" | |
, iobj | |
, O->flags1 | |
, O->px , O->py , O->pz | |
, O->unk1 | |
, O->unk2 , O->model_idx | |
, O->tx, O->ty | |
, O->unk3 | |
, O->ptr_paldata | |
); | |
} | |
} | |
// Models | |
fread(&psx->model_count, sizeof(psx->model_count), 1, fp); | |
assert(psx->model_count <= PSX_MODEL_MAX); | |
printf("Models: %d\n", psx->model_count); | |
fread(psx->model_ptrs, sizeof(psx->model_ptrs[0]), psx->model_count, fp); | |
for(int imdl = 0; imdl < psx->model_count; imdl++) { | |
psx_model_t *M = &psx->models[imdl]; | |
psx_model_header_t *H = &M->header; | |
fseek(fp, psx->model_ptrs[imdl], SEEK_SET); | |
fread(H, sizeof(*H), 1, fp); | |
if(false) { | |
printf("%05d %04X %5d %5d %5d %08X ( %6d %6d %6d ) ( %6d %6d %6d ) %11d \n" | |
, imdl | |
, H->unk1 | |
, H->vertex_count | |
, H->plane_count | |
, H->face_count | |
, H->unk2 | |
, H->xmin, H->ymin, H->zmin | |
, H->xmax, H->ymax, H->zmax | |
, H->unk4 | |
); | |
} | |
assert(H->vertex_count <= PSX_MODEL_VERTEX_MAX); | |
assert(H->plane_count <= PSX_MODEL_PLANE_MAX); | |
assert(H->face_count <= PSX_MODEL_FACE_MAX); | |
fread(M->vertices, sizeof(M->vertices[0]), H->vertex_count, fp); | |
fread(M->planes, sizeof(M->planes[0]), H->plane_count, fp); | |
// Vertices | |
for(int iv = 0; iv < H->vertex_count; iv++) { | |
psx_model_vertex_t *V = &M->vertices[iv]; | |
if(false) { | |
printf(" V %05d %6d %6d %6d %6d\n" | |
, iv | |
, V->x | |
, V->y | |
, V->z | |
, V->pad | |
); | |
} | |
} | |
// Planes | |
for(int ip = 0; ip < H->plane_count; ip++) { | |
psx_model_plane_t *P = &M->planes[ip]; | |
if(false) { | |
printf(" p %05d %6d %6d %6d %6d\n" | |
, ip | |
, P->x | |
, P->y | |
, P->z | |
, P->pad | |
); | |
} | |
} | |
// Faces | |
for(int ifc = 0; ifc < H->face_count; ifc++) { | |
psx_model_face_t *F = &M->faces[ifc]; | |
memset(F, 0, sizeof(*F)); | |
// Fetch the header | |
fread(&F->typ, sizeof(F->typ), 1, fp); | |
fread(&F->len, sizeof(F->len), 1, fp); | |
// Fetch the rest | |
if(F->len >= sizeof(*F)) { | |
fread(&F->vidx_list, sizeof(*F)-4, 1, fp); | |
fseek(fp, F->len-sizeof(*F), SEEK_CUR); | |
} else { | |
fread(&F->vidx_list, F->len-4, 1, fp); | |
} | |
// Data! | |
if(false) { | |
printf(" F %05d %04X %04X [%3d,%3d,%3d,%3d]" | |
" %02X%02X%02X%02X %05d %04X %08X" | |
" ((%3d,%3d), " | |
"(%3d,%3d), " | |
"(%3d,%3d), " | |
"(%3d,%3d))" | |
"\n" | |
, ifc | |
, F->typ, F->len | |
, F->vidx_list[0], F->vidx_list[1] | |
, F->vidx_list[2], F->vidx_list[3] | |
, F->gpu_cmd[3], F->gpu_cmd[2] | |
, F->gpu_cmd[1], F->gpu_cmd[0] | |
, F->plane_idx | |
, F->surf_flags | |
, F->texture_idx | |
, F->texpts[0][0], F->texpts[0][1] | |
, F->texpts[1][0], F->texpts[1][1] | |
, F->texpts[2][0], F->texpts[2][1] | |
, F->texpts[3][0], F->texpts[3][1] | |
); | |
} | |
} | |
//printf("\n"); | |
} | |
// Palette (if it's there) | |
fseek(fp, psx->header.ptr_meta, SEEK_SET); | |
fread(&psx->palette.magic, sizeof(psx->palette.magic), 1, fp); | |
if(!memcmp(psx->palette.magic, "RGBs", 4)) { | |
printf("HAS PALETTE\n"); | |
fread(&psx->palette.len, sizeof(psx->palette.len), 1, fp); | |
printf("Palette bytes: %d\n", psx->palette.len); | |
printf("Palette entries: %d\n", psx->palette.len/4); | |
assert((psx->palette.len&3) == 0); | |
assert((psx->palette.len>>2) >= 0); | |
assert((psx->palette.len>>2) <= 256); | |
fread(psx->palette.colors, psx->palette.len, 1, fp); | |
} else { | |
fseek(fp, -4, SEEK_CUR); | |
} | |
// Whatever this shit is | |
if(true) { | |
for(;;) { | |
printf("TELL - %08X\n", (unsigned int)ftell(fp)); | |
uint32_t wat; | |
fread(&wat, 4, 1, fp); | |
if(wat != 0xFFFFFFFF) { | |
uint32_t wat2; | |
fread(&wat2, 4, 1, fp); | |
printf("XX %08X %08X\n", wat, wat2); | |
// some shit follows | |
fseek(fp, wat2, SEEK_CUR); | |
} else { | |
printf("BACKTRACK\n"); | |
fseek(fp, -4, SEEK_CUR); | |
break; | |
} | |
} | |
} | |
// Texture info shit | |
if(true) { | |
uint32_t wat; | |
fread(&wat, sizeof(wat), 1, fp); | |
printf("XX nam %08X %08X\n", wat, psx->model_count); | |
assert(wat == 0xFFFFFFFF); | |
fread(psx->model_names, sizeof(uint32_t), psx->model_count, fp); | |
// Get texture info | |
fread(&psx->texhash_count, sizeof(psx->texhash_count), 1, fp); | |
printf("Tex hash count: %d\n", psx->texhash_count); | |
assert(psx->texhash_count <= PSX_TEXTURE_MAX); | |
fread(&psx->texhash_list, sizeof(psx->texhash_list[0]), psx->texhash_count, fp); | |
fread(&psx->texpal4_count, sizeof(psx->texpal4_count), 1, fp); | |
printf("Tex pal4 count: %d\n", psx->texpal4_count); | |
assert(psx->texpal4_count <= PSX_TEXTURE_MAX); | |
fread(&psx->texpal4_list, sizeof(psx->texpal4_list[0]), psx->texpal4_count, fp); | |
fread(&psx->texpal8_count, sizeof(psx->texpal8_count), 1, fp); | |
printf("Tex pal8 count: %d\n", psx->texpal8_count); | |
assert(psx->texpal8_count <= PSX_TEXTURE_MAX); | |
fread(&psx->texpal8_list, sizeof(psx->texpal8_list[0]), psx->texpal8_count, fp); | |
fread(&psx->tex_count, sizeof(psx->tex_count), 1, fp); | |
printf("Tex count: %d\n", psx->tex_count); | |
assert(psx->tex_count <= PSX_TEXTURE_MAX); | |
fread(&psx->tex_ptrs, sizeof(psx->tex_ptrs[0]), psx->tex_count, fp); | |
} | |
fclose(fp); | |
return psx; | |
} | |
void prepare_tex_psx_for_gl(psx_t *psx) | |
{ | |
// FIXME: needs to be merged into prepare_psx_for_gl | |
// Create texture reference | |
glGenTextures(1, &psx->tex_atlas_ref); | |
glBindTexture(GL_TEXTURE_2D, psx->tex_atlas_ref); | |
// Disable mipmapping | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); | |
// Allocate texture memory | |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, | |
PSX_TEXTURE_SIZE, PSX_TEXTURE_SIZE, 0, | |
GL_RGBA, GL_UNSIGNED_BYTE, | |
NULL); | |
// Put all textures in | |
for(int itex = 0; itex < psx->tex_count; itex++) { | |
psx_texheader_t *T = &psx->tex_header[itex]; | |
int basex = (itex % PSX_TEXTURE_ATLAS_STEP) * PSX_TEXTURE_ATLAS_SIZE; | |
int basey = (itex / PSX_TEXTURE_ATLAS_STEP) * PSX_TEXTURE_ATLAS_SIZE; | |
for(int by = 0; by+T->height <= 256; by += T->height) { | |
for(int bx = 0; bx+T->width <= 256; bx += T->width) { | |
glTexSubImage2D(GL_TEXTURE_2D, 0, | |
basex+bx, basey+by, T->width, T->height, | |
GL_RGBA, GL_UNSIGNED_BYTE, | |
psx->tex_convdata[itex]); | |
} | |
} | |
} | |
// Set up a blank area | |
uint32_t blank_tex[PSX_TEXTURE_ATLAS_SIZE*PSX_TEXTURE_ATLAS_SIZE]; | |
memset(blank_tex, 0xFF, sizeof(blank_tex)); | |
glTexSubImage2D(GL_TEXTURE_2D, 0, | |
PSX_TEXTURE_SIZE - PSX_TEXTURE_ATLAS_SIZE, | |
PSX_TEXTURE_SIZE - PSX_TEXTURE_ATLAS_SIZE, | |
PSX_TEXTURE_ATLAS_SIZE, | |
PSX_TEXTURE_ATLAS_SIZE, | |
GL_RGBA, GL_UNSIGNED_BYTE, | |
blank_tex); | |
// Unbind | |
glBindTexture(GL_TEXTURE_2D, 0); | |
} | |
void prepare_psx_for_gl(psx_t *psx, psx_t *texture_psx) | |
{ | |
// Build display list | |
float cam_xmin = 0.0f; | |
float cam_ymin = 0.0f; | |
float cam_zmin = 0.0f; | |
float cam_xmax = 0.0f; | |
float cam_ymax = 0.0f; | |
float cam_zmax = 0.0f; | |
psx->displist = glGenLists(1); | |
psx->displist_transp = glGenLists(1); | |
psx->displist_box = glGenLists(1); | |
for(int reps = 0; reps < 3; reps++) { | |
glNewList( | |
reps == 0 ? psx->displist : | |
reps == 1 ? psx->displist_transp : | |
psx->displist_box | |
, GL_COMPILE); | |
glBegin(reps <= 1 ? GL_TRIANGLES : GL_LINES); | |
for(int iobj = 0; iobj < psx->header.object_count; iobj++) { | |
psx_object_t *O = &psx->objects[iobj]; | |
int imdl = O->model_idx; | |
assert(imdl >= 0 && imdl < psx->model_count); | |
psx_model_t *M = &psx->models[imdl]; | |
if(iobj == 0 || O->px < cam_xmin) { cam_xmin = O->px; } | |
if(iobj == 0 || O->py < cam_ymin) { cam_ymin = O->py; } | |
if(iobj == 0 || O->pz < cam_zmin) { cam_zmin = O->pz; } | |
if(iobj == 0 || O->px > cam_xmax) { cam_xmax = O->px; } | |
if(iobj == 0 || O->py > cam_ymax) { cam_ymax = O->py; } | |
if(iobj == 0 || O->pz > cam_zmax) { cam_zmax = O->pz; } | |
if(reps < 2) { | |
for(int ifc = 0; ifc < M->header.face_count; ifc++) { | |
psx_model_face_t *F = &M->faces[ifc]; | |
//if((F->typ & 0x1000) == 0) { continue; } | |
//if((F->typ & 0x0100) != 0) { continue; } // Decals? | |
if((F->typ & 0x0080) != 0 ? reps != 1 : reps != 0) { | |
continue; | |
} // Most useful one for visiblilty | |
//if((F->surf_flags & 0x0100) != 0) { continue; } | |
//if((F->surf_flags & 0x0080) != 0) { continue; } | |
float alpha = 0.2f; | |
bool has_gouraud = ((F->typ & 0x0800) != 0); | |
bool has_tex = ((F->typ & 0x0003) != 0); | |
float cdiv = (has_tex ? 128.0f : 255.0f); | |
//float cdiv = 255.0f; | |
if(!has_gouraud) { | |
glColor4f( | |
F->gpu_cmd[0]/cdiv, | |
F->gpu_cmd[1]/cdiv, | |
F->gpu_cmd[2]/cdiv, | |
alpha); | |
} | |
const uint8_t RIMAP[6] = { | |
2,1,0, | |
1,2,3, | |
}; | |
int ti = (has_tex ? find_tex(psx, texture_psx, F->texture_idx) : PSX_TEXTURE_MAX-1); | |
for(int i = 0; i < ((F->typ & 0x0010) != 0 ? 3 : 6); i++) { | |
int ri = RIMAP[i]; | |
psx_model_vertex_t *V = &M->vertices[F->vidx_list[ri]]; | |
float x = (V->x + O->px / 4096.0) / 4096.0; | |
float y = (V->y + O->py / 4096.0) / 4096.0; | |
float z = (V->z + O->pz / 4096.0) / 4096.0; | |
y = -y; z = -z; | |
float tx = F->texpts[ri][0]; | |
float ty = F->texpts[ri][1]; | |
tx += (ti % PSX_TEXTURE_ATLAS_STEP) * PSX_TEXTURE_ATLAS_SIZE; | |
ty += (ti / PSX_TEXTURE_ATLAS_STEP) * PSX_TEXTURE_ATLAS_SIZE; | |
tx += 0.5f; | |
ty += 0.5f; | |
tx /= PSX_TEXTURE_SIZE; | |
ty /= PSX_TEXTURE_SIZE; | |
glTexCoord2f(tx, ty); | |
if(has_gouraud) { | |
uint8_t *color = psx->palette.colors[F->gpu_cmd[ri]]; | |
glColor4f( | |
color[0]/cdiv, | |
color[1]/cdiv, | |
color[2]/cdiv, | |
alpha); | |
} | |
glVertex3f(x, y, z); | |
} | |
} | |
} else { | |
float alpha = 1.0f; | |
glColor4f(1.0f, 0.0f, 0.0f, alpha); | |
float xmin = (M->header.xmin + O->px / 4096.0f) / 4096.0f; | |
float ymin = -(M->header.ymin + O->py / 4096.0f) / 4096.0f; | |
float zmin = -(M->header.zmin + O->pz / 4096.0f) / 4096.0f; | |
float xmax = (M->header.xmax + O->px / 4096.0f) / 4096.0f; | |
float ymax = -(M->header.ymax + O->py / 4096.0f) / 4096.0f; | |
float zmax = -(M->header.zmax + O->pz / 4096.0f) / 4096.0f; | |
// ymin | |
glVertex3f(xmin, ymin, zmin); | |
glVertex3f(xmax, ymin, zmin); | |
glVertex3f(xmax, ymin, zmin); | |
glVertex3f(xmax, ymin, zmax); | |
glVertex3f(xmax, ymin, zmax); | |
glVertex3f(xmin, ymin, zmax); | |
glVertex3f(xmin, ymin, zmax); | |
glVertex3f(xmin, ymin, zmin); | |
// ymax | |
glVertex3f(xmin, ymax, zmin); | |
glVertex3f(xmax, ymax, zmin); | |
glVertex3f(xmax, ymax, zmin); | |
glVertex3f(xmax, ymax, zmax); | |
glVertex3f(xmax, ymax, zmax); | |
glVertex3f(xmin, ymax, zmax); | |
glVertex3f(xmin, ymax, zmax); | |
glVertex3f(xmin, ymax, zmin); | |
// vertical | |
glVertex3f(xmin, ymin, zmin); | |
glVertex3f(xmin, ymax, zmin); | |
glVertex3f(xmax, ymin, zmin); | |
glVertex3f(xmax, ymax, zmin); | |
glVertex3f(xmax, ymin, zmax); | |
glVertex3f(xmax, ymax, zmax); | |
glVertex3f(xmin, ymin, zmax); | |
glVertex3f(xmin, ymax, zmax); | |
} | |
} | |
glEnd(); | |
glEndList(); | |
} | |
cam_xmin /= 4096.0f * 4096.0f; | |
cam_ymin /= 4096.0f * 4096.0f; | |
cam_zmin /= 4096.0f * 4096.0f; | |
cam_xmax /= 4096.0f * 4096.0f; | |
cam_ymax /= 4096.0f * 4096.0f; | |
cam_zmax /= 4096.0f * 4096.0f; | |
cam_x = (cam_xmax + cam_xmin) / 2.0f; | |
//cam_y = (cam_ymax + cam_ymin) / 2.0f; | |
cam_y = -cam_ymin; | |
cam_z = -(cam_zmax + cam_zmin) / 2.0f; | |
cam_ay = 0.0f; | |
cam_ax = M_PI/4.0f; | |
} | |
int main(int argc, char *argv[]) | |
{ | |
assert(argc > 2); | |
psx_t *texture_psx = load_psx_texlib(argv[2]); | |
psx_t *level_psx = load_psx(argv[1]); | |
//return 0; | |
SDL_Init(SDL_INIT_VIDEO); | |
window = SDL_CreateWindow("THPS model viewer" | |
, SDL_WINDOWPOS_CENTERED | |
, SDL_WINDOWPOS_CENTERED | |
, 1280 | |
, 720 | |
, SDL_WINDOW_OPENGL); | |
assert(window); | |
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); | |
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); | |
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); | |
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); | |
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); | |
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); | |
glctx = SDL_GL_CreateContext(window); | |
assert(glctx); | |
prepare_tex_psx_for_gl(texture_psx); | |
prepare_psx_for_gl(level_psx, texture_psx); | |
bool mv_f = false; | |
bool mv_b = false; | |
bool mv_l = false; | |
bool mv_r = false; | |
bool mv_u = false; | |
bool mv_d = false; | |
bool rt_l = false; | |
bool rt_r = false; | |
bool rt_u = false; | |
bool rt_d = false; | |
for(int i = 0; ; i++) { | |
SDL_Event ev; | |
while(SDL_PollEvent(&ev)) { | |
switch(ev.type) { | |
case SDL_QUIT: | |
SDL_Quit(); | |
return 0; | |
case SDL_KEYDOWN: | |
case SDL_KEYUP: | |
switch(ev.key.keysym.sym) { | |
case SDLK_UP: rt_u = (ev.type == SDL_KEYDOWN); break; | |
case SDLK_DOWN: rt_d = (ev.type == SDL_KEYDOWN); break; | |
case SDLK_LEFT: rt_l = (ev.type == SDL_KEYDOWN); break; | |
case SDLK_RIGHT: rt_r = (ev.type == SDL_KEYDOWN); break; | |
case SDLK_w: mv_f = (ev.type == SDL_KEYDOWN); break; | |
case SDLK_s: mv_b = (ev.type == SDL_KEYDOWN); break; | |
case SDLK_a: mv_l = (ev.type == SDL_KEYDOWN); break; | |
case SDLK_d: mv_r = (ev.type == SDL_KEYDOWN); break; | |
case SDLK_SPACE: mv_u = (ev.type == SDL_KEYDOWN); break; | |
case SDLK_LCTRL: mv_d = (ev.type == SDL_KEYDOWN); break; | |
} break; | |
} | |
} | |
float d_x = 0.0f; | |
float d_y = 0.0f; | |
float d_z = 0.0f; | |
float d_ay = 0.0f; | |
float d_ax = 0.0f; | |
if(rt_l) { d_ay -= 1.0f; } | |
if(rt_r) { d_ay += 1.0f; } | |
if(rt_u) { d_ax -= 1.0f; } | |
if(rt_d) { d_ax += 1.0f; } | |
if(mv_l) { d_x -= 1.0f; } | |
if(mv_r) { d_x += 1.0f; } | |
if(mv_u) { d_y += 1.0f; } | |
if(mv_d) { d_y -= 1.0f; } | |
if(mv_f) { d_z -= 1.0f; } | |
if(mv_b) { d_z += 1.0f; } | |
cam_ay += d_ay*M_PI/60.0f; | |
cam_ax += d_ax*M_PI/60.0f; | |
float sy = sinf(cam_ay); | |
float cy = cosf(cam_ay); | |
float sx = sinf(cam_ax); | |
float cx = cosf(cam_ax); | |
float ii = 1.0f; | |
float oo = 0.0f; | |
float mvspd = 1.0f/60.0f; | |
cam_x += (cy*d_x + sx*sy*d_y + -cx*sy*d_z)*mvspd; | |
cam_y += (oo*d_x + cx*ii*d_y + sx*ii*d_z)*mvspd; | |
cam_z += (sy*d_x + -sx*cy*d_y + cx*cy*d_z)*mvspd; | |
glClearColor(0.0f, 0.0f, 0.2f, 0.0f); | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
glMatrixMode(GL_PROJECTION); | |
glLoadIdentity(); | |
//gluPerspective(90.0, 1280.0/720.0, 0.1, 5000.0); | |
gluPerspective(90.0, 1280.0/720.0, 0.01, 500.0); | |
glMatrixMode(GL_MODELVIEW); | |
glLoadIdentity(); | |
glRotatef(cam_ax*180.0f/M_PI, 1.0f, 0.0f, 0.0f); | |
//glTranslatef(0.0f, -0.2f, -0.3f); | |
glRotatef(cam_ay*180.0f/M_PI, 0.0f, 1.0f, 0.0f); | |
glTranslatef(-cam_x, -cam_y, -cam_z); | |
glEnable(GL_DEPTH_TEST); | |
glEnable(GL_CULL_FACE); | |
glEnable(GL_TEXTURE_2D); | |
glEnable(GL_ALPHA_TEST); | |
glAlphaFunc(GL_GEQUAL, 0.1f); | |
glBindTexture(GL_TEXTURE_2D, texture_psx->tex_atlas_ref); | |
glCallList(level_psx->displist); | |
glBindTexture(GL_TEXTURE_2D, 0); | |
glAlphaFunc(GL_ALWAYS, 0.0f); | |
glDisable(GL_ALPHA_TEST); | |
glDisable(GL_TEXTURE_2D); | |
glDisable(GL_CULL_FACE); | |
// TEMPORARILY DISABLED | |
//glCallList(level_psx->displist_box); | |
glEnable(GL_BLEND); | |
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |
glDepthMask(GL_FALSE); | |
//glBlendFunc(GL_SRC_ALPHA, GL_ONE); | |
// TEMPORARILY DISABLED | |
//glCallList(level_psx->displist_transp); | |
glDepthMask(GL_TRUE); | |
glBlendFunc(GL_ONE, GL_ZERO); | |
glDisable(GL_BLEND); | |
glDisable(GL_DEPTH_TEST); | |
// Immediate box inclusion feedback | |
if(false) { | |
glBegin(GL_LINES); | |
for(int iobj = 0; iobj < level_psx->header.object_count; iobj++) { | |
psx_t *psx = level_psx; | |
psx_object_t *O = &psx->objects[iobj]; | |
int imdl = O->model_idx; | |
assert(imdl >= 0 && imdl < psx->model_count); | |
psx_model_t *M = &psx->models[imdl]; | |
float xmin = (M->header.xmin + O->px / 4096.0f) / 4096.0f; | |
float ymin = -(M->header.ymin + O->py / 4096.0f) / 4096.0f; | |
float zmin = -(M->header.zmin + O->pz / 4096.0f) / 4096.0f; | |
float xmax = (M->header.xmax + O->px / 4096.0f) / 4096.0f; | |
float ymax = -(M->header.ymax + O->py / 4096.0f) / 4096.0f; | |
float zmax = -(M->header.zmax + O->pz / 4096.0f) / 4096.0f; | |
// inequalities make things completely predictable | |
if(xmin <= cam_x && cam_x <= xmax) { | |
if(ymin >= cam_y && cam_y >= ymax) { | |
if(zmin >= cam_z && cam_z >= zmax) { | |
glColor3f(0.4f, 0.4f, 1.0f); | |
// ymin | |
glVertex3f(xmin, ymin, zmin); | |
glVertex3f(xmax, ymin, zmin); | |
glVertex3f(xmax, ymin, zmin); | |
glVertex3f(xmax, ymin, zmax); | |
glVertex3f(xmax, ymin, zmax); | |
glVertex3f(xmin, ymin, zmax); | |
glVertex3f(xmin, ymin, zmax); | |
glVertex3f(xmin, ymin, zmin); | |
// ymax | |
glVertex3f(xmin, ymax, zmin); | |
glVertex3f(xmax, ymax, zmin); | |
glVertex3f(xmax, ymax, zmin); | |
glVertex3f(xmax, ymax, zmax); | |
glVertex3f(xmax, ymax, zmax); | |
glVertex3f(xmin, ymax, zmax); | |
glVertex3f(xmin, ymax, zmax); | |
glVertex3f(xmin, ymax, zmin); | |
// vertical | |
glVertex3f(xmin, ymin, zmin); | |
glVertex3f(xmin, ymax, zmin); | |
glVertex3f(xmax, ymin, zmin); | |
glVertex3f(xmax, ymax, zmin); | |
glVertex3f(xmax, ymin, zmax); | |
glVertex3f(xmax, ymax, zmax); | |
glVertex3f(xmin, ymin, zmax); | |
glVertex3f(xmin, ymax, zmax); | |
} | |
} | |
} | |
} | |
glEnd(); | |
} | |
SDL_GL_SwapWindow(window); | |
SDL_Delay(10); | |
} | |
return 0; | |
} |
Thanks! Cool code.
I've successfully used this on Linux:
clang psxviewer.c -I/usr/include/SDL2 -lSDL2 -lm -lGL -lGLU -o psxviewer
If you don't have clang, gcc should work, too.
It should also work equivalently on macOS or Windows (using msys2 for example) after installing all dependencies. You might have to adapt the SDL2 path.
Then run it like this:
./psxviewer data/SKHAN.PSX data/SKHAN_L.PSX
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is written for Linux, correct? Would you be able to provide a compiled version, or a version usable on Windows?