Skip to content

Instantly share code, notes, and snippets.

@camthesaxman
Created November 4, 2017 17:15
Show Gist options
  • Save camthesaxman/204e1adfeefcbce744487f982f977797 to your computer and use it in GitHub Desktop.
Save camthesaxman/204e1adfeefcbce744487f982f977797 to your computer and use it in GitHub Desktop.
Super Monkey Ball Level viewer
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#define FREEGLUT_STATIC
#include <GL/freeglut.h>
struct Vec3f
{
float x;
float y;
float z;
};
struct Vec2f
{
float x;
float y;
};
struct Material
{
uint32_t flags;
uint16_t texIndex;
};
struct Model
{
uint32_t fileOffset;
struct Material *materials;
int materialsCount;
char name[256];
};
int winWidth = 640;
int winHeight = 480;
float yaw;
float pitch;
float viewX;
float viewY;
float viewZ;
GLint dispList;
GLint *textureIDs;
long int modelCount;
long int modelBaseOffset;
long int modelDataOffset;
struct Model *models;
int wireframe = 0;
int8_t read_8(FILE *file)
{
return fgetc(file);
}
int32_t read_16(FILE *file)
{
int16_t data = 0;
data |= fgetc(file) << 8;
data |= fgetc(file) << 0;
return data;
}
int32_t read_32(FILE *file)
{
int32_t data = 0;
data |= fgetc(file) << 24;
data |= fgetc(file) << 16;
data |= fgetc(file) << 8;
data |= fgetc(file) << 0;
return data;
}
float read_float(FILE *file)
{
union
{
int32_t i;
float f;
} u;
u.i = read_32(file);
return u.f;
}
#define GCMF_SF_16BIT 1 // vertices stored as 16-bit instead of float
#define GCMF_SF_STITCHINGMODEL 4 // has associated transformation matrices
#define GCMF_SF_SKINMODEL 8 // has associated transformation matrices and indexed vertices
#define GCMF_SF_EFFECTIVEMODEL 16 // has indexed vertices
#define GCMF_VF_TRANSMTXREF 1
#define GCMF_VF_HAS_POSITION 0x200
#define GCMF_VF_HAS_NORMALS 0x400
#define GCMF_VF_HAS_COLOR 0x800
#define GCMF_VF_HAS_TEXCOORD1 0x2000
#define GCMF_VF_HAS_TEXCOORD2 0x4000
#define GCMF_VF_HAS_TEXCOORD3 0x8000
#define GCMF_VF_UNK2000000 0x02000000
void load_materials(FILE *file, struct Model *model)
{
int i;
for (i = 0; i < model->materialsCount; i++)
{
struct Material *mtrl = &model->materials[i];
mtrl->flags = read_32(file);
mtrl->texIndex = read_16(file);
read_8(file);
read_8(file); // anisotropy level
assert(read_32(file) == 0);
read_16(file);
assert(i == read_16(file));
read_32(file);
assert(read_32(file) == 0);
assert(read_32(file) == 0);
assert(read_32(file) == 0);
}
}
void load_strip(FILE *file, unsigned int vtxflags)
{
uint8_t type;
uint16_t vertexCount;
int i;
type = read_8(file);
if (type == 0)
return;
if (type != 0x98 && type != 0x99)
{
printf("unknown type value 0x%02X\n", type);
exit(1);
}
vertexCount = read_16(file);
// load vertices
glBegin(GL_TRIANGLE_STRIP);
for (i = 0; i < vertexCount; i++)
{
struct Vec3f pos;
struct Vec3f nrm;
struct Vec2f texc1, texc2, texc3;
uint8_t r, g, b, a;
if (vtxflags & GCMF_VF_TRANSMTXREF)
read_8(file);
if (vtxflags & GCMF_VF_HAS_POSITION)
{
switch (type)
{
case 0x98:
pos.x = read_float(file);
pos.y = read_float(file);
pos.z = read_float(file);
break;
case 0x99:
pos.x = read_16(file);
pos.y = read_16(file);
pos.z = read_16(file);
break;
}
}
else
{
puts("error: no position specified");
exit(1);
}
if (vtxflags & GCMF_VF_HAS_NORMALS)
{
switch (type)
{
case 0x98:
nrm.x = read_float(file);
nrm.y = read_float(file);
nrm.z = read_float(file);
break;
case 0x99:
nrm.x = read_16(file);
nrm.y = read_16(file);
nrm.z = read_16(file);
break;
}
glNormal3f(nrm.x, nrm.y, nrm.z);
}
if (vtxflags & GCMF_VF_HAS_COLOR)
{
r = read_8(file);
g = read_8(file);
b = read_8(file);
a = read_8(file);
glColor4ub(r, g, b, a);
}
if (vtxflags & GCMF_VF_HAS_TEXCOORD1)
{
switch (type)
{
case 0x98:
texc1.x = read_float(file);
texc1.y = read_float(file);
break;
case 0x99:
texc1.x = read_16(file);
texc1.y = read_16(file);
break;
}
glTexCoord2f(texc1.x, texc1.y);
}
if (vtxflags & GCMF_VF_HAS_TEXCOORD2)
{
switch (type)
{
case 0x98:
texc2.x = read_float(file);
texc2.y = read_float(file);
break;
case 0x99:
texc2.x = read_16(file);
texc2.y = read_16(file);
break;
}
}
if (vtxflags & GCMF_VF_HAS_TEXCOORD3)
{
switch (type)
{
case 0x98:
texc3.x = read_float(file);
texc3.y = read_float(file);
break;
case 0x99:
texc3.x = read_16(file);
texc3.y = read_16(file);
break;
}
}
if (vtxflags & GCMF_VF_UNK2000000)
{
fseek(file, 9 * 4, SEEK_CUR);
}
glVertex3f(pos.x, pos.y, pos.z);
}
glEnd();
}
void load_mesh(FILE *file, struct Model *model)
{
uint32_t renderFlags;
uint8_t sectionFlags;
uint32_t vertexFlags;
size_t chunk1size;
size_t chunk2size;
int mtrlIdx1;
struct Vec3f boundingSphereCenter;
size_t savedPos;
int i;
/* Read header */
savedPos = ftell(file);
renderFlags = read_32(file); /* 0x00 */
read_32(file);
read_32(file);
read_32(file);
read_16(file);
read_8(file); // usedMaterialCount
sectionFlags = read_8(file); // sectionFlags
read_16(file);
mtrlIdx1 = read_16(file); // material index 1
read_16(file); // material index 2
read_16(file); // material index 3
glBindTexture(GL_TEXTURE_2D, textureIDs[model->materials[mtrlIdx1].texIndex]);
assert(glGetError() == 0);
vertexFlags = read_32(file);
fseek(file, 8, SEEK_CUR); // transformation matrix stuff
chunk1size = read_32(file);
chunk2size = read_32(file);
boundingSphereCenter.x = read_float(file);
boundingSphereCenter.y = read_float(file);
boundingSphereCenter.z = read_float(file);
read_float(file);
read_32(file);
for (i = 0; i < 7; i++)
{
assert(read_32(file) == 0);
}
assert(ftell(file) == savedPos + 0x60);
/* Read non-indexed vertex data */
if ((sectionFlags & 1) && chunk1size == 0)
{
puts("error: Chunk 1 specified, but size is 0");
exit(1);
}
if (!(sectionFlags & 1) && chunk1size != 0)
{
puts("error: No chunk 1 specified, but with nonzero size");
exit(1);
}
if ((sectionFlags & 2) && chunk2size == 0)
{
puts("error: Chunk 2 specified, but size is 0");
exit(1);
}
if (!(sectionFlags & 2) && chunk2size != 0)
{
puts("error: No chunk 2 specified, but with nonzero size");
exit(1);
}
if (sectionFlags & 1)
{
savedPos = ftell(file);
assert(read_8(file) == 0);
while (ftell(file) < savedPos + chunk1size)
load_strip(file, vertexFlags);
fseek(file, savedPos + chunk1size, SEEK_SET);
}
if (sectionFlags & 2)
{
savedPos = ftell(file);
assert(read_8(file) == 0);
while (ftell(file) < savedPos + chunk2size)
load_strip(file, vertexFlags);
fseek(file, savedPos + chunk2size, SEEK_SET);
}
}
void load_models(FILE *file)
{
int i, j, k, l;
const uint32_t gcmfMagic = ('G' << 24) | ('C' << 16) | ('M' << 8) | ('F' << 0);
dispList = glGenLists(1);
glNewList(dispList, GL_COMPILE);
modelCount = read_32(file);
modelBaseOffset = read_32(file);
models = malloc(modelCount * sizeof(*models));
for (i = 0; i < modelCount; i++)
{
uint32_t offset;
// Get name
fseek(file, 8 + i * 8 + 4, SEEK_SET);
offset = read_32(file);
fseek(file, 8 + modelCount * 8 + offset, SEEK_SET);
for (j = 0; j < 256; j++)
{
char c = fgetc(file);
models[i].name[j] = c;
if (c == '\0')
break;
}
models[i].name[255] = '\0';
printf("model '%s'\n", models[i].name);
// Get file offset
fseek(file, 8 + 8 * i, SEEK_SET);
models[i].fileOffset = read_32(file);
}
for (i = 0; i < modelCount; i++)
{
long int modelOffset = modelBaseOffset + models[i].fileOffset;
long int offset;
size_t headerSize;
int materialsCount;
int totalMeshCount, layer1MeshCount, layer2MeshCount;
struct Vec3f modelOriginOffset;
uint32_t sectionFlags;
if (models[i].fileOffset == 0xFFFFFFFF)
continue;
fseek(file, modelOffset, SEEK_SET);
if (read_32(file) != gcmfMagic)
{
puts("error: bad GCMF header");
exit(1);
}
sectionFlags = read_32(file);
// bounding sphere center?
modelOriginOffset.x = read_float(file);
modelOriginOffset.y = read_float(file);
modelOriginOffset.z = read_float(file);
read_float(file); // bounding sphere radius?
materialsCount = read_16(file);
layer1MeshCount = read_16(file);
layer2MeshCount = read_16(file);
read_8(file);
assert(ftell(file) == modelOffset + 0x1F);
assert(read_8(file) == 0);
headerSize = read_32(file);
assert(read_32(file) == 0);
fseek(file, 8, SEEK_CUR); // transform matrix default IDs?
assert(read_32(file) == 0);
assert(read_32(file) == 0);
assert(read_32(file) == 0);
assert(read_32(file) == 0);
assert(ftell(file) == modelOffset + 0x40);
totalMeshCount = layer1MeshCount + layer2MeshCount;
// Load materials
models[i].materialsCount = materialsCount;
models[i].materials = malloc(materialsCount * sizeof(*models[i].materials));
load_materials(file, &models[i]);
fseek(file, modelOffset + headerSize, SEEK_SET);
if (sectionFlags & (GCMF_SF_SKINMODEL | GCMF_SF_EFFECTIVEMODEL))
{
puts("don't know how to handle this");
exit(1);
}
else
{
for (j = 0; j < totalMeshCount; j++)
load_mesh(file, &models[i]);
}
}
glEndList();
}
void window_on_display(void)
{
int i;
float invAspectRatio = (float)winHeight / (float)winWidth;
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
/* Draw the stage */
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glFrustum(-1, 1, -invAspectRatio, invAspectRatio, 1, 5000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(viewX, viewY, viewZ);
glRotatef(pitch, 1, 0, 0);
glRotatef(yaw, 0, 1, 0);
glCallList(dispList);
/* Draw the text */
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
glDisable(GL_TEXTURE_2D);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, winWidth, winHeight, 0, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glColor3ub(255, 255, 255);
glRasterPos2i(0, 60);
glutBitmapString(GLUT_BITMAP_HELVETICA_12, "models:");
for (i = 0; i < modelCount; i++)
{
glRasterPos2i(0, 75 + i * 15);
glutBitmapString(GLUT_BITMAP_HELVETICA_12, models[i].name);
}
glutSwapBuffers();
}
void window_on_keypress(unsigned char key, int x, int y)
{
switch (key)
{
case 'w':
wireframe = !wireframe;
glPolygonMode(GL_FRONT_AND_BACK, wireframe ? GL_LINE : GL_FILL);
glutPostRedisplay();
break;
case 27: // Escape key
exit(0);
}
}
void window_on_special_keypress(int key, int x, int y)
{
switch(key)
{
case GLUT_KEY_UP:
glutPostRedisplay();
pitch++;
break;
case GLUT_KEY_DOWN:
glutPostRedisplay();
pitch--;
break;
case GLUT_KEY_LEFT:
yaw--;
glutPostRedisplay();
break;
case GLUT_KEY_RIGHT:
yaw++;
glutPostRedisplay();
break;
}
}
static struct
{
int mouseButton;
int mouseX;
int mouseY;
float yaw;
float pitch;
float viewX;
float viewY;
} dragStart;
void window_on_mousebutton(int button, int state, int x, int y)
{
if (state == GLUT_UP)
{
dragStart.mouseButton = 0;
}
else
{
dragStart.mouseButton = button;
switch (button)
{
case GLUT_LEFT_BUTTON:
dragStart.mouseX = x;
dragStart.mouseY = y;
dragStart.viewX = viewX;
dragStart.viewY = viewY;
break;
case GLUT_RIGHT_BUTTON:
dragStart.mouseX = x;
dragStart.mouseY = y;
dragStart.yaw = yaw;
dragStart.pitch = pitch;
break;
case 3: // scroll wheel up
viewZ++;
glutPostRedisplay();
break;
case 4: // scroll wheel down
viewZ--;
glutPostRedisplay();
break;
}
}
}
void window_on_mousemove(int x, int y)
{
int deltaX = x - dragStart.mouseX;
int deltaY = y - dragStart.mouseY;
if (dragStart.mouseButton == GLUT_LEFT_BUTTON)
{
viewX = dragStart.viewX + (float)deltaX / 2;
viewY = dragStart.viewY - (float)deltaY / 2;
glutPostRedisplay();
}
else if (dragStart.mouseButton == GLUT_RIGHT_BUTTON)
{
yaw = dragStart.yaw + (float)deltaX / 2;
pitch = dragStart.pitch + (float)deltaY / 2;
glutPostRedisplay();
}
}
void window_on_reshape(int w, int h)
{
glViewport(0, 0, w, h);
winWidth = w;
winHeight = h;
}
void init_gl(void)
{
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat mat_shininess[] = { 50.0, 0.0, 0.0, 0.0 };
GLfloat mat_ambient[] = { 0.85, 0.85, 0.85, 1.0 };
GLfloat light_position[] = { 1.0, 100.0, 1.0, 0.0 };
GLfloat light_ambient[] = {0.25, 0.25, 0.25, 1.0};
glShadeModel(GL_SMOOTH);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMateriali(GL_FRONT, GL_SHININESS, 50);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glEnable(GL_TEXTURE_2D);
glClearColor(0.5, 0.5, 1.0, 1.0);
}
//------------------------------------------------------------------------------
// TPL loader
//------------------------------------------------------------------------------
struct Texture
{
uint32_t format;
uint32_t offset;
uint16_t width;
uint16_t height;
uint16_t levelCount;
uint8_t *levelData[16];
};
enum
{
TEX_FMT_I4, // 4 bit grayscale
TEX_FMT_I8, // 8 bit grayscale
TEX_FMT_IA4, // 4 bit grayscale, 4 bit transparency
TEX_FMT_IA8, // 8 bit grayscale, 8 bit transparency
TEX_FMT_RGB565,
TEX_FMT_RGB5A3,
TEX_FMT_RGBA8,
TEX_FMT_CI4 = 8,
TEX_FMT_CI8,
TEX_FMT_CI14X2,
TEX_FMT_CMPR = 14,
NUM_TEX_FMTS,
};
struct TextureFormat
{
int tileWidth;
int tileHeight;
int bpp;
void (*tileDecodeFunc)(uint8_t *, int, int, uint8_t *, int);
};
struct ColorRGBA
{
uint8_t Red;
uint8_t Green;
uint8_t Blue;
uint8_t Alpha;
};
void fmt_i8_decode_tile(uint8_t *dst, int dstPos, int stride, uint8_t *src, int srcPos)
{
int srcCurrentPos = srcPos, dstCurrentPos = dstPos;
int tx, ty;
for (ty = 0; ty < 4; ty++, dstCurrentPos += stride - 8 * 4)
{
for (tx = 0; tx < 8; tx++)
{
dst[dstCurrentPos++] = src[srcCurrentPos];
dst[dstCurrentPos++] = src[srcCurrentPos];
dst[dstCurrentPos++] = src[srcCurrentPos];
dst[dstCurrentPos++] = 255;
srcCurrentPos++;
}
}
}
static uint8_t Bits3To8(unsigned int v)
{
return (float)v / ((1 << 3) - 1) * ((1 << 8) - 1);
}
static uint8_t Bits4To8(unsigned int v)
{
return (float)v / ((1 << 4) - 1) * ((1 << 8) - 1);
}
static uint8_t Bits5To8(unsigned int v)
{
return (float)v / ((1 << 5) - 1) * ((1 << 8) - 1);
}
static uint8_t Bits6To8(unsigned int v)
{
return (float)v / ((1 << 6) - 1) * ((1 << 8) - 1);
}
static struct ColorRGBA RGB565ToColor(uint16_t v)
{
return (struct ColorRGBA){
Bits5To8((v >> 11) & 0x1F),
Bits6To8((v >> 5) & 0x3F),
Bits5To8(v & 0x1F),
0xFF
};
}
void fmt_rgb565_decode_tile(uint8_t *dst, int dstPos, int stride, uint8_t *src, int srcPos)
{
int srcCurrentPos = srcPos, dstCurrentPos = dstPos;
int tx, ty;
for (ty = 0; ty < 4; ty++, dstCurrentPos += stride - 4 * 4)
{
for (tx = 0; tx < 4; tx++)
{
uint16_t color16 = (src[srcCurrentPos] << 8) | src[srcCurrentPos + 1];
struct ColorRGBA c = RGB565ToColor(color16);
dst[dstCurrentPos + 0] = c.Red;
dst[dstCurrentPos + 1] = c.Green;
dst[dstCurrentPos + 2] = c.Blue;
dst[dstCurrentPos + 3] = c.Alpha;
srcCurrentPos += 2;
}
}
}
static struct ColorRGBA RGB5A3ToColor(uint16_t v)
{
if ((v & 0x8000) != 0)
{
// RGB555
return (struct ColorRGBA){
Bits5To8((v >> 10) & 0x1F),
Bits5To8((v >> 5) & 0x1F),
Bits5To8(v & 0x1F),
0xFF
};
}
else
{
// RGB4A3
return (struct ColorRGBA){
Bits4To8((v >> 8) & 0x0F),
Bits4To8((v >> 4) & 0x0F),
Bits4To8(v & 0x0F),
Bits3To8((v >> 12) & 0x07)
};
}
}
void fmt_rgb5a3_decode_tile(uint8_t *dst, int dstPos, int stride, uint8_t *src, int srcPos)
{
int srcCurrentPos = srcPos, dstCurrentPos = dstPos;
for (int ty = 0; ty < 4; ty++, dstCurrentPos += stride - 4 * 4)
{
for (int tx = 0; tx < 4; tx++, dstCurrentPos += 4)
{
uint16_t color16 = (src[srcCurrentPos] << 8) | src[srcCurrentPos + 1];
struct ColorRGBA c = RGB5A3ToColor(color16);
dst[dstCurrentPos + 0] = c.Red;
dst[dstCurrentPos + 1] = c.Green;
dst[dstCurrentPos + 2] = c.Blue;
dst[dstCurrentPos + 3] = c.Alpha;
srcCurrentPos += 2;
}
}
}
static void DecodeDxtBlock(uint8_t *dst, int dstIndex, int stride, uint8_t *src, int srcIndex)
{
uint16_t c1 = (src[srcIndex] << 8) | src[srcIndex + 1];
srcIndex += 2;
uint16_t c2 = (src[srcIndex] << 8) | src[srcIndex + 1];
srcIndex += 2;
struct ColorRGBA color1 = RGB565ToColor(c1);
struct ColorRGBA color2 = RGB565ToColor(c2);
uint8_t b1 = color1.Blue;
uint8_t b2 = color2.Blue;
uint8_t g1 = color1.Green;
uint8_t g2 = color2.Green;
uint8_t r1 = color1.Red;
uint8_t r2 = color2.Red;
struct ColorRGBA colors[4];
colors[0] = (struct ColorRGBA){r1, g1, b1, 0xFF};
colors[1] = (struct ColorRGBA){r2, g2, b2, 0xFF};
if (c1 > c2)
{
uint8_t b3 = (uint8_t)(((b2 - b1) >> 1) - ((b2 - b1) >> 3));
uint8_t g3 = (uint8_t)(((g2 - g1) >> 1) - ((g2 - g1) >> 3));
uint8_t r3 = (uint8_t)(((r2 - r1) >> 1) - ((r2 - r1) >> 3));
colors[2] = (struct ColorRGBA){(uint8_t)(r1 + r3), (uint8_t)(g1 + g3), (uint8_t)(b1 + b3), 0xFF};
colors[3] = (struct ColorRGBA){(uint8_t)(r2 - r3), (uint8_t)(g2 - g3), (uint8_t)(b2 - b3), 0xFF};
}
else
{
colors[2] = (struct ColorRGBA){ // Average
(uint8_t)((r1 + r2 + 1) / 2),
(uint8_t)((g1 + g2 + 1) / 2),
(uint8_t)((b1 + b2 + 1) / 2),
0xFF
};
colors[3] = (struct ColorRGBA){r2, g2, b2, 0x00}; // Color2 but transparent
}
for (int ty = 0; ty < 4; ty++)
{
int val = src[srcIndex++];
for (int tx = 0; tx < 4; tx++)
{
//colors[(val >> 6) & 3].Write(dst, dstIndex + 4 * tx + ty * stride);
struct ColorRGBA *c = &colors[(val >> 6) & 3];
dst[dstIndex + 4 * tx + ty * stride + 0] = c->Red;
dst[dstIndex + 4 * tx + ty * stride + 1] = c->Green;
dst[dstIndex + 4 * tx + ty * stride + 2] = c->Blue;
dst[dstIndex + 4 * tx + ty * stride + 3] = c->Alpha;
val <<= 2;
}
}
}
void fmt_cmpr_decode_tile(uint8_t *dst, int dstPos, int stride, uint8_t *src, int srcPos)
{
DecodeDxtBlock(dst, dstPos + 0 * 4 + 0 * stride, stride, src, srcPos + 0); // x + 0, y + 0
DecodeDxtBlock(dst, dstPos + 4 * 4 + 0 * stride, stride, src, srcPos + 8); // x + 4, y + 0
DecodeDxtBlock(dst, dstPos + 0 * 4 + 4 * stride, stride, src, srcPos + 16); // x + 0, y + 4
DecodeDxtBlock(dst, dstPos + 4 * 4 + 4 * stride, stride, src, srcPos + 24); // x + 4, y + 4
}
/* These appear to be the only formats used in Super Monkey Ball, so I didn't
* bother implementing the rest of them. */
static const struct TextureFormat texFormats[NUM_TEX_FMTS] =
{
[TEX_FMT_I8] = {8, 4, 8, fmt_i8_decode_tile},
[TEX_FMT_RGB565] = {4, 4, 16, fmt_rgb565_decode_tile},
[TEX_FMT_RGB5A3] = {4, 4, 16, fmt_rgb5a3_decode_tile},
[TEX_FMT_CMPR] = {8, 8, 4, fmt_cmpr_decode_tile},
};
size_t get_texture_level_size(int level, int format, int width, int height)
{
int tileWidth, tileHeight, bpp;
width >>= level;
height >>= level;
if (texFormats[format].tileDecodeFunc != NULL)
{
tileWidth = texFormats[format].tileWidth;
tileHeight = texFormats[format].tileHeight;
bpp = texFormats[format].bpp;
}
else
{
printf("unsupported texture format %i\n", format);
exit(1);
}
width = (width + tileWidth - 1) & ~(tileWidth - 1);
height = (height + tileHeight - 1) & ~(tileHeight - 1);
return width * height * bpp / 8;
}
void get_texture_level_dimensions(int level, int format, int *width, int *height)
{
int w, h;
int tileWidth, tileHeight;
if (texFormats[format].tileDecodeFunc == NULL)
{
printf("format %i unsupported\n", format);
exit(1);
}
tileWidth = texFormats[format].tileWidth;
tileHeight = texFormats[format].tileHeight;
w = *width >> level;
h = *height >> level;
*width = (w + tileWidth - 1) & ~(tileWidth - 1);
*height = (h + tileHeight - 1) & ~(tileHeight - 1);
}
uint8_t *convert_to_rgba(struct Texture *tex, int level, int *outWidth, int *outHeight)
{
int width = tex->width;
int height = tex->height;
get_texture_level_dimensions(level, tex->format, &width, &height);
size_t inputsize = get_texture_level_size(level, tex->format, tex->width, tex->height);
size_t outputsize = width * height * 4;
uint8_t *output = calloc(1, outputsize);
uint8_t *input = tex->levelData[level];
int tileWidth = texFormats[tex->format].tileWidth;
int tileHeight = texFormats[tex->format].tileHeight;
int tileSize = tileWidth * tileHeight * texFormats[tex->format].bpp / 8;
int x, y;
int stride = width * 4;
int srcPos = 0;
int dstPos = 0;
*outWidth = width;
*outHeight = height;
if (texFormats[tex->format].tileDecodeFunc == NULL)
{
printf("unable to convert format %i\n", tex->format);
return output;
}
for (y = 0; y < height; y += tileHeight)
{
for (x = 0; x < width; x += tileWidth)
{
if (x + tileWidth <= width && y + tileHeight <= height)
{
// Tile is contained completely in the destination buffer
// This should be the case for all power of two textures
texFormats[tex->format].tileDecodeFunc(output, dstPos + x * 4 + y * stride, stride, input, srcPos);
}
else
{
// Tile is not contained completely in the destination buffer
// Create a temporary array, and then copy it to the destination buffer.
assert(0);
}
srcPos += tileSize;
}
}
return output;
}
struct Texture *textures;
int texturesCount;
void load_tpl(FILE *file)
{
int i;
int level;
texturesCount = read_32(file);
textures = malloc(texturesCount * sizeof(*textures));
textureIDs = malloc(texturesCount * sizeof(*textureIDs));
for (i = 0; i < texturesCount; i++)
{
textures[i].format = read_32(file);
textures[i].offset = read_32(file);
textures[i].width = read_16(file);
textures[i].height = read_16(file);
textures[i].levelCount = read_16(file);
assert(read_16(file) == 0x1234);
assert(textures[i].levelCount <= 16);
}
/* Load image data from file */
for (i = 0; i < texturesCount; i++)
{
struct Texture *tex = &textures[i];
if (tex->offset != 0 && tex->width != 0 && tex->height != 0 && tex->levelCount != 0)
{
fseek(file, tex->offset, SEEK_SET);
for (level = 0; level < tex->levelCount; level++)
{
size_t size = get_texture_level_size(level, tex->format, tex->width, tex->height);
tex->levelData[level] = malloc(size);
fread(tex->levelData[level], 1, size, file);
}
}
}
/* Create the OpenGL textures */
glGenTextures(texturesCount, textureIDs);
for (i = 0; i < texturesCount; i++)
{
struct Texture *tex = &textures[i];
for (level = 0; level < tex->levelCount; level++)
{
uint8_t *output;
int width, height;
unsigned int error;
// decode the image
output = convert_to_rgba(tex, level, &width, &height);
glBindTexture(GL_TEXTURE_2D, textureIDs[i]);
glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, output);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
error = glGetError();
if (error)
printf("failed to create texture %i level %i: error 0x%X\n", i, level, error);
free(output);
}
}
}
int main(int argc, char **argv)
{
char filename[256];
FILE *gmaFile;
FILE *tplFile;
// disable MSYS2's buggy console buffering
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
if (argc < 2)
{
puts("bad args");
return 1;
}
sprintf(filename, "%s.gma", argv[1]);
gmaFile = fopen(filename, "rb");
if (gmaFile == NULL)
{
fprintf(stderr, "Failed to open '%s' for reading\n", filename);
//return 1;
}
sprintf(filename, "%s.tpl", argv[1]);
tplFile = fopen(filename, "rb");
if (tplFile == NULL)
{
fprintf(stderr, "Failed to open '%s' for reading\n", filename);
//return 1;
}
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
glutInitWindowSize(winWidth, winHeight);
glutCreateWindow("Super Monkey Ball stage viewer");
glutDisplayFunc(window_on_display);
glutKeyboardFunc(window_on_keypress);
glutSpecialFunc(window_on_special_keypress);
glutMouseFunc(window_on_mousebutton);
glutMotionFunc(window_on_mousemove);
glutReshapeFunc(window_on_reshape);
init_gl();
load_tpl(tplFile);
fclose(tplFile);
load_models(gmaFile);
fclose(gmaFile);
yaw = 45;
pitch = 45;
viewX = 0;
viewY = 0;
viewZ = -50;
glutMainLoop();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment