I've recently started working on my own game engine and I have been thinking about creating my own model format. Since text usually takes a long time to load and process I decided to make a binary file. After a little work, I already had the basic concept:
The concept
- The header:
The header of the file contains the magic number and the current major and minor version of the file. The magic number I chose is706D6600
. An example header would like this:706D6600 0100 0000
- The body
I thought for a long time how I could make the body. In the end, I decided on the following: Models consist of vertices, texture coordinates, normals, and faces. Each type has a unique identifier and always the same size. This fixed size makes it easier for the file reader to read the file. Here is a list of all ID's:- EOF -
0000FFF1
- Vertex -
0000FFF2
3x Float(12 bytes) - Texture Coordinate
0000FFF3
2x Float(8 bytes) - Normal
0000FFF4
3x Float(12 bytes) - Face
0000FFF5
9x Integer(36 bytes) (Sorry for not choosingFACE
😀)
- EOF -
The Reader
To test the file out, I wrote a simple reader which would print the file like a Wavefront Obj Model.
While writing, I didn't pay attention to performance: it was just to make the function of the file clearer. Here is the code:
pmf_types.h
#define PMF_TYPES_H
#include <stdint.h>
const unsigned char pmf_magicnumber[4] = {0x70, 0x6D, 0x66, 0x00};
const uint32_t pmf_min_major = 1;
const uint32_t pmf_min_minor = 0;
const uint32_t pmf_max_major = 1;
const uint32_t pmf_max_minor = 0;
const unsigned char pmf_eof_id[4] = {0x00, 0x00, 0xFF, 0xF1};
const unsigned char pmf_vertex_id[4] = {0x00, 0x00, 0xFF, 0xF2};
const unsigned char pmf_texture_coordinate_id[4] = {0x00, 0x00, 0xFF, 0xF3};
const unsigned char pmf_normal_id[4] = {0x00, 0x00, 0xFF, 0xF4};
const unsigned char pmf_face_id[4] = {0x00, 0x00, 0xFF, 0xF5};
#endif
main.c
#include "pmf_types.h"
int main() {
FILE *file = fopen("example.pmf", "rb");
fseek(file, 0, SEEK_END);
size_t fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
unsigned char magicnumber[4];
fread(magicnumber, sizeof(magicnumber), 1, file);
if(memcmp(magicnumber, pmf_magicnumber, sizeof(magicnumber)) != 0) {
printf("Invalid Magic Number. (Perhaps wrong format)\n");
return -1;
}
unsigned char version[4];
fseek(file, 4, SEEK_SET);
fread(version, sizeof(version), 1, file);
uint32_t major, minor;
memcpy(&major, (unsigned char[2]){version[0], version[1]}, 2);
memcpy(&minor, (unsigned char[2]){version[2], version[3]}, 2);
if(major < pmf_min_major || (major == pmf_min_major && minor < pmf_max_minor)) {
printf("The file is outdated.\nSupported maximum version: %d.%d\n", pmf_max_major, pmf_max_minor);
return -1;
}
if(major > pmf_max_major || (major == pmf_max_major && minor > pmf_max_minor)) {
printf("The reader is outdated.\nSupported maximum version: %d.%d\n", pmf_max_major, pmf_max_minor);
return -1;
}
int offset = 8;
while(1) {
if(offset>fileSize) {
printf("Unexpected end of file.\n");
break;
}
unsigned char identifier[4];
fseek(file, offset, SEEK_SET);
fread(&identifier, sizeof(identifier), 1, file);
offset+=4;
if(memcmp(identifier, pmf_vertex_id, sizeof(identifier)) == 0) {
if(offset+12>fileSize) {
printf("Marked as vertex but not enough values supplied.\n");
break;
}
float x, y, z;
fseek(file, offset, SEEK_SET);
fread((void*)(&x), sizeof(x), 1, file);
offset += 4;
fseek(file, offset, SEEK_SET);
fread((void*)(&y), sizeof(y), 1, file);
offset += 4;
fseek(file, offset, SEEK_SET);
fread((void*)(&z), sizeof(z), 1, file);
offset += 4;
printf("Vertex %f %f %f\n", x, y, z);
} else if(memcmp(identifier, pmf_texture_coordinate_id, sizeof(identifier)) == 0) {
if(offset+8>fileSize) {
printf("Marked as texture coordinate but not enough values supplied.\n");
break;
}
float x, y;
fseek(file, offset, SEEK_SET);
fread((void*)(&x), sizeof(x), 1, file);
offset+=4;
fseek(file, offset, SEEK_SET);
fread((void*)(&y), 4, 1, file);
offset+=4;
printf("Texture coordinate %f %f\n", x, y);
} else if(memcmp(identifier, pmf_normal_id, sizeof(identifier)) == 0) {
if(offset+12>fileSize) {
printf("Marked as normal but not enough values supplied.\n");
break;
}
float x=0, y=0, z=0;
fseek(file, offset, SEEK_SET);
fread(&x, 4, 1, file);
offset+=4;
fseek(file, offset, SEEK_SET);
fread(&y, 4, 1, file);
offset+=4;
fseek(file, offset, SEEK_SET);
fread(&z, 4, 1, file);
offset+=4;
printf("Normal %f %f %f\n", x, y, z);
} else if(memcmp(identifier, pmf_face_id, sizeof(identifier)) == 0) {
if(offset+36>fileSize) {
printf("Marked as face but not enough values supplied.\n");
break;
}
uint32_t vx, vy, vz, vtx, vty, vtz, vnx, vny, vnz;
fseek(file, offset, SEEK_SET);
fread(&vx, 4, 1, file);
offset+=4;
fseek(file, offset, SEEK_SET);
fread(&vy, 4, 1, file);
offset+=4;
fseek(file, offset, SEEK_SET);
fread(&vz, 4, 1, file);
offset+=4;
fseek(file, offset, SEEK_SET);
fread(&vtx, 4, 1, file);
offset+=4;
fseek(file, offset, SEEK_SET);
fread(&vty, 4, 1, file);
offset+=4;
fseek(file, offset, SEEK_SET);
fread(&vtz, 4, 1, file);
offset+=4;
fseek(file, offset, SEEK_SET);
fread(&vnx, 4, 1, file);
offset+=4;
fseek(file, offset, SEEK_SET);
fread(&vny, 4, 1, file);
offset+=4;
fseek(file, offset, SEEK_SET);
fread(&vnz, 4, 1, file);
offset+=4;
printf("Face %d/%d/%d %d/%d/%d %d/%d/%d\n", vx, vtx, vnx, vy, vty, vny, vz, vtz, vnz);
} else if(memcmp(identifier, pmf_eof_id, sizeof(identifier)) == 0) {
//End of File
break;
} else {
printf("Unknown identifier!\n");
break;
}
}
}
What I want
I'm not sure if it's a good idea to use such identifiers or if floats and integers can be stored in a better way.
Anyway, I want to get tips and criticism (Especially about my format).
Miscellaneous
Here is a link to the cube file: https://hastebin.com/tuqayebila.apache
The file extension of my file is pmf
which stands for Plutonium File Format
. (Plutonium is the name of my engine)