Skip to content

Instantly share code, notes, and snippets.

@iamgreaser
Created August 19, 2017 00:31
Show Gist options
  • Save iamgreaser/2a67f7473d9c48a70946018b73fa1e40 to your computer and use it in GitHub Desktop.
Save iamgreaser/2a67f7473d9c48a70946018b73fa1e40 to your computer and use it in GitHub Desktop.
THPS2 model viewer - quick release 1
/*
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;
}
@krystalgamer
Copy link

Nice stuff. A friend sent me this and this will be valuable for my spidey-tools project. Is it fine if I use your findings, crediting you of course?
By the way is there any way i can contact you outside of github?

@lowresjpeg
Copy link

This is written for Linux, correct? Would you be able to provide a compiled version, or a version usable on Windows?

@JayFoxRox
Copy link

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