And some code to display them with OpenGL. Works on my Mac, but as I'm using GLUT it should work on Windows, too.
I'm not sure if this is enough to make spell effects work, but you can always calculate normals and add a colour array as well.
Could probably be optimised a bit more, but even in this form it should draw a lot faster than on the PSX...
It doesn't do alpha-blending, just some alpha-kill so you can see through trees and bushes.
Enjoy.
(Small fix to remember the current directory.)
// System
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// STL
#include <string>
#include <cassert>
#if 1
// GLUT
#include <GLUT/glut.h>
// OpenGL
#include <OpenGL/OpenGL.h>
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#else
// GLUT
#include <glut/glut.h>
// OpenGL
#include <gl/gl.h>
#include <gl/glu.h>
#endif
using namespace std;
typedef unsigned char u8;
typedef unsigned short int u16;
typedef unsigned long int u32;
typedef signed char s8;
typedef signed short int s16;
typedef signed long int s32;
// utility functions
u16 get_u16le(void const * const buf)
{
return ((u8*)buf)[0] | (((u8*)buf)[1]<<8);
}
u32 get_u32le(void const * const buf)
{
return ((u8*)buf)[0] | (((u8*)buf)[1]<<8) | (((u8*)buf)[2]<<16) | (((u8*)buf)[3]<<24);
}
int load( const string & name, void ** data )
{
size_t size = 0;
*data = NULL;
int fd = open( name.c_str(), O_RDONLY, 0 );
if (fd >= 0) {
struct stat sb;
if ( fstat( fd, &sb ) >= 0) {
assert( sb.st_size > 0 );
void *tmp = malloc( sb.st_size );
if ( tmp!=NULL ) {
if (read( fd, tmp, sb.st_size ) == sb.st_size) {
*data = tmp;
size = sb.st_size;
} else {
free(tmp);
}
}
}
close( fd );
}
return size;
}
int load_lzs(const string & name, void ** data )
{
// read compressed data
u8 *buf;
int tsize = load( name, (void**)&buf );
int isize = get_u32le(buf) + 4;
if (isize != tsize) {
free(buf);
*data = NULL;
return 0;
}
// decompress;
int osize = (isize + 255) & ~255;
u8 * obuf = (u8 *)malloc(osize);
int iofs = 4, oofs = 0;
u8 cmd=0, bit=0;
while (iofs < isize) {
if (bit == 0) {
cmd = buf[iofs++];
bit = 8;
}
if (cmd&1) {
obuf[oofs++]=buf[iofs++];
if (oofs==osize) {
osize+=256;
obuf = (u8*)realloc(obuf, osize);
}
} else {
u8 a = buf[iofs++];
u8 b = buf[iofs++];
u16 o = a | ((b&0xF0)<<4);
u8 len = (b&0xF)+3;
int rofs = oofs - ((oofs - 18 - o) & 0xFFF);
for (int j=0; j<len; j++) {
if (rofs < 0) {
obuf[oofs++]=0;
} else {
obuf[oofs++]=obuf[rofs];
}
if (oofs==osize) {
osize+=256;
obuf = (u8*)realloc(obuf, osize);
}
rofs++;
}
}
cmd>>=1;
bit--;
}
free( buf );
*data = obuf;
return oofs;
}
// Background loading and storage
struct Vertex {
s16 x, y, z;
s16 u, v;
};
struct Element {
u16 a, b, c, s; // s = shader
};
struct Mesh {
int texture;
int element_start;
int element_count;
};
struct Shader {
int texture;
int palette;
};
struct Background {
int vertex_count;
Vertex *vertex;
int element_count;
Element *element;
int *indexbuffer;
int shader_count;
Shader *shader;
GLuint *textures;
int mesh_count;
Mesh *mesh;
};
int
add_vertex(Background *bg, s16 x, s16 y, s16 z, u8 u, u8 v)
{
for (int i=0; i<bg->vertex_count; i++) {
if (bg->vertex[i].x == x && bg->vertex[i].y == y && bg->vertex[i].z == z && bg->vertex[i].u == u && bg->vertex[i].v == v) {
return i;
}
}
int idx = bg->vertex_count++;
bg->vertex = (Vertex *)realloc(bg->vertex, bg->vertex_count * sizeof(Vertex));
bg->vertex[idx].x = x;
bg->vertex[idx].y = y;
bg->vertex[idx].z = z;
bg->vertex[idx].u = u;
bg->vertex[idx].v = v;
return idx;
}
int
add_triangle(Background *bg, u16 a, u16 b, u16 c, u16 s)
{
int idx = bg->element_count++;
bg->element = (Element*)realloc(bg->element, bg->element_count * sizeof(Element));
bg->element[idx].a = a;
bg->element[idx].b = b;
bg->element[idx].c = c;
bg->element[idx].s = s;
return idx;
}
int
add_shader(Background *bg, int texture, int palette)
{
for (int i=0; i<bg->shader_count; i++) {
if (bg->shader[i].texture == texture && bg->shader[i].palette == palette) {
return i;
}
}
int idx = bg->shader_count++;
bg->shader = (Shader *)realloc(bg->shader, bg->shader_count * sizeof(Shader));
bg->shader[idx].texture = texture;
bg->shader[idx].palette = palette;
return idx;
}
void
load_background(const string & filename, Background *bg)
{
bg->vertex_count = 0;
bg->vertex = NULL;
bg->element_count = 0;
bg->element = NULL;
bg->textures = NULL;
bg->shader_count = 0;
bg->shader = NULL;
bg->mesh_count = 0;
bg->mesh = NULL;
u8 * data;
int size = load_lzs(filename, (void**)&data);
int num_pointer = get_u32le(data);
// background texture
int texdata = get_u32le(data + num_pointer * 4);
int npal = get_u16le(data + texdata + 18);
int paldata_size = get_u32le(data+texdata+8);
int pal_ofs = texdata + 20;
int picdata_size = get_u32le(data+texdata+8+paldata_size);
int pic_ofs = texdata + 20 + npal*512 + 12;
int xsize = get_u16le(data+pic_ofs-4)*2;
int ysize = get_u16le(data+pic_ofs-2);
// convert mesh data
for (int i=1; i<num_pointer-1; i++) {
int base = get_u32le(data + 4 + i * 4);
// triangles
int triangle_offset = base + 4 + get_u32le(data + base);
int num_triangles = get_u16le(data + triangle_offset);
int mesh_tri_flags = get_u16le(data + triangle_offset + 2);
int texture_idx = ((mesh_tri_flags & 0x0E)-6) / 2;
for (int j=0; j<num_triangles; j++) {
int point[3];
int ofs = triangle_offset + 4 + j * 16;
for (int k=0; k<3; k++) {
int p = get_u16le(data + ofs + k * 2) + base + 4;
s16 x = ((s16)get_u16le(data + p + 0*2));
s16 y = -((s16)get_u16le(data + p + 1*2));
s16 z = ((s16)get_u16le(data + p + 2*2));
const int uv_offsets[3] = { 8, 12, 14 };
u8 u = data[ofs + uv_offsets[k] + 0];
u8 v = data[ofs + uv_offsets[k] + 1];
point[k] = add_vertex(bg, x,y,z,u,v);
}
u16 flags1 = get_u16le(data + ofs + 6);
u16 flags2 = get_u16le(data + ofs + 10);
int palette_idx = (flags2 >> 6) & 7;
int shader_idx = add_shader(bg, texture_idx, palette_idx);
add_triangle(bg, point[0], point[1], point[2], shader_idx);
}
// quads
int quad_offset = triangle_offset + 4 + num_triangles * 16;
int num_quads = get_u16le(data + quad_offset);
int mesh_quad_flags = get_u16le(data + quad_offset + 2);
for (int j=0; j<num_quads; j++) {
int point[4];
int ofs = quad_offset + 4 + j * 20;
for (int k=0; k<4; k++) {
int p = get_u16le(data + ofs + k * 2) + base + 4;
s16 x = ((s16)get_u16le(data + p + 0 * 2));
s16 y = -((s16)get_u16le(data + p + 1 * 2));
s16 z = ((s16)get_u16le(data + p + 2 * 2));
const int uv_offsets[4] = { 8, 12, 14, 16 };
u8 u = data[ofs + uv_offsets[k] + 0];
u8 v = data[ofs + uv_offsets[k] + 1];
point[k] = add_vertex(bg, x,y,z,u,v);
}
u16 flags1 = get_u16le(data + ofs + 18);
u16 flags2 = get_u16le(data + ofs + 10);
int palette_idx = (flags2 >> 6) & 7;
int shader_idx = add_shader(bg, texture_idx, palette_idx);
add_triangle(bg, point[0], point[1], point[2], shader_idx);
add_triangle(bg, point[3], point[2], point[1], shader_idx);
}
}
// build meshes
bg->indexbuffer = (int*)malloc(bg->element_count * 3 * sizeof(int));
int cur_mesh = 0;
for (int i=0; i<bg->element_count; i++) {
bg->indexbuffer[i * 3 + 0] = bg->element[i].a;
bg->indexbuffer[i * 3 + 1] = bg->element[i].b;
bg->indexbuffer[i * 3 + 2] = bg->element[i].c;
if (bg->mesh_count > 0 && bg->mesh[cur_mesh].texture == bg->element[i].s) {
bg->mesh[cur_mesh].element_count++;
} else {
cur_mesh = bg->mesh_count++;
bg->mesh = (Mesh*)realloc(bg->mesh, bg->mesh_count * sizeof(Mesh));
bg->mesh[cur_mesh].texture = bg->element[i].s;
bg->mesh[cur_mesh].element_start = i*3;
bg->mesh[cur_mesh].element_count = 1;
}
}
// build required textures
struct RGBA {
u8 r, g, b, a;
};
RGBA *clut = (RGBA*)malloc(sizeof(RGBA) * 256 * (npal));
for (int j = 0; j < npal; j++) {
for (int i=0; i<256; i++) {
u16 col = (data[i*2 + pal_ofs + j*512 + 1]<<8) | data[i*2 + pal_ofs + j * 512];
clut[i+j*256].r = (((col ) & 31) * 255 + 15) / 31;
clut[i+j*256].g = (((col >> 5) & 31) * 255 + 15) / 31;
clut[i+j*256].b = (((col >> 10) & 31) * 255 + 15) / 31;
clut[i+j*256].a = ((col & 0x8000) || (col == 0)) ? 0 : 255;
}
}
bg->textures = (GLuint*)malloc(sizeof(GLuint) * bg->shader_count);
glGenTextures( bg->shader_count, bg->textures);
RGBA *texture = (RGBA*)malloc(sizeof(RGBA) * 256 * 256);
for (int i=0; i<bg->shader_count; i++) {
// build
#if 1
if ( bg->shader[i].palette >= npal ) {
printf("%i %i\n", bg->shader[i].palette, npal);
bg->shader[i].palette = 0;
}
#endif
for (int y=0; y<256; y++) {
for (int x=0; x<256; x++) {
texture[y*256+x] = clut[data[pic_ofs + y * xsize + x + bg->shader[i].texture * 256] + bg->shader[i].palette * 256];
}
}
// copy to GL
glBindTexture( GL_TEXTURE_2D, bg->textures[i]);
#if 0
// blocky
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, texture);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
#else
// interpolated
gluBuild2DMipmaps( GL_TEXTURE_2D, GL_RGBA, 256, 256, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, texture);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR );
#endif
}
free(texture);
free(clut);
free(data);
}
// GLUT and OpenGL stuff
Background battle;
int rot_y;
int rot_x;
void ReshapeWindowFunc(int width, int height)
{
const double kFOVy = 0.57735;
const double kZNear = 0.1;
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
GLdouble aspect = ((GLfloat)width / (GLfloat)height) * (kZNear * kFOVy);
glFrustum(-aspect, aspect, -(kZNear * kFOVy), (kZNear * kFOVy), kZNear, 1000.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void MainRenderLoop(void)
{
// render
glClearColor(0, 0, 0, 1.00);
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable( GL_DEPTH_TEST );
glDepthFunc( GL_LEQUAL );
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 1.0f/255.0f);
glEnable( GL_TEXTURE_2D );
glEnable( GL_CULL_FACE );
glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
glDisable( GL_LIGHTING );
glDisable( GL_BLEND );
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef( (float)rot_x * 360.0f / 256.0f, 1.0f, 0.0f, 0.0f );
glRotatef( (float)rot_y * 360.0f / 256.0f, 0.0f, 1.0f, 0.0f );
glTranslatef( 0, -16, 0);
glScalef( 1.0f/256.0f, 1.0f/256.0f, 1.0f/256.0f );
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glScalef(1.0f/256.0f, 1.0f/256.0f, 0);
glTranslatef( 0.5f, 0.5f, 0);
glEnableClientState( GL_VERTEX_ARRAY );
glEnableClientState( GL_TEXTURE_COORD_ARRAY );
glVertexPointer( 3, GL_SHORT, sizeof(Vertex), &(battle.vertex[0].x) );
glTexCoordPointer( 2, GL_SHORT, sizeof(Vertex), &(battle.vertex[0].u) );
for (int i = 0; i < battle.mesh_count; i++) {
glBindTexture(GL_TEXTURE_2D, battle.textures[battle.mesh[i].texture]);
glDrawElements(GL_TRIANGLES, battle.mesh[i].element_count * 3, GL_UNSIGNED_INT, battle.indexbuffer + battle.mesh[i].element_start);
}
glDisableClientState( GL_TEXTURE_COORD_ARRAY );
glDisableClientState( GL_VERTEX_ARRAY );
glFlush();
}
void SpecialHandler(int key, int x, int y)
{
switch (key) {
case GLUT_KEY_UP:
rot_x -= 4;
if (rot_x < -64) {
rot_x = -64;
}
break;
case GLUT_KEY_DOWN:
rot_x += 4;
if (rot_x > 64) {
rot_x = 64;
}
break;
case GLUT_KEY_LEFT:
rot_y -= 5;
break;
case GLUT_KEY_RIGHT:
rot_y += 5;
break;
}
glutPostRedisplay();
}
void KeyboardHandler(unsigned char key, int x, int y)
{
switch (key) {
case 27:
exit(0);
break;
}
}
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "stageview <stage.lzs>\n");
} else {
// get current directory
char cwd[1024];
getcwd(cwd, sizeof(cwd));
string path;
if (argv[1][0] != 0 && argv[1][0] != '/' && argv[1][1] != 0 && argv[1][1] != ':' && argv[1][2] != 0 && argv[1][2] != '\\') {
path = string(cwd) + "/";
}
// init opengl and glut
glutInit(&argc, (char **) argv);
glutInitWindowSize(640, 480);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA | GLUT_DEPTH);
glutCreateWindow(argv[0]);
glutDisplayFunc(MainRenderLoop);
glutIdleFunc(MainRenderLoop);
glutReshapeFunc(ReshapeWindowFunc);
glutKeyboardFunc(KeyboardHandler);
glutSpecialFunc(SpecialHandler);
// load data
load_background(path + argv[1], &battle);
rot_y = rot_x = 0;
// main (this will never return)
glutMainLoop();
}
return 0;
}