Created
November 4, 2017 17:15
-
-
Save camthesaxman/204e1adfeefcbce744487f982f977797 to your computer and use it in GitHub Desktop.
Super Monkey Ball Level viewer
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
#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