-
Notifications
You must be signed in to change notification settings - Fork 51
ObjToMD2Main
Igor Karpov edited this page Apr 26, 2015
·
1 revision
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <sstream>
using namespace std;
//-----------------------------------------------------------
#define CHAR_BYTES 1
#define SHORT_BYTES 2
#define INT_BYTES 4
#define FLOAT_BYTES 4
#define CHARS_PER_LINE 512
//-----------------------------------------------------------
/*
================
MD2 FORMAT NOTES
================
Bytes Per Type
--------------
char: 1
short: 2
int: 4
float: 4
Byte order: Little-Endian
-------------------------
With integers I1 and I2, least-significant and
most-significant bytes are ordered as follows.
[I1 LSB, ..., I1 MSB], [I2 LSB, ..., I2 MSB], ...
To analyze output file with od
------------------------------
Options:
-t c // Print bytes as ASCII characters.
-t d4 // Print 4 byte groups as signed decimal numbers.
-t u1 // Print bytes as unsigned decimal numbers.
-t x1 // Print bytes as hexadecimal.
--width=1 // Print 4 bytes per line. (Short option fails.)
-A d // Use decimal radix for byte numbers.
Orientation
-----------
obj -> md2:
x -> y
y -> z
z -> x
*/
//-----------------------------------------------------------
void write_char(ofstream& md2_file, char c)
{
md2_file.write(&c, 1);
}
//-----------------------------------------------------------
void write_unsigned_char(ofstream& md2_file, unsigned char c)
{
md2_file.write((char*) &c, 1);
}
//-----------------------------------------------------------
void write_short(ofstream& md2_file, short s)
{
md2_file.write((char*) &s, 2);
}
//-----------------------------------------------------------
void write_unsigned_short
(ofstream& md2_file, unsigned short s)
{
md2_file.write((char*) &s, 2);
}
//-----------------------------------------------------------
void write_int(ofstream& md2_file, int i)
{
md2_file.write((char*) &i, 4);
}
//-----------------------------------------------------------
void write_float(ofstream& md2_file, float f)
{
md2_file.write((char*) &f, 4);
}
//-----------------------------------------------------------
void write_string
(ofstream& md2_file, const string& s, unsigned int length)
{
for (unsigned int i = 0; i < length; ++i) {
if (i < s.size())
write_char(md2_file, s.at(i));
else
write_char(md2_file, '\0');
}
}
//-----------------------------------------------------------
void gather_obj_data
(const string& obj_name, int& vertex_count,
int& texture_vertex_count, int& triangle_count)
{
ifstream obj_file;
char line_chars[CHARS_PER_LINE];
istringstream line_stream;
string first_string;
// Open a representative obj file.
obj_file.open(obj_name.c_str());
// Initialize counts to 0.
vertex_count = 0;
texture_vertex_count = 0;
triangle_count = 0;
// Scan file, accumulating counts.
// (Note: The items I want to count are all indicated by the
// first string of a line.)
// While there's something left in the file:
while (obj_file.eof() == false) {
// Read until next newline (or end of file).
obj_file.getline(line_chars, CHARS_PER_LINE);
line_stream.clear();
line_stream.str(line_chars);
line_stream >> first_string;
if (!line_stream.fail()) {
// A string was read. Check for count items.
if (first_string == "v")
++vertex_count;
else if (first_string == "vt")
++texture_vertex_count;
else if (first_string == "f")
++triangle_count;
}
}
// Close obj file.
obj_file.close();
}
//-----------------------------------------------------------
// Use the texture file name along with information from the
// first obj file to write the header to the md2 file.
void write_header
(ofstream& md2_file, const vector<string>& obj_names,
int texture_width, int texture_height)
{
int vertex_count;
int texture_vertex_count;
int triangle_count;
int frame_count;
int bytes_per_frame;
int offset;
// Write magic number "IDP2".
write_char(md2_file, 'I');
write_char(md2_file, 'D');
write_char(md2_file, 'P');
write_char(md2_file, '2');
// Write version. (Must be 8.)
write_int(md2_file, 8);
// Write texture width and height.
write_int(md2_file, texture_width);
write_int(md2_file, texture_height);
// Gather data from obj files.
gather_obj_data
(obj_names.front(), vertex_count, texture_vertex_count,
triangle_count);
frame_count = obj_names.size();
// Write number of bytes per frame.
bytes_per_frame =
3 * FLOAT_BYTES + // scale
3 * FLOAT_BYTES + // translate
16 * CHAR_BYTES + // name
vertex_count * (4 * CHAR_BYTES); // vertices
write_int(md2_file, bytes_per_frame);
// Write number of skins. (Like uniforms. Not used here.)
write_int(md2_file, 1);
// Write number of vertices.
write_int(md2_file, vertex_count);
// Write number of texture vertices.
write_int(md2_file, texture_vertex_count);
// Write number of triangles.
write_int(md2_file, triangle_count);
// Write number of OpenGL commands. (Not used here.)
write_int(md2_file, 0);
// Write number of frames.
write_int(md2_file, frame_count);
// Compute size of entire header to use as first explicit
// offset value. (17 ints will be written altogether.)
offset = 17 * INT_BYTES;
// Write offset for skin data and compute next offset.
// (Skin data is a 64-byte string for texture filename, and
// I use only one such file.)
write_int(md2_file, offset);
offset += 1 * (64 * CHAR_BYTES);
// Write offset for texture vertex data and compute next
// offset.
// (A texture vertex is one short for each dimension.)
write_int(md2_file, offset);
offset += texture_vertex_count * (2 * SHORT_BYTES);
// Write offset for triangle data and compute next offset.
// (A triangle is 3 shorts of vertex indices + 3 shorts of
// texture vertex indices.)
write_int(md2_file, offset);
offset += triangle_count * (6 * SHORT_BYTES);
// Write offset for frame data and compute next offset.
// (Frame data size is number of frames times bytes per
// frame.)
write_int(md2_file, offset);
offset += frame_count * bytes_per_frame;
// Write offset for OpenGL commands. (Not used.)
// (Since there is no data of this type, the next offset
// will be the same as this one.)
write_int(md2_file, offset);
// Write offset for end of file.
// (I assume this means the offset for the first byte after
// the last byte of data.)
write_int(md2_file, offset);
}
//-----------------------------------------------------------
// Convert texture coordinates from [0,1] to
// [0, width|height - 1].
// Note: Orientation of texture vertical dimension (v or t) is
// opposite of that used in obj.
void try_to_copy_texture_vertex
(ofstream& md2_file, const char* line_chars,
int texture_width, int texture_height)
{
istringstream line_stream;
string one_string;
double old_u;
double old_v;
short new_u;
short new_v;
// Check if this line contains a texture vertex.
line_stream.clear();
line_stream.str(line_chars);
line_stream >> one_string;
if (line_stream.fail()) {
return;
}
else {
if (one_string != "vt")
return;
}
// Get u and v in [0,1] from obj line.
line_stream >> old_u;
line_stream >> old_v;
// Convert to u and v in [0, width|height - 1], flipping
// vertical value.
// (Truncation in assignment to new_u|v is intentional.)
new_u = old_u * texture_width;
new_v = (1.0 - old_v) * texture_height;
if (new_u >= texture_width)
new_u = texture_width;
if (new_v >= texture_height)
new_v = texture_height;
// Write result to md2 file.
write_short(md2_file, new_u);
write_short(md2_file, new_v);
}
//-----------------------------------------------------------
// Note: Vertex order in face entries is v/vt/vn.
// Note: obj indices start at 1.
// Question: What index do md2 vertices start at?
// I'll assume 0 until proven otherwise.
void try_to_copy_triangle
(ofstream& md2_file, const char* line_chars)
{
istringstream line_stream;
string one_string;
unsigned short vertices[3];
unsigned short texture_vertices[3];
// Check if this line contains a triangle (face).
line_stream.clear();
line_stream.str(line_chars);
line_stream >> one_string;
if (line_stream.fail()) {
return;
}
else {
if (one_string != "f")
return;
}
// Repeatedly get vertex and texture vertex indices from
// obj.
// (Note: I may be assuming that texture vertices exist in
// this parsing.)
for (int i = 0; i < 3; ++i) {
line_stream >> vertices[i]; // Get v.
line_stream.get(); // Skip '/'.
line_stream >> texture_vertices[i]; // Get vt.
line_stream.get(); // Skip '/'.
line_stream >> one_string; // Skip vn.
}
// Write triangle to md2.
// Note: I'll assume that md2 uses indices from 0, but I
// have no real information on this yet.
// Note: I think they specify triangles in the opposite
// order as obj. Reverse during write.
for (int i = 2; i >= 0; --i)
write_unsigned_short(md2_file, vertices[i] - 1);
for (int i = 2; i >= 0; --i)
write_unsigned_short(md2_file, texture_vertices[i] - 1);
}
//-----------------------------------------------------------
void write_post_header_data
(ofstream& md2_file, const vector<string>& obj_names,
const string& texture_name, int texture_width,
int texture_height)
{
ifstream obj_file;
char line_chars[CHARS_PER_LINE];
// Write skin (texture) name as 64 chars.
write_string(md2_file, texture_name, 64);
// Open representative obj file.
obj_file.open(obj_names.front().c_str());
// Gather and write texture vertices.
while (obj_file.eof() == false) {
obj_file.getline(line_chars, CHARS_PER_LINE);
try_to_copy_texture_vertex
(md2_file, line_chars, texture_width, texture_height);
}
// Rewind obj file.
// (Note: Apparently clear must be called before seekg for
// this to work.)
obj_file.clear();
obj_file.seekg(0);
// Gather and write triangles.
while (obj_file.eof() == false) {
obj_file.getline(line_chars, CHARS_PER_LINE);
try_to_copy_triangle(md2_file, line_chars);
}
// Close obj file.
obj_file.close();
}
//-----------------------------------------------------------
// Note: Define scale and translate so as to go from
// compressed (unsigned char) to uncompressed (float).
void compute_scale_and_translate
(const string& obj_name, float* scale, float* translate)
{
ifstream obj_file;
char line_chars[CHARS_PER_LINE];
bool min_and_max_initialized;
istringstream line_stream;
string one_string;
float vertex_value;
float min[3];
float max[3];
// Find min and max of vertices in each dimension. (Ugly?)
obj_file.open(obj_name.c_str());
min_and_max_initialized = false;
while (obj_file.eof() == false) {
obj_file.getline(line_chars, CHARS_PER_LINE);
line_stream.clear();
line_stream.str(line_chars);
line_stream >> one_string;
if (!line_stream.fail() && one_string == "v") {
for (int i = 0; i < 3; ++i) {
line_stream >> vertex_value;
if (vertex_value < min[i] || !min_and_max_initialized)
min[i] = vertex_value;
if (vertex_value > max[i] || !min_and_max_initialized)
max[i] = vertex_value;
}
min_and_max_initialized = true;
}
}
obj_file.close();
// Compute translate.
translate[0] = min[0];
translate[1] = min[1];
translate[2] = min[2];
// Compute scale.
// (Unsigned char of compressed version is in [0,255].)
scale[0] = (max[0] - min[0]) / 255.0;
scale[1] = (max[1] - min[1]) / 255.0;
scale[2] = (max[2] - min[2]) / 255.0;
}
//-----------------------------------------------------------
// Write this obj's data to the md2 file.
void write_frame(ofstream& md2_file, const string& obj_name)
{
float scale[3];
float translate[3];
string obj_name_base;
ifstream obj_file;
char line_chars[CHARS_PER_LINE];
istringstream line_stream;
string one_string;
float old_vertex_value;
unsigned char new_vertex_values[3];
// Compute scale and translate.
compute_scale_and_translate(obj_name, scale, translate);
// Write scale (with change from obj to md2 space).
write_float(md2_file, scale[2]);
write_float(md2_file, scale[0]);
write_float(md2_file, scale[1]);
// Write translate (with change from obj to md2 space).
write_float(md2_file, translate[2]);
write_float(md2_file, translate[0]);
write_float(md2_file, translate[1]);
// Write frame name as 16 chars. (Use obj name without
// extension.)
obj_name_base = obj_name.substr(0, obj_name.size() - 4);
write_string(md2_file, obj_name_base, 16);
// Write vertices.
// Note: I haven't checked for boundary/truncation problems.
obj_file.open(obj_name.c_str());
while (obj_file.eof() == false) {
obj_file.getline(line_chars, CHARS_PER_LINE);
line_stream.clear();
line_stream.str(line_chars);
line_stream >> one_string;
if (!line_stream.fail() && one_string == "v") {
// Compute compressed vertex.
for (int i = 0; i < 3; ++i) {
line_stream >> old_vertex_value;
new_vertex_values[i] =
(old_vertex_value - translate[i]) / scale[i];
}
// Write compressed vertex with change from obj to md2
// space.
write_unsigned_char(md2_file, new_vertex_values[2]);
write_unsigned_char(md2_file, new_vertex_values[0]);
write_unsigned_char(md2_file, new_vertex_values[1]);
// Write indexed normal.
// Note: Assuming index from 0, but I don't really know.
// (This actually may cause problems, since I think it
// requires one vertex normal per vertex.)
/* HACK */
// Fake for now.
write_unsigned_char(md2_file, 0);
/* HACK */
}
}
obj_file.close();
}
//-----------------------------------------------------------
int main(int argc, char** argv)
{
int i;
vector<string> obj_names;
string texture_name;
istringstream iss;
int texture_width;
int texture_height;
string md2_name;
ofstream md2_file;
vector<string>::const_iterator obj_name_iter;
// If no arguments, show usage and exit.
if (argc == 1) {
cout << "usage: obj_to_md2 "
<< "<obj_1> ... <obj_n> "
<< "<texture_name> <texture_width> <texture_height> "
<< "<md2_name>"
<< endl;
exit(0);
}
// Parse arguments.
for (i = 1; i < argc - 4; ++i)
obj_names.push_back(argv[i]);
texture_name = argv[argc - 4];
iss.clear();
iss.str(argv[argc - 3]);
iss >> texture_width;
iss.clear();
iss.str(argv[argc - 2]);
iss >> texture_height;
md2_name = argv[argc - 1];
// Open output file.
md2_file.open(md2_name.c_str(), ofstream::binary);
// Write header.
write_header
(md2_file, obj_names, texture_width, texture_height);
// Write non-frame post-header data.
write_post_header_data
(md2_file, obj_names, texture_name, texture_width,
texture_height);
// For each obj, write frame to md2.
for (obj_name_iter = obj_names.begin();
obj_name_iter != obj_names.end();
++obj_name_iter)
write_frame(md2_file, *obj_name_iter);
// Close output file.
md2_file.close();
}
//-----------------------------------------------------------